inspec 0.15.0 → 0.16.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 +40 -2
- data/Gemfile +2 -3
- data/README.md +2 -0
- data/Rakefile +8 -0
- data/bin/inspec +1 -157
- data/docs/resources.rst +79 -78
- data/examples/profile/controls/example.rb +3 -1
- data/lib/fetchers/mock.rb +27 -0
- data/lib/fetchers/tar.rb +3 -2
- data/lib/fetchers/zip.rb +3 -1
- data/lib/inspec/cli.rb +164 -0
- data/lib/inspec/plugins/resource.rb +6 -2
- data/lib/inspec/profile.rb +28 -17
- data/lib/inspec/resource.rb +5 -1
- data/lib/inspec/rspec_json_formatter.rb +42 -0
- data/lib/inspec/rule.rb +24 -1
- data/lib/inspec/runner.rb +15 -7
- data/lib/inspec/runner_mock.rb +6 -1
- data/lib/inspec/runner_rspec.rb +29 -1
- data/lib/inspec/version.rb +1 -1
- data/lib/resources/{script.rb → powershell.rb} +19 -5
- data/lib/resources/registry_key.rb +1 -1
- data/test/{integration/cookbooks → cookbooks}/os_prepare/files/empty.iso +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/files/example.csv +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/files/example.ini +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/files/example.json +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/files/example.yml +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/metadata.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/_runit_service_centos.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/_upstart_service_centos.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/apache.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/apt.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/auditctl.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/default.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/file.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/iptables.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/json_yaml_csv_ini.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/mount.rb +2 -2
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/package.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/postgres.rb +6 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/registry_key.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/recipes/service.rb +0 -0
- data/test/{integration/cookbooks → cookbooks}/os_prepare/templates/default/sv-default-svlog-run.erb +0 -0
- data/test/functional/command_test.rb +390 -0
- data/test/helper.rb +6 -0
- data/test/integration/{test/integration/default → default}/_debug_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/apache_conf_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/apt_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/auditd_rules_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/compare_matcher_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/csv_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/etc_group_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/file_spec.rb +3 -2
- data/test/integration/{test/integration/default → default}/group_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/ini_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/iptables_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/json_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/kernel_module_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/kernel_parameter_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/mount_spec.rb +1 -1
- data/test/integration/{test/integration/default → default}/os_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/package_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/port_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/postgres_session_spec.rb +0 -0
- data/test/integration/default/powershell_spec.rb +13 -0
- data/test/integration/{test/integration/default → default}/registry_key_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/secpol_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/service_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/user_spec.rb +0 -0
- data/test/integration/{test/integration/default → default}/yaml_spec.rb +0 -0
- data/test/unit/control_test.rb +58 -0
- data/test/unit/fetchers/mock_test.rb +43 -0
- data/test/unit/plugins/resource_test.rb +60 -0
- data/test/unit/resources/{script_test.rb → powershell_test.rb} +10 -1
- metadata +107 -101
- data/test/integration/.kitchen.ec2.yml +0 -75
- data/test/integration/.kitchen.yml +0 -45
- data/test/integration/Berksfile +0 -5
data/lib/inspec/runner.rb
CHANGED
|
@@ -55,25 +55,26 @@ module Inspec
|
|
|
55
55
|
def add_profile(profile, options = {})
|
|
56
56
|
return unless options[:ignore_supports] ||
|
|
57
57
|
profile.metadata.supports_transport?(@backend)
|
|
58
|
+
@test_collector.add_profile(profile)
|
|
58
59
|
|
|
59
60
|
libs = profile.libraries.map do |k, v|
|
|
60
61
|
{ ref: k, content: v }
|
|
61
62
|
end
|
|
62
63
|
|
|
63
|
-
profile.tests.
|
|
64
|
+
tests = profile.tests.map do |ref, content|
|
|
64
65
|
r = profile.source_reader.target.abs_path(ref)
|
|
65
|
-
|
|
66
|
-
add_content(test, libs, options)
|
|
66
|
+
{ ref: r, content: content }
|
|
67
67
|
end
|
|
68
|
+
|
|
69
|
+
add_content(tests, libs, options)
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
def create_context(options = {})
|
|
71
73
|
Inspec::ProfileContext.new(@profile_id, @backend, @conf.merge(options))
|
|
72
74
|
end
|
|
73
75
|
|
|
74
|
-
def add_content(
|
|
75
|
-
|
|
76
|
-
return if content.nil? || content.empty?
|
|
76
|
+
def add_content(tests, libs, options = {})
|
|
77
|
+
return if tests.nil? || tests.empty?
|
|
77
78
|
|
|
78
79
|
# load all libraries
|
|
79
80
|
ctx = create_context(options)
|
|
@@ -83,7 +84,8 @@ module Inspec
|
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
# evaluate the test content
|
|
86
|
-
|
|
87
|
+
tests = [tests] unless tests.is_a? Array
|
|
88
|
+
tests.each { |t| add_test_to_context(t, ctx) }
|
|
87
89
|
|
|
88
90
|
# process the resulting rules
|
|
89
91
|
filter_controls(ctx.rules, options[:controls]).each do |rule_id, rule|
|
|
@@ -96,6 +98,12 @@ module Inspec
|
|
|
96
98
|
|
|
97
99
|
private
|
|
98
100
|
|
|
101
|
+
def add_test_to_context(test, ctx)
|
|
102
|
+
content = test[:content]
|
|
103
|
+
return if content.nil? || content.empty?
|
|
104
|
+
ctx.load(content, test[:ref], test[:line] || 1)
|
|
105
|
+
end
|
|
106
|
+
|
|
99
107
|
def filter_controls(controls_map, include_list)
|
|
100
108
|
return controls_map if include_list.nil? || include_list.empty?
|
|
101
109
|
controls_map.select { |k, _| include_list.include?(k) }
|
data/lib/inspec/runner_mock.rb
CHANGED
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
module Inspec
|
|
6
6
|
class RunnerMock
|
|
7
|
-
attr_reader :tests
|
|
7
|
+
attr_reader :tests, :profiles
|
|
8
8
|
def initialize
|
|
9
9
|
@tests = []
|
|
10
|
+
@profiles = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add_profile(profile)
|
|
14
|
+
@profiles.push(profile)
|
|
10
15
|
end
|
|
11
16
|
|
|
12
17
|
def add_test(example, _rule_id, _rule)
|
data/lib/inspec/runner_rspec.rb
CHANGED
|
@@ -29,6 +29,18 @@ module Inspec
|
|
|
29
29
|
RSpec::Core::ExampleGroup.describe(*args, &block)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Add a full profile to the runner. Only pulls in metadata
|
|
33
|
+
#
|
|
34
|
+
# @param [Inspec::Profile] profile
|
|
35
|
+
# @return [nil]
|
|
36
|
+
def add_profile(profile)
|
|
37
|
+
RSpec.configuration.formatters
|
|
38
|
+
.find_all { |c| c.is_a? InspecRspecFormatter }
|
|
39
|
+
.each do |fmt|
|
|
40
|
+
fmt.add_profile(profile)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
32
44
|
# Add an example group to the list of registered tests.
|
|
33
45
|
#
|
|
34
46
|
# @param [RSpecExampleGroup] example test
|
|
@@ -75,7 +87,15 @@ module Inspec
|
|
|
75
87
|
#
|
|
76
88
|
# @return [nil]
|
|
77
89
|
def configure_output
|
|
78
|
-
|
|
90
|
+
if !@conf['output'] || @conf['output'] == '-'
|
|
91
|
+
RSpec.configuration.output_stream = $stdout
|
|
92
|
+
else
|
|
93
|
+
RSpec.configuration.output_stream = @conf['output']
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
format = @conf['format'] || 'progress'
|
|
97
|
+
format = 'InspecRspecFormatter' if format == 'fulljson'
|
|
98
|
+
RSpec.configuration.add_formatter(format)
|
|
79
99
|
RSpec.configuration.color = @conf['color']
|
|
80
100
|
|
|
81
101
|
setup_reporting if @conf['report']
|
|
@@ -96,9 +116,17 @@ module Inspec
|
|
|
96
116
|
def set_rspec_ids(example, id, rule)
|
|
97
117
|
example.metadata[:id] = id
|
|
98
118
|
example.metadata[:impact] = rule.impact
|
|
119
|
+
example.metadata[:title] = rule.title
|
|
120
|
+
example.metadata[:desc] = rule.desc
|
|
121
|
+
example.metadata[:code] = rule.instance_variable_get(:@__code)
|
|
122
|
+
example.metadata[:source_location] = rule.instance_variable_get(:@__source_location)
|
|
99
123
|
example.filtered_examples.each do |e|
|
|
100
124
|
e.metadata[:id] = id
|
|
101
125
|
e.metadata[:impact] = rule.impact
|
|
126
|
+
e.metadata[:title] = rule.title
|
|
127
|
+
e.metadata[:desc] = rule.desc
|
|
128
|
+
e.metadata[:code] = rule.instance_variable_get(:@__code)
|
|
129
|
+
e.metadata[:source_location] = rule.instance_variable_get(:@__source_location)
|
|
102
130
|
end
|
|
103
131
|
example.children.each do |child|
|
|
104
132
|
set_rspec_ids(child, id, rule)
|
data/lib/inspec/version.rb
CHANGED
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
# license: All rights reserved
|
|
6
6
|
|
|
7
7
|
module Inspec::Resources
|
|
8
|
-
class
|
|
9
|
-
name '
|
|
10
|
-
desc 'Use the
|
|
8
|
+
class PowershellScript < Cmd
|
|
9
|
+
name 'powershell'
|
|
10
|
+
desc 'Use the powershell InSpec audit resource to test a Windows PowerShell script on the Microsoft Windows platform.'
|
|
11
11
|
example "
|
|
12
12
|
script = <<-EOH
|
|
13
13
|
# you powershell script
|
|
14
14
|
EOH
|
|
15
15
|
|
|
16
|
-
describe
|
|
16
|
+
describe powershell(script) do
|
|
17
17
|
its('matcher') { should eq 'output' }
|
|
18
18
|
end
|
|
19
19
|
"
|
|
@@ -37,7 +37,21 @@ module Inspec::Resources
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def to_s
|
|
40
|
-
'
|
|
40
|
+
'Powershell'
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# this is deprecated syntax and will be removed in future versions
|
|
45
|
+
class LegacyPowershellScript < PowershellScript
|
|
46
|
+
name 'script'
|
|
47
|
+
|
|
48
|
+
def initialize(script)
|
|
49
|
+
deprecated
|
|
50
|
+
super(script)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def deprecated
|
|
54
|
+
warn '[DEPRECATION] `script(script)` is deprecated. Please use `powershell(script)` instead.'
|
|
41
55
|
end
|
|
42
56
|
end
|
|
43
57
|
end
|
|
@@ -111,7 +111,7 @@ module Inspec::Resources
|
|
|
111
111
|
$object | ConvertTo-Json
|
|
112
112
|
EOH
|
|
113
113
|
|
|
114
|
-
cmd = inspec.
|
|
114
|
+
cmd = inspec.powershell(script)
|
|
115
115
|
|
|
116
116
|
# cannot rely on exit code for now, successful command returns exit code 1
|
|
117
117
|
# return nil if cmd.exit_status != 0, try to parse json
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -9,7 +9,7 @@ when 'ubuntu', 'rhel', 'centos', 'fedora'
|
|
|
9
9
|
|
|
10
10
|
# copy iso file for mount tests
|
|
11
11
|
# NB created using `mkdir empty; mkisofs -o empty.iso empty/`
|
|
12
|
-
cookbook_file '/
|
|
12
|
+
cookbook_file '/tmp/empty.iso' do
|
|
13
13
|
owner 'root'
|
|
14
14
|
group 'root'
|
|
15
15
|
mode '0755'
|
|
@@ -26,7 +26,7 @@ when 'ubuntu', 'rhel', 'centos', 'fedora'
|
|
|
26
26
|
|
|
27
27
|
# mount -o loop /root/empty.iso /mnt/iso-disk
|
|
28
28
|
mount '/mnt/iso-disk' do
|
|
29
|
-
device '/
|
|
29
|
+
device '/tmp/empty.iso'
|
|
30
30
|
options 'loop'
|
|
31
31
|
action [:mount, :enable]
|
|
32
32
|
end
|
|
File without changes
|
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
# hw-cookbooks/postgresql is tested on these platforms
|
|
7
7
|
case node['platform']
|
|
8
8
|
when 'ubuntu', 'centos'
|
|
9
|
+
|
|
10
|
+
# also skip it on ubuntu 15.10, because the cookbook is not supported
|
|
11
|
+
# with `enable_pgdg_apt` yet
|
|
12
|
+
return if node['platform_version'] == "15.10"
|
|
13
|
+
|
|
14
|
+
node.default['postgresql']['enable_pgdg_apt'] = true
|
|
9
15
|
node.default['postgresql']['config']['listen_addresses'] = 'localhost'
|
|
10
16
|
node.default['postgresql']['password']['postgres'] = 'md506be11be01439cb4abd537e454df34ea' # "inspec"
|
|
11
17
|
include_recipe 'postgresql::server'
|
|
File without changes
|
|
File without changes
|
data/test/{integration/cookbooks → cookbooks}/os_prepare/templates/default/sv-default-svlog-run.erb
RENAMED
|
File without changes
|
|
@@ -0,0 +1,390 @@
|
|
|
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
|