inspec 2.2.102 → 2.2.112

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/CHANGELOG.md +25 -7
  4. data/Rakefile +8 -2
  5. data/docs/profiles.md +9 -0
  6. data/docs/resources/aws_security_group.md.erb +19 -2
  7. data/docs/resources/gem.md.erb +24 -5
  8. data/docs/resources/mssql_session.md.erb +8 -0
  9. data/lib/inspec/plugin/v2/loader.rb +33 -7
  10. data/lib/inspec/reporters/json_automate.rb +1 -1
  11. data/lib/inspec/version.rb +1 -1
  12. data/lib/plugins/README.md +16 -0
  13. data/lib/plugins/inspec-artifact/lib/inspec-artifact.rb +12 -0
  14. data/lib/plugins/inspec-artifact/lib/inspec-artifact/base.rb +162 -0
  15. data/lib/plugins/inspec-artifact/lib/inspec-artifact/cli.rb +114 -0
  16. data/lib/plugins/inspec-artifact/test/functional/inspec_artifact_test.rb +46 -0
  17. data/lib/plugins/inspec-habitat/lib/inspec-habitat.rb +11 -0
  18. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +39 -0
  19. data/lib/plugins/inspec-habitat/lib/inspec-habitat/profile.rb +394 -0
  20. data/lib/plugins/inspec-habitat/test/unit/profile_test.rb +184 -0
  21. data/lib/{bundles → plugins}/inspec-init/README.md +0 -0
  22. data/lib/plugins/inspec-init/lib/inspec-init.rb +12 -0
  23. data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +28 -0
  24. data/lib/plugins/inspec-init/lib/inspec-init/renderer.rb +81 -0
  25. data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/README.md +0 -0
  26. data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/controls/example.rb +0 -0
  27. data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/inspec.yml +0 -0
  28. data/lib/{bundles → plugins/inspec-init/lib}/inspec-init/templates/profile/libraries/.gitkeep +0 -0
  29. data/lib/plugins/inspec-init/test/functional/inspec_init_test.rb +30 -0
  30. data/lib/plugins/shared/core_plugin_test_helper.rb +50 -0
  31. data/lib/resources/aws/aws_security_group.rb +38 -6
  32. data/lib/resources/gem.rb +7 -1
  33. data/lib/resources/mssql_session.rb +4 -2
  34. metadata +21 -17
  35. data/lib/bundles/inspec-artifact.rb +0 -7
  36. data/lib/bundles/inspec-artifact/README.md +0 -1
  37. data/lib/bundles/inspec-artifact/cli.rb +0 -278
  38. data/lib/bundles/inspec-habitat.rb +0 -12
  39. data/lib/bundles/inspec-habitat/cli.rb +0 -37
  40. data/lib/bundles/inspec-habitat/log.rb +0 -10
  41. data/lib/bundles/inspec-habitat/profile.rb +0 -391
  42. data/lib/bundles/inspec-init.rb +0 -12
  43. data/lib/bundles/inspec-init/cli.rb +0 -39
  44. data/lib/bundles/inspec-init/renderer.rb +0 -79
