inspec 1.0.0.beta2 → 1.0.0.beta3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|