inspec 0.35.0 → 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -2
- data/Gemfile +6 -0
- data/Rakefile +3 -55
- data/docs/README.md +20 -0
- data/docs/cli.rst +6 -0
- data/docs/dsl_inspec.md +245 -0
- data/docs/dsl_resource.md +93 -0
- data/docs/inspec_and_friends.md +102 -0
- data/docs/matchers.md +136 -0
- data/docs/plugin_kitchen_inspec.html.md +55 -0
- data/docs/profiles.md +271 -0
- data/docs/resources.rst +1 -1
- data/docs/shell.md +150 -0
- data/inspec.gemspec +1 -1
- data/lib/bundles/inspec-compliance/api.rb +28 -18
- data/lib/bundles/inspec-compliance/cli.rb +19 -27
- data/lib/fetchers/git.rb +4 -0
- data/lib/fetchers/local.rb +16 -1
- data/lib/fetchers/mock.rb +4 -0
- data/lib/fetchers/url.rb +40 -12
- data/lib/inspec/base_cli.rb +4 -0
- data/lib/inspec/cli.rb +6 -8
- data/lib/inspec/control_eval_context.rb +8 -0
- data/lib/inspec/dependencies/{vendor_index.rb → cache.rb} +5 -4
- data/lib/inspec/dependencies/dependency_set.rb +8 -14
- data/lib/inspec/dependencies/requirement.rb +10 -20
- data/lib/inspec/dependencies/resolver.rb +2 -2
- data/lib/inspec/dsl.rb +9 -0
- data/lib/inspec/fetcher.rb +1 -1
- data/lib/inspec/objects/test.rb +8 -2
- data/lib/inspec/plugins/fetcher.rb +11 -12
- data/lib/inspec/plugins/resource.rb +3 -0
- data/lib/inspec/profile.rb +60 -14
- data/lib/inspec/profile_context.rb +28 -7
- data/lib/inspec/resource.rb +17 -2
- data/lib/inspec/rspec_json_formatter.rb +80 -35
- data/lib/inspec/runner.rb +42 -18
- data/lib/inspec/shell.rb +5 -16
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/apache_conf.rb +1 -1
- data/lib/resources/gem.rb +1 -0
- data/lib/resources/oneget.rb +1 -0
- data/lib/resources/os.rb +1 -1
- data/lib/resources/package.rb +3 -1
- data/lib/resources/pip.rb +1 -1
- data/lib/resources/ssl.rb +9 -11
- metadata +15 -15
- data/docs/dsl_inspec.rst +0 -259
- data/docs/dsl_resource.rst +0 -90
- data/docs/inspec_and_friends.rst +0 -85
- data/docs/matchers.rst +0 -137
- data/docs/profiles.rst +0 -169
- data/docs/readme.rst +0 -105
- data/docs/shell.rst +0 -130
- data/docs/template.rst +0 -51
@@ -17,7 +17,8 @@ module Inspec
|
|
17
17
|
'attributes' => attributes })
|
18
18
|
end
|
19
19
|
|
20
|
-
attr_reader :attributes, :
|
20
|
+
attr_reader :attributes, :profile_id, :resource_registry, :backend
|
21
|
+
attr_accessor :rules
|
21
22
|
def initialize(profile_id, backend, conf)
|
22
23
|
if backend.nil?
|
23
24
|
fail 'ProfileContext is initiated with a backend == nil. ' \
|
@@ -27,7 +28,8 @@ module Inspec
|
|
27
28
|
@backend = backend
|
28
29
|
@conf = conf.dup
|
29
30
|
@rules = {}
|
30
|
-
@
|
31
|
+
@control_subcontexts = []
|
32
|
+
@lib_subcontexts = []
|
31
33
|
@require_loader = ::Inspec::RequireLoader.new
|
32
34
|
@attributes = []
|
33
35
|
# A local resource registry that only contains resources defined
|
@@ -45,7 +47,7 @@ module Inspec
|
|
45
47
|
end
|
46
48
|
|
47
49
|
def to_resources_dsl
|
48
|
-
Inspec::Resource.create_dsl(
|
50
|
+
Inspec::Resource.create_dsl(self)
|
49
51
|
end
|
50
52
|
|
51
53
|
def control_eval_context
|
@@ -65,20 +67,34 @@ module Inspec
|
|
65
67
|
@conf['profile'].supports_os?
|
66
68
|
end
|
67
69
|
|
68
|
-
def
|
70
|
+
def all_controls
|
69
71
|
ret = @rules.values
|
70
|
-
ret += @
|
72
|
+
ret += @control_subcontexts.map(&:all_rules).flatten
|
71
73
|
ret
|
72
74
|
end
|
75
|
+
alias all_rules all_controls
|
76
|
+
|
77
|
+
def subcontext_by_name(name)
|
78
|
+
found = @lib_subcontexts.find { |c| c.profile_id == name }
|
79
|
+
if !found
|
80
|
+
@lib_subcontexts.each do |c|
|
81
|
+
found = c.subcontext_by_name(name)
|
82
|
+
break if found
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
found
|
87
|
+
end
|
73
88
|
|
74
89
|
def add_resources(context)
|
75
90
|
@resource_registry.merge!(context.resource_registry)
|
76
91
|
control_eval_context.add_resources(context)
|
92
|
+
@lib_subcontexts << context
|
77
93
|
reload_dsl
|
78
94
|
end
|
79
95
|
|
80
96
|
def add_subcontext(context)
|
81
|
-
@
|
97
|
+
@control_subcontexts << context
|
82
98
|
end
|
83
99
|
|
84
100
|
def load_libraries(libs)
|
@@ -132,7 +148,12 @@ module Inspec
|
|
132
148
|
|
133
149
|
def register_rule(r)
|
134
150
|
# get the full ID
|
135
|
-
|
151
|
+
file = if @current_load.nil?
|
152
|
+
'unknown'
|
153
|
+
else
|
154
|
+
@current_load[:file] || 'unknown'
|
155
|
+
end
|
156
|
+
r.instance_variable_set(:@__file, file)
|
136
157
|
r.instance_variable_set(:@__group_title, current_load[:title])
|
137
158
|
|
138
159
|
# add the rule to the registry
|
data/lib/inspec/resource.rb
CHANGED
@@ -6,6 +6,8 @@
|
|
6
6
|
require 'inspec/plugins'
|
7
7
|
|
8
8
|
module Inspec
|
9
|
+
class ProfileNotFound < StandardError; end
|
10
|
+
|
9
11
|
class Resource
|
10
12
|
def self.default_registry
|
11
13
|
@default_registry ||= {}
|
@@ -25,9 +27,22 @@ module Inspec
|
|
25
27
|
#
|
26
28
|
# @param backend [BackendRunner] exposing the target to resources
|
27
29
|
# @return [ResourcesDSL]
|
28
|
-
def self.create_dsl(
|
29
|
-
|
30
|
+
def self.create_dsl(profile_context)
|
31
|
+
backend = profile_context.backend
|
32
|
+
my_registry = profile_context.resource_registry
|
33
|
+
|
30
34
|
Module.new do
|
35
|
+
define_method :resource_class do |profile_name, resource_name|
|
36
|
+
inner_context = if profile_name == profile_context.profile_id
|
37
|
+
profile_context
|
38
|
+
else
|
39
|
+
profile_context.subcontext_by_name(profile_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
fail ProfileNotFound, "Cannot find profile named: #{profile_name}" if inner_context.nil?
|
43
|
+
inner_context.resource_registry[resource_name]
|
44
|
+
end
|
45
|
+
|
31
46
|
my_registry.each do |id, r|
|
32
47
|
define_method id.to_sym do |*args|
|
33
48
|
r.new(backend, id.to_s, *args)
|
@@ -36,11 +36,8 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
|
|
36
36
|
# Called after stop has been called and the run is complete.
|
37
37
|
def dump_summary(summary)
|
38
38
|
@output_hash[:version] = Inspec::VERSION
|
39
|
-
@output_hash[:
|
39
|
+
@output_hash[:statistics] = {
|
40
40
|
duration: summary.duration,
|
41
|
-
example_count: summary.example_count,
|
42
|
-
failure_count: summary.failure_count,
|
43
|
-
skip_count: summary.pending_count,
|
44
41
|
}
|
45
42
|
end
|
46
43
|
|
@@ -86,7 +83,7 @@ class InspecRspecMiniJson < RSpec::Core::Formatters::JsonFormatter
|
|
86
83
|
end
|
87
84
|
end
|
88
85
|
|
89
|
-
class InspecRspecJson < InspecRspecMiniJson
|
86
|
+
class InspecRspecJson < InspecRspecMiniJson # rubocop:disable Metrics/ClassLength
|
90
87
|
RSpec::Core::Formatters.register self, :start, :stop, :dump_summary
|
91
88
|
attr_writer :backend
|
92
89
|
|
@@ -108,7 +105,7 @@ class InspecRspecJson < InspecRspecMiniJson
|
|
108
105
|
def start(_notification)
|
109
106
|
# Note that the default profile may have no name - therefore
|
110
107
|
# the hash may have a valid nil => entry.
|
111
|
-
@profiles_info
|
108
|
+
@profiles_info = @profiles.map(&:info!).map(&:dup)
|
112
109
|
end
|
113
110
|
|
114
111
|
def dump_one_example(example, control)
|
@@ -133,20 +130,59 @@ class InspecRspecJson < InspecRspecMiniJson
|
|
133
130
|
@output_hash[:other_checks] = missing
|
134
131
|
end
|
135
132
|
|
136
|
-
def
|
137
|
-
|
133
|
+
def controls_summary
|
134
|
+
failed = 0
|
135
|
+
skipped = 0
|
136
|
+
passed = 0
|
137
|
+
critical = 0
|
138
|
+
major = 0
|
139
|
+
minor = 0
|
140
|
+
|
141
|
+
@control_tests.each do |control|
|
142
|
+
next if control[:id].start_with? '(generated from '
|
143
|
+
next unless control[:results]
|
144
|
+
if control[:results].any? { |r| r[:status] == 'failed' }
|
145
|
+
failed += 1
|
146
|
+
if control[:impact] >= 0.7
|
147
|
+
critical += 1
|
148
|
+
elsif control[:impact] >= 0.4
|
149
|
+
major += 1
|
150
|
+
else
|
151
|
+
minor += 1
|
152
|
+
end
|
153
|
+
elsif control[:results].any? { |r| r[:status] == 'skipped' }
|
154
|
+
skipped += 1
|
155
|
+
else
|
156
|
+
passed += 1
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
total = failed + passed + skipped
|
161
|
+
|
162
|
+
{ 'total' => total,
|
163
|
+
'failed' => {
|
164
|
+
'total' => failed,
|
165
|
+
'critical' => critical,
|
166
|
+
'major' => major,
|
167
|
+
'minor' => minor,
|
168
|
+
},
|
169
|
+
'skipped' => skipped,
|
170
|
+
'passed' => passed }
|
171
|
+
end
|
172
|
+
|
173
|
+
def tests_summary
|
138
174
|
total = 0
|
139
175
|
failed = 0
|
140
176
|
skipped = 0
|
141
177
|
passed = 0
|
142
178
|
|
143
|
-
@
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
if
|
179
|
+
all_tests = @anonymous_tests + @control_tests
|
180
|
+
all_tests.each do |control|
|
181
|
+
next unless control[:results]
|
182
|
+
control[:results].each do |result|
|
183
|
+
if result[:status] == 'failed'
|
148
184
|
failed += 1
|
149
|
-
elsif
|
185
|
+
elsif result[:status] == 'skipped'
|
150
186
|
skipped += 1
|
151
187
|
else
|
152
188
|
passed += 1
|
@@ -154,16 +190,11 @@ class InspecRspecJson < InspecRspecMiniJson
|
|
154
190
|
end
|
155
191
|
end
|
156
192
|
|
157
|
-
|
193
|
+
{ 'total' => total, 'failed' => failed, 'skipped' => skipped, 'passed' => passed }
|
158
194
|
end
|
159
195
|
|
160
196
|
private
|
161
197
|
|
162
|
-
def profile_info(profile)
|
163
|
-
info = profile.info.dup
|
164
|
-
[info[:name], info]
|
165
|
-
end
|
166
|
-
|
167
198
|
#
|
168
199
|
# TODO(ssd+vj): We should probably solve this by either ensuring the example has
|
169
200
|
# the profile_id of the top level profile when it is included as a dependency, or
|
@@ -171,7 +202,7 @@ class InspecRspecJson < InspecRspecMiniJson
|
|
171
202
|
# this heuristic matching.
|
172
203
|
#
|
173
204
|
def example2profile(example, profiles)
|
174
|
-
profiles.
|
205
|
+
profiles.find { |p| profile_contains_example?(p, example) }
|
175
206
|
end
|
176
207
|
|
177
208
|
def profile_contains_example?(profile, example)
|
@@ -180,7 +211,7 @@ class InspecRspecJson < InspecRspecMiniJson
|
|
180
211
|
# Case 2: The profile contains a control that matches the id of the example
|
181
212
|
if profile[:name] == example[:profile_id]
|
182
213
|
true
|
183
|
-
elsif profile[:controls] && profile[:controls].
|
214
|
+
elsif profile[:controls] && profile[:controls].any? { |x| x[:id] == example[:id] }
|
184
215
|
true
|
185
216
|
else
|
186
217
|
false
|
@@ -188,8 +219,9 @@ class InspecRspecJson < InspecRspecMiniJson
|
|
188
219
|
end
|
189
220
|
|
190
221
|
def example2control(example, profiles)
|
191
|
-
|
192
|
-
|
222
|
+
profile = example2profile(example, profiles)
|
223
|
+
return nil unless profile && profile[:controls]
|
224
|
+
profile[:controls].find { |x| x[:id] == example[:id] }
|
193
225
|
end
|
194
226
|
|
195
227
|
def format_example(example)
|
@@ -246,16 +278,17 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
246
278
|
@current_profile = nil
|
247
279
|
@missing_controls = []
|
248
280
|
@anonymous_tests = []
|
281
|
+
@control_tests = []
|
249
282
|
super(*args)
|
250
283
|
end
|
251
284
|
|
252
|
-
def close(_notification)
|
285
|
+
def close(_notification) # rubocop:disable Metrics/AbcSize
|
253
286
|
flush_current_control
|
254
287
|
output.puts('') unless @current_control.nil?
|
255
288
|
print_tests
|
256
289
|
output.puts('')
|
257
290
|
|
258
|
-
@profiles_info.each do |
|
291
|
+
@profiles_info.each do |profile|
|
259
292
|
next if profile[:already_printed]
|
260
293
|
@current_profile = profile
|
261
294
|
next unless print_current_profile
|
@@ -266,13 +299,20 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
266
299
|
output.puts('')
|
267
300
|
end
|
268
301
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
COLORS['
|
274
|
-
COLORS['
|
275
|
-
|
302
|
+
controls_res = controls_summary
|
303
|
+
tests_res = tests_summary
|
304
|
+
|
305
|
+
s = format('Profile Summary: %s%d successful%s, %s%d failures%s, %s%d skipped%s',
|
306
|
+
COLORS['passed'], controls_res['passed'], COLORS['reset'],
|
307
|
+
COLORS['failed'], controls_res['failed']['total'], COLORS['reset'],
|
308
|
+
COLORS['skipped'], controls_res['skipped'], COLORS['reset'])
|
309
|
+
output.puts(s) if controls_res['total'] > 0
|
310
|
+
|
311
|
+
s = format('Test Summary: %s%d successful%s, %s%d failures%s, %s%d skipped%s',
|
312
|
+
COLORS['passed'], tests_res['passed'], COLORS['reset'],
|
313
|
+
COLORS['failed'], tests_res['failed'], COLORS['reset'],
|
314
|
+
COLORS['skipped'], tests_res['skipped'], COLORS['reset'])
|
315
|
+
output.puts(s) if !@anonymous_tests.empty? || @current_control.nil?
|
276
316
|
end
|
277
317
|
|
278
318
|
private
|
@@ -371,7 +411,11 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
371
411
|
test_color = @colors[test_status]
|
372
412
|
indicator = @indicators[x[:status]]
|
373
413
|
indicator = @indicators['empty'] if indicator.nil?
|
374
|
-
|
414
|
+
if x[:message]
|
415
|
+
msg = x[:code_desc] + "\n" + x[:message]
|
416
|
+
else
|
417
|
+
msg = x[:skip_message] || x[:code_desc]
|
418
|
+
end
|
375
419
|
print_line(
|
376
420
|
color: test_color,
|
377
421
|
indicator: @indicators['small'] + indicator,
|
@@ -414,7 +458,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
414
458
|
return if @current_control.nil?
|
415
459
|
|
416
460
|
prev_profile = @current_profile
|
417
|
-
@current_profile = @profiles_info[@current_control[:profile_id]
|
461
|
+
@current_profile = @profiles_info.find { |i| i[:id] == @current_control[:profile_id] }
|
418
462
|
print_current_profile if prev_profile != @current_profile
|
419
463
|
|
420
464
|
fails, skips, passes, summary_indicator = current_control_infos
|
@@ -425,6 +469,7 @@ class InspecRspecCli < InspecRspecJson # rubocop:disable Metrics/ClassLength
|
|
425
469
|
if control_id.start_with? '(generated from '
|
426
470
|
@anonymous_tests.push(@current_control)
|
427
471
|
else
|
472
|
+
@control_tests.push(@current_control)
|
428
473
|
print_line(
|
429
474
|
color: @colors[summary_indicator] || '',
|
430
475
|
indicator: @indicators[summary_indicator] || @indicators['unknown'],
|
data/lib/inspec/runner.rb
CHANGED
@@ -11,6 +11,7 @@ require 'inspec/profile_context'
|
|
11
11
|
require 'inspec/profile'
|
12
12
|
require 'inspec/metadata'
|
13
13
|
require 'inspec/secrets'
|
14
|
+
require 'inspec/dependencies/cache'
|
14
15
|
# spec requirements
|
15
16
|
|
16
17
|
module Inspec
|
@@ -32,7 +33,6 @@ module Inspec
|
|
32
33
|
extend Forwardable
|
33
34
|
|
34
35
|
def_delegator :@test_collector, :report
|
35
|
-
def_delegator :@test_collector, :reset
|
36
36
|
|
37
37
|
attr_reader :backend, :rules, :attributes
|
38
38
|
def initialize(conf = {})
|
@@ -42,7 +42,8 @@ module Inspec
|
|
42
42
|
@target_profiles = []
|
43
43
|
@controls = @conf[:controls] || []
|
44
44
|
@ignore_supports = @conf[:ignore_supports]
|
45
|
-
|
45
|
+
@create_lockfile = @conf[:create_lockfile]
|
46
|
+
@cache = Inspec::Cache.new(@conf[:cache])
|
46
47
|
@test_collector = @conf.delete(:test_collector) || begin
|
47
48
|
require 'inspec/runner_rspec'
|
48
49
|
RunnerRspec.new(@conf)
|
@@ -64,12 +65,20 @@ module Inspec
|
|
64
65
|
@test_collector.backend = @backend
|
65
66
|
end
|
66
67
|
|
67
|
-
def
|
68
|
-
|
68
|
+
def reset
|
69
|
+
@test_collector.reset
|
70
|
+
@target_profiles.each do |profile|
|
71
|
+
profile.runner_context.rules = {}
|
72
|
+
end
|
73
|
+
@rules = []
|
74
|
+
end
|
75
|
+
|
76
|
+
def load
|
69
77
|
all_controls = []
|
70
78
|
|
71
79
|
@target_profiles.each do |profile|
|
72
80
|
@test_collector.add_profile(profile)
|
81
|
+
write_lockfile(profile) if @create_lockfile
|
73
82
|
profile.locked_dependencies
|
74
83
|
profile.load_libraries
|
75
84
|
@attributes |= profile.runner_context.attributes
|
@@ -79,7 +88,27 @@ module Inspec
|
|
79
88
|
all_controls.each do |rule|
|
80
89
|
register_rule(rule)
|
81
90
|
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def run(with = nil)
|
94
|
+
Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
|
95
|
+
load
|
96
|
+
run_tests(with)
|
97
|
+
end
|
82
98
|
|
99
|
+
def write_lockfile(profile)
|
100
|
+
return false if !profile.writable?
|
101
|
+
|
102
|
+
if profile.lockfile_exists?
|
103
|
+
Inspec::Log.debug "Using existing lockfile #{profile.lockfile_path}"
|
104
|
+
else
|
105
|
+
Inspec::Log.debug "Creating lockfile: #{profile.lockfile_path}"
|
106
|
+
lockfile = profile.generate_lockfile
|
107
|
+
File.write(profile.lockfile_path, lockfile.to_yaml)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def run_tests(with = nil)
|
83
112
|
@test_collector.run(with)
|
84
113
|
end
|
85
114
|
|
@@ -126,6 +155,7 @@ module Inspec
|
|
126
155
|
#
|
127
156
|
def add_target(target, _opts = [])
|
128
157
|
profile = Inspec::Profile.for_target(target,
|
158
|
+
cache: @cache,
|
129
159
|
backend: @backend,
|
130
160
|
controls: @controls,
|
131
161
|
attributes: @conf[:attributes])
|
@@ -133,20 +163,6 @@ module Inspec
|
|
133
163
|
@target_profiles << profile if supports_profile?(profile)
|
134
164
|
end
|
135
165
|
|
136
|
-
#
|
137
|
-
# This is used by inspec-shell and inspec-detect. This should
|
138
|
-
# probably be cleaned up a bit.
|
139
|
-
#
|
140
|
-
# @params [Hash] Options
|
141
|
-
# @returns [Inspec::ProfileContext]
|
142
|
-
#
|
143
|
-
def create_context(options = {})
|
144
|
-
meta = options[:metadata]
|
145
|
-
profile_id = nil
|
146
|
-
profile_id = meta.params[:name] unless meta.nil?
|
147
|
-
Inspec::ProfileContext.new(profile_id, @backend, @conf.merge(options))
|
148
|
-
end
|
149
|
-
|
150
166
|
def supports_profile?(profile)
|
151
167
|
return true if @ignore_supports
|
152
168
|
|
@@ -180,6 +196,14 @@ module Inspec
|
|
180
196
|
new_tests
|
181
197
|
end
|
182
198
|
|
199
|
+
def eval_with_virtual_profile(command)
|
200
|
+
require 'fetchers/mock'
|
201
|
+
add_target({ 'inspec.yml' => 'name: inspec-shell' })
|
202
|
+
our_profile = @target_profiles.first
|
203
|
+
ctx = our_profile.runner_context
|
204
|
+
ctx.load(command)
|
205
|
+
end
|
206
|
+
|
183
207
|
private
|
184
208
|
|
185
209
|
def block_source_info(block)
|