inspec 0.20.1 → 0.21.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.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -2
  3. data/docs/dsl_inspec.rst +2 -2
  4. data/docs/resources.rst +9 -9
  5. data/docs/ruby_usage.rst +145 -0
  6. data/inspec.gemspec +1 -0
  7. data/lib/bundles/inspec-compliance/cli.rb +15 -2
  8. data/lib/inspec/cli.rb +23 -10
  9. data/lib/inspec/dsl.rb +0 -52
  10. data/lib/inspec/objects/or_test.rb +1 -0
  11. data/lib/inspec/objects/test.rb +4 -4
  12. data/lib/inspec/profile.rb +76 -61
  13. data/lib/inspec/profile_context.rb +12 -11
  14. data/lib/inspec/rspec_json_formatter.rb +93 -40
  15. data/lib/inspec/rule.rb +7 -29
  16. data/lib/inspec/runner.rb +15 -4
  17. data/lib/inspec/runner_mock.rb +1 -1
  18. data/lib/inspec/runner_rspec.rb +26 -24
  19. data/lib/inspec/version.rb +1 -1
  20. data/lib/matchers/matchers.rb +3 -3
  21. data/lib/resources/auditd_rules.rb +2 -2
  22. data/lib/resources/host.rb +1 -1
  23. data/lib/resources/interface.rb +1 -1
  24. data/lib/resources/kernel_parameter.rb +1 -1
  25. data/lib/resources/mount.rb +2 -1
  26. data/lib/resources/mysql_session.rb +1 -1
  27. data/lib/resources/os_env.rb +2 -2
  28. data/lib/resources/passwd.rb +33 -93
  29. data/lib/resources/port.rb +47 -3
  30. data/lib/resources/processes.rb +3 -3
  31. data/lib/resources/service.rb +33 -1
  32. data/lib/resources/user.rb +15 -15
  33. data/lib/utils/base_cli.rb +1 -3
  34. data/lib/utils/filter.rb +30 -7
  35. data/test/cookbooks/os_prepare/recipes/_upstart_service_centos.rb +4 -0
  36. data/test/functional/helper.rb +1 -0
  37. data/test/functional/inheritance_test.rb +1 -1
  38. data/test/functional/inspec_compliance_test.rb +4 -3
  39. data/test/functional/inspec_exec_json_test.rb +122 -0
  40. data/test/functional/inspec_exec_test.rb +23 -117
  41. data/test/functional/{inspec_json_test.rb → inspec_json_profile_test.rb} +13 -15
  42. data/test/functional/inspec_test.rb +15 -2
  43. data/test/helper.rb +5 -1
  44. data/test/integration/default/auditd_rules_spec.rb +3 -3
  45. data/test/integration/default/kernel_parameter_spec.rb +6 -6
  46. data/test/integration/default/service_spec.rb +4 -0
  47. data/test/resource/command_test.rb +9 -9
  48. data/test/resource/dsl_test.rb +1 -1
  49. data/test/resource/file_test.rb +17 -17
  50. data/test/unit/control_test.rb +1 -1
  51. data/test/unit/mock/cmd/hpux-netstat-inet +10 -0
  52. data/test/unit/mock/cmd/hpux-netstat-inet6 +11 -0
  53. data/test/unit/mock/profiles/skippy-profile-os/controls/one.rb +1 -1
  54. data/test/unit/profile_context_test.rb +2 -2
  55. data/test/unit/profile_test.rb +11 -14
  56. data/test/unit/resources/passwd_test.rb +13 -14
  57. data/test/unit/resources/port_test.rb +14 -0
  58. data/test/unit/resources/processes_test.rb +3 -3
  59. data/test/unit/resources/service_test.rb +103 -39
  60. data/test/unit/utils/filter_table_test.rb +35 -3
  61. metadata +25 -4
@@ -12,8 +12,7 @@ module Inspec
12
12
  class Rule # rubocop:disable Metrics/ClassLength
13
13
  include ::RSpec::Matchers
14
14
 
15
- def initialize(id, _opts, &block)
16
- @id = id
15
+ def initialize(id, profile_id, _opts, &block)
17
16
  @impact = nil
18
17
  @title = nil
19
18
  @desc = nil
@@ -24,7 +23,8 @@ module Inspec
24
23
  @__block = block
25
24
  @__code = __get_block_source(&block)
26
25
  @__source_location = __get_block_source_location(&block)
27
- @__rule_id = nil
26
+ @__rule_id = id
27
+ @__profile_id = profile_id
28
28
  @__checks = []
29
29
  @__skip_rule = nil
30
30
 
@@ -119,6 +119,10 @@ module Inspec
119
119
  rule.instance_variable_set(:@__rule_id, value)
120
120
  end
121
121
 
122
+ def self.profile_id(rule)
123
+ rule.instance_variable_get(:@__profile_id)
124
+ end
125
+
122
126
  def self.checks(rule)
123
127
  rule.instance_variable_get(:@__checks)
124
128
  end
@@ -167,32 +171,6 @@ module Inspec
167
171
  set_skip_rule(dst, sr) unless sr.nil?
168
172
  end
169
173
 
170
- # Get the full id consisting of profile id + rule id
171
- # for the rule. If the rule's profile id is empty,
172
- # the given profile_id will be used instead and also
173
- # set for the rule.
174
- def self.full_id(profile_id, rule)
175
- if rule.is_a?(String) or rule.nil?
176
- rid = rule
177
- else
178
- # As the profile context is exclusively pulled with a
179
- # profile ID, attach it to the rule if necessary.
180
- rid = rule.instance_variable_get(:@id)
181
- if rid.nil?
182
- # TODO: Message about skipping this rule
183
- # due to missing ID
184
- return nil
185
- end
186
- end
187
- pid = rule_id(rule)
188
- pid = set_rule_id(rule, profile_id) if pid.nil?
189
-
190
- # if we don't have a profile id, just return the rule's ID
191
- return rid if pid.nil? or pid.empty?
192
- # otherwise combine them
193
- "#{pid}/#{rid}"
194
- end
195
-
196
174
  private
197
175
 
198
176
  def __add_check(describe_or_expect, values, block)
@@ -18,7 +18,6 @@ module Inspec
18
18
  attr_reader :backend, :rules
19
19
  def initialize(conf = {})
20
20
  @rules = {}
21
- @profile_id = conf[:id]
22
21
  @conf = conf.dup
23
22
  @conf[:logger] ||= Logger.new(nil)
24
23
 
@@ -74,6 +73,7 @@ module Inspec
74
73
 
75
74
  @test_collector.add_profile(profile)
76
75
  options[:metadata] = profile.metadata
76
+ options[:profile] = profile
77
77
 
78
78
  libs = profile.libraries.map do |k, v|
79
79
  { ref: k, content: v }
@@ -88,7 +88,10 @@ module Inspec
88
88
  end
89
89
 
90
90
  def create_context(options = {})
91
- Inspec::ProfileContext.new(@profile_id, @backend, @conf.merge(options))
91
+ meta = options['metadata']
92
+ profile_id = nil
93
+ profile_id = meta.params[:name] unless meta.nil?
94
+ Inspec::ProfileContext.new(profile_id, @backend, @conf.merge(options))
92
95
  end
93
96
 
94
97
  def add_content(tests, libs, options = {})
@@ -101,6 +104,11 @@ module Inspec
101
104
  ctx.reload_dsl
102
105
  end
103
106
 
107
+ # hand the context to the profile for further evaluation
108
+ unless (profile = options['profile']).nil?
109
+ profile.runner_context = ctx
110
+ end
111
+
104
112
  # evaluate the test content
105
113
  tests = [tests] unless tests.is_a? Array
106
114
  tests.each { |t| add_test_to_context(t, ctx) }
@@ -124,7 +132,10 @@ module Inspec
124
132
 
125
133
  def filter_controls(controls_map, include_list)
126
134
  return controls_map if include_list.nil? || include_list.empty?
127
- controls_map.select { |k, _| include_list.include?(k) }
135
+ controls_map.select do |_, c|
136
+ id = ::Inspec::Rule.rule_id(c)
137
+ include_list.include?(id)
138
+ end
128
139
  end
129
140
 
130
141
  def block_source_info(block)
@@ -186,7 +197,7 @@ module Inspec
186
197
  # scope.
187
198
  dsl = Inspec::Resource.create_dsl(backend)
188
199
  example.send(:include, dsl)
189
- @test_collector.add_test(example, rule_id, rule)
200
+ @test_collector.add_test(example, rule)
190
201
  end
191
202
  end
192
203
  end
@@ -14,7 +14,7 @@ module Inspec
14
14
  @profiles.push(profile)
15
15
  end
16
16
 
17
- def add_test(example, _rule_id, _rule)
17
+ def add_test(example, _rule)
18
18
  @tests.push(example)
19
19
  end
20
20
 
@@ -7,10 +7,8 @@ require 'rspec/its'
7
7
  require 'inspec/rspec_json_formatter'
8
8
 
9
9
  # There be dragons!! Or borgs, or something...
10
- # This file and all its contents cannot yet be tested. Once it is included
11
- # in our unit test suite, it deactivates all other checks completely.
12
- # To circumvent this, we need functional tests which tackle the RSpec runner
13
- # or a separate suite of unit tests to which get along with this.
10
+ # This file and all its contents cannot be unit-tested. both test-suits
11
+ # collide and disable all unit tests that have been added.
14
12
 
15
13
  module Inspec
16
14
  class RunnerRspec
@@ -35,7 +33,7 @@ module Inspec
35
33
  # @return [nil]
36
34
  def add_profile(profile)
37
35
  RSpec.configuration.formatters
38
- .find_all { |c| c.is_a? InspecRspecFormatter }
36
+ .find_all { |c| c.is_a? InspecRspecJson }
39
37
  .each do |fmt|
40
38
  fmt.add_profile(profile)
41
39
  end
@@ -46,8 +44,8 @@ module Inspec
46
44
  # @param [RSpecExampleGroup] example test
47
45
  # @param [String] rule_id the ID associated with this check
48
46
  # @return [nil]
49
- def add_test(example, rule_id, rule)
50
- set_rspec_ids(example, rule_id, rule)
47
+ def add_test(example, rule)
48
+ set_rspec_ids(example, rule)
51
49
  @tests.example_groups.push(example)
52
50
  end
53
51
 
@@ -83,6 +81,12 @@ module Inspec
83
81
  RSpec.configuration.reset
84
82
  end
85
83
 
84
+ FORMATTERS = {
85
+ 'json-min' => 'InspecRspecMiniJson',
86
+ 'json' => 'InspecRspecJson',
87
+ 'json-rspec' => 'InspecRspecVanilla',
88
+ }.freeze
89
+
86
90
  # Configure the output formatter and stream to be used with RSpec.
87
91
  #
88
92
  # @return [nil]
@@ -93,8 +97,7 @@ module Inspec
93
97
  RSpec.configuration.output_stream = @conf['output']
94
98
  end
95
99
 
96
- format = @conf['format'] || 'progress'
97
- format = 'InspecRspecFormatter' if format == 'fulljson'
100
+ format = FORMATTERS[@conf['format']] || @conf['format'] || 'progress'
98
101
  RSpec.configuration.add_formatter(format)
99
102
  RSpec.configuration.color = @conf['color']
100
103
 
@@ -111,27 +114,26 @@ module Inspec
111
114
  # by the InSpec adjusted json formatter (rspec_json_formatter).
112
115
  #
113
116
  # @param [RSpecExampleGroup] example object which contains a check
114
- # @param [Type] id describe id
115
117
  # @return [Type] description of returned object
116
- def set_rspec_ids(example, id, rule)
117
- example.metadata[:id] = id
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)
118
+ def set_rspec_ids(example, rule)
119
+ assign_rspec_ids(example.metadata, rule)
123
120
  example.filtered_examples.each do |e|
124
- e.metadata[:id] = id
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)
121
+ assign_rspec_ids(e.metadata, rule)
130
122
  end
131
123
  example.children.each do |child|
132
- set_rspec_ids(child, id, rule)
124
+ set_rspec_ids(child, rule)
133
125
  end
134
126
  end
127
+
128
+ def assign_rspec_ids(metadata, rule)
129
+ metadata[:id] = ::Inspec::Rule.rule_id(rule)
130
+ metadata[:profile_id] = ::Inspec::Rule.profile_id(rule)
131
+ metadata[:impact] = rule.impact
132
+ metadata[:title] = rule.title
133
+ metadata[:desc] = rule.desc
134
+ metadata[:code] = rule.instance_variable_get(:@__code)
135
+ metadata[:source_location] = rule.instance_variable_get(:@__source_location)
136
+ end
135
137
  end
136
138
 
137
139
  class RSpecReporter < RSpec::Core::Formatters::JsonFormatter
@@ -3,5 +3,5 @@
3
3
  # author: Christoph Hartmann
4
4
 
5
5
  module Inspec
6
- VERSION = '0.20.1'.freeze
6
+ VERSION = '0.21.0'.freeze
7
7
  end
@@ -106,7 +106,7 @@ RSpec::Matchers.define :be_installed do
106
106
  end
107
107
 
108
108
  chain :with_version do |version|
109
- warn "[DEPRECATION] `with_version` is deprecated. Please use `its(:version) { should eq '1.4.1' }` instead."
109
+ warn "[DEPRECATION] `with_version` is deprecated. Please use `its('version') { should eq '1.4.1' }` instead."
110
110
  @version = version
111
111
  end
112
112
  end
@@ -146,7 +146,7 @@ end
146
146
  # Deprecated: You should not use this matcher anymore
147
147
  RSpec::Matchers.define :belong_to_group do |compare_group|
148
148
  match do |user|
149
- warn "[DEPRECATION] `belong_to_group` is deprecated. Please use `its(:groups) { should include('root') }` instead."
149
+ warn "[DEPRECATION] `belong_to_group` is deprecated. Please use `its('groups') { should include('root') }` instead."
150
150
  user.groups.include?(compare_group)
151
151
  end
152
152
 
@@ -159,7 +159,7 @@ end
159
159
  # Deprecated: You should not use this matcher anymore
160
160
  RSpec::Matchers.define :belong_to_primary_group do |compare_group|
161
161
  match do |user|
162
- warn "[DEPRECATION] `belong_to_primary_group` is deprecated. Please use `its(:group) { should eq 'root' }` instead."
162
+ warn "[DEPRECATION] `belong_to_primary_group` is deprecated. Please use `its('group') { should eq 'root' }` instead."
163
163
  user.group == compare_group
164
164
  end
165
165
 
@@ -67,11 +67,11 @@ module Inspec::Resources
67
67
  end
68
68
 
69
69
  describe auditd_rules.key('sshd_config') do
70
- its(:permissions) { should contain_match(/x/) }
70
+ its('permissions') { should contain_match(/x/) }
71
71
  end
72
72
 
73
73
  describe auditd_rules do
74
- its(:lines) { should contain_match(%r{-w /etc/ssh/sshd_config/}) }
74
+ its('lines') { should contain_match(%r{-w /etc/ssh/sshd_config/}) }
75
75
  end
76
76
  "
77
77
 
@@ -6,7 +6,7 @@
6
6
  # describe host('example.com') do
7
7
  # it { should be_resolvable }
8
8
  # it { should be_reachable }
9
- # its(:ipaddress) { should include '93.184.216.34' }
9
+ # its('ipaddress') { should include '93.184.216.34' }
10
10
  # end
11
11
  #
12
12
  # To verify a hostname with protocol and port
@@ -12,7 +12,7 @@ module Inspec::Resources
12
12
  describe interface('eth0') do
