inspec 1.0.0.beta2 → 1.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -2
- data/Gemfile +4 -0
- data/Rakefile +2 -1
- data/docs/.gitignore +2 -0
- data/docs/README.md +21 -1
- data/docs/resources/apache_conf.md.erb +75 -0
- data/docs/resources/apt.md.erb +84 -0
- data/docs/resources/audit_policy.md.erb +61 -0
- data/docs/resources/auditd_conf.md.erb +79 -0
- data/docs/resources/auditd_rules.md.erb +132 -0
- data/docs/resources/bash.md.erb +84 -0
- data/docs/resources/bond.md.erb +97 -0
- data/docs/resources/bridge.md.erb +67 -0
- data/docs/resources/bsd_service.md.erb +76 -0
- data/docs/resources/command.md.erb +151 -0
- data/docs/resources/csv.md.erb +62 -0
- data/docs/resources/directory.md.erb +43 -0
- data/docs/resources/etc_group.md.erb +116 -0
- data/docs/resources/etc_passwd.md.erb +155 -0
- data/docs/resources/etc_shadow.md.erb +149 -0
- data/docs/resources/file.md.erb +460 -0
- data/docs/resources/gem.md.erb +73 -0
- data/docs/resources/group.md.erb +74 -0
- data/docs/resources/grub_conf.md.erb +115 -0
- data/docs/resources/host.md.erb +85 -0
- data/docs/resources/iis_site.md.erb +142 -0
- data/docs/resources/inetd_conf.md.erb +99 -0
- data/docs/resources/ini.md.erb +69 -0
- data/docs/resources/interface.md.erb +66 -0
- data/docs/resources/iptables.md.erb +70 -0
- data/docs/resources/json.md.erb +76 -0
- data/docs/resources/kernel_module.md.erb +60 -0
- data/docs/resources/kernel_parameter.md.erb +72 -0
- data/docs/resources/launchd_service.md.erb +76 -0
- data/docs/resources/limits_conf.md.erb +80 -0
- data/docs/resources/login_def.md.erb +77 -0
- data/docs/resources/mount.md.erb +83 -0
- data/docs/resources/mysql_conf.md.erb +102 -0
- data/docs/resources/mysql_session.md.erb +63 -0
- data/docs/resources/npm.md.erb +75 -0
- data/docs/resources/ntp_conf.md.erb +76 -0
- data/docs/resources/oneget.md.erb +67 -0
- data/docs/resources/os.md.erb +154 -0
- data/docs/resources/os_env.md.erb +98 -0
- data/docs/resources/package.md.erb +115 -0
- data/docs/resources/parse_config.md.erb +122 -0
- data/docs/resources/parse_config_file.md.erb +143 -0
- data/docs/resources/pip.md.erb +74 -0
- data/docs/resources/port.md.erb +150 -0
- data/docs/resources/postgres_conf.md.erb +90 -0
- data/docs/resources/postgres_session.md.erb +75 -0
- data/docs/resources/powershell.md.erb +116 -0
- data/docs/resources/process.md.erb +73 -0
- data/docs/resources/registry_key.md.erb +149 -0
- data/docs/resources/runit_service.md.erb +76 -0
- data/docs/resources/security_policy.md.erb +61 -0
- data/docs/resources/service.md.erb +135 -0
- data/docs/resources/ssh_config.md.erb +94 -0
- data/docs/resources/sshd_config.md.erb +97 -0
- data/docs/resources/ssl.md.erb +133 -0
- data/docs/resources/sys_info.md.erb +55 -0
- data/docs/resources/systemd_service.md.erb +76 -0
- data/docs/resources/sysv_service.md.erb +76 -0
- data/docs/resources/upstart_service.md.erb +76 -0
- data/docs/resources/user.md.erb +154 -0
- data/docs/resources/users.md.erb +140 -0
- data/docs/resources/vbscript.md.erb +69 -0
- data/docs/resources/windows_feature.md.erb +61 -0
- data/docs/resources/wmi.md.erb +95 -0
- data/docs/resources/xinetd_conf.md.erb +170 -0
- data/docs/resources/yaml.md.erb +69 -0
- data/docs/resources/yum.md.erb +103 -0
- data/docs/ruby_usage.md +154 -0
- data/docs/shared/matcher_be.md.erb +1 -0
- data/docs/shared/matcher_cmp.md.erb +45 -0
- data/docs/shared/matcher_eq.md.erb +3 -0
- data/docs/shared/matcher_include.md.erb +1 -0
- data/docs/shared/matcher_match.md.erb +1 -0
- data/lib/fetchers/url.rb +27 -29
- data/lib/inspec/cached_fetcher.rb +67 -0
- data/lib/inspec/dependencies/requirement.rb +6 -7
- data/lib/inspec/objects/each_loop.rb +5 -2
- data/lib/inspec/plugins/fetcher.rb +2 -0
- data/lib/inspec/profile.rb +9 -41
- data/lib/inspec/resource.rb +1 -1
- data/lib/inspec/rspec_json_formatter.rb +11 -5
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/groups.rb +190 -0
- data/lib/resources/users.rb +3 -2
- metadata +79 -6
- data/docs/cli.rst +0 -448
- data/docs/resources.rst +0 -4836
- data/docs/ruby_usage.rst +0 -145
- data/lib/resources/group.rb +0 -137
@@ -0,0 +1 @@
|
|
1
|
+
Use the `be` matcher to use a comparison operator---`=` (equal to), `>` (greater than), `<` (less than), `>=` (greater than or equal to), and `<=` (less than or equal to)---to compare two values: `its('value') { should be >= value }`, `its('value') { should be < value }`, and so on.
|
@@ -0,0 +1,45 @@
|
|
1
|
+
Use the `cmp` matcher compare two values, such as comparing strings to numbers, comparing a single value to an array of values, comparing an array of strings to a regular expression, improving the printing of octal values, and comparing while ignoring case sensitivity.
|
2
|
+
|
3
|
+
Compare a single value to an array:
|
4
|
+
|
5
|
+
describe some_resource do
|
6
|
+
its('users') { should cmp 'root' }
|
7
|
+
its('users') { should cmp ['root'] }
|
8
|
+
end
|
9
|
+
|
10
|
+
Compare strings and regular expressions:
|
11
|
+
|
12
|
+
describe some_resource do
|
13
|
+
its('setting') { should cmp /raw/i }
|
14
|
+
end
|
15
|
+
|
16
|
+
Compare strings and numbers:
|
17
|
+
|
18
|
+
describe some_resource do
|
19
|
+
its('setting') { should eq '2' }
|
20
|
+
end
|
21
|
+
|
22
|
+
vs:
|
23
|
+
|
24
|
+
describe some_resource do
|
25
|
+
its('setting') { should cmp '2' }
|
26
|
+
its('setting') { should cmp 2 }
|
27
|
+
end
|
28
|
+
|
29
|
+
Ignoring case sensitivity:
|
30
|
+
|
31
|
+
.. code-block:: ruby
|
32
|
+
|
33
|
+
describe some_resource do
|
34
|
+
its('setting') { should cmp 'raw' }
|
35
|
+
its('setting') { should cmp 'RAW' }
|
36
|
+
end
|
37
|
+
|
38
|
+
Printing octal values:
|
39
|
+
|
40
|
+
describe some_resource('/proc/cpuinfo') do
|
41
|
+
its('mode') { should cmp '0345' }
|
42
|
+
end
|
43
|
+
|
44
|
+
expected: 0345
|
45
|
+
got: 0444
|
@@ -0,0 +1 @@
|
|
1
|
+
Use the `include` matcher to verify that a string value is included in a list: `its('list') { should include 'string' }`.
|
@@ -0,0 +1 @@
|
|
1
|
+
Use the `match` matcher to check if a string matches a regular expression: `its('string') { should_not match /regex/ }`.
|
data/lib/fetchers/url.rb
CHANGED
@@ -85,21 +85,12 @@ module Fetchers
|
|
85
85
|
@archive_path ||= download_archive(path)
|
86
86
|
end
|
87
87
|
|
88
|
-
def sha256
|
89
|
-
c = if @archive_path
|
90
|
-
File.read(@archive_path)
|
91
|
-
else
|
92
|
-
content
|
93
|
-
end
|
94
|
-
Digest::SHA256.hexdigest c
|
95
|
-
end
|
96
|
-
|
97
88
|
def resolved_source
|
98
89
|
@resolved_source ||= { url: @target, sha256: sha256 }
|
99
90
|
end
|
100
91
|
|
101
92
|
def cache_key
|
102
|
-
sha256
|
93
|
+
@archive_shasum ||= sha256
|
103
94
|
end
|
104
95
|
|
105
96
|
def to_s
|
@@ -108,16 +99,9 @@ module Fetchers
|
|
108
99
|
|
109
100
|
private
|
110
101
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
http_opts['ssl_verify_mode'.to_sym] = OpenSSL::SSL::VERIFY_NONE if @insecure
|
115
|
-
http_opts['Authorization'] = "Bearer #{@token}" if @token
|
116
|
-
open(@target, http_opts)
|
117
|
-
end
|
118
|
-
|
119
|
-
def content
|
120
|
-
open_target.read
|
102
|
+
def sha256
|
103
|
+
file = @archive_path || temp_archive_path
|
104
|
+
Digest::SHA256.hexdigest File.read(file)
|
121
105
|
end
|
122
106
|
|
123
107
|
def file_type_from_remote(remote)
|
@@ -132,20 +116,34 @@ module Fetchers
|
|
132
116
|
file_type
|
133
117
|
end
|
134
118
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
119
|
+
def temp_archive_path
|
120
|
+
@temp_archive_path ||= download_archive_to_temp
|
121
|
+
end
|
122
|
+
|
123
|
+
# Downloads archive to temporary file with side effect :( of setting @archive_type
|
124
|
+
def download_archive_to_temp
|
125
|
+
return @temp_archive_path if ! @temp_archive_path.nil?
|
126
|
+
Inspec::Log.debug("Fetching URL: #{@target}")
|
127
|
+
http_opts = {}
|
128
|
+
http_opts['ssl_verify_mode'.to_sym] = OpenSSL::SSL::VERIFY_NONE if @insecure
|
129
|
+
http_opts['Authorization'] = "Bearer #{@token}" if @token
|
130
|
+
remote = open(@target, http_opts)
|
131
|
+
@archive_type = file_type_from_remote(remote) # side effect :(
|
132
|
+
archive = Tempfile.new(['inspec-dl-', @archive_type])
|
143
133
|
archive.binmode
|
144
134
|
archive.write(remote.read)
|
145
135
|
archive.rewind
|
146
136
|
archive.close
|
147
|
-
|
137
|
+
Inspec::Log.debug("Archive stored at temporary location: #{archive.path}")
|
138
|
+
@temp_archive_path = archive.path
|
139
|
+
end
|
140
|
+
|
141
|
+
def download_archive(path)
|
142
|
+
download_archive_to_temp
|
143
|
+
final_path = "#{path}#{@archive_type}"
|
144
|
+
FileUtils.mv(temp_archive_path, final_path)
|
148
145
|
Inspec::Log.debug("Fetched archive moved to: #{final_path}")
|
146
|
+
@temp_archive_path = nil
|
149
147
|
final_path
|
150
148
|
end
|
151
149
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'inspec/fetcher'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Inspec
|
6
|
+
class CachedFetcher
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
attr_reader :cache, :target, :fetcher
|
10
|
+
def initialize(target, cache)
|
11
|
+
@target = target
|
12
|
+
@fetcher = Inspec::Fetcher.resolve(target)
|
13
|
+
|
14
|
+
if @fetcher.nil?
|
15
|
+
fail("Could not fetch inspec profile in #{target.inspect}.")
|
16
|
+
end
|
17
|
+
|
18
|
+
@cache = cache
|
19
|
+
end
|
20
|
+
|
21
|
+
def resolved_source
|
22
|
+
fetch
|
23
|
+
@fetcher.resolved_source
|
24
|
+
end
|
25
|
+
|
26
|
+
def cache_key
|
27
|
+
k = if target.is_a?(Hash)
|
28
|
+
target[:sha256] || target[:ref]
|
29
|
+
end
|
30
|
+
|
31
|
+
if k.nil?
|
32
|
+
fetcher.cache_key
|
33
|
+
else
|
34
|
+
k
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch
|
39
|
+
if cache.exists?(cache_key)
|
40
|
+
Inspec::Log.debug "Using cached dependency for #{target}"
|
41
|
+
[cache.prefered_entry_for(cache_key), false]
|
42
|
+
else
|
43
|
+
Inspec::Log.debug "Dependency does not exist in the cache #{target}"
|
44
|
+
fetcher.fetch(cache.base_path_for(fetcher.cache_key))
|
45
|
+
assert_cache_sanity!
|
46
|
+
[fetcher.archive_path, fetcher.writable?]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def assert_cache_sanity!
|
51
|
+
if target.respond_to?(:key?) && target.key?(:sha256)
|
52
|
+
if fetcher.resolved_source[:sha256] != target[:sha256]
|
53
|
+
fail <<EOF
|
54
|
+
The remote source #{fetcher} no longer has the requested content:
|
55
|
+
|
56
|
+
Request Content Hash: #{target[:sha256]}
|
57
|
+
Actual Content Hash: #{fetcher.resolved_source[:sha256]}
|
58
|
+
|
59
|
+
For URL, supermarket, compliance, and other sources that do not
|
60
|
+
provide versioned artifacts, this likely means that the remote source
|
61
|
+
has changed since your lockfile was generated.
|
62
|
+
EOF
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require 'inspec/
|
2
|
+
require 'inspec/cached_fetcher'
|
3
3
|
require 'inspec/dependencies/dependency_set'
|
4
4
|
require 'digest'
|
5
5
|
|
@@ -82,9 +82,7 @@ module Inspec
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def fetcher
|
85
|
-
@fetcher ||= Inspec::
|
86
|
-
fail "No fetcher for #{name} (options: #{opts})" if @fetcher.nil?
|
87
|
-
@fetcher
|
85
|
+
@fetcher ||= Inspec::CachedFetcher.new(opts, @cache)
|
88
86
|
end
|
89
87
|
|
90
88
|
def dependencies
|
@@ -94,17 +92,18 @@ module Inspec
|
|
94
92
|
end
|
95
93
|
|
96
94
|
def to_s
|
97
|
-
|
95
|
+
name
|
98
96
|
end
|
99
97
|
|
100
98
|
def profile
|
99
|
+
return @profile if ! @profile.nil?
|
100
|
+
|
101
101
|
opts = @opts.dup
|
102
|
-
opts[:cache] = @cache
|
103
102
|
opts[:backend] = @backend
|
104
103
|
if !@dependencies.nil?
|
105
104
|
opts[:dependencies] = Inspec::DependencySet.from_array(@dependencies, @cwd, @cache, @backend)
|
106
105
|
end
|
107
|
-
@profile
|
106
|
+
@profile = Inspec::Profile.for_fetcher(fetcher, opts)
|
108
107
|
end
|
109
108
|
end
|
110
109
|
end
|
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
module Inspec
|
4
4
|
class EachLoop < List
|
5
|
-
attr_reader :tests
|
5
|
+
attr_reader :tests, :variables
|
6
6
|
def initialize
|
7
7
|
super
|
8
8
|
@tests = []
|
9
|
+
@variables = []
|
9
10
|
end
|
10
11
|
|
11
12
|
def add_test(t = nil)
|
@@ -24,9 +25,11 @@ module Inspec
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def to_ruby
|
28
|
+
vars = variables.map(&:to_ruby).join("\n")
|
29
|
+
vars += "\n" unless vars.empty?
|
27
30
|
obj = super
|
28
31
|
all_tests = @tests.map(&:to_ruby).join("\n").gsub("\n", "\n ")
|
29
|
-
format("%s.each do |entry|\n %s\nend", obj, all_tests)
|
32
|
+
format("%s%s.each do |entry|\n %s\nend", vars, obj, all_tests)
|
30
33
|
end
|
31
34
|
end
|
32
35
|
end
|
data/lib/inspec/profile.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
require 'forwardable'
|
7
7
|
require 'inspec/polyfill'
|
8
|
-
require 'inspec/
|
8
|
+
require 'inspec/cached_fetcher'
|
9
9
|
require 'inspec/file_provider'
|
10
10
|
require 'inspec/source_reader'
|
11
11
|
require 'inspec/metadata'
|
@@ -21,45 +21,8 @@ module Inspec
|
|
21
21
|
class Profile # rubocop:disable Metrics/ClassLength
|
22
22
|
extend Forwardable
|
23
23
|
|
24
|
-
#
|
25
|
-
# TODO: This function is getting pretty gross.
|
26
|
-
#
|
27
24
|
def self.resolve_target(target, cache = nil)
|
28
|
-
cache
|
29
|
-
fetcher = Inspec::Fetcher.resolve(target)
|
30
|
-
|
31
|
-
if fetcher.nil?
|
32
|
-
fail("Could not fetch inspec profile in #{target.inspect}.")
|
33
|
-
end
|
34
|
-
|
35
|
-
cache_key = if target.is_a?(Hash)
|
36
|
-
target[:sha256] || target[:ref] || fetcher.cache_key
|
37
|
-
else
|
38
|
-
fetcher.cache_key
|
39
|
-
end
|
40
|
-
|
41
|
-
if cache.exists?(cache_key)
|
42
|
-
Inspec::Log.debug "Using cached dependency for #{target}"
|
43
|
-
[cache.prefered_entry_for(cache_key), false]
|
44
|
-
else
|
45
|
-
fetcher.fetch(cache.base_path_for(fetcher.cache_key))
|
46
|
-
if target.respond_to?(:key?) && target.key?(:sha256)
|
47
|
-
if fetcher.resolved_source[:sha256] != target[:sha256]
|
48
|
-
fail <<EOF
|
49
|
-
The remote source #{fetcher} no longer has the requested content:
|
50
|
-
|
51
|
-
Request Content Hash: #{target[:sha256]}
|
52
|
-
Actual Content Hash: #{fetcher.resolved_source[:sha256]}
|
53
|
-
|
54
|
-
For URL, supermarket, compliance, and other sources that do not
|
55
|
-
provide versioned artifacts, this likely means that the remote source
|
56
|
-
has changed since your lockfile was generated.
|
57
|
-
EOF
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
[fetcher.archive_path, fetcher.writable?]
|
62
|
-
end
|
25
|
+
Inspec::CachedFetcher.new(target, cache || Cache.new)
|
63
26
|
end
|
64
27
|
|
65
28
|
def self.for_path(path, opts)
|
@@ -72,9 +35,14 @@ EOF
|
|
72
35
|
new(reader, opts)
|
73
36
|
end
|
74
37
|
|
38
|
+
def self.for_fetcher(fetcher, opts)
|
39
|
+
path, writable = fetcher.fetch
|
40
|
+
for_path(path, opts.merge(target: fetcher.target, writable: writable))
|
41
|
+
end
|
42
|
+
|
75
43
|
def self.for_target(target, opts = {})
|
76
|
-
|
77
|
-
|
44
|
+
fetcher = resolve_target(target, opts[:cache])
|
45
|
+
for_fetcher(fetcher, opts)
|
78
46
|
end
|
79
47
|
|
80
48
|
attr_reader :source_reader, :backend, :runner_context
|
data/lib/inspec/resource.rb
CHANGED
@@ -83,7 +83,7 @@ require 'resources/directory'
|
|
83
83
|
require 'resources/etc_group'
|
84
84
|
require 'resources/file'
|
85
85
|
require 'resources/gem'
|
86
|
-
require 'resources/
|
86
|
+
require 'resources/groups'
|
87
87
|
require 'resources/grub_conf'
|
88
88
|
require 'resources/host'
|
89
89
|
require 'resources/iis_site'
|
@@ -63,10 +63,18 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
|
|
63
63
|
private
|
64
64
|
|
65
65
|
def format_example(example)
|
66
|
+
if example.metadata[:description_args].length == 0
|
67
|
+
code_description = example.metadata[:full_description]
|
68
|
+
else
|
69
|
+
# For skipped profiles, rspec returns in full_description the skip_message as well. We don't want
|
70
|
+
# to mix the two, so we pick the full_description from the example.metadata[:example_group] hash.
|
71
|
+
code_description = example.metadata[:example_group][:description]
|
72
|
+
end
|
73
|
+
|
66
74
|
res = {
|
67
75
|
id: example.metadata[:id],
|
68
76
|
status: example.execution_result.status.to_s,
|
69
|
-
code_desc:
|
77
|
+
code_desc: code_description,
|
70
78
|
}
|
71
79
|
|
72
80
|
unless (pid = example.metadata[:profile_id]).nil?
|
@@ -374,8 +382,6 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
374
382
|
if res.length == 1
|
375
383
|
# Single test - be nice and just print the exception message if the test
|
376
384
|
# failed. No need to say "1 failed".
|
377
|
-
fails.clear
|
378
|
-
skips.clear
|
379
385
|
res[0][:message].to_s
|
380
386
|
else
|
381
387
|
[
|
@@ -425,7 +431,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
425
431
|
end
|
426
432
|
end
|
427
433
|
|
428
|
-
def print_tests
|
434
|
+
def print_tests # rubocop:disable Metrics/AbcSize
|
429
435
|
@anonymous_tests.each do |control|
|
430
436
|
control_result = control[:results]
|
431
437
|
title = control_result[0][:code_desc].split[0..1].join(' ')
|
@@ -438,7 +444,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
438
444
|
test_result = test[:message]
|
439
445
|
else
|
440
446
|
# determine title
|
441
|
-
test_result = test[:code_desc].split[2..-1].join(' ')
|
447
|
+
test_result = test[:skip_message] || test[:code_desc].split[2..-1].join(' ')
|
442
448
|
# show error message
|
443
449
|
test_result += "\n" + test[:message] unless test[:message].nil?
|
444
450
|
end
|
data/lib/inspec/version.rb
CHANGED
@@ -0,0 +1,190 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Christoph Hartmann
|
3
|
+
# author: Dominik Richter
|
4
|
+
|
5
|
+
require 'utils/filter'
|
6
|
+
|
7
|
+
module Inspec::Resources
|
8
|
+
# This file contains two resources, the `group` and `groups` resource.
|
9
|
+
# The `group` resource is optimized for requests that verify specific groups
|
10
|
+
# that you know upfront for testing. If you need to query all groups or search
|
11
|
+
# specific groups with certain properties, use the `groups` resource.
|
12
|
+
module GroupManagementSelector
|
13
|
+
# select group provider based on the operating system
|
14
|
+
# returns nil, if no group manager was found for the operating system
|
15
|
+
def select_group_manager(os)
|
16
|
+
if os.unix?
|
17
|
+
@group_provider = UnixGroup.new(inspec)
|
18
|
+
elsif os.windows?
|
19
|
+
@group_provider = WindowsGroup.new(inspec)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Groups < Inspec.resource(1)
|
25
|
+
include GroupManagementSelector
|
26
|
+
|
27
|
+
name 'groups'
|
28
|
+
desc 'Use the group InSpec audit resource to test groups on the system. Groups can be filtered.'
|
29
|
+
example "
|
30
|
+
describe groups.where { name == 'root'} do
|
31
|
+
its('names') { should eq ['root'] }
|
32
|
+
its('gids') { should eq [0] }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe groups.where { name == 'Administrators'} do
|
36
|
+
its('names') { should eq ['Administrators'] }
|
37
|
+
its('gids') { should eq ['S-1-5-32-544'] }
|
38
|
+
end
|
39
|
+
"
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
# select group manager
|
43
|
+
@group_provider = select_group_manager(inspec.os)
|
44
|
+
return skip_resource 'The `groups` resource is not supported on your OS yet.' if @group_provider.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
filter = FilterTable.create
|
48
|
+
filter.add_accessor(:where)
|
49
|
+
.add_accessor(:entries)
|
50
|
+
.add(:names, field: 'name')
|
51
|
+
.add(:gids, field: 'gid')
|
52
|
+
.add(:domains, field: 'domain')
|
53
|
+
.add(:exists?) { |x| !x.entries.empty? }
|
54
|
+
filter.connect(self, :collect_group_details)
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
'Groups'
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# collects information about every group
|
63
|
+
def collect_group_details
|
64
|
+
return @groups_cache ||= @group_provider.groups unless @group_provider.nil?
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Usage:
|
70
|
+
# describe group('root') do
|
71
|
+
# it { should exist }
|
72
|
+
# its('gid') { should eq 0 }
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# deprecated has matcher
|
76
|
+
# describe group('root') do
|
77
|
+
# it { should have_gid 0 }
|
78
|
+
# end
|
79
|
+
class Group < Inspec.resource(1)
|
80
|
+
include GroupManagementSelector
|
81
|
+
|
82
|
+
name 'group'
|
83
|
+
desc 'Use the group InSpec audit resource to test groups on the system.'
|
84
|
+
example "
|
85
|
+
describe group('root') do
|
86
|
+
it { should exist }
|
87
|
+
its('gid') { should eq 0 }
|
88
|
+
end
|
89
|
+
"
|
90
|
+
|
91
|
+
def initialize(groupname)
|
92
|
+
@group = groupname
|
93
|
+
@group = @group.downcase unless inspec.os.windows?
|
94
|
+
|
95
|
+
# select group manager
|
96
|
+
@group_provider = select_group_manager(inspec.os)
|
97
|
+
return skip_resource 'The `group` resource is not supported on your OS yet.' if @group_provider.nil?
|
98
|
+
end
|
99
|
+
|
100
|
+
# verifies if a group exists
|
101
|
+
def exists?
|
102
|
+
group_info.entries.size > 0
|
103
|
+
end
|
104
|
+
|
105
|
+
def gid
|
106
|
+
gids = group_info.gids
|
107
|
+
if gids.size == 0
|
108
|
+
nil
|
109
|
+
# the default case should be one group
|
110
|
+
elsif gids.size == 1
|
111
|
+
gids.entries[0]
|
112
|
+
else
|
113
|
+
fail 'found more than one group with the same name, please use `groups` resource'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# implements rspec has matcher, to be compatible with serverspec
|
118
|
+
def has_gid?(compare_gid)
|
119
|
+
gid == compare_gid
|
120
|
+
end
|
121
|
+
|
122
|
+
def local
|
123
|
+
# at this point the implementation only returns local groups
|
124
|
+
true
|
125
|
+
end
|
126
|
+
|
127
|
+
def to_s
|
128
|
+
"Group #{@group}"
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def group_info
|
134
|
+
# we need a local copy for the block
|
135
|
+
group = @group.dup
|
136
|
+
@groups_cache ||= inspec.groups.where { name == group }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
class GroupInfo
|
141
|
+
attr_reader :inspec
|
142
|
+
def initialize(inspec)
|
143
|
+
@inspec = inspec
|
144
|
+
end
|
145
|
+
|
146
|
+
def groups
|
147
|
+
fail 'group provider must implement the `groups` method'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# implements generic unix groups via /etc/group
|
152
|
+
class UnixGroup < GroupInfo
|
153
|
+
def groups
|
154
|
+
inspec.etc_group.entries
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class WindowsGroup < GroupInfo
|
159
|
+
# returns all local groups
|
160
|
+
def groups
|
161
|
+
script = <<-EOH
|
162
|
+
Function ConvertTo-SID { Param([byte[]]$BinarySID)
|
163
|
+
(New-Object System.Security.Principal.SecurityIdentifier($BinarySID,0)).Value
|
164
|
+
}
|
165
|
+
|
166
|
+
$Computername = $Env:Computername
|
167
|
+
$adsi = [ADSI]"WinNT://$Computername"
|
168
|
+
$groups = $adsi.Children | where {$_.SchemaClassName -eq 'group'} | ForEach {
|
169
|
+
$name = $_.Name[0]
|
170
|
+
$sid = ConvertTo-SID -BinarySID $_.ObjectSID[0]
|
171
|
+
$group =[ADSI]$_.Path
|
172
|
+
new-object psobject -property @{name = $group.Name[0]; gid = $sid; domain=$Computername}
|
173
|
+
}
|
174
|
+
$groups | ConvertTo-Json -Depth 3
|
175
|
+
EOH
|
176
|
+
cmd = inspec.powershell(script)
|
177
|
+
# cannot rely on exit code for now, successful command returns exit code 1
|
178
|
+
# return nil if cmd.exit_status != 0, try to parse json
|
179
|
+
begin
|
180
|
+
groups = JSON.parse(cmd.stdout)
|
181
|
+
rescue JSON::ParserError => _e
|
182
|
+
return []
|
183
|
+
end
|
184
|
+
|
185
|
+
# ensure we have an array of groups
|
186
|
+
groups = [groups] if !groups.is_a?(Array)
|
187
|
+
groups
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|