mirror of
https://github.com/inspec/inspec
synced 2024-11-10 23:24:18 +00:00
implement filter table for group/groups resource
This commit is contained in:
parent
e1aae91283
commit
f7ec24a337
7 changed files with 215 additions and 186 deletions
|
@ -83,7 +83,7 @@ require 'resources/directory'
|
|||
require 'resources/etc_group'
|
||||
require 'resources/file'
|
||||
require 'resources/gem'
|
||||
require 'resources/group'
|
||||
require 'resources/groups'
|
||||
require 'resources/grub_conf'
|
||||
require 'resources/host'
|
||||
require 'resources/iis_site'
|
||||
|
|
|
@ -1,137 +0,0 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
|
||||
# Usage:
|
||||
# describe group('root') do
|
||||
# it { should exist }
|
||||
# its('gid') { should eq 0 }
|
||||
# end
|
||||
#
|
||||
# deprecated has matcher
|
||||
# describe group('root') do
|
||||
# it { should have_gid 0 }
|
||||
# end
|
||||
|
||||
module Inspec::Resources
|
||||
class Group < Inspec.resource(1)
|
||||
name 'group'
|
||||
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
|
||||
"
|
||||
|
||||
def initialize(groupname, domain = nil)
|
||||
@group = groupname.downcase
|
||||
@domain = domain
|
||||
@domain = @domain.downcase unless @domain.nil?
|
||||
|
||||
@cache = nil
|
||||
|
||||
# select group manager
|
||||
@group_provider = nil
|
||||
if inspec.os.unix?
|
||||
@group_provider = UnixGroup.new(inspec)
|
||||
elsif inspec.os.windows?
|
||||
@group_provider = WindowsGroup.new(inspec)
|
||||
else
|
||||
return skip_resource 'The `group` resource is not supported on your OS yet.'
|
||||
end
|
||||
end
|
||||
|
||||
# verifies if a group exists
|
||||
def exists?
|
||||
# ensure that we found one group
|
||||
!group_info.nil? && group_info.size > 0
|
||||
end
|
||||
|
||||
def gid
|
||||
return nil if group_info.nil? || group_info.size == 0
|
||||
|
||||
# the default case should be one group
|
||||
return group_info[0][:gid] if group_info.size == 1
|
||||
|
||||
# return array if we got multiple gids
|
||||
group_info.map { |grp| grp[:gid] }
|
||||
end
|
||||
|
||||
# implements rspec has matcher, to be compatible with serverspec
|
||||
def has_gid?(compare_gid)
|
||||
gid == compare_gid
|
||||
end
|
||||
|
||||
def local
|
||||
return nil if group_info.nil? || group_info.size == 0
|
||||
|
||||
# the default case should be one group
|
||||
return group_info[0][:local] if group_info.size == 1
|
||||
|
||||
# return array if we got multiple gids
|
||||
group_info.map { |grp| grp[:local] }
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Group #{@group}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_info
|
||||
return @cache if !@cache.nil?
|
||||
@cache = @group_provider.group_info(@group, @domain) if !@group_provider.nil?
|
||||
end
|
||||
end
|
||||
|
||||
class GroupInfo
|
||||
attr_reader :inspec
|
||||
def initialize(inspec)
|
||||
@inspec = inspec
|
||||
end
|
||||
end
|
||||
|
||||
# implements generic unix groups via /etc/group
|
||||
class UnixGroup < GroupInfo
|
||||
def group_info(group, _domain = nil)
|
||||
inspec.etc_group.where(name: group).entries.map { |grp|
|
||||
{
|
||||
name: grp['name'],
|
||||
gid: grp['gid'],
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class WindowsGroup < GroupInfo
|
||||
def group_info(compare_group, compare_domain = nil)
|
||||
cmd = inspec.command('Get-WmiObject Win32_Group | Select-Object -Property Caption, Domain, Name, SID, LocalAccount | ConvertTo-Json')
|
||||
|
||||
# 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 nil
|
||||
end
|
||||
|
||||
# ensure we have an array of groups
|
||||
groups = [groups] if !groups.is_a?(Array)
|
||||
|
||||
# reduce list
|
||||
groups.each_with_object([]) do |grp, grp_collection|
|
||||
# map object
|
||||
grp_info = {
|
||||
name: grp['Name'],
|
||||
domain: grp['Domain'],
|
||||
caption: grp['Caption'],
|
||||
gid: nil,
|
||||
sid: grp['SID'],
|
||||
local: grp['LocalAccount'],
|
||||
}
|
||||
return grp_collection.push(grp_info) if grp_info[:name].casecmp(compare_group) == 0 && (compare_domain.nil? || grp_info[:domain].casecmp(compare_domain) == 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
190
lib/resources/groups.rb
Normal file
190
lib/resources/groups.rb
Normal file
|
@ -0,0 +1,190 @@
|
|||
# encoding: utf-8
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
|
||||
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)
|
||||
if os.unix?
|
||||
@group_provider = UnixGroup.new(inspec)
|
||||
elsif os.windows?
|
||||
@group_provider = WindowsGroup.new(inspec)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Groups < Inspec.resource(1)
|
||||
include GroupManagementSelector
|
||||
|
||||
name 'groups'
|
||||
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.add_accessor(:where)
|
||||
.add_accessor(:entries)
|
||||
.add(:names, field: 'name')
|
||||
.add(:gids, field: 'gid')
|
||||
.add(:domains, field: 'domain')
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
filter.connect(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
|
||||
#
|
||||
# deprecated has matcher
|
||||
# describe group('root') do
|
||||
# it { should have_gid 0 }
|
||||
# end
|
||||
class Group < Inspec.resource(1)
|
||||
include GroupManagementSelector
|
||||
|
||||
name 'group'
|
||||
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
|
||||
"
|
||||
|
||||
def initialize(groupname)
|
||||
@group = groupname
|
||||
@group = @group.downcase unless inspec.os.windows?
|
||||
|
||||
# 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.size > 0
|
||||
end
|
||||
|
||||
def gid
|
||||
gids = group_info.gids
|
||||
if gids.size == 0
|
||||
nil
|
||||
# the default case should be one group
|
||||
elsif gids.size == 1
|
||||
gids.entries[0]
|
||||
else
|
||||
fail 'found more than one group with the same name, please use `groups` resource'
|
||||
end
|
||||
end
|
||||
|
||||
# implements rspec has matcher, to be compatible with serverspec
|
||||
def has_gid?(compare_gid)
|
||||
gid == compare_gid
|
||||
end
|
||||
|
||||
def local
|
||||
# at this point the implementation only returns local groups
|
||||
true
|
||||
end
|
||||
|
||||
def to_s
|
||||
"Group #{@group}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
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
|
||||
fail '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
|
||||
|
||||
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
|
||||
new-object psobject -property @{name = $group.Name[0]; gid = $sid; domain=$Computername}
|
||||
}
|
||||
$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] if !groups.is_a?(Array)
|
||||
groups
|
||||
end
|
||||
end
|
||||
end
|
|
@ -211,7 +211,7 @@ class MockLoader
|
|||
# user info for windows (winrm 1.6.0, 1.6.1)
|
||||
'942eeec2b290bda610229d4bd29981ee945ed27b0f4ce7cca099aabe38af6386' => cmd.call('GetUserAccount'),
|
||||
# group info for windows
|
||||
'Get-WmiObject Win32_Group | Select-Object -Property Caption, Domain, Name, SID, LocalAccount | ConvertTo-Json' => cmd.call('GetWin32Group'),
|
||||
'd8d5b3e3355650399e23857a526ee100b4e49e5c2404a0a5dbb7d85d7f4de5cc' => cmd.call('adsigroups'),
|
||||
# network interface
|
||||
'9e80f048a1af5a0f6ab8a465e46ea5ed5ba6587e9b5e54a7a0c0a1a02bb6f663' => cmd.call('find-net-interface'),
|
||||
'c33821dece09c8b334e03a5bb9daefdf622007f73af4932605e758506584ec3f' => empty.call,
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
[
|
||||
{
|
||||
"Caption": "WIN-K0AKLED332V\\Administrators",
|
||||
"Domain": "WIN-K0AKLED332V",
|
||||
"Name": "Administrators",
|
||||
"SID": "S-1-5-32-544",
|
||||
"LocalAccount": true
|
||||
},
|
||||
{
|
||||
"Caption": "WIN-K0AKLED332V\\Users",
|
||||
"Domain": "WIN-K0AKLED332V",
|
||||
"Name": "Users",
|
||||
"SID": "S-1-5-32-545",
|
||||
"LocalAccount": true
|
||||
},
|
||||
{
|
||||
"Caption": "EXAMPLE\\Domain Admins",
|
||||
"Domain": "EXAMPLE",
|
||||
"Name": "Domain Admins",
|
||||
"SID": "S-1-5-21-725088257-906184668-2367214287-512",
|
||||
"LocalAccount": false
|
||||
}
|
||||
]
|
22
test/unit/mock/cmd/adsigroups
Normal file
22
test/unit/mock/cmd/adsigroups
Normal file
|
@ -0,0 +1,22 @@
|
|||
[
|
||||
{
|
||||
"gid": "S-1-5-32-544",
|
||||
"domain": "WIN-CIV7VMLVHLD",
|
||||
"name": "Administrators"
|
||||
},
|
||||
{
|
||||
"gid": "S-1-5-32-546",
|
||||
"domain": "WIN-CIV7VMLVHLD",
|
||||
"name": "Guests"
|
||||
},
|
||||
{
|
||||
"gid": "S-1-5-32-547",
|
||||
"domain": "WIN-CIV7VMLVHLD",
|
||||
"name": "Power Users"
|
||||
},
|
||||
{
|
||||
"gid": "S-1-5-32-545",
|
||||
"domain": "WIN-CIV7VMLVHLD",
|
||||
"name": "Users"
|
||||
}
|
||||
]
|
|
@ -50,30 +50,7 @@ describe 'Inspec::Resources::Group' do
|
|||
it 'verify group on windows' do
|
||||
resource = MockLoader.new(:windows).load_resource('group', 'Administrators')
|
||||
_(resource.exists?).must_equal true
|
||||
_(resource.gid).must_equal nil
|
||||
_(resource.has_gid?(0)).must_equal false
|
||||
end
|
||||
|
||||
it 'verify group on windows' do
|
||||
resource = MockLoader.new(:windows).load_resource('group', 'Administrators', 'WIN-K0AKLED332V')
|
||||
_(resource.exists?).must_equal true
|
||||
_(resource.gid).must_equal nil
|
||||
_(resource.has_gid?(0)).must_equal false
|
||||
end
|
||||
|
||||
# windows with domain group
|
||||
it 'verify domain group on windows' do
|
||||
resource = MockLoader.new(:windows).load_resource('group', 'Domain Admins', 'EXAMPLE')
|
||||
_(resource.exists?).must_equal true
|
||||
_(resource.gid).must_equal nil
|
||||
_(resource.has_gid?(0)).must_equal false
|
||||
end
|
||||
|
||||
# windows with domain group
|
||||
it 'verify domain group on windows wiht lower case' do
|
||||
resource = MockLoader.new(:windows).load_resource('group', 'domain admins', 'example')
|
||||
_(resource.exists?).must_equal true
|
||||
_(resource.gid).must_equal nil
|
||||
_(resource.gid).must_equal 'S-1-5-32-544'
|
||||
_(resource.has_gid?(0)).must_equal false
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue