inspec 0.20.1 → 0.21.0

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