inspec 1.31.0 → 1.45.9

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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +2599 -1
  4. data/Gemfile +0 -1
  5. data/README.md +24 -34
  6. data/Rakefile +0 -31
  7. data/docs/dsl_inspec.md +10 -0
  8. data/docs/matchers.md +29 -0
  9. data/docs/plugin_kitchen_inspec.md +18 -24
  10. data/docs/profiles.md +101 -81
  11. data/docs/resources/aide_conf.md.erb +71 -0
  12. data/docs/resources/apache_conf.md.erb +19 -33
  13. data/docs/resources/apt.md.erb +22 -36
  14. data/docs/resources/audit_policy.md.erb +9 -24
  15. data/docs/resources/auditd.md.erb +78 -0
  16. data/docs/resources/auditd_conf.md.erb +20 -34
  17. data/docs/resources/auditd_rules.md.erb +8 -24
  18. data/docs/resources/bash.md.erb +4 -26
  19. data/docs/resources/bond.md.erb +25 -40
  20. data/docs/resources/bridge.md.erb +5 -25
  21. data/docs/resources/bsd_service.md.erb +5 -25
  22. data/docs/resources/command.md.erb +35 -50
  23. data/docs/resources/cpan.md.erb +62 -0
  24. data/docs/resources/cran.md.erb +54 -0
  25. data/docs/resources/crontab.md.erb +9 -23
  26. data/docs/resources/csv.md.erb +12 -27
  27. data/docs/resources/dh_params.md +1 -0
  28. data/docs/resources/directory.md.erb +5 -25
  29. data/docs/resources/docker.md.erb +61 -58
  30. data/docs/resources/docker_container.md.erb +23 -19
  31. data/docs/resources/docker_image.md.erb +20 -16
  32. data/docs/resources/elasticsearch.md.erb +245 -0
  33. data/docs/resources/etc_fstab.md.erb +120 -0
  34. data/docs/resources/etc_group.md.erb +29 -45
  35. data/docs/resources/etc_hosts.md.erb +68 -0
  36. data/docs/resources/etc_hosts_allow.md.erb +68 -0
  37. data/docs/resources/etc_hosts_deny.md.erb +68 -0
  38. data/docs/resources/file.md.erb +227 -216
  39. data/docs/resources/firewalld.md.erb +104 -0
  40. data/docs/resources/gem.md.erb +21 -35
  41. data/docs/resources/group.md.erb +16 -30
  42. data/docs/resources/grub_conf.md.erb +9 -24
  43. data/docs/resources/host.md.erb +32 -49
  44. data/docs/resources/http.md.erb +38 -44
  45. data/docs/resources/iis_app.md.erb +25 -35
  46. data/docs/resources/iis_site.md.erb +26 -40
  47. data/docs/resources/inetd_conf.md.erb +27 -42
  48. data/docs/resources/ini.md.erb +9 -23
  49. data/docs/resources/interface.md.erb +5 -25
  50. data/docs/resources/iptables.md.erb +15 -29
  51. data/docs/resources/json.md.erb +12 -27
  52. data/docs/resources/kernel_module.md.erb +47 -61
  53. data/docs/resources/kernel_parameter.md.erb +15 -29
  54. data/docs/resources/key_rsa.md.erb +3 -0
  55. data/docs/resources/launchd_service.md.erb +5 -25
  56. data/docs/resources/limits_conf.md.erb +15 -29
  57. data/docs/resources/login_def.md.erb +15 -30
  58. data/docs/resources/mount.md.erb +18 -33
  59. data/docs/resources/mssql_session.md.erb +9 -12
  60. data/docs/resources/mysql_conf.md.erb +28 -32
  61. data/docs/resources/mysql_session.md.erb +15 -29
  62. data/docs/resources/nginx.md.erb +78 -0
  63. data/docs/resources/nginx_conf.md.erb +127 -0
  64. data/docs/resources/npm.md.erb +19 -35
  65. data/docs/resources/ntp_conf.md.erb +20 -37
  66. data/docs/resources/oneget.md.erb +15 -30
  67. data/docs/resources/oracledb_session.md.erb +9 -11
  68. data/docs/resources/os.md.erb +29 -43
  69. data/docs/resources/os_env.md.erb +23 -44
  70. data/docs/resources/package.md.erb +34 -36
  71. data/docs/resources/parse_config.md.erb +5 -25
  72. data/docs/resources/parse_config_file.md.erb +31 -43
  73. data/docs/resources/{etc_passwd.md.erb → passwd.md.erb} +24 -39
  74. data/docs/resources/pip.md.erb +20 -35
  75. data/docs/resources/port.md.erb +43 -57
  76. data/docs/resources/postgres_conf.md.erb +17 -31
  77. data/docs/resources/postgres_hba_conf.md.erb +26 -38
  78. data/docs/resources/postgres_ident_conf.md.erb +25 -37
  79. data/docs/resources/postgres_session.md.erb +15 -29
  80. data/docs/resources/powershell.md.erb +27 -42
  81. data/docs/resources/processes.md.erb +17 -33
  82. data/docs/resources/rabbitmq_config.md.erb +9 -24
  83. data/docs/resources/registry_key.md.erb +27 -42
  84. data/docs/resources/runit_service.md.erb +5 -25
  85. data/docs/resources/security_policy.md.erb +12 -27
  86. data/docs/resources/service.md.erb +27 -42
  87. data/docs/resources/{etc_shadow.md.erb → shadow.md.erb} +41 -47
  88. data/docs/resources/ssh_config.md.erb +19 -34
  89. data/docs/resources/sshd_config.md.erb +19 -34
  90. data/docs/resources/ssl.md.erb +39 -54
  91. data/docs/resources/sys_info.md.erb +12 -26
  92. data/docs/resources/systemd_service.md.erb +5 -25
  93. data/docs/resources/sysv_service.md.erb +5 -25
  94. data/docs/resources/upstart_service.md.erb +5 -25
  95. data/docs/resources/user.md.erb +29 -44
  96. data/docs/resources/users.md.erb +12 -26
  97. data/docs/resources/vbscript.md.erb +9 -24
  98. data/docs/resources/virtualization.md.erb +8 -23
  99. data/docs/resources/windows_feature.md.erb +15 -30
  100. data/docs/resources/windows_hotfix.md.erb +50 -0
  101. data/docs/resources/windows_task.md.erb +12 -26
  102. data/docs/resources/wmi.md.erb +9 -24
  103. data/docs/resources/x509_certificate.md.erb +4 -0
  104. data/docs/resources/xinetd_conf.md.erb +65 -80
  105. data/docs/resources/xml.md.erb +61 -0
  106. data/docs/resources/yaml.md.erb +12 -27
  107. data/docs/resources/yum.md.erb +37 -51
  108. data/docs/resources/zfs_dataset.md.erb +15 -26
  109. data/docs/resources/zfs_pool.md.erb +9 -20
  110. data/docs/ruby_usage.md +0 -1
  111. data/examples/meta-profile/README.md +1 -1
  112. data/examples/profile-sensitive/README.md +29 -0
  113. data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -0
  114. data/examples/profile-sensitive/controls/sensitive.rb +9 -0
  115. data/examples/profile-sensitive/inspec.yml +8 -0
  116. data/inspec.gemspec +3 -3
  117. data/lib/bundles/inspec-artifact/cli.rb +5 -5
  118. data/lib/bundles/inspec-compliance/README.md +24 -22
  119. data/lib/bundles/inspec-compliance/api/login.rb +152 -0
  120. data/lib/bundles/inspec-compliance/api.rb +46 -16
  121. data/lib/bundles/inspec-compliance/cli.rb +58 -165
  122. data/lib/bundles/inspec-compliance/target.rb +2 -3
  123. data/lib/bundles/inspec-habitat/cli.rb +7 -2
  124. data/lib/bundles/inspec-habitat/profile.rb +44 -20
  125. data/lib/bundles/inspec-init/templates/profile/README.md +1 -1
  126. data/lib/bundles/inspec-init/templates/profile/controls/example.rb +1 -1
  127. data/lib/bundles/inspec-supermarket/api.rb +1 -1
  128. data/lib/inspec/backend.rb +8 -0
  129. data/lib/inspec/cli.rb +1 -1
  130. data/lib/inspec/control_eval_context.rb +16 -6
  131. data/lib/inspec/dependencies/requirement.rb +22 -8
  132. data/lib/inspec/dependencies/resolver.rb +1 -1
  133. data/lib/inspec/exceptions.rb +4 -0
  134. data/lib/inspec/file_provider.rb +38 -9
  135. data/lib/inspec/metadata.rb +17 -3
  136. data/lib/inspec/objects/attribute.rb +16 -7
  137. data/lib/inspec/objects/control.rb +10 -2
  138. data/lib/inspec/plugins/resource.rb +34 -5
  139. data/lib/inspec/profile.rb +10 -1
  140. data/lib/inspec/profile_context.rb +3 -1
  141. data/lib/inspec/resource.rb +17 -5
  142. data/lib/inspec/rspec_json_formatter.rb +48 -14
  143. data/lib/inspec/rule.rb +4 -0
  144. data/lib/inspec/runner.rb +72 -30
  145. data/lib/inspec/shell.rb +24 -19
  146. data/lib/inspec/version.rb +1 -1
  147. data/lib/matchers/matchers.rb +40 -0
  148. data/lib/resources/aide_conf.rb +162 -0
  149. data/lib/resources/auditd.rb +231 -0
  150. data/lib/resources/auditd_rules.rb +3 -1
  151. data/lib/resources/command.rb +1 -1
  152. data/lib/resources/cpan.rb +60 -0
  153. data/lib/resources/cran.rb +66 -0
  154. data/lib/resources/docker.rb +8 -1
  155. data/lib/resources/docker_container.rb +19 -2
  156. data/lib/resources/elasticsearch.rb +172 -0
  157. data/lib/resources/etc_fstab.rb +102 -0
  158. data/lib/resources/etc_group.rb +1 -1
  159. data/lib/resources/etc_hosts.rb +81 -0
  160. data/lib/resources/etc_hosts_allow_deny.rb +122 -0
  161. data/lib/resources/file.rb +87 -10
  162. data/lib/resources/firewalld.rb +144 -0
  163. data/lib/resources/groups.rb +0 -1
  164. data/lib/resources/host.rb +30 -15
  165. data/lib/resources/http.rb +178 -22
  166. data/lib/resources/mssql_session.rb +3 -3
  167. data/lib/resources/mysql_conf.rb +10 -0
  168. data/lib/resources/mysql_session.rb +1 -1
  169. data/lib/resources/nginx.rb +97 -0
  170. data/lib/resources/nginx_conf.rb +134 -1
  171. data/lib/resources/oracledb_session.rb +9 -8
  172. data/lib/resources/os_env.rb +7 -4
  173. data/lib/resources/package.rb +14 -2
  174. data/lib/resources/packages.rb +1 -1
  175. data/lib/resources/pip.rb +28 -20
  176. data/lib/resources/port.rb +251 -3
  177. data/lib/resources/postgres.rb +1 -1
  178. data/lib/resources/postgres_session.rb +1 -1
  179. data/lib/resources/processes.rb +106 -20
  180. data/lib/resources/registry_key.rb +11 -2
  181. data/lib/resources/service.rb +5 -24
  182. data/lib/resources/ssh_conf.rb +1 -1
  183. data/lib/resources/ssl.rb +4 -3
  184. data/lib/resources/toml.rb +2 -2
  185. data/lib/resources/windows_hotfix.rb +35 -0
  186. data/lib/resources/xinetd.rb +4 -6
  187. data/lib/resources/xml.rb +27 -0
  188. data/lib/source_readers/inspec.rb +3 -3
  189. data/lib/utils/find_files.rb +2 -1
  190. data/lib/utils/nginx_parser.rb +10 -1
  191. data/lib/utils/object_traversal.rb +35 -10
  192. data/lib/utils/parser.rb +21 -3
  193. metadata +48 -18
  194. data/docs/cli.md +0 -392
