inspec 0.31.0 → 0.32.0
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 +56 -2
- data/Gemfile +6 -2
- data/MAINTAINERS.md +3 -1
- data/MAINTAINERS.toml +1 -1
- data/README.md +20 -1
- data/Rakefile +8 -0
- data/docs/cli.rst +18 -2
- data/docs/resources.rst +55 -3
- data/inspec.gemspec +2 -2
- data/lib/bundles/inspec-supermarket/api.rb +1 -0
- data/lib/fetchers/local.rb +12 -1
- data/lib/fetchers/tar.rb +4 -0
- data/lib/fetchers/url.rb +4 -0
- data/lib/inspec/base_cli.rb +17 -0
- data/lib/inspec/cli.rb +33 -12
- data/lib/inspec/dependencies/dependency_set.rb +50 -5
- data/lib/inspec/dependencies/lockfile.rb +94 -0
- data/lib/inspec/dependencies/requirement.rb +93 -53
- data/lib/inspec/dependencies/resolver.rb +53 -170
- data/lib/inspec/dependencies/vendor_index.rb +11 -4
- data/lib/inspec/dsl.rb +23 -15
- data/lib/inspec/errors.rb +1 -7
- data/lib/inspec/log.rb +2 -25
- data/lib/inspec/profile.rb +68 -28
- data/lib/inspec/profile_context.rb +28 -5
- data/lib/inspec/rspec_json_formatter.rb +48 -25
- data/lib/inspec/rule.rb +7 -0
- data/lib/inspec/runner.rb +26 -15
- data/lib/inspec/runner_rspec.rb +2 -6
- data/lib/inspec/shell.rb +35 -26
- data/lib/inspec/version.rb +2 -1
- data/lib/resources/host.rb +13 -6
- data/lib/resources/iis_site.rb +1 -0
- data/lib/resources/os.rb +1 -1
- data/lib/resources/package.rb +22 -6
- data/lib/resources/port.rb +1 -11
- data/lib/resources/service.rb +9 -0
- data/lib/resources/user.rb +8 -8
- metadata +14 -7
data/lib/inspec/rule.rb
CHANGED
@@ -27,6 +27,7 @@ module Inspec
|
|
27
27
|
@__profile_id = profile_id
|
28
28
|
@__checks = []
|
29
29
|
@__skip_rule = nil
|
30
|
+
@__merge_count = 0
|
30
31
|
|
31
32
|
# evaluate the given definition
|
32
33
|
instance_eval(&block) if block_given?
|
@@ -135,6 +136,10 @@ module Inspec
|
|
135
136
|
rule.instance_variable_set(:@__skip_rule, value)
|
136
137
|
end
|
137
138
|
|
139
|
+
def self.merge_count(rule)
|
140
|
+
rule.instance_variable_get(:@__merge_count)
|
141
|
+
end
|
142
|
+
|
138
143
|
def self.prepare_checks(rule)
|
139
144
|
msg = skip_status(rule)
|
140
145
|
return checks(rule) unless msg
|
@@ -169,6 +174,8 @@ module Inspec
|
|
169
174
|
dst.instance_variable_set(:@__checks, sc) unless sc.empty?
|
170
175
|
sr = skip_status(src)
|
171
176
|
set_skip_rule(dst, sr) unless sr.nil?
|
177
|
+
# increment merge count
|
178
|
+
dst.instance_variable_set(:@__merge_count, merge_count(dst) + 1)
|
172
179
|
end
|
173
180
|
|
174
181
|
private
|
data/lib/inspec/runner.rb
CHANGED
@@ -18,7 +18,7 @@ module Inspec
|
|
18
18
|
extend Forwardable
|
19
19
|
attr_reader :backend, :rules, :attributes
|
20
20
|
def initialize(conf = {})
|
21
|
-
@rules =
|
21
|
+
@rules = []
|
22
22
|
@conf = conf.dup
|
23
23
|
@conf[:logger] ||= Logger.new(nil)
|
24
24
|
|
@@ -131,26 +131,37 @@ module Inspec
|
|
131
131
|
profile.runner_context = ctx
|
132
132
|
end
|
133
133
|
|
134
|
-
append_content(ctx, tests, libs, options)
|
135
|
-
end
|
136
|
-
|
137
|
-
# Returns the profile context used to evaluate the given content.
|
138
|
-
def append_content(ctx, tests, _libs, options = {})
|
139
134
|
# evaluate the test content
|
140
|
-
tests
|
141
|
-
tests.each { |t| add_test_to_context(t, ctx) }
|
135
|
+
Array(tests).each { |t| add_test_to_context(t, ctx) }
|
142
136
|
|
143
137
|
# merge and collect all attributes
|
144
138
|
@attributes |= ctx.attributes
|
145
139
|
|
146
140
|
# process the resulting rules
|
147
|
-
filter_controls(ctx.
|
148
|
-
register_rule(
|
141
|
+
filter_controls(ctx.all_rules, options[:controls]).each do |rule|
|
142
|
+
register_rule(rule)
|
149
143
|
end
|
150
144
|
|
151
145
|
ctx
|
152
146
|
end
|
153
147
|
|
148
|
+
# In some places we read the rules off of the runner, in other
|
149
|
+
# places we read it off of the profile context. To keep the API's
|
150
|
+
# the same, we provide an #all_rules method here as well.
|
151
|
+
def all_rules
|
152
|
+
@rules
|
153
|
+
end
|
154
|
+
|
155
|
+
def register_rules(ctx)
|
156
|
+
new_tests = false
|
157
|
+
ctx.rules.each do |rule_id, rule|
|
158
|
+
next if block_given? && !(yield rule_id, rule)
|
159
|
+
new_tests = true
|
160
|
+
register_rule(rule)
|
161
|
+
end
|
162
|
+
new_tests
|
163
|
+
end
|
164
|
+
|
154
165
|
def_delegator :@test_collector, :run
|
155
166
|
def_delegator :@test_collector, :report
|
156
167
|
def_delegator :@test_collector, :reset
|
@@ -163,9 +174,9 @@ module Inspec
|
|
163
174
|
ctx.load(content, test[:ref], test[:line])
|
164
175
|
end
|
165
176
|
|
166
|
-
def filter_controls(
|
167
|
-
return
|
168
|
-
|
177
|
+
def filter_controls(controls_array, include_list)
|
178
|
+
return controls_array if include_list.nil? || include_list.empty?
|
179
|
+
controls_array.select do |c|
|
169
180
|
id = ::Inspec::Rule.rule_id(c)
|
170
181
|
include_list.include?(id)
|
171
182
|
end
|
@@ -214,8 +225,8 @@ module Inspec
|
|
214
225
|
nil
|
215
226
|
end
|
216
227
|
|
217
|
-
def register_rule(
|
218
|
-
@rules
|
228
|
+
def register_rule(rule)
|
229
|
+
@rules << rule
|
219
230
|
checks = ::Inspec::Rule.prepare_checks(rule)
|
220
231
|
examples = checks.map do |m, a, b|
|
221
232
|
get_check_example(m, a, b)
|
data/lib/inspec/runner_rspec.rb
CHANGED
@@ -18,11 +18,6 @@ module Inspec
|
|
18
18
|
reset
|
19
19
|
end
|
20
20
|
|
21
|
-
def reset
|
22
|
-
reset_tests
|
23
|
-
configure_output
|
24
|
-
end
|
25
|
-
|
26
21
|
# Create a new RSpec example group from arguments and block.
|
27
22
|
#
|
28
23
|
# @param [Type] *args list of arguments for this example
|
@@ -94,10 +89,11 @@ module Inspec
|
|
94
89
|
# Empty the list of registered tests.
|
95
90
|
#
|
96
91
|
# @return [nil]
|
97
|
-
def
|
92
|
+
def reset
|
98
93
|
@tests = RSpec::Core::World.new
|
99
94
|
# resets "pending examples" in reporter
|
100
95
|
RSpec.configuration.reset
|
96
|
+
configure_output
|
101
97
|
end
|
102
98
|
|
103
99
|
private
|
data/lib/inspec/shell.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# author: Christoph Hartmann
|
4
4
|
|
5
5
|
require 'rspec/core/formatters/base_text_formatter'
|
6
|
+
require 'pry'
|
6
7
|
|
7
8
|
module Inspec
|
8
9
|
# A pry based shell for inspec. Given a runner (with a configured backend and
|
@@ -11,18 +12,25 @@ module Inspec
|
|
11
12
|
class Shell
|
12
13
|
def initialize(runner)
|
13
14
|
@runner = runner
|
14
|
-
# load and configure pry
|
15
|
-
require 'pry'
|
16
|
-
configure_pry
|
17
15
|
end
|
18
16
|
|
19
17
|
def start
|
20
|
-
#
|
21
|
-
|
22
|
-
|
18
|
+
# Create an in-memory empty runner so that we can add tests to it later.
|
19
|
+
# This context lasts for the duration of this "start" method call/pry
|
20
|
+
# session.
|
21
|
+
@ctx = @runner.create_context
|
22
|
+
configure_pry
|
23
|
+
|
24
|
+
# This will hold a single evaluation binding context as opened within
|
25
|
+
# the instance_eval context of the anonymous class that the profile
|
26
|
+
# context creates to evaluate each individual test file. We want to
|
27
|
+
# pretend like we are constantly appending to the same file and want
|
28
|
+
# to capture the local variable context from inside said class.
|
29
|
+
@ctx_binding = @ctx.load('binding')
|
30
|
+
@ctx_binding.pry
|
23
31
|
end
|
24
32
|
|
25
|
-
def configure_pry
|
33
|
+
def configure_pry # rubocop:disable Metrics/AbcSize
|
26
34
|
# Remove all hooks and checks
|
27
35
|
Pry.hooks.clear_all
|
28
36
|
that = self
|
@@ -37,25 +45,34 @@ module Inspec
|
|
37
45
|
Pry.prompt = [proc { "#{readline_ignore("\e[0;32m")}#{Pry.config.prompt_name}> #{readline_ignore("\e[0m")}" }]
|
38
46
|
|
39
47
|
# Add a help menu as the default intro
|
40
|
-
Pry.hooks.add_hook(:before_session,
|
48
|
+
Pry.hooks.add_hook(:before_session, 'inspec_intro') do
|
41
49
|
intro
|
42
50
|
end
|
43
51
|
|
44
|
-
#
|
45
|
-
Pry.hooks.add_hook(:
|
46
|
-
|
47
|
-
|
52
|
+
# Track the rules currently registered and what their merge count is.
|
53
|
+
Pry.hooks.add_hook(:before_eval, 'inspec_before_eval') do
|
54
|
+
@current_eval_rules = @ctx.rules.each_with_object({}) do |(rule_id, rule), h|
|
55
|
+
h[rule_id] = Inspec::Rule.merge_count(rule)
|
56
|
+
end
|
48
57
|
@runner.reset
|
49
|
-
|
50
|
-
|
58
|
+
end
|
59
|
+
|
60
|
+
# After pry has evaluated a commanding within the binding context of a
|
61
|
+
# test file, register all the rules it discovered.
|
62
|
+
Pry.hooks.add_hook(:after_eval, 'inspec_after_eval') do
|
63
|
+
@current_eval_new_tests =
|
64
|
+
@runner.register_rules(@ctx) do |rule_id, rule|
|
65
|
+
@current_eval_rules[rule_id] != Inspec::Rule.merge_count(rule)
|
66
|
+
end
|
67
|
+
@runner.run if @current_eval_new_tests
|
51
68
|
end
|
52
69
|
|
53
70
|
# Don't print out control class inspection when the user uses DSL methods.
|
54
71
|
# Instead produce a result of evaluating their control.
|
55
|
-
Pry.config.print = proc do |
|
56
|
-
next if
|
57
|
-
|
58
|
-
pager.print
|
72
|
+
Pry.config.print = proc do |_output_, value, pry|
|
73
|
+
next if @current_eval_new_tests
|
74
|
+
pry.pager.open do |pager|
|
75
|
+
pager.print pry.config.output_prefix
|
59
76
|
Pry::ColorPrinter.pp(value, pager, Pry::Terminal.width! - 1)
|
60
77
|
end
|
61
78
|
end
|
@@ -141,12 +158,4 @@ EOF
|
|
141
158
|
puts Inspec::Resource.registry.keys.join(' ')
|
142
159
|
end
|
143
160
|
end
|
144
|
-
|
145
|
-
class NoSummaryFormatter < RSpec::Core::Formatters::BaseTextFormatter
|
146
|
-
RSpec::Core::Formatters.register self, :dump_summary
|
147
|
-
|
148
|
-
def dump_summary(*_args)
|
149
|
-
# output nothing
|
150
|
-
end
|
151
|
-
end
|
152
161
|
end
|
data/lib/inspec/version.rb
CHANGED
data/lib/resources/host.rb
CHANGED
@@ -32,6 +32,10 @@ module Inspec::Resources
|
|
32
32
|
describe host('example.com') do
|
33
33
|
it { should be_reachable }
|
34
34
|
end
|
35
|
+
|
36
|
+
describe host('example.com', port: '80') do
|
37
|
+
it { should be_reachable }
|
38
|
+
end
|
35
39
|
"
|
36
40
|
|
37
41
|
def initialize(hostname, params = {})
|
@@ -49,7 +53,7 @@ module Inspec::Resources
|
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
52
|
-
# if we get the IP
|
56
|
+
# if we get the IP address, the host is resolvable
|
53
57
|
def resolvable?(type = nil)
|
54
58
|
warn "The `host` resource ignores #{type} parameters. Continue to resolve host." if !type.nil?
|
55
59
|
resolve.nil? || resolve.empty? ? false : true
|
@@ -60,7 +64,7 @@ module Inspec::Resources
|
|
60
64
|
ping.nil? ? false : ping
|
61
65
|
end
|
62
66
|
|
63
|
-
# returns all A records of the IP
|
67
|
+
# returns all A records of the IP address, will return an array
|
64
68
|
def ipaddress
|
65
69
|
resolve.nil? || resolve.empty? ? nil : resolve
|
66
70
|
end
|
@@ -122,9 +126,7 @@ module Inspec::Resources
|
|
122
126
|
# TCP and port: Test-NetConnection -ComputerName www.microsoft.com -RemotePort 80
|
123
127
|
request = "Test-NetConnection -ComputerName #{hostname}"
|
124
128
|
request += " -RemotePort #{port}" unless port.nil?
|
125
|
-
request += '| Select-Object -Property ComputerName,
|
126
|
-
p request
|
127
|
-
request += '| Select-Object -Property ComputerName, PingSucceeded | ConvertTo-Json'
|
129
|
+
request += '| Select-Object -Property ComputerName, TcpTestSucceeded, PingSucceeded | ConvertTo-Json'
|
128
130
|
cmd = inspec.command(request)
|
129
131
|
|
130
132
|
begin
|
@@ -133,7 +135,12 @@ module Inspec::Resources
|
|
133
135
|
return nil
|
134
136
|
end
|
135
137
|
|
136
|
-
|
138
|
+
# Logic being if you provided a port you wanted to check it was open
|
139
|
+
if port.nil?
|
140
|
+
ping['PingSucceeded']
|
141
|
+
else
|
142
|
+
ping['TcpTestSucceeded']
|
143
|
+
end
|
137
144
|
end
|
138
145
|
|
139
146
|
def resolve(hostname)
|
data/lib/resources/iis_site.rb
CHANGED
data/lib/resources/os.rb
CHANGED
@@ -21,7 +21,7 @@ module Inspec::Resources
|
|
21
21
|
"
|
22
22
|
|
23
23
|
# reuse helper methods from backend
|
24
|
-
%w{aix? redhat? debian? suse? bsd? solaris? linux? unix? windows? hpux?}.each do |os_family|
|
24
|
+
%w{aix? redhat? debian? suse? bsd? solaris? linux? unix? windows? hpux? darwin?}.each do |os_family|
|
25
25
|
define_method(os_family.to_sym) do
|
26
26
|
inspec.backend.os.send(os_family)
|
27
27
|
end
|
data/lib/resources/package.rb
CHANGED
@@ -184,14 +184,27 @@ module Inspec::Resources
|
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
187
|
-
# Determines the installed packages on Windows
|
188
|
-
# Currently we use 'Get-WmiObject -Class Win32_Product' as a detection method
|
189
|
-
# TODO: evaluate if alternative methods as proposed by Microsoft are still valid:
|
187
|
+
# Determines the installed packages on Windows using the Windows package registry entries.
|
190
188
|
# @see: http://blogs.technet.com/b/heyscriptingguy/archive/2013/11/15/use-powershell-to-find-installed-software.aspx
|
191
189
|
class WindowsPkg < PkgManagement
|
192
190
|
def info(package_name)
|
191
|
+
search_paths = [
|
192
|
+
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
193
|
+
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
|
194
|
+
]
|
195
|
+
|
196
|
+
# add 64 bit search paths
|
197
|
+
if inspec.os.arch == 'x86_64'
|
198
|
+
search_paths << 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
199
|
+
search_paths << 'HKCU:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
|
200
|
+
end
|
201
|
+
|
193
202
|
# Find the package
|
194
|
-
cmd = inspec.command
|
203
|
+
cmd = inspec.command <<-EOF.gsub(/^\s*/, '')
|
204
|
+
Get-ItemProperty (@("#{search_paths.join('", "')}") | Where-Object { Test-Path $_ }) |
|
205
|
+
Where-Object { $_.DisplayName -like "#{package_name}*" -or $_.PSChildName -like "#{package_name}" } |
|
206
|
+
Select-Object -Property DisplayName,DisplayVersion | ConvertTo-Json
|
207
|
+
EOF
|
195
208
|
|
196
209
|
begin
|
197
210
|
package = JSON.parse(cmd.stdout)
|
@@ -199,10 +212,13 @@ module Inspec::Resources
|
|
199
212
|
return nil
|
200
213
|
end
|
201
214
|
|
215
|
+
# What if we match multiple packages? just pick the first one for now.
|
216
|
+
package = package[0] if package.is_a?(Array)
|
217
|
+
|
202
218
|
{
|
203
|
-
name: package['
|
219
|
+
name: package['DisplayName'],
|
204
220
|
installed: true,
|
205
|
-
version: package['
|
221
|
+
version: package['DisplayVersion'],
|
206
222
|
type: 'windows',
|
207
223
|
}
|
208
224
|
end
|
data/lib/resources/port.rb
CHANGED
@@ -5,17 +5,6 @@
|
|
5
5
|
require 'utils/parser'
|
6
6
|
require 'utils/filter'
|
7
7
|
|
8
|
-
# Usage:
|
9
|
-
# describe port(80) do
|
10
|
-
# it { should be_listening }
|
11
|
-
# its('protocol') {should eq 'tcp'}
|
12
|
-
# end
|
13
|
-
#
|
14
|
-
# not supported serverspec syntax
|
15
|
-
# describe port(80) do
|
16
|
-
# it { should be_listening.with('tcp') }
|
17
|
-
# end
|
18
|
-
#
|
19
8
|
# TODO: currently we return local ip only
|
20
9
|
# TODO: improve handling of same port on multiple interfaces
|
21
10
|
module Inspec::Resources
|
@@ -26,6 +15,7 @@ module Inspec::Resources
|
|
26
15
|
describe port(80) do
|
27
16
|
it { should be_listening }
|
28
17
|
its('protocols') {should eq ['tcp']}
|
18
|
+
its('addresses') {should eq ['127.0.0.1']}
|
29
19
|
end
|
30
20
|
|
31
21
|
describe port.where { protocol =~ /tcp/ && port > 80 } do
|
data/lib/resources/service.rb
CHANGED
@@ -77,6 +77,7 @@ module Inspec::Resources
|
|
77
77
|
it { should be_enabled }
|
78
78
|
it { should be_running }
|
79
79
|
its('type') { should be 'systemd' }
|
80
|
+
its ('startmode') { should be 'Auto'}
|
80
81
|
end
|
81
82
|
|
82
83
|
describe service('service_name').runlevels(3, 5) do
|
@@ -210,6 +211,12 @@ module Inspec::Resources
|
|
210
211
|
info[:description]
|
211
212
|
end
|
212
213
|
|
214
|
+
# returns the service start up mode from info
|
215
|
+
def startmode
|
216
|
+
return nil if info.nil?
|
217
|
+
info[:startmode]
|
218
|
+
end
|
219
|
+
|
213
220
|
def to_s
|
214
221
|
"Service #{@service_name}"
|
215
222
|
end
|
@@ -547,6 +554,7 @@ module Inspec::Resources
|
|
547
554
|
#
|
548
555
|
# Until StartMode is not added to Get-Service, we need to do a workaround
|
549
556
|
# @see: https://connect.microsoft.com/PowerShell/feedback/details/424948/i-would-like-to-see-the-property-starttype-added-to-get-services
|
557
|
+
# Also see: https://msdn.microsoft.com/en-us/library/aa384896(v=vs.85).aspx
|
550
558
|
# Use the following powershell to determine the start mode
|
551
559
|
# PS: Get-WmiObject -Class Win32_Service | Where-Object {$_.Name -eq $name -or $_.DisplayName -eq $name} | Select-Object -Prop
|
552
560
|
# erty Name, StartMode, State, Status | ConvertTo-Json
|
@@ -587,6 +595,7 @@ module Inspec::Resources
|
|
587
595
|
installed: true,
|
588
596
|
running: service_running?(service),
|
589
597
|
enabled: service_enabled?(service),
|
598
|
+
startmode: service['WMI']['StartMode'],
|
590
599
|
type: 'windows',
|
591
600
|
}
|
592
601
|
end
|
data/lib/resources/user.rb
CHANGED
@@ -438,7 +438,7 @@ module Inspec::Resources
|
|
438
438
|
ConvertTo-Json
|
439
439
|
EOH
|
440
440
|
|
441
|
-
cmd = inspec.
|
441
|
+
cmd = inspec.powershell(script)
|
442
442
|
|
443
443
|
# cannot rely on exit code for now, successful command returns exit code 1
|
444
444
|
# return nil if cmd.exit_status != 0, try to parse json
|
@@ -448,18 +448,18 @@ module Inspec::Resources
|
|
448
448
|
return nil
|
449
449
|
end
|
450
450
|
|
451
|
-
|
452
|
-
|
451
|
+
user_hash = params['User'] || {}
|
452
|
+
group_hashes = params['Groups'] || []
|
453
453
|
# if groups is no array, generate one
|
454
|
-
|
455
|
-
|
454
|
+
group_hashes = [group_hashes] unless group_hashes.is_a?(Array)
|
455
|
+
group_names = group_hashes.map { |grp| grp['Caption'] }
|
456
456
|
|
457
457
|
{
|
458
|
-
uid:
|
459
|
-
user:
|
458
|
+
uid: user_hash['SID'],
|
459
|
+
user: user_hash['Caption'],
|
460
460
|
gid: nil,
|
461
461
|
group: nil,
|
462
|
-
groups:
|
462
|
+
groups: group_names,
|
463
463
|
}
|
464
464
|
end
|
465
465
|
|