inspec 2.2.102 → 2.2.112

Sign up to get free protection for your applications and to get access to all the features.
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