data/lib/inspec/runner.rb CHANGED
@@ -124,11 +124,13 @@ module Inspec
124
124
  return options[:attributes] if secrets_targets.nil?
125
125
 
126
126
  secrets_targets.each do |target|
127
+ validate_attributes_file_readability!(target)
128
+
127
129
  secrets = Inspec::SecretsBackend.resolve(target)
128
130
  if secrets.nil?
129
131
  raise Inspec::Exceptions::SecretsBackendNotFound,
130
- "Unable to find a parser for attributes file #{target}. " \
131
- 'Check to make sure the file exists and has the appropriate extension.'
132
+ "Cannot find parser for attributes file '#{target}'. " \
133
+ 'Check to make sure file has the appropriate extension.'
132
134
  end
133
135
 
134
136
  next if secrets.attributes.nil?
@@ -229,35 +231,18 @@ module Inspec
229
231
  def get_check_example(method_name, arg, block)
230
232
  opts = block_source_info(block)
231
233
 
232
- if !arg.empty? &&
233
- arg[0].respond_to?(:resource_skipped) &&
234
- !arg[0].resource_skipped.nil?
235
- return @test_collector.example_group(*arg, opts) do
236
- it arg[0].resource_skipped
237
- end
238
- else
239
- # add the resource
240
- case method_name
241
- when 'describe'
242
- return @test_collector.example_group(*arg, opts, &block)
243
- when 'expect'
244
- return block.example_group
245
- when 'describe.one'
246
- tests = arg.map do |x|
247
- @test_collector.example_group(x[1][0], block_source_info(x[2]), &x[2])
248
- end
249
- return nil if tests.empty?
250
- ok_tests = tests.find_all(&:run)
251
- # return all tests if none succeeds; we will just report full failure
252
- return tests if ok_tests.empty?
253
- # otherwise return all working tests
254
- return ok_tests
255
- else
256
- raise "A rule was registered with #{method_name.inspect}, "\
257
- "which isn't understood and cannot be processed."
258
- end
234
+ return nil if arg.empty?
235
+
236
+ if arg[0].respond_to?(:resource_skipped?) && arg[0].resource_skipped?
237
+ return rspec_skipped_block(arg, opts, arg[0].resource_exception_message)
238
+ end
239
+
240
+ if arg[0].respond_to?(:resource_failed?) && arg[0].resource_failed?
241
+ return rspec_failed_block(arg, opts, arg[0].resource_exception_message)
259
242
  end
