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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +2599 -1
- data/Gemfile +0 -1
- data/README.md +24 -34
- data/Rakefile +0 -31
- data/docs/dsl_inspec.md +10 -0
- data/docs/matchers.md +29 -0
- data/docs/plugin_kitchen_inspec.md +18 -24
- data/docs/profiles.md +101 -81
- data/docs/resources/aide_conf.md.erb +71 -0
- data/docs/resources/apache_conf.md.erb +19 -33
- data/docs/resources/apt.md.erb +22 -36
- data/docs/resources/audit_policy.md.erb +9 -24
- data/docs/resources/auditd.md.erb +78 -0
- data/docs/resources/auditd_conf.md.erb +20 -34
- data/docs/resources/auditd_rules.md.erb +8 -24
- data/docs/resources/bash.md.erb +4 -26
- data/docs/resources/bond.md.erb +25 -40
- data/docs/resources/bridge.md.erb +5 -25
- data/docs/resources/bsd_service.md.erb +5 -25
- data/docs/resources/command.md.erb +35 -50
- data/docs/resources/cpan.md.erb +62 -0
- data/docs/resources/cran.md.erb +54 -0
- data/docs/resources/crontab.md.erb +9 -23
- data/docs/resources/csv.md.erb +12 -27
- data/docs/resources/dh_params.md +1 -0
- data/docs/resources/directory.md.erb +5 -25
- data/docs/resources/docker.md.erb +61 -58
- data/docs/resources/docker_container.md.erb +23 -19
- data/docs/resources/docker_image.md.erb +20 -16
- data/docs/resources/elasticsearch.md.erb +245 -0
- data/docs/resources/etc_fstab.md.erb +120 -0
- data/docs/resources/etc_group.md.erb +29 -45
- data/docs/resources/etc_hosts.md.erb +68 -0
- data/docs/resources/etc_hosts_allow.md.erb +68 -0
- data/docs/resources/etc_hosts_deny.md.erb +68 -0
- data/docs/resources/file.md.erb +227 -216
- data/docs/resources/firewalld.md.erb +104 -0
- data/docs/resources/gem.md.erb +21 -35
- data/docs/resources/group.md.erb +16 -30
- data/docs/resources/grub_conf.md.erb +9 -24
- data/docs/resources/host.md.erb +32 -49
- data/docs/resources/http.md.erb +38 -44
- data/docs/resources/iis_app.md.erb +25 -35
- data/docs/resources/iis_site.md.erb +26 -40
- data/docs/resources/inetd_conf.md.erb +27 -42
- data/docs/resources/ini.md.erb +9 -23
- data/docs/resources/interface.md.erb +5 -25
- data/docs/resources/iptables.md.erb +15 -29
- data/docs/resources/json.md.erb +12 -27
- data/docs/resources/kernel_module.md.erb +47 -61
- data/docs/resources/kernel_parameter.md.erb +15 -29
- data/docs/resources/key_rsa.md.erb +3 -0
- data/docs/resources/launchd_service.md.erb +5 -25
- data/docs/resources/limits_conf.md.erb +15 -29
- data/docs/resources/login_def.md.erb +15 -30
- data/docs/resources/mount.md.erb +18 -33
- data/docs/resources/mssql_session.md.erb +9 -12
- data/docs/resources/mysql_conf.md.erb +28 -32
- data/docs/resources/mysql_session.md.erb +15 -29
- data/docs/resources/nginx.md.erb +78 -0
- data/docs/resources/nginx_conf.md.erb +127 -0
- data/docs/resources/npm.md.erb +19 -35
- data/docs/resources/ntp_conf.md.erb +20 -37
- data/docs/resources/oneget.md.erb +15 -30
- data/docs/resources/oracledb_session.md.erb +9 -11
- data/docs/resources/os.md.erb +29 -43
- data/docs/resources/os_env.md.erb +23 -44
- data/docs/resources/package.md.erb +34 -36
- data/docs/resources/parse_config.md.erb +5 -25
- data/docs/resources/parse_config_file.md.erb +31 -43
- data/docs/resources/{etc_passwd.md.erb → passwd.md.erb} +24 -39
- data/docs/resources/pip.md.erb +20 -35
- data/docs/resources/port.md.erb +43 -57
- data/docs/resources/postgres_conf.md.erb +17 -31
- data/docs/resources/postgres_hba_conf.md.erb +26 -38
- data/docs/resources/postgres_ident_conf.md.erb +25 -37
- data/docs/resources/postgres_session.md.erb +15 -29
- data/docs/resources/powershell.md.erb +27 -42
- data/docs/resources/processes.md.erb +17 -33
- data/docs/resources/rabbitmq_config.md.erb +9 -24
- data/docs/resources/registry_key.md.erb +27 -42
- data/docs/resources/runit_service.md.erb +5 -25
- data/docs/resources/security_policy.md.erb +12 -27
- data/docs/resources/service.md.erb +27 -42
- data/docs/resources/{etc_shadow.md.erb → shadow.md.erb} +41 -47
- data/docs/resources/ssh_config.md.erb +19 -34
- data/docs/resources/sshd_config.md.erb +19 -34
- data/docs/resources/ssl.md.erb +39 -54
- data/docs/resources/sys_info.md.erb +12 -26
- data/docs/resources/systemd_service.md.erb +5 -25
- data/docs/resources/sysv_service.md.erb +5 -25
- data/docs/resources/upstart_service.md.erb +5 -25
- data/docs/resources/user.md.erb +29 -44
- data/docs/resources/users.md.erb +12 -26
- data/docs/resources/vbscript.md.erb +9 -24
- data/docs/resources/virtualization.md.erb +8 -23
- data/docs/resources/windows_feature.md.erb +15 -30
- data/docs/resources/windows_hotfix.md.erb +50 -0
- data/docs/resources/windows_task.md.erb +12 -26
- data/docs/resources/wmi.md.erb +9 -24
- data/docs/resources/x509_certificate.md.erb +4 -0
- data/docs/resources/xinetd_conf.md.erb +65 -80
- data/docs/resources/xml.md.erb +61 -0
- data/docs/resources/yaml.md.erb +12 -27
- data/docs/resources/yum.md.erb +37 -51
- data/docs/resources/zfs_dataset.md.erb +15 -26
- data/docs/resources/zfs_pool.md.erb +9 -20
- data/docs/ruby_usage.md +0 -1
- data/examples/meta-profile/README.md +1 -1
- data/examples/profile-sensitive/README.md +29 -0
- data/examples/profile-sensitive/controls/sensitive-failures.rb +9 -0
- data/examples/profile-sensitive/controls/sensitive.rb +9 -0
- data/examples/profile-sensitive/inspec.yml +8 -0
- data/inspec.gemspec +3 -3
- data/lib/bundles/inspec-artifact/cli.rb +5 -5
- data/lib/bundles/inspec-compliance/README.md +24 -22
- data/lib/bundles/inspec-compliance/api/login.rb +152 -0
- data/lib/bundles/inspec-compliance/api.rb +46 -16
- data/lib/bundles/inspec-compliance/cli.rb +58 -165
- data/lib/bundles/inspec-compliance/target.rb +2 -3
- data/lib/bundles/inspec-habitat/cli.rb +7 -2
- data/lib/bundles/inspec-habitat/profile.rb +44 -20
- data/lib/bundles/inspec-init/templates/profile/README.md +1 -1
- data/lib/bundles/inspec-init/templates/profile/controls/example.rb +1 -1
- data/lib/bundles/inspec-supermarket/api.rb +1 -1
- data/lib/inspec/backend.rb +8 -0
- data/lib/inspec/cli.rb +1 -1
- data/lib/inspec/control_eval_context.rb +16 -6
- data/lib/inspec/dependencies/requirement.rb +22 -8
- data/lib/inspec/dependencies/resolver.rb +1 -1
- data/lib/inspec/exceptions.rb +4 -0
- data/lib/inspec/file_provider.rb +38 -9
- data/lib/inspec/metadata.rb +17 -3
- data/lib/inspec/objects/attribute.rb +16 -7
- data/lib/inspec/objects/control.rb +10 -2
- data/lib/inspec/plugins/resource.rb +34 -5
- data/lib/inspec/profile.rb +10 -1
- data/lib/inspec/profile_context.rb +3 -1
- data/lib/inspec/resource.rb +17 -5
- data/lib/inspec/rspec_json_formatter.rb +48 -14
- data/lib/inspec/rule.rb +4 -0
- data/lib/inspec/runner.rb +72 -30
- data/lib/inspec/shell.rb +24 -19
- data/lib/inspec/version.rb +1 -1
- data/lib/matchers/matchers.rb +40 -0
- data/lib/resources/aide_conf.rb +162 -0
- data/lib/resources/auditd.rb +231 -0
- data/lib/resources/auditd_rules.rb +3 -1
- data/lib/resources/command.rb +1 -1
- data/lib/resources/cpan.rb +60 -0
- data/lib/resources/cran.rb +66 -0
- data/lib/resources/docker.rb +8 -1
- data/lib/resources/docker_container.rb +19 -2
- data/lib/resources/elasticsearch.rb +172 -0
- data/lib/resources/etc_fstab.rb +102 -0
- data/lib/resources/etc_group.rb +1 -1
- data/lib/resources/etc_hosts.rb +81 -0
- data/lib/resources/etc_hosts_allow_deny.rb +122 -0
- data/lib/resources/file.rb +87 -10
- data/lib/resources/firewalld.rb +144 -0
- data/lib/resources/groups.rb +0 -1
- data/lib/resources/host.rb +30 -15
- data/lib/resources/http.rb +178 -22
- data/lib/resources/mssql_session.rb +3 -3
- data/lib/resources/mysql_conf.rb +10 -0
- data/lib/resources/mysql_session.rb +1 -1
- data/lib/resources/nginx.rb +97 -0
- data/lib/resources/nginx_conf.rb +134 -1
- data/lib/resources/oracledb_session.rb +9 -8
- data/lib/resources/os_env.rb +7 -4
- data/lib/resources/package.rb +14 -2
- data/lib/resources/packages.rb +1 -1
- data/lib/resources/pip.rb +28 -20
- data/lib/resources/port.rb +251 -3
- data/lib/resources/postgres.rb +1 -1
- data/lib/resources/postgres_session.rb +1 -1
- data/lib/resources/processes.rb +106 -20
- data/lib/resources/registry_key.rb +11 -2
- data/lib/resources/service.rb +5 -24
- data/lib/resources/ssh_conf.rb +1 -1
- data/lib/resources/ssl.rb +4 -3
- data/lib/resources/toml.rb +2 -2
- data/lib/resources/windows_hotfix.rb +35 -0
- data/lib/resources/xinetd.rb +4 -6
- data/lib/resources/xml.rb +27 -0
- data/lib/source_readers/inspec.rb +3 -3
- data/lib/utils/find_files.rb +2 -1
- data/lib/utils/nginx_parser.rb +10 -1
- data/lib/utils/object_traversal.rb +35 -10
- data/lib/utils/parser.rb +21 -3
- metadata +48 -18
- 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
|
-
"
|
|
131
|
-
'Check to make sure
|
|
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
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
30
|
-
Pry.
|
|
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[
|
|
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
|
-
"
|
|
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:
|
|
105
|
-
OS family:
|
|
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
|
-
|
|
138
|
-
#{mark 'Name:'} #{topic}
|
|
139
|
-
|
|
140
|
-
#{mark 'Description:'}
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/inspec/version.rb
CHANGED
data/lib/matchers/matchers.rb
CHANGED
|
@@ -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
|
data/lib/resources/command.rb
CHANGED
|
@@ -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
|
|
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}\"'")
|