mirror of
https://github.com/inspec/inspec
synced 2024-11-26 22:50:36 +00:00
7a6119e7a6
* Add missing tests for groups resource, document members propery, and assorted fixes. Update existing documentation for group resource. Add documentation for groups resource. Update group resource tests to test members property. Change groups resource members property to have simple style. (this ensures members is a single array) remove deprecated have_gid propery. change `if !` to `unless` Remove early return from members method. This prevented members from working correctly on any OS other than Windows. Add missing tests for the groups resource. remove tests for has_gid Signed-off-by: Miah Johnson <miah@chia-pet.org> * Fix comments Signed-off-by: Jared Quick <jquick@chef.io>
220 lines
6.4 KiB
Ruby
220 lines
6.4 KiB
Ruby
# encoding: utf-8
|
|
|
|
require 'utils/filter'
|
|
|
|
module Inspec::Resources
|
|
# This file contains two resources, the `group` and `groups` resource.
|
|
# The `group` resource is optimized for requests that verify specific groups
|
|
# that you know upfront for testing. If you need to query all groups or search
|
|
# specific groups with certain properties, use the `groups` resource.
|
|
module GroupManagementSelector
|
|
# select group provider based on the operating system
|
|
# returns nil, if no group manager was found for the operating system
|
|
def select_group_manager(os)
|
|
@group_provider = if os.darwin?
|
|
DarwinGroup.new(inspec)
|
|
elsif os.unix?
|
|
UnixGroup.new(inspec)
|
|
elsif os.windows?
|
|
WindowsGroup.new(inspec)
|
|
end
|
|
end
|
|
end
|
|
|
|
class Groups < Inspec.resource(1)
|
|
include GroupManagementSelector
|
|
|
|
name 'groups'
|
|
supports platform: 'unix'
|
|
supports platform: 'windows'
|
|
desc 'Use the group InSpec audit resource to test groups on the system. Groups can be filtered.'
|
|
example "
|
|
describe groups.where { name == 'root'} do
|
|
its('names') { should eq ['root'] }
|
|
its('gids') { should eq [0] }
|
|
end
|
|
|
|
describe groups.where { name == 'Administrators'} do
|
|
its('names') { should eq ['Administrators'] }
|
|
its('gids') { should eq ['S-1-5-32-544'] }
|
|
end
|
|
"
|
|
|
|
def initialize
|
|
# select group manager
|
|
@group_provider = select_group_manager(inspec.os)
|
|
return skip_resource 'The `groups` resource is not supported on your OS yet.' if @group_provider.nil?
|
|
end
|
|
|
|
filter = FilterTable.create
|
|
filter.register_custom_matcher(:exists?) { |x| !x.entries.empty? }
|
|
filter.register_column(:names, field: 'name')
|
|
.register_column(:gids, field: 'gid')
|
|
.register_column(:domains, field: 'domain')
|
|
.register_column(:members, field: 'members', style: :simple)
|
|
filter.install_filter_methods_on_resource(self, :collect_group_details)
|
|
|
|
def to_s
|
|
'Groups'
|
|
end
|
|
|
|
private
|
|
|
|
# collects information about every group
|
|
def collect_group_details
|
|
return @groups_cache ||= @group_provider.groups unless @group_provider.nil?
|
|
[]
|
|
end
|
|
end
|
|
|
|
# Usage:
|
|
# describe group('root') do
|
|
# it { should exist }
|
|
# its('gid') { should eq 0 }
|
|
# end
|
|
#
|
|
class Group < Inspec.resource(1)
|
|
include GroupManagementSelector
|
|
|
|
name 'group'
|
|
supports platform: 'unix'
|
|
supports platform: 'windows'
|
|
desc 'Use the group InSpec audit resource to test groups on the system.'
|
|
example "
|
|
describe group('root') do
|
|
it { should exist }
|
|
its('gid') { should eq 0 }
|
|
end
|
|
|
|
describe group('Administrators') do
|
|
its('members') { should include 'Administrator' }
|
|
end
|
|
"
|
|
|
|
def initialize(groupname)
|
|
@group = groupname
|
|
|
|
# select group manager
|
|
@group_provider = select_group_manager(inspec.os)
|
|
return skip_resource 'The `group` resource is not supported on your OS yet.' if @group_provider.nil?
|
|
end
|
|
|
|
# verifies if a group exists
|
|
def exists?
|
|
!group_info.entries.empty?
|
|
end
|
|
|
|
def gid
|
|
flatten_entry(group_info, 'gid')
|
|
end
|
|
|
|
def members
|
|
flatten_entry(group_info, 'members')
|
|
end
|
|
|
|
def local
|
|
# at this point the implementation only returns local groups
|
|
true
|
|
end
|
|
|
|
def to_s
|
|
"Group #{@group}"
|
|
end
|
|
|
|
private
|
|
|
|
def flatten_entry(group_info, prop)
|
|
entries = group_info.entries
|
|
if entries.empty?
|
|
nil
|
|
elsif entries.size == 1
|
|
entries.first.send(prop)
|
|
else
|
|
raise 'found more than one group with the same name, please use `groups` resource'
|
|
end
|
|
end
|
|
|
|
def group_info
|
|
# we need a local copy for the block
|
|
group = @group.dup
|
|
@groups_cache ||= inspec.groups.where { name == group }
|
|
end
|
|
end
|
|
|
|
class GroupInfo
|
|
attr_reader :inspec
|
|
def initialize(inspec)
|
|
@inspec = inspec
|
|
end
|
|
|
|
def groups
|
|
raise 'group provider must implement the `groups` method'
|
|
end
|
|
end
|
|
|
|
# implements generic unix groups via /etc/group
|
|
class UnixGroup < GroupInfo
|
|
def groups
|
|
inspec.etc_group.entries
|
|
end
|
|
end
|
|
|
|
# OSX uses opendirectory for groups, so `/etc/group` may not be fully accurate
|
|
# This uses `dscacheutil` to get the group info instead of `etc_group`
|
|
class DarwinGroup < GroupInfo
|
|
def groups
|
|
group_info = inspec.command('dscacheutil -q group').stdout.split("\n\n")
|
|
|
|
groups = []
|
|
regex = /^([^:]*?)\s*:\s(.*?)\s*$/
|
|
group_info.each do |data|
|
|
groups << inspec.parse_config(data, assignment_regex: regex).params
|
|
end
|
|
|
|
# Convert the `dscacheutil` groups to match `inspec.etc_group.entries`
|
|
groups.each { |g| g['gid'] = g['gid'].to_i }
|
|
groups.each do |g|
|
|
next if g['users'].nil?
|
|
g['members'] = g.delete('users')
|
|
g['members'].tr!(' ', ',')
|
|
end
|
|
end
|
|
end
|
|
|
|
class WindowsGroup < GroupInfo
|
|
# returns all local groups
|
|
def groups
|
|
script = <<-EOH
|
|
Function ConvertTo-SID { Param([byte[]]$BinarySID)
|
|
(New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value
|
|
}
|
|
$Computername = $Env:Computername
|
|
$adsi = [ADSI]"WinNT://$Computername"
|
|
$groups = $adsi.Children | where {$_.SchemaClassName -eq 'group'} | ForEach {
|
|
$name = $_.Name[0]
|
|
$sid = ConvertTo-SID -BinarySID $_.ObjectSID[0]
|
|
$group =[ADSI]$_.Path
|
|
$members = $_.Members() | Foreach-Object { $_.GetType().InvokeMember('Name', 'GetProperty', $null, $_, $null) }
|
|
# An empty collection of these objects isn't properly converted to an empty array by ConvertTo-Json
|
|
if(-not [bool]$members) {
|
|
$members = @()
|
|
}
|
|
new-object psobject -property @{name = $group.Name[0]; gid = $sid; domain = $Computername; members = $members}
|
|
}
|
|
$groups | ConvertTo-Json -Depth 3
|
|
EOH
|
|
cmd = inspec.powershell(script)
|
|
# cannot rely on exit code for now, successful command returns exit code 1
|
|
# return nil if cmd.exit_status != 0, try to parse json
|
|
begin
|
|
groups = JSON.parse(cmd.stdout)
|
|
rescue JSON::ParserError => _e
|
|
return []
|
|
end
|
|
|
|
# ensure we have an array of groups
|
|
groups = [groups] unless groups.is_a?(Array)
|
|
groups
|
|
end
|
|
end
|
|
end
|