260
- nil
243
+
244
+ # If neither skipped nor failed then add the resource
245
+ add_resource(method_name, arg, opts, block)
261
246
  end
262
247
 
263
248
  def register_rule(rule)
@@ -270,5 +255,62 @@ module Inspec
270
255
 
271
256
  examples.each { |e| @test_collector.add_test(e, rule) }
272
257
  end
258
+
259
+ def validate_attributes_file_readability!(target)
260
+ unless File.exist?(target)
261
+ raise Inspec::Exceptions::AttributesFileDoesNotExist,
262
+ "Cannot find attributes file '#{target}'. " \
263
+ 'Check to make sure file exists.'
264
+ end
265
+
266
+ unless File.readable?(target)
267
+ raise Inspec::Exceptions::AttributesFileNotReadable,
268
+ "Cannot read attributes file '#{target}'. " \
269
+ 'Check to make sure file is readable.'
270
+ end
271
+
272
+ true
273
+ end
274
+
275
+ def rspec_skipped_block(arg, opts, message)
276
+ @test_collector.example_group(*arg, opts) do
277
+ # Send custom `it` block to RSpec
278
+ it message
279
+ end
280
+ end
281
+
282
+ def rspec_failed_block(arg, opts, message)
283
+ @test_collector.example_group(*arg, opts) do
284
+ # Send custom `it` block to RSpec
285
+ it '' do
286
+ # Raising here to fail the test and get proper formatting
287
+ raise Inspec::Exceptions::ResourceFailed, message
288
+ end
289
+ end
290
+ end
291
+
292
+ def add_resource(method_name, arg, opts, block)
293
+ case method_name
294
+ when 'describe'
295
+ @test_collector.example_group(*arg, opts, &block)
296
+ when 'expect'
297
+ block.example_group
298
+ when 'describe.one'
299
+ tests = arg.map do |x|
300
+ @test_collector.example_group(x[1][0], block_source_info(x[2]), &x[2])
301
+ end
302
+ return nil if tests.empty?
303
+
304
+ successful_tests = tests.find_all(&:run)
305
+
306
+ # Return all tests if none succeeds; we will just report full failure
307
+ return tests if successful_tests.empty?
308
+
309
+ successful_tests
310
+ else
311
+ raise "A rule was registered with #{method_name.inspect}," \
312
+ "which isn't understood and cannot be processed."
313
+ end
314
+ end
273
315
  end