13
13
  it { should exist }
14
14
  it { should be_up }
15
- its(:speed) { should eq 1000 }
15
+ its('speed') { should eq 1000 }
16
16
  end
17
17
  "
18
18
  def initialize(iface)
@@ -8,7 +8,7 @@ module Inspec::Resources
8
8
  desc 'Use the kernel_parameter InSpec audit resource to test kernel parameters on Linux platforms.'
9
9
  example "
10
10
  describe kernel_parameter('net.ipv4.conf.all.forwarding') do
11
- its(:value) { should eq 0 }
11
+ its('value') { should eq 0 }
12
12
  end
13
13
  "
14
14
 
@@ -11,10 +11,11 @@ module Inspec::Resources
11
11
  example "
12
12
  describe mount('/') do
13
13
  it { should be_mounted }
14
- its(:count) { should eq 1 }
14
+ its('count') { should eq 1 }
15
15
  its('device') { should eq '/dev/mapper/VolGroup-lv_root' }
16
16
  its('type') { should eq 'ext4' }
17
17
  its('options') { should eq ['rw', 'mode=620'] }
18
+ its('options') { should include 'nodev' }
18
19
  end
19
20
  "
20
21
  include MountParser
@@ -11,7 +11,7 @@ module Inspec::Resources
11
11
  example "
12
12
  sql = mysql_session('my_user','password')
13
13
  describe sql.query('show databases like \'test\';') do
14
- its(:stdout) { should_not match(/test/) }
14
+ its('stdout') { should_not match(/test/) }
15
15
  end
16
16
  "
17
17
 
@@ -7,8 +7,8 @@
7
7
  # Usage:
8
8
  #
9
9
  # describe os_env('PATH') do
10
- # its(:split) { should_not include('') }
11
- # its(:split) { should_not include('.') }
10
+ # its('split') { should_not include('') }
11
+ # its('split') { should_not include('.') }
12
12
  # end
13
13
 
14
14
  require 'utils/simpleconfig'
@@ -14,9 +14,10 @@
14
14
  # - command
15
15
 
16
16
  require 'utils/parser'
17
+ require 'utils/filter'
17
18
 
18
19
  module Inspec::Resources
19
- class Passwd < Inspec.resource(1) # rubocop:disable Metrics/ClassLength
20
+ class Passwd < Inspec.resource(1)
20
21
  name 'passwd'
21
22
  desc 'Use the passwd InSpec audit resource to test the contents of /etc/passwd, which contains the following information for users that may log into the system and/or as users that own running processes.'
22
23
  example "
@@ -37,7 +38,6 @@ module Inspec::Resources
37
38
 
38
39
  include PasswdParser
39
40
 
40
- attr_reader :uid
41
41
  attr_reader :params
42
42
  attr_reader :content
43
43
  attr_reader :lines
@@ -47,111 +47,51 @@ module Inspec::Resources
47
47
  @path = path || '/etc/passwd'
48
48
  @content = opts[:content] || inspec.file(@path).content
49
49
  @lines = @content.to_s.split("\n")
50
- @filters = opts[:filters] || ''
51
50
  @params = parse_passwd(@content)
52
51
  end
53
52
 
54
- def filter(hm = {})
55
- return self if hm.nil? || hm.empty?
56
- res = @params
57
- filters = ''
58
- hm.each do |attr, condition|
59
- res, filters = filter_attribute(attr, condition, res, filters)
60
- end
61
- content = res.map { |x| x.values.join(':') }.join("\n")
62
- Passwd.new(@path, content: content, filters: @filters + filters)
63
- end
53
+ filter = FilterTable.create
54
+ filter.add_accessor(:where)
55
+ .add_accessor(:entries)
56
+ .add(:users, field: 'user')
57
+ .add(:passwords, field: 'password')
58
+ .add(:uids, field: 'uid')
59
+ .add(:gids, field: 'gid')
60
+ .add(:descs, field: 'desc')
61
+ .add(:homes, field: 'home')
62
+ .add(:shells, field: 'shell')
64
63
 
