inspec 0.16.3 → 0.16.4
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 +26 -2
- data/docs/dsl_inspec.rst +43 -0
- data/examples/inheritance/controls/example.rb +1 -1
- data/examples/inheritance/inspec.yml +1 -1
- data/examples/profile/README.md +26 -8
- data/examples/profile/controls/gordon.rb +4 -2
- data/examples/profile/controls/meta.rb +34 -0
- data/examples/profile/inspec.yml +1 -1
- data/examples/profile/libraries/gordon_config.rb +28 -2
- data/lib/bundles/inspec-compliance/cli.rb +3 -1
- data/lib/inspec/backend.rb +5 -0
- data/lib/inspec/cli.rb +2 -0
- data/lib/inspec/profile_context.rb +2 -0
- data/lib/inspec/runner.rb +1 -1
- data/lib/inspec/shell.rb +1 -1
- data/lib/inspec/version.rb +1 -1
- data/test/functional/helper.rb +36 -0
- data/test/functional/inheritance_test.rb +49 -0
- data/test/functional/inspec_archive_test.rb +80 -0
- data/test/functional/inspec_exec_test.rb +141 -0
- data/test/functional/inspec_json_test.rb +104 -0
- data/test/functional/inspec_test.rb +54 -0
- data/test/unit/profile_context_test.rb +3 -3
- metadata +15 -7
- data/examples/resource/controls/tiny.rb +0 -3
- data/examples/resource/inspec.yml +0 -10
- data/examples/resource/libraries/tiny.rb +0 -3
- data/test/functional/command_test.rb +0 -390
@@ -1,10 +0,0 @@
|
|
1
|
-
name: resource
|
2
|
-
title: InSpec Example Resources
|
3
|
-
maintainer: Chef Software, Inc.
|
4
|
-
copyright: Chef Software, Inc.
|
5
|
-
copyright_email: support@chef.io
|
6
|
-
license: Apache 2 license
|
7
|
-
summary: Demonstrates the use of InSpec custom resources
|
8
|
-
version: 1.0.0
|
9
|
-
supports:
|
10
|
-
- linux
|
@@ -1,390 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
# author: Dominik Richter
|
3
|
-
# author: Christoph Hartmann
|
4
|
-
|
5
|
-
require 'helper'
|
6
|
-
require 'minitest/hell'
|
7
|
-
class Minitest::Test
|
8
|
-
parallelize_me!
|
9
|
-
end
|
10
|
-
|
11
|
-
describe 'Inspec::InspecCLI' do
|
12
|
-
let(:repo_path) { File.expand_path(File.join( __FILE__, '..', '..', '..')) }
|
13
|
-
let(:exec_inspec) { File.join(repo_path, 'bin', 'inspec') }
|
14
|
-
let(:profile_path) { File.join(repo_path, 'test', 'unit', 'mock', 'profiles') }
|
15
|
-
let(:examples_path) { File.join(repo_path, 'examples') }
|
16
|
-
let(:dst) {
|
17
|
-
# create a temporary path, but we only want an auto-clean helper
|
18
|
-
# so remove the file and give back the path
|
19
|
-
res = Tempfile.new('inspec-shred')
|
20
|
-
FileUtils.rm(res.path)
|
21
|
-
TMP_CACHE[res.path] = res
|
22
|
-
}
|
23
|
-
|
24
|
-
def inspec(commandline)
|
25
|
-
CMD.run_command("#{exec_inspec} #{commandline}")
|
26
|
-
end
|
27
|
-
|
28
|
-
describe 'detect' do
|
29
|
-
it 'runs well on all nodes' do
|
30
|
-
out = inspec('detect')
|
31
|
-
out.stderr.must_equal ''
|
32
|
-
out.exit_status.must_equal 0
|
33
|
-
j = JSON.load(out.stdout)
|
34
|
-
j.keys.must_include 'name'
|
35
|
-
j.keys.must_include 'family'
|
36
|
-
j.keys.must_include 'arch'
|
37
|
-
j.keys.must_include 'release'
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
describe 'version' do
|
42
|
-
it 'provides the version number on stdout' do
|
43
|
-
out = inspec('version')
|
44
|
-
out.stderr.must_equal ''
|
45
|
-
out.exit_status.must_equal 0
|
46
|
-
out.stdout.must_equal Inspec::VERSION+"\n"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe 'shell' do
|
51
|
-
it 'provides a help command' do
|
52
|
-
out = CMD.run_command("echo \"help\nexit\" | #{exec_inspec} shell")
|
53
|
-
out.exit_status.must_equal 0
|
54
|
-
out.stdout.must_include 'Available commands:'
|
55
|
-
out.stdout.must_include 'You are currently running on:'
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'exposes all resources' do
|
59
|
-
out = CMD.run_command("echo \"os\nexit\" | #{exec_inspec} shell")
|
60
|
-
out.exit_status.must_equal 0
|
61
|
-
out.stdout.must_match /^=> .*Operating.* .*System.* .*Detection.*$/
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
describe 'example profile' do
|
66
|
-
let(:path) { File.join(examples_path, 'profile') }
|
67
|
-
|
68
|
-
it 'check is successful' do
|
69
|
-
out = inspec('check ' + path)
|
70
|
-
out.stdout.must_match /Valid.*true/
|
71
|
-
out.exit_status.must_equal 0
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'archive is successful' do
|
75
|
-
out = inspec('archive ' + path + ' --overwrite')
|
76
|
-
out.exit_status.must_equal 0
|
77
|
-
out.stdout.must_match /Generate archive [^ ]*profile.tar.gz/
|
78
|
-
out.stdout.must_include 'Finished archive generation.'
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'archives to output file' do
|
82
|
-
out = inspec('archive ' + path + ' --output ' + dst.path)
|
83
|
-
out.stderr.must_equal ''
|
84
|
-
out.stdout.must_include 'Generate archive '+dst.path
|
85
|
-
out.stdout.must_include 'Finished archive generation.'
|
86
|
-
out.exit_status.must_equal 0
|
87
|
-
File.exist?(dst.path).must_equal true
|
88
|
-
end
|
89
|
-
|
90
|
-
it 'auto-archives when no --output is given' do
|
91
|
-
auto_dst = File.join(repo_path, 'profile.tar.gz')
|
92
|
-
out = inspec('archive ' + path + ' --overwrite')
|
93
|
-
out.stderr.must_equal ''
|
94
|
-
out.stdout.must_include 'Generate archive '+auto_dst
|
95
|
-
out.stdout.must_include 'Finished archive generation.'
|
96
|
-
out.exit_status.must_equal 0
|
97
|
-
File.exist?(auto_dst).must_equal true
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'archive on invalid archive' do
|
101
|
-
out = inspec('archive /proc --output ' + dst.path)
|
102
|
-
# out.stdout.must_equal '' => we have partial stdout output right now
|
103
|
-
out.stderr.must_include "Don't understand inspec profile in \"/proc\""
|
104
|
-
out.exit_status.must_equal 1
|
105
|
-
File.exist?(dst.path).must_equal false
|
106
|
-
end
|
107
|
-
|
108
|
-
it 'archive wont overwrite existing files' do
|
109
|
-
x = rand.to_s
|
110
|
-
File.write(dst.path, x)
|
111
|
-
out = inspec('archive ' + path + ' --output ' + dst.path)
|
112
|
-
out.stderr.must_equal '' # uh...
|
113
|
-
out.stdout.must_include "Archive #{dst.path} exists already. Use --overwrite."
|
114
|
-
out.exit_status.must_equal 1
|
115
|
-
File.read(dst.path).must_equal x
|
116
|
-
end
|
117
|
-
|
118
|
-
it 'archive will overwrite files if necessary' do
|
119
|
-
x = rand.to_s
|
120
|
-
File.write(dst.path, x)
|
121
|
-
out = inspec('archive ' + path + ' --output ' + dst.path + ' --overwrite')
|
122
|
-
out.stderr.must_equal ''
|
123
|
-
out.stdout.must_include 'Generate archive '+dst.path
|
124
|
-
out.exit_status.must_equal 0
|
125
|
-
File.read(dst.path).wont_equal x
|
126
|
-
end
|
127
|
-
|
128
|
-
it 'creates valid tar.gz archives' do
|
129
|
-
out = inspec('archive ' + path + ' --output ' + dst.path + ' --tar')
|
130
|
-
out.stderr.must_equal ''
|
131
|
-
out.stdout.must_include 'Generate archive '+dst.path
|
132
|
-
out.exit_status.must_equal 0
|
133
|
-
t = Zlib::GzipReader.open(dst.path)
|
134
|
-
Gem::Package::TarReader.new(t).entries.map(&:header).map(&:name).must_include 'inspec.yml'
|
135
|
-
end
|
136
|
-
|
137
|
-
it 'creates valid zip archives' do
|
138
|
-
out = inspec('archive ' + path + ' --output ' + dst.path + ' --zip')
|
139
|
-
out.stderr.must_equal ''
|
140
|
-
out.stdout.must_include 'Generate archive '+dst.path
|
141
|
-
out.exit_status.must_equal 0
|
142
|
-
Zip::File.new(dst.path).entries.map(&:name).must_include 'inspec.yml'
|
143
|
-
end
|
144
|
-
|
145
|
-
it 'read the profile json' do
|
146
|
-
out = inspec('json ' + path)
|
147
|
-
out.stderr.must_equal ''
|
148
|
-
out.exit_status.must_equal 0
|
149
|
-
s = out.stdout
|
150
|
-
JSON.load(s).must_be_kind_of Hash
|
151
|
-
end
|
152
|
-
|
153
|
-
describe 'json profile data' do
|
154
|
-
let(:json) { JSON.load(inspec('json '+path).stdout) }
|
155
|
-
|
156
|
-
it 'has a name' do
|
157
|
-
json['name'].must_equal 'profile'
|
158
|
-
end
|
159
|
-
|
160
|
-
it 'has a title' do
|
161
|
-
json['title'].must_equal 'InSpec Example Profile'
|
162
|
-
end
|
163
|
-
|
164
|
-
it 'has a summary' do
|
165
|
-
json['summary'].must_equal 'Demonstrates the use of InSpec Compliance Profile'
|
166
|
-
end
|
167
|
-
|
168
|
-
it 'has a version' do
|
169
|
-
json['version'].must_equal '1.0.0'
|
170
|
-
end
|
171
|
-
|
172
|
-
it 'has a maintainer' do
|
173
|
-
json['maintainer'].must_equal 'Chef Software, Inc.'
|
174
|
-
end
|
175
|
-
|
176
|
-
it 'has a copyright' do
|
177
|
-
json['copyright'].must_equal 'Chef Software, Inc.'
|
178
|
-
end
|
179
|
-
|
180
|
-
it 'has rules' do
|
181
|
-
json['rules'].length.must_equal 2 # TODO: flatten out or search deeper!
|
182
|
-
end
|
183
|
-
|
184
|
-
describe 'a rule' do
|
185
|
-
let(:rule) { json['rules']['controls/example.rb']['rules']['tmp-1.0'] }
|
186
|
-
|
187
|
-
it 'has a title' do
|
188
|
-
rule['title'].must_equal 'Create /tmp directory'
|
189
|
-
end
|
190
|
-
|
191
|
-
it 'has a description' do
|
192
|
-
rule['desc'].must_equal 'An optional description...'
|
193
|
-
end
|
194
|
-
|
195
|
-
it 'has an impact' do
|
196
|
-
rule['impact'].must_equal 0.7
|
197
|
-
end
|
198
|
-
|
199
|
-
it 'has a ref' do
|
200
|
-
rule['refs'].must_equal([{'ref' => 'Document A-12', 'url' => 'http://...'}])
|
201
|
-
end
|
202
|
-
|
203
|
-
it 'has a source location' do
|
204
|
-
loc = File.join(path, '/controls/example.rb')
|
205
|
-
rule['source_location'].must_equal [loc, 8]
|
206
|
-
end
|
207
|
-
|
208
|
-
it 'has a the source code' do
|
209
|
-
rule['code'].must_match /\Acontrol \"tmp-1.0\" do.*end\n\Z/m
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
it 'writes json to file' do
|
215
|
-
out = inspec('json ' + path + ' --output ' + dst.path)
|
216
|
-
out.stderr.must_equal ''
|
217
|
-
out.exit_status.must_equal 0
|
218
|
-
hm = JSON.load(File.read(dst.path))
|
219
|
-
hm['name'].must_equal 'profile'
|
220
|
-
hm['rules'].length.must_equal 2 # TODO: flatten out or search deeper!
|
221
|
-
end
|
222
|
-
|
223
|
-
it 'can execute the profile' do
|
224
|
-
out = inspec('exec ' + path)
|
225
|
-
out.stderr.must_equal ''
|
226
|
-
out.exit_status.must_equal 0
|
227
|
-
out.stdout.must_match /^Pending: /
|
228
|
-
out.stdout.must_include '3 examples, 0 failures, 1 pending'
|
229
|
-
end
|
230
|
-
|
231
|
-
it 'can execute the profile with the json formatter' do
|
232
|
-
out = inspec('exec ' + path + ' --format json')
|
233
|
-
out.stderr.must_equal ''
|
234
|
-
out.exit_status.must_equal 0
|
235
|
-
JSON.load(out.stdout).must_be_kind_of Hash
|
236
|
-
end
|
237
|
-
|
238
|
-
describe 'execute a profile with json formatting' do
|
239
|
-
let(:json) { JSON.load(inspec('exec ' + path + ' --format json').stdout) }
|
240
|
-
let(:examples) { json['examples'] }
|
241
|
-
let(:ex1) { examples.find{|x| x['id'] == 'tmp-1.0'} }
|
242
|
-
let(:ex2) { examples.find{|x| x['id'] =~ /generated/} }
|
243
|
-
let(:ex3) { examples.find{|x| x['id'] == 'gordon-1.0'} }
|
244
|
-
|
245
|
-
it 'must have 3 examples' do
|
246
|
-
json['examples'].length.must_equal 3
|
247
|
-
end
|
248
|
-
|
249
|
-
it 'id in json' do
|
250
|
-
examples.find { |ex| !ex.key? 'id' }.must_be :nil?
|
251
|
-
end
|
252
|
-
|
253
|
-
it 'impact in json' do
|
254
|
-
ex1['impact'].must_equal 0.7
|
255
|
-
ex2['impact'].must_be :nil?
|
256
|
-
end
|
257
|
-
|
258
|
-
it 'status in json' do
|
259
|
-
ex1['status'].must_equal 'passed'
|
260
|
-
ex3['status'].must_equal 'pending'
|
261
|
-
end
|
262
|
-
|
263
|
-
it 'pending message in json' do
|
264
|
-
ex1['pending_message'].must_be :nil?
|
265
|
-
ex3['pending_message'].must_equal 'Not yet implemented'
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
describe 'execute a profile with fulljson formatting' do
|
270
|
-
let(:json) { JSON.load(inspec('exec ' + path + ' --format fulljson').stdout) }
|
271
|
-
let(:examples) { json['examples'] }
|
272
|
-
let(:metadata) { json['profiles'][0] }
|
273
|
-
let(:ex1) { examples.find{|x| x['id'] == 'tmp-1.0'} }
|
274
|
-
let(:ex2) { examples.find{|x| x['id'] =~ /generated/} }
|
275
|
-
let(:ex3) { examples.find{|x| x['id'] == 'gordon-1.0'} }
|
276
|
-
|
277
|
-
it 'has all the metadata' do
|
278
|
-
metadata.must_equal({
|
279
|
-
"name" => "profile",
|
280
|
-
"title" => "InSpec Example Profile",
|
281
|
-
"maintainer" => "Chef Software, Inc.",
|
282
|
-
"copyright" => "Chef Software, Inc.",
|
283
|
-
"copyright_email" => "support@chef.io",
|
284
|
-
"license" => "Apache 2 license",
|
285
|
-
"summary" => "Demonstrates the use of InSpec Compliance Profile",
|
286
|
-
"version" => "1.0.0",
|
287
|
-
"supports" => [{"os-family" => "linux"}]
|
288
|
-
})
|
289
|
-
end
|
290
|
-
|
291
|
-
it 'must have 3 examples' do
|
292
|
-
json['examples'].length.must_equal 3
|
293
|
-
end
|
294
|
-
|
295
|
-
it 'id in json' do
|
296
|
-
examples.find { |ex| !ex.key? 'id' }.must_be :nil?
|
297
|
-
end
|
298
|
-
|
299
|
-
it 'title in json' do
|
300
|
-
ex3['title'].must_equal 'Verify the version number of Gordon'
|
301
|
-
end
|
302
|
-
|
303
|
-
it 'desc in json' do
|
304
|
-
ex3['desc'].must_equal 'An optional description...'
|
305
|
-
end
|
306
|
-
|
307
|
-
it 'code in json' do
|
308
|
-
ex3['code'].wont_be :nil?
|
309
|
-
end
|
310
|
-
|
311
|
-
it 'code_desc in json' do
|
312
|
-
ex3['code_desc'].wont_be :nil?
|
313
|
-
end
|
314
|
-
|
315
|
-
it 'impact in json' do
|
316
|
-
ex1['impact'].must_equal 0.7
|
317
|
-
ex2['impact'].must_be :nil?
|
318
|
-
end
|
319
|
-
|
320
|
-
it 'status in json' do
|
321
|
-
ex1['status'].must_equal 'passed'
|
322
|
-
ex3['status'].must_equal 'pending'
|
323
|
-
end
|
324
|
-
|
325
|
-
it 'ref in json' do
|
326
|
-
ex1['ref'].must_match %r{examples/profile/controls/example.rb$}
|
327
|
-
end
|
328
|
-
|
329
|
-
it 'ref_line in json' do
|
330
|
-
ex1['ref_line'].must_equal 14
|
331
|
-
end
|
332
|
-
|
333
|
-
it 'run_time in json' do
|
334
|
-
ex1['run_time'].wont_be :nil?
|
335
|
-
end
|
336
|
-
|
337
|
-
it 'start_time in json' do
|
338
|
-
ex1['start_time'].wont_be :nil?
|
339
|
-
end
|
340
|
-
|
341
|
-
it 'pending message in json' do
|
342
|
-
ex1['pending'].must_be :nil?
|
343
|
-
ex3['pending'].must_equal "Can't find file \"/etc/gordon/config.yaml\""
|
344
|
-
end
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
describe 'example inheritance profile' do
|
349
|
-
let(:path) { File.join(examples_path, 'inheritance') }
|
350
|
-
|
351
|
-
[
|
352
|
-
'archive %s --overwrite',
|
353
|
-
'check %s',
|
354
|
-
'json %s',
|
355
|
-
].each do |cmd|
|
356
|
-
it cmd[/^\w/] + ' fails without --profiles-path' do
|
357
|
-
out = inspec(format(cmd, path))
|
358
|
-
out.stderr.must_include 'You must supply a --profiles-path to inherit'
|
359
|
-
# out.stdout.must_equal '' => we still get partial output
|
360
|
-
out.exit_status.must_equal 1
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
it 'check succeeds with --profiles-path' do
|
365
|
-
out = inspec('check ' + path + ' --profiles-path ' + examples_path)
|
366
|
-
out.stderr.must_equal ''
|
367
|
-
out.stdout.must_match /Valid.*true/
|
368
|
-
out.exit_status.must_equal 0
|
369
|
-
end
|
370
|
-
|
371
|
-
it 'archive is successful with --profiles-path' do
|
372
|
-
out = inspec('archive ' + path + ' --output ' + dst.path + ' --profiles-path ' + examples_path)
|
373
|
-
out.stderr.must_equal ''
|
374
|
-
out.stdout.must_include 'Generate archive '+dst.path
|
375
|
-
out.stdout.must_include 'Finished archive generation.'
|
376
|
-
out.exit_status.must_equal 0
|
377
|
-
File.exist?(dst.path).must_equal true
|
378
|
-
end
|
379
|
-
|
380
|
-
it 'read the profile json with --profiles-path' do
|
381
|
-
out = inspec('json ' + path + ' --profiles-path '+examples_path)
|
382
|
-
out.stderr.must_equal ''
|
383
|
-
out.exit_status.must_equal 0
|
384
|
-
s = out.stdout
|
385
|
-
hm = JSON.load(s)
|
386
|
-
hm['name'].must_equal 'inheritance'
|
387
|
-
hm['rules'].length.must_equal 1 # TODO: flatten out or search deeper!
|
388
|
-
end
|
389
|
-
end
|
390
|
-
end
|