@@ -0,0 +1,184 @@
1
+ require 'mixlib/log'
2
+ require 'ostruct'
3
+ require 'minitest/autorun'
4
+ require 'mocha/setup'
5
+ require_relative '../../lib/inspec-habitat/profile.rb'
6
+
7
+ describe InspecPlugins::Habitat::Profile do
8
+ let(:profile) do
9
+ OpenStruct.new(
10
+ name: 'my_profile',
11
+ version: '1.2.3',
12
+ files: %w(file1 file2)
13
+ )
14
+ end
15
+
16
+ let(:subject) { InspecPlugins::Habitat::Profile.new('/path/to/profile', { 'log_level' => 'fatal' }) }
17
+
18
+ before do
19
+ Inspec::Log.level(:fatal)
20
+ end
21
+
22
+ describe '#verify_profile' do
23
+ it 'exits if the profile is not valid' do
24
+ profile = mock
25
+ profile.stubs(:check).returns(summary: { valid: false })
26
+ subject.expects(:profile).returns(profile)
27
+ proc { subject.send(:verify_profile) }.must_raise SystemExit
28
+ end
29
+
30
+ it 'does not exist if the profile is valid' do
31
+ profile = mock
32
+ profile.stubs(:check).returns(summary: { valid: true })
33
+ subject.expects(:profile).returns(profile)
34
+ subject.send(:verify_profile)
35
+ end
36
+ end
37
+
38
+ describe '#vendor_profile_dependencies' do
39
+ let(:profile_vendor) do
40
+ profile_vendor = mock
41
+ profile_vendor.stubs(:lockfile).returns(lockfile)
42
+ profile_vendor.stubs(:cache_path).returns(cache_path)
43
+ profile_vendor
44
+ end
45
+ let(:lockfile) { mock }
46
+ let(:cache_path) { mock }
47
+
48
+ before do
49
+ Inspec::ProfileVendor.expects(:new).returns(profile_vendor)
50
+ end
51
+
52
+ describe 'when lockfile exists and cache dir exists' do
53
+ it 'does not vendor the dependencies' do
54
+ lockfile.expects(:exist?).returns(true)
55
+ cache_path.expects(:exist?).returns(true)
56
+ profile_vendor.expects(:vendor!).never
57
+ profile_vendor.expects(:make_readable).never
58
+ subject.send(:vendor_profile_dependencies)
59
+ end
60
+ end
61
+
62
+ describe 'when the lockfile exists but the cache dir does not' do
63
+ it 'vendors the dependencies and refreshes the profile object' do
64
+ lockfile.expects(:exist?).returns(true)
65
+ cache_path.expects(:exist?).returns(false)
66
+ profile_vendor.expects(:vendor!)
67
+ profile_vendor.expects(:make_readable)
68
+ subject.expects(:create_profile_object)
69
+
70
+ subject.send(:vendor_profile_dependencies)
71
+ end
72
+ end
73
+
74
+ describe 'when the lockfile does not exist' do
75
+ it 'vendors the dependencies and refreshes the profile object' do
76
+ lockfile.expects(:exist?).returns(false)
77
+ profile_vendor.expects(:vendor!)
78
+ profile_vendor.expects(:make_readable)
79
+ subject.expects(:create_profile_object)
80
+
81
+ subject.send(:vendor_profile_dependencies)
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#validate_habitat_installed' do
87
+ it 'exits if hab --version fails' do
88
+ cmd = mock
89
+ cmd.stubs(:error?).returns(true)
90
+ cmd.stubs(:run_command)
91
+ cmd.stubs(:stdout)
92
+ cmd.stubs(:stderr)
93
+ Mixlib::ShellOut.expects(:new).with('hab --version').returns(cmd)
94
+ proc { subject.send(:validate_habitat_installed) }.must_raise SystemExit
95
+ end
96
+ end
97
+
98
+ describe '#validate_habitat_origin' do
99
+ it 'does not exit if the origin key exists' do
100
+ subject.expects(:habitat_origin).returns('12345')
101
+ subject.send(:validate_habitat_origin)
102
+ end
103
+
104
+ it 'exits if no origin key exists' do
105
+ subject.expects(:habitat_origin).returns(nil)
106
+ proc { subject.send(:validate_habitat_origin) }.must_raise SystemExit
107
+ end
108
+ end
109
+
110
+ describe '#validate_habitat_auth_token' do
111
+ it 'does not exit if the auth_token exists' do
112
+ subject.expects(:habitat_auth_token).returns('12345')
113
+ subject.send(:validate_habitat_auth_token)
114
+ end
115
+
116
+ it 'exits if no auth_token exists' do
117
+ subject.expects(:habitat_auth_token).returns(nil)
118
+ proc { subject.send(:validate_habitat_auth_token) }.must_raise SystemExit
119
+ end
120
+ end
121
+
122
+ describe '#build_hart' do
123
+ before do
124
+ subject.expects(:work_dir).at_least_once.returns(Dir.tmpdir)
125
+ end
126
+
127
+ it 'exits if the build fails' do
128
+ subject.expects(:system).returns(false)
129
+ proc { subject.send(:build_hart) }.must_raise SystemExit
130
+ end
131
+
132
+ it 'exits if more than one hart is created' do
133
+ subject.expects(:system).returns(true)
134
+ Dir.expects(:glob).returns(%w(hart1 hart2))
135
+ proc { subject.send(:build_hart) }.must_raise SystemExit
136
+ end
137
+
138
+ it 'exits if more than no hart is created' do
139
+ subject.expects(:system).returns(true)
140
+ Dir.expects(:glob).returns([])
141
+ proc { subject.send(:build_hart) }.must_raise SystemExit
142
+ end
143
+
144
+ it 'returns the hart filename' do
145
+ subject.expects(:system).returns(true)
146
+ Dir.expects(:glob).returns(%w(hart1))
147
+ subject.send(:build_hart).must_equal('hart1')
148
+ end
149
+ end
150
+
151
+ describe '#upload_hart' do
152
+ it 'exits if the upload failed' do
153
+ env = {
154
+ 'TERM' => 'vt100',
155
+ 'HAB_AUTH_TOKEN' => 'my_token',
156
+ 'HAB_NONINTERACTIVE' => 'true',
157
+ }
158
+
159
+ cmd = mock
160
+ cmd.stubs(:run_command)
161
+ cmd.stubs(:error?).returns(true)
162
+ cmd.stubs(:stdout)
163
+ cmd.stubs(:stderr)
164
+
165
+ subject.expects(:habitat_auth_token).returns('my_token')
166
+ Mixlib::ShellOut.expects(:new).with("hab pkg upload my_hart", env: env).returns(cmd)
167
+ proc { subject.send(:upload_hart, 'my_hart') }.must_raise SystemExit
168
+ end
169
+ end
170
+
171
+ describe '#habitat_cli_config' do
172
+ it 'returns an empty hash if the CLI config does not exist' do
173
+ File.expects(:exist?).with(File.join(ENV['HOME'], '.hab', 'etc', 'cli.toml')).returns(false)
174
+ subject.send(:habitat_cli_config).must_equal({})
175
+ end
176
+
177
+ it 'returns parsed TOML from the hab config file' do
178
+ config_file = File.join(ENV['HOME'], '.hab', 'etc', 'cli.toml')
179
+ File.expects(:exist?).with(config_file).returns(true)
180
+ Tomlrb.expects(:load_file).with(config_file).returns(foo: 1)
181
+ subject.send(:habitat_cli_config).must_equal(foo: 1)
182
+ end
183
+ end
184
+ end
File without changes
@@ -0,0 +1,12 @@
1
+ module InspecPlugins
2
+ module Init
3
+ class Plugin < Inspec.plugin(2)
4
+ plugin_name :'inspec-init'
5
+
6
+ cli_command :init do
7
+ require_relative 'inspec-init/cli'
8
+ InspecPlugins::Init::CLI
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require 'pathname'
4
+ require_relative 'renderer'
5
+
6
+ module InspecPlugins
7
+ module Init
8
+ class CLI < Inspec.plugin(2, :cli_command)
9
+ subcommand_desc 'init SUBCOMMAND', 'Initialize InSpec objects'
10
+
11
+ # Look in the 'template' directory, and register a subcommand
12
+ # for each template directory found there.
13
+ template_dir = File.join(File.dirname(__FILE__), 'templates')
14
+ Dir.glob(File.join(template_dir, '*')) do |template|
15
+ template_name = Pathname.new(template).relative_path_from(Pathname.new(template_dir)).to_s
16
+
17
+ # register command for the template
18
+ desc "#{template_name} NAME", "Create a new #{template_name}"
19
+ option :overwrite, type: :boolean, default: false,
20
+ desc: 'Overwrites existing directory'
21
+ define_method template_name.to_sym do |name_for_new_structure|
22
+ renderer = InspecPlugins::Init::Renderer.new(self, options)
23
+ renderer.render_with_values(template_name, name: name_for_new_structure)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,81 @@
1
+ require 'fileutils'
2
+ require 'erb'
3
+
4
+ module InspecPlugins
5
+ module Init
6
+ class Renderer
7
+ # Creates a renderer able to render the given template type
8
+ # 1. iterate over all files
9
+ # 2. read content in erb
10
+ # 3. write to full_destination_root_path
11
+
12
+ attr_reader :overwrite_mode, :ui
13
+ def initialize(cli_ui, cli_options = {})
14
+ @ui = cli_ui
15
+ @overwrite_mode = cli_options['overwrite']
16
+ end
17
+
18
+ # rubocop: disable Metrics/AbcSize
19
+ def render_with_values(template_type, template_values = {})
20
+ # look for template directory
21
+ base_dir = File.join(File.dirname(__FILE__), 'templates', template_type)
22
+ # prepare glob for all subdirectories and files
23
+ template_glob = File.join(base_dir, '**', '{*,.*}')
24
+ # Use the name attribute to define the path to the profile.
25
+ profile_path = template_values[:name]
26
+ # Use slashes (\, /) to split up the name into an Array then use the last entry
27
+ # to reset the name of the profile.
28
+ template_values[:name] = template_values[:name].split(%r{\\|\/}).last
29
+ # Generate the full full_destination_root_path path on disk
30
+ full_destination_root_path = Pathname.new(Dir.pwd).join(profile_path)
31
+ ui.plain_text "Create new #{template_type} at #{ui.mark_text(full_destination_root_path)}"
32
+
33
+ # check that the directory does not exist
34
+ if File.exist?(full_destination_root_path) && !overwrite_mode
35
+ ui.plain_text "#{ui.mark_text(full_destination_root_path)} exists already, use --overwrite"
36
+ ui.exit(1)
37
+ end
38
+
39
+ # ensure that full_destination_root_path directory is available
40
+ FileUtils.mkdir_p(full_destination_root_path)
41
+
42
+ # iterate over files and write to full_destination_root_path
43
+ Dir.glob(template_glob) do |file|
44
+ relative_destination_item_path = Pathname.new(file).relative_path_from(Pathname.new(base_dir))
45
+ full_destination_item_path = Pathname.new(full_destination_root_path).join(relative_destination_item_path)
46
+ if File.directory?(file)
47
+ ui.li "Create directory #{ui.mark_text(relative_destination_item_path)}"
48
+ FileUtils.mkdir_p(full_destination_item_path)
49
+ elsif File.file?(file)
50
+ ui.li "Create file #{ui.mark_text(relative_destination_item_path)}"
51
+ # read & render content
52
+ content = render(File.read(file), template_values)
53
+ # write file content
54
+ File.write(full_destination_item_path, content)
55
+ else
56
+ ui.plain_text "Ignore #{file}, because its not an file or directoy"
57
+ end
58
+ end
59
+ end
60
+ # rubocop: enable Metrics/AbcSize
61
+
62
+ # This is a render helper to bind hash values to a ERB template
63
+ # ERB provides result_with_hash in ruby 2.5.0+, which does exactly this
64
+ def render(template_content, hash)
65
+ # create a new binding class
66
+ cls = Class.new do
67
+ hash.each do |key, value|
68
+ define_method key.to_sym do
69
+ value
70
+ end
71
+ end
72
+ # expose binding
73
+ define_method :bind do
74
+ binding
75
+ end
76
+ end
77
+ ERB.new(template_content).result(cls.new.bind)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../../../shared/core_plugin_test_helper.rb'
4
+
5
+ class InitCli < MiniTest::Test
6
+ include CorePluginFunctionalHelper
7
+
8
+ def test_generating_inspec_profile
9
+ Dir.mktmpdir do |dir|
10
+ profile = File.join(dir, 'test-profile')
11
+ out = run_inspec_process("init profile test-profile", prefix: "cd #{dir} &&")
12
+ assert_equal 0, out.exit_status
13
+ assert_includes out.stdout, 'Create new profile at'
14
+ assert_includes out.stdout, profile
15
+ assert_includes Dir.entries(profile).join, 'inspec.yml'
16
+ assert_includes Dir.entries(profile).join, 'README.md'
17
+ end
18
+ end
19
+
20
+ def test_profile_with_slash_name
21
+ Dir.mktmpdir do |dir|
22
+ profile = dir + '/test/deeper/profile'
23
+ out = run_inspec_process("init profile test/deeper/profile", prefix: "cd #{dir} &&")
24
+ assert_equal 0, out.exit_status
25
+ assert_equal true, File.exist?(profile)
26
+ profile = YAML.load_file("#{profile}/inspec.yml")
27
+ assert_equal 'profile', profile['name']
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+
2
+ # Load test harness - MiniTest
3
+ require 'minitest/autorun'
4
+ require 'minitest/unit'
5
+ require 'minitest/pride'
6
+ require 'minitest/spec'
7
+
8
+ # Data formats commonly used in testing
9
+ require 'json'
10
+ require 'ostruct'
11
+
12
+ # Utilities often needed
13
+ require 'fileutils'
14
+
15
+ # Configure MiniTest to expose things like `let`
16
+ class Module
17
+ include Minitest::Spec::DSL
18
+ end
19
+
20
+ module CorePluginBaseHelper
21
+ let(:repo_path) { File.expand_path(File.join(__FILE__, '..', '..', '..', '..')) }
22
+ let(:inspec_bin_path) { File.join(repo_path, 'bin', 'inspec') }
23
+ let(:core_mock_path) { File.join(repo_path, 'test', 'unit', 'mock') }
24
+ let(:core_fixture_plugins_path) { File.join(core_mock_path, 'plugins') }
25
+ let(:core_config_dir_path) { File.join(core_mock_path, 'config_dirs') }
26
+
27
+ let(:registry) { Inspec::Plugin::V2::Registry.instance }
28
+ end
29
+
30
+ module CorePluginFunctionalHelper
31
+ include CorePluginBaseHelper
32
+
33
+ require 'train'
34
+ TRAIN_CONNECTION = Train.create('local', command_runner: :generic).connection
35
+
36
+ def run_inspec_process(command_line, opts = {})
37
+ prefix = ''
38
+ if opts.key?(:prefix)
39
+ prefix = opts[:prefix]
40
+ elsif opts.key?(:env)
41
+ prefix = opts[:env].to_a.map { |assignment| "#{assignment[0]}=#{assignment[1]}" }.join(' ')
42
+ end
43
+ TRAIN_CONNECTION.run_command("#{prefix} #{inspec_bin_path} #{command_line}")
44
+ end
45
+ end
46
+
47
+ module CorePluginUnitHelper
48
+ include CorePluginBaseHelper
49
+ require 'inspec'
50
+ end
@@ -12,7 +12,7 @@ class AwsSecurityGroup < Inspec.resource(1)
12
12
  supports platform: 'aws'