65
- def usernames
64
+ filter.add(:count) { |t, _|
65
+ warn '[DEPRECATION] `passwd.count` is deprecated. Please use `passwd.entries.length` instead. It will be removed in version 1.0.0.'
66
+ t.entries.length
67
+ }
68
+
69
+ filter.add(:usernames) { |t, x|
66
70
  warn '[DEPRECATION] `passwd.usernames` is deprecated. Please use `passwd.users` instead. It will be removed in version 1.0.0.'
67
- users
68
- end
71
+ t.users(x)
72
+ }
69
73
 
70
- def username
71
- warn '[DEPRECATION] `passwd.user` is deprecated. Please use `passwd.users` instead. It will be removed in version 1.0.0.'
72
- users[0]
73
- end
74
+ filter.add(:username) { |t, x|
75
+ warn '[DEPRECATION] `passwd.username` is deprecated. Please use `passwd.users` instead. It will be removed in version 1.0.0.'
76
+ t.users(x)[0]
77
+ }
78
+
79
+ # rebuild the passwd line from raw content
80
+ filter.add(:content) { |t, _|
81
+ t.entries.map do |e|
82
+ [e.user, e.password, e.uid, e.gid, e.desc, e.home, e.shell].join(':')
83
+ end.join("\n")
84
+ }
74
85
 
75
86
  def uid(x)
76
87
  warn '[DEPRECATION] `passwd.uid(arg)` is deprecated. Please use `passwd.uids(arg)` instead. It will be removed in version 1.0.0.'
77
88
  uids(x)
78
89
  end
79
90
 
80
- def users(name = nil)
81
- name.nil? ? map_data('user') : filter(user: name)
82
- end
83
-
84
- def passwords(password = nil)
85
- password.nil? ? map_data('password') : filter(password: password)
86
- end
87
-
88
- def uids(uid = nil)
89
- uid.nil? ? map_data('uid') : filter(uid: uid)
90
- end
91
-
92
- def gids(gid = nil)
93
- gid.nil? ? map_data('gid') : filter(gid: gid)
94
- end
95
-
96
- def homes(home = nil)
97
- home.nil? ? map_data('home') : filter(home: home)
98
- end
99
-
100
- def shells(shell = nil)
101
- shell.nil? ? map_data('shell') : filter(shell: shell)
102
- end
91
+ filter.connect(self, :params)
103
92
 
104
93
  def to_s
105
- f = @filters.empty? ? '' : ' with'+@filters
106
- "/etc/passwd#{f}"
107
- end
108
-
109
- def count
110
- @params.length
111
- end
112
-
113
- private
114
-
115
- def map_data(id)
116
- @params.map { |x| x[id] }
117
- end
118
-
119
- def filter_res_line(item, matcher, condition, positive)
120
- # TODO: REWORK ALL OF THESE, please don't depend on them except for simple equality!
121
- case matcher
122
- when '<'
123
- item.to_i < condition
124
- when '<='
125
- item.to_i <= condition
126
- when '>'
127
- item.to_i > condition
128
- when '>='
129
- item.to_i >= condition
130
- else
131
- condition = condition.to_s if condition.is_a? Integer
132
- case item
133
- when condition
134
- positive
135
- else
136
- !positive
137
- end
138
- end
139
- end
140
-
141
- def filter_attribute(attr, condition, res, filters)
142
- matcher = '=='
143
- positive = true
144
- if condition.is_a?(Hash) && condition.length == 1
145
- matcher = condition.keys[0].to_s
146
- condition = condition.values[0]
147
- end
148
- positive = false if matcher == '!='
149
-
150
- a = res.find_all do |line|
151
- filter_res_line(line[attr.to_s], matcher, condition, positive)
152
- end
153
- b = filters + " #{attr} #{matcher} #{condition.inspect}"
154
- [a, b]
94
+ '/etc/passwd'
155
95
  end
156
96
  end
157
97
  end