274
316
  end
data/lib/inspec/shell.rb CHANGED
@@ -26,8 +26,13 @@ module Inspec
26
26
  end
27
27
 
28
28
  def configure_pry # rubocop:disable Metrics/AbcSize
29
- # Remove all hooks and checks
30
- Pry.hooks.clear_all
29
+ # Delete any before_session, before_eval, and after_eval hooks so we can
30
+ # replace them with our own. Pry 0.10 used to have a single method to clear
31
+ # all hooks, but this was removed in Pry 0.11.
32
+ [:before_session, :before_eval, :after_eval].each do |event|
33
+ Pry.hooks.get_hooks(event).keys.map { |hook| Pry.hooks.delete_hook(event, hook) }
34
+ end
35
+
31
36
  that = self
32
37
 
33
38
  # Add the help command
@@ -37,7 +42,7 @@ module Inspec
37
42
 
38
43
  # configure pry shell prompt
39
44
  Pry.config.prompt_name = 'inspec'
40
- Pry.prompt = [proc { "#{readline_ignore("\e[0;32m")}#{Pry.config.prompt_name}> #{readline_ignore("\e[0m")}" }]
45
+ Pry.prompt = [proc { "#{readline_ignore("\e[1m\e[32m")}#{Pry.config.prompt_name}> #{readline_ignore("\e[0m")}" }]
41
46
 
42
47
  # Add a help menu as the default intro
43
48
  Pry.hooks.add_hook(:before_session, 'inspec_intro') do
@@ -74,7 +79,7 @@ module Inspec
74
79
  end
75
80
 
76
81
  def mark(x)
77
- "#{readline_ignore("\033[1m")}#{x}#{readline_ignore("\033[0m")}"
82
+ "\e[1m\e[39m#{x}\e[0m"
78
83
  end
79
84
 
80
85
  def print_example(example)
@@ -101,8 +106,8 @@ module Inspec
101
106
  puts <<EOF
102
107
  You are currently running on:
103
108
 
104
- OS platform: #{mark ctx.os[:name] || 'unknown'}
105
- OS family: #{mark ctx.os[:family] || 'unknown'}
109
+ OS platform: #{mark ctx.os[:name] || 'unknown'}
110
+ OS family: #{mark ctx.os[:family] || 'unknown'}
106
111
  OS release: #{mark ctx.os[:release] || 'unknown'}
107
112
  EOF
108
113
  end
@@ -134,21 +139,21 @@ EOF
134
139
  elsif topic == 'matchers'
135
140
  print_matchers_help
136
141
  elsif !Inspec::Resource.registry[topic].nil?
137
- puts <<EOF
138
- #{mark 'Name:'} #{topic}
139
-
140
- #{mark 'Description:'}
141
-
142
- #{Inspec::Resource.registry[topic].desc}
143
-
144
- #{mark 'Example:'}
145
- #{print_example(Inspec::Resource.registry[topic].example)}
146
-
147
- #{mark 'Web Reference:'}
142
+ topic_info = Inspec::Resource.registry[topic]
143
+ info = "#{mark 'Name:'} #{topic}\n\n"
144
+ unless topic_info.desc.nil?
145
+ info += "#{mark 'Description:'}\n\n"
146
+ info += "#{topic_info.desc}\n\n"
147
+ end
148
148
 
149
- https://www.inspec.io/docs/reference/resources/#{topic}
149
+ unless topic_info.example.nil?
150
+ info += "#{mark 'Example:'}\n"
151
+ info += "#{print_example(topic_info.example)}\n\n"
152
+ end
150
153
 
151
- EOF
154
+ info += "#{mark 'Web Reference:'}\n\n"
155
+ info += "https://www.inspec.io/docs/reference/resources/#{topic}\n\n"
156
+ puts info
152
157
  else
153
158
  puts "The resource #{topic} does not exist. For a list of valid resources, type: help resources"
154
159
  end
@@ -4,5 +4,5 @@
4
4
  # author: Christoph Hartmann
5
5
 
6
6
  module Inspec
7
- VERSION = '1.31.0'.freeze
7
+ VERSION = '1.45.9'
8
8
  end
@@ -222,6 +222,44 @@ RSpec::Matchers.define :contain do |rule|
222
222
  end
223
223
  end
224
224
 
225
+ # `be_in` matcher
226
+ # You can use it in the following cases:
227
+ # - check if an item or array is included in a given array
228
+ # eg:
229
+ # describe nginx do
230
+ # its('user') { should be_in AUTHORIZED_USER_LIST }
231
+ # end
232
+ # describe nginx do
233
+ # its('module_list') { should be_in AUTHORIZED_MODULE_LIST }
234
+ # end
235
+ RSpec::Matchers.define :be_in do |list|
236
+ match do |item|
237
+ # Handle both single item and array
238
+ item.is_a?(Array) ? (item - list).empty? : list.include?(item)
239
+ end
240
+
241
+ match_when_negated do |item|
242
+ # Handle both single item and array
243
+ item.is_a?(Array) ? (item & list).empty? : !list.include?(item)
244
+ end
245
+
246
+ failure_message do |item|
247
+ if item.is_a?(Array)
248
+ "expected `#{item}` to be in the list: `#{list}` \nDiff:\n #{(item - list)}"
249
+ else
250
+ "expected `#{item}` to be in the list: `#{list}`"
251
+ end
252
+ end
253
+
254
+ failure_message_when_negated do |item|
255
+ if item.is_a?(Array)
256
+ "expected `#{item}` not to be in the list: `#{list}` \nComm:\n #{(item & list)}"
257
+ else
258
+ "expected `#{item}` not to be in the list: `#{list}`"
259
+ end
260
+ end
261
+ end
262
+
225
263
  # This matcher implements a compare feature that cannot be covered by the default
226
264
  # `eq` matcher
227
265
  # You can use it in the following cases:
@@ -278,6 +316,8 @@ RSpec::Matchers.define :cmp do |first_expected|
278
316
  return actual.to_i.method(op).call(expected)
279
317
  elsif expected.is_a?(Float) && float?(actual)
280
318
  return actual.to_f.method(op).call(expected)
319
+ elsif actual.is_a?(Symbol) && expected.is_a?(String)
320
+ return actual.to_s.method(op).call(expected)
281
321
  elsif octal?(expected) && actual.is_a?(Integer)
282
322
  return actual.method(op).call(expected.to_i(8))
283
323
  end
@@ -0,0 +1,162 @@
1
+ # encoding: utf-8
2
+ # author: Jen Burns, burnsjennifere@gmail.com
3
+
4
+ require 'utils/filter'
5
+ require 'utils/parser'
6
+
7
+ # rubocop:disable Metrics/ClassLength
8
+ module Inspec::Resources
9
+ class AideConf < Inspec.resource(1)
10
+ name 'aide_conf'
11
+ desc 'Use the aide_conf InSpec audit resource to test the rules established for
12
+ the file integrity tool AIDE. Controlled by the aide.conf file typically at /etc/aide.conf.'
13
+ example "
14
+ describe aide_conf do
15
+ its('selection_lines') { should include '/sbin' }
16
+ end
17
+
18
+ describe aide_conf.where { selection_line == '/bin' } do
19
+ its('rules.flatten') { should include 'r' }
20
+ end
21
+
22
+ describe aide_conf.all_have_rule('sha512') do
23
+ it { should eq true }
24
+ end
25
+ "
26
+
27
+ attr_reader :params
28
+
29
+ include CommentParser
30
+
31
+ def initialize(aide_conf_path = nil)
32
+ return skip_resource 'The `aide_conf` resource is not supported on your OS.' unless inspec.os.linux?
33
+ @conf_path = aide_conf_path || '/etc/aide.conf'
34
+ @content = nil
35
+ @rules = nil
36
+ read_content
37
+ end
38
+
39
+ def all_have_rule(rule)
40
+ # Case when file didn't exist or perms didn't allow an open
41
+ return false if @content.nil?
42
+
43
+ lines = @params.reject { |line| line['rules'].include? rule }
44
+ lines.empty?
45
+ end
46
+
47
+ filter = FilterTable.create
48
+ filter.add_accessor(:where)
49
+ .add_accessor(:entries)
50
+ .add(:selection_lines, field: 'selection_line')
51
+ .add(:rules, field: 'rules')
52
+
53
+ filter.connect(self, :params)
54
+
55
+ private
56
+
57
+ def read_content
58
+ return @content unless @content.nil?
59
+ @rules = {}
60
+
61
+ file = inspec.file(@conf_path)
62
+ if !file.file?
63
+ return skip_resource "Can't find file \"#{@conf_path}\""
64
+ end
65
+ raw_conf = file.content
66
+ if raw_conf.nil?
67
+ return skip_resource "File can't be opened or is empty \"#{@conf_path}\""
68
+ end
69
+ if raw_conf.empty? && !file.empty?
70
+ return skip_resource "Can't read file \"#{@conf_path}\""
71
+ end
72
+
73
+ # If there is a file and it contains content, continue
74
+ @content = filter_comments(inspec.file(@conf_path).content.lines)
75
+ @params = parse_conf(@content)
76
+ end
77
+
78
+ def filter_comments(data)
79
+ content = []
80
+ data.each do |line|
81
+ content_line, = parse_comment_line(line, comment_char: '#', standalone_comments: false)
82
+ content.push(content_line)
83
+ end
84
+ content
85
+ end
86
+
87
+ def parse_conf(content)
88
+ params = []
89
+ content.each do |line|
90
+ param = parse_line(line)
91
+ if !param['selection_line'].nil?
92
+ params.push(param)
93
+ end
94
+ end
95
+ params
96
+ end
97
+
98
+ def parse_line(line)
99
+ line_and_rules = {}
100
+ # Case when line is a rule line
101
+ if line.include?(' = ')
102
+ parse_rule_line(line)
103
+ # Case when line is a selection line
104
+ elsif line.start_with?('/', '!', '=')
105
+ line_and_rules = parse_selection_line(line)
106
+ end
107
+ line_and_rules
108
+ end
109
+
110
+ def parse_rule_line(line)
111
+ line.gsub!(/\s+/, '')
112
+ rule_line_arr = line.split('=')
113
+ rules_list = rule_line_arr.last.split('+')
114
+ rule_name = rule_line_arr.first
115
+ rules_list.each_index do |i|
116
+ # Cases where rule respresents one or more other rules
117
+ if @rules.key?(rules_list[i])
118
+ rules_list[i] = @rules[rules_list[i]]
119
+ end
120
+ rules_list[i] = handle_multi_rule(rules_list, i)
121
+ end
122
+ @rules[rule_name] = rules_list.flatten
123
+ end
124
+
125
+ def parse_selection_line(line)
126
+ selec_line_arr = line.split(' ')
127
+ selection_line = selec_line_arr.first
128
+ selection_line.chop! if selection_line.end_with?('/')
129
+ rule_list = selec_line_arr.last.split('+')
130
+ rule_list.each_index do |i|
131
+ hash_list = @rules[rule_list[i]]
132
+ # Cases where rule respresents one or more other rules
133
+ if !hash_list.nil?
134
+ rule_list[i] = hash_list
135
+ end
136
+ rule_list[i] = handle_multi_rule(rule_list, i)
137
+ end
138
+ rule_list.flatten!
139
+ {
140
+ 'selection_line' => selection_line,
141
+ 'rules' => rule_list,
142
+ }
143
+ end
144
+
145
+ def handle_multi_rule(rule_list, i)
146
+ # Rules that represent multiple rules (R,L,>)
147
+ r_rules = %w{p i l n u g s m c md5}
148
+ l_rules = %w{p i l n u g}
149
+ grow_log_rules = %w{p l u g i n S}
150
+
151
+ case rule_list[i]
152
+ when 'R'
153
+ return r_rules
154
+ when 'L'
155
+ return l_rules
156
+ when '>'
157
+ return grow_log_rules
158
+ end
159
+ rule_list[i]
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,231 @@
1
+ # encoding: utf-8
2
+ # author: Christoph Hartmann
3
+ # author: Dominik Richter
4
+ # author: Jen Burns
5
+
6
+ require 'forwardable'
7
+ require 'utils/filter_array'
8
+ require 'utils/filter'
9
+ require 'utils/parser'
10
+
11
+ module Inspec::Resources
12
+ # rubocop:disable Metrics/ClassLength
13
+ class AuditDaemon < Inspec.resource(1)
14
+ extend Forwardable
15
+ attr_accessor :lines
16
+ attr_reader :params
17
+
18
+ name 'auditd'
19
+ desc 'Use the auditd InSpec audit resource to test the rules for logging that exist on the system. The audit.rules file is typically located under /etc/audit/ and contains the list of rules that define what is captured in log files. These rules are output using the auditcl -l command.'
20
+ example "
21
+ describe auditd.syscall('chown').where {arch == 'b32'} do
22
+ its('action') { should eq ['always'] }
23
+ its('list') { should eq ['exit'] }
24
+ end
25
+
26
+ describe auditd.where {key == 'privileged'} do
27
+ its('permissions') { should include ['x'] }
28
+ end
29
+
30
+ describe auditd do
31
+ its('lines') { should include %r(-w /etc/ssh/sshd_config) }
32
+ end
33
+ "
34
+
35
+ def initialize
36
+ @content = inspec.command('/sbin/auditctl -l').stdout.chomp
37
+ @params = []
38
+
39
+ if @content =~ /^LIST_RULES:/
40
+ return skip_resource 'The version of audit is outdated. The `auditd` resource supports versions of audit >= 2.3.'
41
+ end
42
+ parse_content
43
+ end
44
+
45
+ filter = FilterTable.create
46
+ filter.add_accessor(:where)
47
+ .add_accessor(:entries)
48
+ .add(:file, field: 'file')
49
+ .add(:list, field: 'list')
50
+ .add(:action, field: 'action')
51
+ .add(:fields, field: 'fields')
52
+ .add(:fields_nokey, field: 'fields_nokey')
53
+ .add(:syscall, field: 'syscall')
54
+ .add(:key, field: 'key')
55
+ .add(:arch, field: 'arch')
56
+ .add(:path, field: 'path')
57
+ .add(:permissions, field: 'permissions')
58
+ .add(:exit, field: 'exit')
59
+
60
+ filter.connect(self, :params)
61
+
62
+ def status(name = nil)
63
+ @status_content ||= inspec.command('/sbin/auditctl -s').stdout.chomp
64
+ @status_params ||= Hash[@status_content.scan(/^([^ ]+) (.*)$/)]
65
+
66
+ return @status_params[name] if name
67
+ @status_params
68
+ end
69
+
70
+ def parse_content
71
+ @lines = @content.lines.map(&:chomp)
72
+
73
+ lines.each do |line|
74
+ if is_file_syscall_syntax?(line)
75
+ file_syscall_syntax_rules_for(line)
76
+ end
77
+
78
+ if is_syscall?(line)
79
+ syscall_rules_for(line)
80
+
81
+ elsif is_file?(line)
82
+ file_rules_for(line)
83
+ end
84
+ end
85
+ end
86
+
87
+ def file_syscall_syntax_rules_for(line)
88
+ file = file_syscall_syntax_for(line)
89
+ action, list = action_list_for(line)
90
+ fields = rule_fields_for(line)
91
+ key_field, fields_nokey = remove_key_from(fields)
92
+ key = key_in(key_field.join(''))
93
+ perms = perms_in(fields)
94
+
95
+ @params.push(
96
+ {
97
+ 'file' => file,
98
+ 'list' => list,
99
+ 'action' => action,
100
+ 'fields' => fields,
101
+ 'permissions' => perms,
102
+ 'key' => key,
103
+ 'fields_nokey' => fields_nokey,
104
+ },)
105
+ end
106
+
107
+ def syscall_rules_for(line)
108
+ syscalls = syscalls_for(line)
109
+ action, list = action_list_for(line)
110
+ fields = rule_fields_for(line)
111
+ key_field, fields_nokey = remove_key_from(fields)
112
+ key = key_in(key_field.join(''))
113
+ arch = arch_in(fields)
114
+ path = path_in(fields)
115
+ perms = perms_in(fields)
116
+ exit_field = exit_in(fields)
117
+
118
+ syscalls.each do |s|
119
+ @params.push(
120
+ {
121
+ 'syscall' => s,
122
+ 'list' => list,
123
+ 'action' => action,
124
+ 'fields' => fields,
125
+ 'key' => key,
126
+ 'arch' => arch,
127
+ 'path' => path,
128
+ 'permissions' => perms,
129
+ 'exit' => exit_field,
130
+ 'fields_nokey' => fields_nokey,
131
+ },)
132
+ end
133
+ end
134
+
135
+ def file_rules_for(line)
136
+ file = file_for(line)
137
+ perms = permissions_for(line)
138
+ key = key_for(line)
139
+
140
+ @params.push(
141
+ {
142
+ 'file' => file,
143
+ 'key' => key,
144
+ 'permissions' => perms,
145
+ },)
146
+ end
147
+
148
+ def to_s
149
+ 'Auditd Rules'
150
+ end
151
+
152
+ private
153
+
154
+ def is_syscall?(line)
155
+ line.match(/-S /)
156
+ end
157
+
158
+ def is_file?(line)
159
+ line.match(/-w /)
160
+ end
161
+
162
+ def is_file_syscall_syntax?(line)
163
+ line.match(/-F path=/)
164
+ end
165
+
166
+ def syscalls_for(line)
167
+ line.scan(/-S ([^ ]+)\s?/).flatten.first.split(',')
168
+ end
169
+
170
+ def action_list_for(line)
171
+ line.scan(/-a ([^,]+),([^ ]+)\s?/).flatten
172
+ end
173
+
174
+ def key_for(line)
175
+ line.match(/-k ([^ ]+)\s?/)[1] if line.include?('-k ')
176
+ end
177
+
178
+ def file_for(line)
179
+ line.match(/-w ([^ ]+)\s?/)[1]
180
+ end
181
+
182
+ def file_syscall_syntax_for(line)
183
+ line.match(/-F path=(\S+)\s?/)[1]
184
+ end
185
+
186
+ def permissions_for(line)
187
+ line.match(/-p ([^ ]+)/)[1].scan(/\w/)
188
+ end
189
+
190
+ def rule_fields_for(line)
191
+ line.gsub(/-[aS] [^ ]+ /, '').split('-F ').map { |l| l.split(' ') }.flatten
192
+ end
193
+
194
+ def arch_in(fields)
195
+ fields.each do |field|
196
+ return field.match(/arch=(\S+)\s?/)[1] if field.start_with?('arch=')
197
+ end
198
+ nil
199
+ end
200
+
201
+ def perms_in(fields)
202
+ fields.each do |field|
203
+ return field.match(/perm=(\S+)\s?/)[1].scan(/\w/) if field.start_with?('perm=')
204
+ end
205
+ nil
206
+ end
207
+
208
+ def path_in(fields)
209
+ fields.each do |field|
210
+ return field.match(/path=(\S+)\s?/)[1] if field.start_with?('path=')
211
+ end
212
+ nil
213
+ end
214
+
215
+ def exit_in(fields)
216
+ fields.each do |field|
217
+ return field.match(/exit=(\S+)\s?/)[1] if field.start_with?('exit=')
218
+ end
219
+ nil
220
+ end
221
+
222
+ def key_in(field)
223
+ _, v = field.split('=')
224
+ v
225
+ end
226
+
227
+ def remove_key_from(fields)
228
+ fields.partition { |x| x.start_with? 'key' }
229
+ end
230
+ end
231
+ end
@@ -87,6 +87,8 @@ module Inspec::Resources
87
87
  parse_content
88
88
  @legacy = nil
89
89
  end
90
+
91
+ warn '[DEPRECATION] The `auditd_rules` resource is deprecated and will be removed in InSpec 2.0. Use the `auditd` resource instead.'
90
92
  end
91
93
 
92
94
  # non-legacy instances are not asked for `its('LIST_RULES')`
@@ -177,7 +179,7 @@ module Inspec::Resources
177
179
 
178
180
  # NB only in file lines
179
181
  def get_key(line)
180
- line.match(/-k ([^ ]+)/)[1]
182
+ line.match(/-k ([^ ]+)/)[1] if line.include?('-k ')
181
183
  end
182
184
 
183
185
  # NOTE there are NO precautions wrt. filenames containing spaces in auditctl
@@ -47,7 +47,7 @@ module Inspec::Resources
47
47
 
48
48
  def exist?
49
49
  # silent for mock resources
50
- return false if inspec.os[:name].to_s == 'unknown'
50
+ return false if inspec.os.name.nil?
51
51
 
52
52
  if inspec.os.linux?
53
53
  res = inspec.backend.run_command("bash -c 'type \"#{@command}\"'")