inspec/lib/resources/file.rb
David Alexander 71b90f1b22 file resource: fix NilClass error when using advanced windows permissions (#2344)
* Fixes #2343 (Windows file permissions regression)

Signed-off-by: David Alexander <opensource@thelonelyghost.com>

* Updates docs with missing info on `be_allowed()` matcher

Signed-off-by: David Alexander <opensource@thelonelyghost.com>
2017-11-28 09:18:49 +01:00

296 lines
9.3 KiB
Ruby

# encoding: utf-8
# copyright: 2015, Vulcano Security GmbH
# author: Dominik Richter
# author: Christoph Hartmann
require 'shellwords'
module Inspec::Resources
module FilePermissionsSelector
def select_file_perms_style(os)
if os.unix?
UnixFilePermissions.new(inspec)
elsif os.windows?
WindowsFilePermissions.new(inspec)
end
end
end
class FileResource < Inspec.resource(1)
include FilePermissionsSelector
include LinuxMountParser
name 'file'
desc 'Use the file InSpec audit resource to test all system file types, including files, directories, symbolic links, named pipes, sockets, character devices, block devices, and doors.'
example "
describe file('path') do
it { should exist }
it { should be_file }
it { should be_readable }
it { should be_writable }
it { should be_executable.by_user('root') }
it { should be_owned_by 'root' }
its('mode') { should cmp '0644' }
end
"
attr_reader :file, :mount_options
def initialize(path)
# select permissions style
@perms_provider = select_file_perms_style(inspec.os)
@file = inspec.backend.file(path)
end
%w{
type exist? file? block_device? character_device? socket? directory?
symlink? pipe? mode mode? owner owned_by? group grouped_into?
link_path linked_to? mtime size selinux_label immutable?
product_version file_version version? md5sum sha256sum
path basename source source_path uid gid
}.each do |m|
define_method m.to_sym do |*args|
file.method(m.to_sym).call(*args)
end
end
def content
res = file.content
return nil if res.nil?
res.force_encoding('utf-8')
end
def contain(*_)
raise 'Contain is not supported. Please use standard RSpec matchers.'
end
def readable?(by_usergroup, by_specific_user)
return false unless exist?
return skip_resource '`readable?` is not supported on your OS yet.' if @perms_provider.nil?
file_permission_granted?('read', by_usergroup, by_specific_user)
end
def writable?(by_usergroup, by_specific_user)
return false unless exist?
return skip_resource '`writable?` is not supported on your OS yet.' if @perms_provider.nil?
file_permission_granted?('write', by_usergroup, by_specific_user)
end
def executable?(by_usergroup, by_specific_user)
return false unless exist?
return skip_resource '`executable?` is not supported on your OS yet.' if @perms_provider.nil?
file_permission_granted?('execute', by_usergroup, by_specific_user)
end
def allowed?(permission, opts = {})
return false unless exist?
return skip_resource '`allowed?` is not supported on your OS yet.' if @perms_provider.nil?
file_permission_granted?(permission, opts[:by], opts[:by_user])
end
def mounted?(expected_options = nil, identical = false)
mounted = file.mounted
# return if no additional parameters have been provided
return file.mounted? if expected_options.nil?
# deprecation warning, this functionality will be removed in future version
warn "[DEPRECATION] `be_mounted.with and be_mounted.only_with` are deprecated. Please use `mount('#{source_path}')` instead."
# we cannot read mount data on non-Linux systems
return nil if !inspec.os.linux?
# parse content if we are on linux
@mount_options ||= parse_mount_options(mounted.stdout, true)
if identical
# check if the options should be identical
@mount_options == expected_options
else
# otherwise compare the selected values
@mount_options.contains(expected_options)
end
end
def suid
(mode & 04000) > 0
end
alias setuid? suid
def sgid
(mode & 02000) > 0
end
alias setgid? sgid
def sticky
(mode & 01000) > 0
end
alias sticky? sticky
def to_s
"File #{source_path}"
end
private
def file_permission_granted?(access_type, by_usergroup, by_specific_user)
raise '`file_permission_granted?` is not supported on your OS' if @perms_provider.nil?
if by_specific_user.nil? || by_specific_user.empty?
@perms_provider.check_file_permission_by_mask(file, access_type, by_usergroup, by_specific_user)
else
@perms_provider.check_file_permission_by_user(access_type, by_specific_user, source_path)
end
end
end
class FilePermissions
attr_reader :inspec
def initialize(inspec)
@inspec = inspec
end
end
class UnixFilePermissions < FilePermissions
def permission_flag(access_type)
case access_type
when 'read'
'r'
when 'write'
'w'
when 'execute'
'x'
else
raise 'Invalid access_type provided'
end
end
def usergroup_for(usergroup, specific_user)
if usergroup == 'others'
'other'
elsif (usergroup.nil? || usergroup.empty?) && specific_user.nil?
'all'
else
usergroup
end
end
def check_file_permission_by_mask(file, access_type, usergroup, specific_user)
usergroup = usergroup_for(usergroup, specific_user)
flag = permission_flag(access_type)
mask = file.unix_mode_mask(usergroup, flag)
raise 'Invalid usergroup/owner provided' if mask.nil?
(file.mode & mask) != 0
end
def check_file_permission_by_user(access_type, user, path)
flag = permission_flag(access_type)
if inspec.os.linux?
perm_cmd = "su -s /bin/sh -c \"test -#{flag} #{path}\" #{user}"
elsif inspec.os.bsd? || inspec.os.solaris?
perm_cmd = "sudo -u #{user} test -#{flag} #{path}"
elsif inspec.os.aix?
perm_cmd = "su #{user} -c test -#{flag} #{path}"
elsif inspec.os.hpux?
perm_cmd = "su #{user} -c \"test -#{flag} #{path}\""
else
return skip_resource 'The `file` resource does not support `by_user` on your OS.'
end
cmd = inspec.command(perm_cmd)
cmd.exit_status == 0 ? true : false
end
end
class WindowsFilePermissions < FilePermissions
def check_file_permission_by_mask(_file, _access_type, _usergroup, _specific_user)
raise '`check_file_permission_by_mask` is not supported on Windows'
end
def check_file_permission_by_user(access_type, user, path)
access_rule = translate_perm_names(access_type)
access_rule = convert_to_powershell_array(access_rule)
cmd = inspec.command("@(@((Get-Acl '#{path}').access | Where-Object {$_.AccessControlType -eq 'Allow' -and $_.IdentityReference -eq '#{user}' }) | Where-Object {($_.FileSystemRights.ToString().Split(',') | % {$_.trim()} | ? {#{access_rule} -contains $_}) -ne $null}) | measure | % { $_.Count }")
cmd.stdout.chomp == '0' ? false : true
end
private
def convert_to_powershell_array(arr)
if arr.empty?
'@()'
else
%{@('#{arr.join("', '")}')}
end
end
# Translates a developer-friendly string into a list of acceptable
# FileSystemRights that match it, because Windows has a fun heirarchy
# of permissions that are able to be noted in multiple ways.
#
# See also: https://www.codeproject.com/Reference/871338/AccessControl-FileSystemRights-Permissions-Table
def translate_perm_names(access_type)
names = translate_common_perms(access_type)
names ||= translate_granular_perms(access_type)
names ||= translate_uncommon_perms(access_type)
raise 'Invalid access_type provided' unless names
names
end
def translate_common_perms(access_type)
case access_type
when 'full-control'
%w{FullControl}
when 'modify'
translate_perm_names('full-control') + %w{Modify}
when 'read'
translate_perm_names('modify') + %w{ReadAndExecute Read}
when 'write'
translate_perm_names('modify') + %w{Write}
when 'execute'
translate_perm_names('modify') + %w{ReadAndExecute ExecuteFile Traverse}
when 'delete'
translate_perm_names('modify') + %w{Delete}
end
end
def translate_uncommon_perms(access_type)
case access_type
when 'delete-subdirectories-and-files'
translate_perm_names('full-control') + %w{DeleteSubdirectoriesAndFiles}
when 'change-permissions'
translate_perm_names('full-control') + %w{ChangePermissions}
when 'take-ownership'
translate_perm_names('full-control') + %w{TakeOwnership}
end
end
def translate_granular_perms(access_type)
case access_type
when 'write-data', 'create-files'
translate_perm_names('write') + %w{WriteData CreateFiles}
when 'append-data', 'create-directories'
translate_perm_names('write') + %w{CreateDirectories AppendData}
when 'write-extended-attributes'
translate_perm_names('write') + %w{WriteExtendedAttributes}
when 'write-attributes'
translate_perm_names('write') + %w{WriteAttributes}
when 'read-data', 'list-directory'
translate_perm_names('read') + %w{ReadData ListDirectory}
when 'read-attributes'
translate_perm_names('read') + %w{ReadAttributes}
when 'read-extended-attributes'
translate_perm_names('read') + %w{ReadExtendedAttributes}
when 'read-permissions'
translate_perm_names('read') + %w{ReadPermissions}
end
end
end
end