13
13
 
14
14
  include AwsSingularResourceMixin
15
- attr_reader :description, :group_id, :group_name, :vpc_id, :inbound_rules, :outbound_rules
15
+ attr_reader :description, :group_id, :group_name, :vpc_id, :inbound_rules, :outbound_rules, :inbound_rules_count, :outbound_rules_count
16
16
 
17
17
  def to_s
18
18
  "EC2 Security Group #{@group_id}"
@@ -57,6 +57,7 @@ class AwsSecurityGroup < Inspec.resource(1)
57
57
  matched &&= allow__match_port(rule, criteria)
58
58
  matched &&= allow__match_protocol(rule, criteria)
59
59
  matched &&= allow__match_ipv4_range(rule, criteria)
60
+ matched &&= allow__match_ipv6_range(rule, criteria)
60
61
  matched
61
62
  end
62
63
  end
@@ -65,6 +66,7 @@ class AwsSecurityGroup < Inspec.resource(1)
65
66
  allowed_criteria = [
66
67
  :from_port,
67
68
  :ipv4_range,
69
+ :ipv6_range,
68
70
  :port,
69
71
  :position,
70
72
  :protocol,
@@ -149,11 +151,17 @@ class AwsSecurityGroup < Inspec.resource(1)
149
151
  rule[:ip_protocol] == prot
150
152
  end
151
153
 
152
- def allow__match_ipv4_range(rule, criteria)
153
- return true unless criteria.key?(:ipv4_range)
154
- query = criteria[:ipv4_range]
155
- query = [query] unless query.is_a?(Array)
156
- ranges = rule[:ip_ranges].map { |rng| rng[:cidr_ip] }
154
+ def match_ipv4_or_6_range(rule, criteria)
155
+ if criteria.key?(:ipv4_range)
156
+ query = criteria[:ipv4_range]
157
+ query = [query] unless query.is_a?(Array)
158
+ ranges = rule[:ip_ranges].map { |rng| rng[:cidr_ip] }
159
+ else # IPv6
160
+ query = criteria[:ipv6_range]
161
+ query = [query] unless query.is_a?(Array)
162
+ ranges = rule[:ipv_6_ranges].map { |rng| rng[:cidr_ipv_6] }
163
+ end
164
+
157
165
  if criteria[:exact]
158
166
  Set.new(query) == Set.new(ranges)
159
167
  else
@@ -169,6 +177,16 @@ class AwsSecurityGroup < Inspec.resource(1)
169
177
  end
170
178
  end
171
179
 
180
+ def allow__match_ipv4_range(rule, criteria)
181
+ return true unless criteria.key?(:ipv4_range)
182
+ match_ipv4_or_6_range(rule, criteria)
183
+ end
184
+
185
+ def allow__match_ipv6_range(rule, criteria)
186
+ return true unless criteria.key?(:ipv6_range)
187
+ match_ipv4_or_6_range(rule, criteria)
188
+ end
189
+
172
190
  def validate_params(raw_params)
173
191
  recognized_params = check_resource_param_names(
174
192
  raw_params: raw_params,
@@ -196,6 +214,18 @@ class AwsSecurityGroup < Inspec.resource(1)
196
214
  validated_params
197
215
  end
198
216
 
217
+ def count_sg_rules(ip_permissions)
218
+ rule_count = 0
219
+ ip_permissions.each do |ip_permission|
220
+ [:ip_ranges, :ipv_6_ranges, :user_id_group_pairs].each do |key|
221
+ if ip_permission.key? key
222
+ rule_count += ip_permission[key].length
223
+ end
224
+ end
225
+ end
226
+ rule_count
227
+ end
228
+
199
229
  def fetch_from_api # rubocop: disable Metrics/AbcSize
200
230
  backend = BackendFactory.create(inspec_runner)
201
231
 
@@ -233,7 +263,9 @@ class AwsSecurityGroup < Inspec.resource(1)
233
263
  @group_name = dsg_response.security_groups[0].group_name
234
264
  @vpc_id = dsg_response.security_groups[0].vpc_id
235
265
  @inbound_rules = dsg_response.security_groups[0].ip_permissions.map(&:to_h)
266
+ @inbound_rules_count = count_sg_rules(dsg_response.security_groups[0].ip_permissions.map(&:to_h))
236
267
  @outbound_rules = dsg_response.security_groups[0].ip_permissions_egress.map(&:to_h)
268
+ @outbound_rules_count = count_sg_rules(dsg_response.security_groups[0].ip_permissions_egress.map(&:to_h))
237
269
  end
238
270
 
239
271
  class Backend