inspec-core 5.7.9 → 5.14.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.
- checksums.yaml +4 -4
- data/etc/deprecations.json +0 -5
- data/lib/inspec/cli.rb +2 -0
- data/lib/inspec/plugin/v2/installer.rb +9 -2
- data/lib/inspec/plugin/v2/loader.rb +13 -0
- data/lib/inspec/plugin/v2/status.rb +2 -1
- data/lib/inspec/profile.rb +7 -5
- data/lib/inspec/resources/apt.rb +12 -6
- data/lib/inspec/resources/cgroup.rb +101 -0
- data/lib/inspec/resources/default_gateway.rb +61 -0
- data/lib/inspec/resources/docker_container.rb +21 -0
- data/lib/inspec/resources/docker_image.rb +53 -0
- data/lib/inspec/resources/file.rb +91 -0
- data/lib/inspec/resources/groups.rb +5 -0
- data/lib/inspec/resources/linux_audit_system.rb +81 -0
- data/lib/inspec/resources/lxc.rb +57 -0
- data/lib/inspec/resources/mail_alias.rb +46 -0
- data/lib/inspec/resources/oracledb_session.rb +7 -3
- data/lib/inspec/resources/postgres_session.rb +4 -2
- data/lib/inspec/resources/routing_table.rb +137 -0
- data/lib/inspec/resources/service.rb +87 -1
- data/lib/inspec/resources/user.rb +12 -0
- data/lib/inspec/resources/users.rb +79 -14
- data/lib/inspec/resources/virtualization.rb +9 -3
- data/lib/inspec/ui.rb +9 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/plugins/inspec-artifact/inspec-artifact.gemspec +9 -0
- data/lib/plugins/inspec-compliance/inspec-compliance.gemspec +9 -0
- data/lib/plugins/inspec-habitat/inspec-habitat.gemspec +9 -0
- data/lib/plugins/inspec-init/inspec-init.gemspec +9 -0
- data/lib/plugins/inspec-plugin-manager-cli/inspec-plugin-manager-cli.gemspec +10 -0
- data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +15 -11
- data/lib/plugins/inspec-reporter-html2/inspec-reporter-html2.gemspec +9 -0
- data/lib/plugins/inspec-reporter-json-min/inspec-reporter-json-min.gemspec +9 -0
- data/lib/plugins/inspec-reporter-junit/inspec-reporter-junit.gemspec +9 -0
- data/lib/plugins/inspec-streaming-reporter-progress-bar/inspec-streaming-reporter-progress-bar.gemspec +9 -0
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +32 -21
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3cb92e0f21964ecac43d5d9f208e7a2b07a57c2daa0fef4aad06515f933b9072
|
4
|
+
data.tar.gz: 6c4d5f59ed6dde73193198f4ad32c801515cec12d9c04924765d1ac7ee4bd5bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8397f679eea47dead2cb1952026b74c800adf7ea650120597b714c3ccdef1c7e0eeb6af387458a31c028173a6bc332b07de103d4d545dbd37257573b77c6e0b1
|
7
|
+
data.tar.gz: 96d8ac046aa00ec95fc173e81c28999d744ed823af7a4a6401e9d1776817e303e451e7c8d46450d2c39c02e6ef4ff4c82d1c425683e82299effa2f3384f9f93e
|
data/etc/deprecations.json
CHANGED
@@ -83,11 +83,6 @@
|
|
83
83
|
"suffix": "This resource was removed in InSpec 4.0.",
|
84
84
|
"comment": "Needed for ServerSpec compatibility"
|
85
85
|
},
|
86
|
-
"resource_ppa": {
|
87
|
-
"action": "exit",
|
88
|
-
"suffix": "This resource was removed in InSpec 4.0.",
|
89
|
-
"comment": "Needed for ServerSpec compatibility"
|
90
|
-
},
|
91
86
|
"resource_script": {
|
92
87
|
"action": "exit",
|
93
88
|
"suffix": "This resource will be removed in InSpec 4.0"
|
data/lib/inspec/cli.rb
CHANGED
@@ -95,6 +95,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|
95
95
|
desc "check PATH", "verify all tests at the specified PATH"
|
96
96
|
option :format, type: :string,
|
97
97
|
desc: "The output format to use doc (default), json. If valid format is not provided then it will use the default."
|
98
|
+
option :with_cookstyle, type: :boolean,
|
99
|
+
desc: "Enable or disable cookstyle checks.", default: false
|
98
100
|
profile_options
|
99
101
|
def check(path) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
100
102
|
o = config
|
@@ -149,12 +149,19 @@ module Inspec::Plugin::V2
|
|
149
149
|
|
150
150
|
gem_info = {}
|
151
151
|
matched_tuples.each do |tuple|
|
152
|
-
gem_info[tuple.first.name] ||=
|
153
|
-
gem_info[tuple.first.name]
|
152
|
+
gem_info[tuple.first.name] ||= {}
|
153
|
+
gem_info[tuple.first.name]["versions"] ||= []
|
154
|
+
gem_info[tuple.first.name]["versions"] << tuple.first.version.to_s
|
155
|
+
gem_info[tuple.first.name]["description"] ||= fetch_plugin_specs(fetcher, tuple.first.name)&.summary
|
154
156
|
end
|
155
157
|
gem_info
|
156
158
|
end
|
157
159
|
|
160
|
+
def fetch_plugin_specs(fetcher, gem_name)
|
161
|
+
plugin_dependency = Gem::Dependency.new(gem_name)
|
162
|
+
fetcher.spec_for_dependency(plugin_dependency).flatten.first
|
163
|
+
end
|
164
|
+
|
158
165
|
# Testing API. Performs a hard reset on the installer and registry, and reloads the loader.
|
159
166
|
# Not for public use.
|
160
167
|
# TODO: bad timing coupling in tests
|
@@ -259,10 +259,15 @@ module Inspec::Plugin::V2
|
|
259
259
|
status.entry_point = File.join(plugin_dir, "lib", status.name.to_s + ".rb")
|
260
260
|
status.installation_type = :core
|
261
261
|
status.loaded = false
|
262
|
+
status.description = fetch_gemspec(File.join(plugin_dir, status.name.to_s + ".gemspec"))&.summary
|
262
263
|
registry[status.name.to_sym] = status
|
263
264
|
end
|
264
265
|
end
|
265
266
|
|
267
|
+
def fetch_gemspec(spec_file)
|
268
|
+
Gem::Specification.load(spec_file)
|
269
|
+
end
|
270
|
+
|
266
271
|
def read_conf_file_into_registry
|
267
272
|
conf_file.each do |plugin_entry|
|
268
273
|
status = Inspec::Plugin::V2::Status.new
|
@@ -273,6 +278,7 @@ module Inspec::Plugin::V2
|
|
273
278
|
when :user_gem
|
274
279
|
status.entry_point = status.name.to_s
|
275
280
|
status.version = plugin_entry[:version]
|
281
|
+
status.description = fetch_plugin_specs(status.name.to_s)&.summary
|
276
282
|
when :path
|
277
283
|
status.entry_point = plugin_entry[:installation_path]
|
278
284
|
end
|
@@ -281,6 +287,12 @@ module Inspec::Plugin::V2
|
|
281
287
|
end
|
282
288
|
end
|
283
289
|
|
290
|
+
def fetch_plugin_specs(plugin_name)
|
291
|
+
fetcher = Gem::SpecFetcher.fetcher
|
292
|
+
plugin_dependency = Gem::Dependency.new(plugin_name)
|
293
|
+
fetcher.spec_for_dependency(plugin_dependency).flatten.first
|
294
|
+
end
|
295
|
+
|
284
296
|
def fixup_train_plugin_status(status)
|
285
297
|
status.api_generation = :'train-1'
|
286
298
|
if status.installation_type == :user_gem
|
@@ -327,6 +339,7 @@ module Inspec::Plugin::V2
|
|
327
339
|
status.version = plugin_spec.version.to_s
|
328
340
|
status.loaded = false
|
329
341
|
status.installation_type = :system_gem
|
342
|
+
status.description = plugin_spec.summary
|
330
343
|
|
331
344
|
if train_plugin_name?(status[:name])
|
332
345
|
# Train plugins are not true InSpec plugins; we need to decorate them a
|
@@ -14,7 +14,8 @@ module Inspec::Plugin::V2
|
|
14
14
|
:loaded, # true, false False could mean not attempted or failed
|
15
15
|
:load_exception, # Exception class if it failed to load
|
16
16
|
:name, # String name
|
17
|
-
:version
|
17
|
+
:version, # three-digit version. Core / bundled plugins use InSpec version here.
|
18
|
+
:description # Description of plugin.
|
18
19
|
) do
|
19
20
|
def initialize(*)
|
20
21
|
super
|
data/lib/inspec/profile.rb
CHANGED
@@ -105,6 +105,7 @@ module Inspec
|
|
105
105
|
@check_mode = options[:check_mode] || false
|
106
106
|
@parent_profile = options[:parent_profile]
|
107
107
|
@legacy_profile_path = options[:profiles_path] || false
|
108
|
+
@check_cookstyle = options[:with_cookstyle]
|
108
109
|
Metadata.finalize(@source_reader.metadata, @profile_id, options)
|
109
110
|
|
110
111
|
# if a backend has already been created, clone it so each profile has its own unique backend object
|
@@ -655,12 +656,13 @@ module Inspec
|
|
655
656
|
end
|
656
657
|
|
657
658
|
# Running cookstyle to check for code offenses
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
659
|
+
if @check_cookstyle
|
660
|
+
cookstyle_linting_check.each do |lint_output|
|
661
|
+
data = lint_output.split(":")
|
662
|
+
msg = "#{data[-2]}:#{data[-1]}"
|
663
|
+
offense.call(data[0], data[1], data[2], nil, msg)
|
664
|
+
end
|
662
665
|
end
|
663
|
-
|
664
666
|
# profile is valid if we could not find any error & offenses
|
665
667
|
result[:summary][:valid] = result[:errors].empty? && result[:offenses].empty?
|
666
668
|
|
data/lib/inspec/resources/apt.rb
CHANGED
@@ -135,19 +135,25 @@ module Inspec::Resources
|
|
135
135
|
|
136
136
|
class PpaRepository < AptRepository
|
137
137
|
name "ppa"
|
138
|
+
desc "Use the ppa InSpec audit resource to verify PPA repositories on the Debian-based linux platforms."
|
139
|
+
example <<~EXAMPLE
|
140
|
+
describe ppa('ubuntu-wine/ppa') do
|
141
|
+
it { should exist }
|
142
|
+
it { should be_enabled }
|
143
|
+
end
|
144
|
+
|
145
|
+
describe ppa('ppa:ubuntu-wine/ppa') do
|
146
|
+
it { should exist }
|
147
|
+
it { should be_enabled }
|
148
|
+
end
|
149
|
+
EXAMPLE
|
138
150
|
|
139
151
|
def exists?
|
140
|
-
deprecated
|
141
152
|
super()
|
142
153
|
end
|
143
154
|
|
144
155
|
def enabled?
|
145
|
-
deprecated
|
146
156
|
super()
|
147
157
|
end
|
148
|
-
|
149
|
-
def deprecated
|
150
|
-
Inspec.deprecate(:resource_ppa, "The `ppa` resource is deprecated. Please use `apt`")
|
151
|
-
end
|
152
158
|
end
|
153
159
|
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "inspec/resources/command"
|
2
|
+
module Inspec::Resources
|
3
|
+
class Cgroup < Inspec.resource(1)
|
4
|
+
name "cgroup"
|
5
|
+
# Restrict to only run on the below platform
|
6
|
+
supports platform: "linux"
|
7
|
+
desc "Use the cgroup InSpec audit resource to test cgroup subsytem's parameters."
|
8
|
+
|
9
|
+
example <<~EXAMPLE
|
10
|
+
describe cgroup("foo") do
|
11
|
+
its("cpuset.cpus") { should eq 0 }
|
12
|
+
its("memory.limit_in_bytes") { should eq 499712 }
|
13
|
+
its("memory.limit_in_bytes") { should be <= 500000 }
|
14
|
+
its("memory.numa_stat") { should match /total=0/ }
|
15
|
+
end
|
16
|
+
EXAMPLE
|
17
|
+
|
18
|
+
# Resource initialization.
|
19
|
+
def initialize(cgroup_name)
|
20
|
+
raise Inspec::Exceptions::ResourceSkipped, "The `cgroup` resource is not supported on your OS yet." unless inspec.os.linux?
|
21
|
+
|
22
|
+
@cgroup_name = cgroup_name
|
23
|
+
@valid_queries, @valid_queries_split = [], []
|
24
|
+
find_valid_queries
|
25
|
+
# Used to track the method calls in an "its" query
|
26
|
+
@cgroup_info_query = []
|
27
|
+
end
|
28
|
+
|
29
|
+
def resource_id
|
30
|
+
@cgroup_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"cgroup #{resource_id}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(param)
|
38
|
+
# Add the latest param we've seen to the list and form the query with all the params we've seen so far.
|
39
|
+
@cgroup_info_query << param.to_s
|
40
|
+
query = @cgroup_info_query.join(".")
|
41
|
+
|
42
|
+
# The ith level param must match with atleast one row's ith column of @valid_queries_split
|
43
|
+
# Else there is no way, we would find any valid query in further iteration, so raise exception.
|
44
|
+
if @valid_queries_split.map { |e| e[@cgroup_info_query.length - 1] }.include?(param.to_s)
|
45
|
+
# If the query form so far is part of @valid_queries, we are good to trigger find_cgroup_info
|
46
|
+
# else go for next level of param
|
47
|
+
if @valid_queries.include?(query)
|
48
|
+
@cgroup_info_query = []
|
49
|
+
find_cgroup_info(query)
|
50
|
+
else
|
51
|
+
self
|
52
|
+
end
|
53
|
+
else
|
54
|
+
@cgroup_info_query = []
|
55
|
+
|
56
|
+
raise Inspec::Exceptions::ResourceFailed, "The query #{query} does not appear to be valid."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# Method to find cgget tool
|
63
|
+
def find_cgget_or_error
|
64
|
+
%w{/usr/sbin/cgget /sbin/cgget cgget}.each do |cmd|
|
65
|
+
return cmd if inspec.command(cmd).exist?
|
66
|
+
end
|
67
|
+
|
68
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find `cgget`"
|
69
|
+
end
|
70
|
+
|
71
|
+
# find the cgroup info of the query which is given as input by the user
|
72
|
+
def find_cgroup_info(query)
|
73
|
+
bin = find_cgget_or_error
|
74
|
+
cgget_cmd = "#{bin} -n -r #{query} #{@cgroup_name}"
|
75
|
+
cmd = inspec.command(cgget_cmd)
|
76
|
+
|
77
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing cgget failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
|
78
|
+
|
79
|
+
# For complex returns the user must use match /the_regex/
|
80
|
+
param_value = cmd.stdout.split(":")
|
81
|
+
return nil if param_value.nil? || param_value.empty?
|
82
|
+
|
83
|
+
param_value = param_value[1].strip.split("\t").join
|
84
|
+
param_value.match(/^\d+$/) ? param_value.to_i : param_value
|
85
|
+
end
|
86
|
+
|
87
|
+
# find all the information about all relevant controllers for the current cgroup
|
88
|
+
def find_valid_queries
|
89
|
+
bin = find_cgget_or_error
|
90
|
+
cgget_all_cmd = "#{bin} -n -a #{@cgroup_name}"
|
91
|
+
cmd = inspec.command(cgget_all_cmd)
|
92
|
+
|
93
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing cgget failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
|
94
|
+
|
95
|
+
queries = cmd.stdout.to_s.gsub(/:.*/, "").gsub(/^\s+.*/, "").split("\n")
|
96
|
+
# store the relevant controller parameters in @valid_queries and the dot splitted paramters into @valid_queries_split
|
97
|
+
@valid_queries = queries.map { |q| q if q.length > 0 }.compact
|
98
|
+
@valid_queries_split = @valid_queries.map { |q| q.split(".") }.compact
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "inspec/resources/command"
|
2
|
+
require_relative "routing_table"
|
3
|
+
|
4
|
+
module Inspec::Resources
|
5
|
+
class Defaultgateway < Routingtable
|
6
|
+
# resource internal name.
|
7
|
+
name "default_gateway"
|
8
|
+
|
9
|
+
# Restrict to only run on the below platforms (if none were given,
|
10
|
+
# all OS's and cloud API's supported)
|
11
|
+
supports platform: "unix"
|
12
|
+
supports platform: "windows"
|
13
|
+
|
14
|
+
desc "Use the `default_gateway` Chef InSpec audit resource to test the assigned ip address and interface for the default route."
|
15
|
+
|
16
|
+
example <<~EXAMPLE
|
17
|
+
describe default_gateway do
|
18
|
+
its(:ipaddress) { should eq '172.31.80.1' }
|
19
|
+
end
|
20
|
+
describe default_gateway do
|
21
|
+
its("interface") { should eq 'eth0' }
|
22
|
+
end
|
23
|
+
EXAMPLE
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
skip_resource "The `default_gateway` resource is not yet available on your OS." unless inspec.os.unix? || inspec.os.windows?
|
27
|
+
# invoke the routing_table initialize; which populates the @routing_info
|
28
|
+
super()
|
29
|
+
end
|
30
|
+
|
31
|
+
# resource appearance in test reports.
|
32
|
+
def to_s
|
33
|
+
"default_gateway"
|
34
|
+
end
|
35
|
+
|
36
|
+
# fetches the ipaddress assigned to the default gateway
|
37
|
+
# default gateway's destination is either `default` or `0.0.0.0`
|
38
|
+
def ipaddress
|
39
|
+
# @routing_info is the hash populated in routing_table resource
|
40
|
+
# @routing_info contain values as:
|
41
|
+
# {
|
42
|
+
# destination1: [ [gateway1x, interface1x], [gateway1y, interface1y] ],
|
43
|
+
# destination2: [gateway2, interface2]
|
44
|
+
# }
|
45
|
+
%w{default 0.0.0.0}.each do |destination|
|
46
|
+
return @routing_info[destination][0][0] if @routing_info.key?(destination)
|
47
|
+
end
|
48
|
+
# raise exception because no destination with value default or 0.0.0.0 is found in the routing table
|
49
|
+
raise Inspec::Exceptions::ResourceFailed, "No routing found as part of default gateway"
|
50
|
+
end
|
51
|
+
|
52
|
+
# fetches the interface assigned to the default gateway
|
53
|
+
def interface
|
54
|
+
%w{default 0.0.0.0}.each do |destination|
|
55
|
+
return @routing_info[destination][0][1] if @routing_info.key?(destination)
|
56
|
+
end
|
57
|
+
# raise exception because no destination with value default or 0.0.0.0 is found in the routing table
|
58
|
+
raise Inspec::Exceptions::ResourceFailed, "No routing found as part of default gateway"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -43,6 +43,19 @@ module Inspec::Resources
|
|
43
43
|
status.downcase.start_with?("up") if object_info.entries.length == 1
|
44
44
|
end
|
45
45
|
|
46
|
+
# has_volume? matcher checks if the volume specified in source path of host is mounted in destination path of docker
|
47
|
+
def has_volume?(destination, source)
|
48
|
+
# volume_info is the hash which contains the low-level information about the container
|
49
|
+
# if Mounts key is not present or is nil; raise exception
|
50
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find any mounted volumes for your container" unless volume_info.Mounts[0]
|
51
|
+
|
52
|
+
# Iterate through the list of mounted volumes and check if it matches with the given destination and source
|
53
|
+
# is_mounted flag is used to handle to return explict boolean values of true or false
|
54
|
+
is_mounted = false
|
55
|
+
volume_info.Mounts.detect { |mount| is_mounted = mount.Destination == destination && mount.Source == source }
|
56
|
+
is_mounted
|
57
|
+
end
|
58
|
+
|
46
59
|
def status
|
47
60
|
object_info.status[0] if object_info.entries.length == 1
|
48
61
|
end
|
@@ -87,5 +100,13 @@ module Inspec::Resources
|
|
87
100
|
opts = @opts
|
88
101
|
@info = inspec.docker.containers.where { names == opts[:name] || (!id.nil? && !opts[:id].nil? && (id == opts[:id] || id.start_with?(opts[:id]))) }
|
89
102
|
end
|
103
|
+
|
104
|
+
# volume_info returns the low-level information obtained on docker inspect [container_name/id]
|
105
|
+
def volume_info
|
106
|
+
return @mount_info if defined?(@mount_info)
|
107
|
+
|
108
|
+
# Check for either docker inspect [container_name] or docker inspect [container_id]
|
109
|
+
@mount_info = inspec.docker.object(@opts[:name] || @opts[:id])
|
110
|
+
end
|
90
111
|
end
|
91
112
|
end
|
@@ -48,6 +48,25 @@ module Inspec::Resources
|
|
48
48
|
object_info.tags[0] if object_info.entries.size == 1
|
49
49
|
end
|
50
50
|
|
51
|
+
# method_missing handles when hash_keys are invoked to check information obtained on docker inspect [image_name]
|
52
|
+
def method_missing(*hash_keys)
|
53
|
+
# User can test the low-level inspect information in three ways:
|
54
|
+
# Way 1: Serverspec style: its(['Config.Cmd']) { should include some_value }
|
55
|
+
# here, the value for hash_keys recieved is [:[], "Config.Cmd"]
|
56
|
+
# Way 2: InSpec style: its(['Config','Cmd']) { should include some_value }
|
57
|
+
# here, the value for hash_keys recieved is [:[], "Config", "Cmd"]
|
58
|
+
# Way 3: Mix of both: its(['GraphDriver.Data','MergedDir']) { should include some_value }
|
59
|
+
# here, the value for hash_keys recieved is [:[], "GraphDriver.Data", "MergedDir"]
|
60
|
+
|
61
|
+
# hash_keys are passed to this method to evaluate the value
|
62
|
+
image_hash_inspection(hash_keys)
|
63
|
+
end
|
64
|
+
|
65
|
+
# inspection property allows to test any of the hash key-value pairs as part of the image_inspect_info
|
66
|
+
def inspection
|
67
|
+
image_inspect_info
|
68
|
+
end
|
69
|
+
|
51
70
|
def to_s
|
52
71
|
img = @opts[:image] || @opts[:id]
|
53
72
|
"Docker Image #{img}"
|
@@ -80,5 +99,39 @@ module Inspec::Resources
|
|
80
99
|
(repository == opts[:repo] && tag == opts[:tag]) || (!id.nil? && !opts[:id].nil? && (id == opts[:id] || id.start_with?(opts[:id])))
|
81
100
|
end
|
82
101
|
end
|
102
|
+
|
103
|
+
# image_inspect_info returns the complete inspect hash_values of the image
|
104
|
+
def image_inspect_info
|
105
|
+
return @inspect_info if defined?(@inspect_info)
|
106
|
+
|
107
|
+
@inspect_info = inspec.docker.object(@opts[:image] || (!@opts[:id].nil? && @opts[:id]))
|
108
|
+
end
|
109
|
+
|
110
|
+
# image_hash_inspection formats the input hash_keys and checks if any value exists for such keys in @inspect_info(image_inspect_info)
|
111
|
+
def image_hash_inspection(hash_keys)
|
112
|
+
# The hash_keys recieved are in three formats as mentioned in method_missing
|
113
|
+
# The hash_keys recieved must be in array format [] and the zeroth index must be :[]
|
114
|
+
# Check for the conditions and remove the zeroth element from the hash_keys
|
115
|
+
|
116
|
+
hash_keys.shift if hash_keys.is_a?(Array) && hash_keys[0] == :[]
|
117
|
+
|
118
|
+
# When received hash_keys in Serverspec style or mix of both
|
119
|
+
# The hash_keys are to be splitted at '.' (dot) and flatten it so that it doesn't become array of arrays
|
120
|
+
# After splitting and flattening is done, hash_keys is now an array with individual keys
|
121
|
+
hash_keys = hash_keys.map { |key| key.split(".") }.flatten
|
122
|
+
|
123
|
+
# image_inspect_info returns the complete inspect hash_values of the image
|
124
|
+
# dig() finds the nested value specified by the sequence of the key object by calling dig at each step.
|
125
|
+
# hash_keys is the key object. If one of the key is bad, value will be nil.
|
126
|
+
hash_value = image_inspect_info.dig(*hash_keys)
|
127
|
+
|
128
|
+
# If one of the key is bad, hash_value will be nil, so raise exception which throws it in rescue block
|
129
|
+
# else return hash_value
|
130
|
+
raise Inspec::Exceptions::ResourceFailed if hash_value.nil?
|
131
|
+
|
132
|
+
hash_value
|
133
|
+
rescue
|
134
|
+
raise Inspec::Exceptions::ResourceFailed, "#{hash_keys.join(".")} is not a valid key for your image or has nil value."
|
135
|
+
end
|
83
136
|
end
|
84
137
|
end
|
@@ -181,6 +181,34 @@ module Inspec::Resources
|
|
181
181
|
inv_mode & file.mode != 0
|
182
182
|
end
|
183
183
|
|
184
|
+
def immutable?
|
185
|
+
raise Inspec::Exceptions::ResourceSkipped, "The `be_immutable` matcher is not supported on your OS yet." unless inspec.os.unix?
|
186
|
+
|
187
|
+
if inspec.os.linux?
|
188
|
+
file_info = LinuxImmutableFlagCheck.new(inspec, file)
|
189
|
+
else
|
190
|
+
file_info = UnixImmutableFlagCheck.new(inspec, file)
|
191
|
+
end
|
192
|
+
|
193
|
+
file_info.is_immutable?
|
194
|
+
end
|
195
|
+
|
196
|
+
# parse the json file content and returns the content
|
197
|
+
def content_as_json
|
198
|
+
require "json" unless defined?(JSON)
|
199
|
+
JSON.parse(file.content)
|
200
|
+
rescue => e
|
201
|
+
raise Inspec::Exceptions::ResourceFailed, "Unable to parse the given JSON file: #{e.message}"
|
202
|
+
end
|
203
|
+
|
204
|
+
# parse the yaml file content and returns the content
|
205
|
+
def content_as_yaml
|
206
|
+
require "yaml" unless defined?(YAML)
|
207
|
+
YAML.load(file.content)
|
208
|
+
rescue => e
|
209
|
+
raise Inspec::Exceptions::ResourceFailed, "Unable to parse the given YAML file: #{e.message}"
|
210
|
+
end
|
211
|
+
|
184
212
|
def to_s
|
185
213
|
if file
|
186
214
|
"File #{source_path}"
|
@@ -373,4 +401,67 @@ module Inspec::Resources
|
|
373
401
|
end
|
374
402
|
end
|
375
403
|
end
|
404
|
+
|
405
|
+
# Helper class for immutable matcher.
|
406
|
+
class ImmutableFlagCheck
|
407
|
+
attr_reader :inspec, :file_path
|
408
|
+
def initialize(inspec, file)
|
409
|
+
@inspec = inspec
|
410
|
+
@file_path = file.path
|
411
|
+
end
|
412
|
+
|
413
|
+
def find_utility_or_error(utility_name)
|
414
|
+
[
|
415
|
+
"/usr/sbin/#{utility_name}",
|
416
|
+
"/sbin/#{utility_name}",
|
417
|
+
"/usr/bin/#{utility_name}",
|
418
|
+
"/bin/#{utility_name}",
|
419
|
+
"#{utility_name}",
|
420
|
+
].each do |cmd|
|
421
|
+
return cmd if inspec.command(cmd).exist?
|
422
|
+
end
|
423
|
+
|
424
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find `#{utility_name}`"
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
class LinuxImmutableFlagCheck < ImmutableFlagCheck
|
429
|
+
def is_immutable?
|
430
|
+
# Check if lsattr is available. In general, all linux system has lsattr & chattr
|
431
|
+
# This logic check is valid for immutable flag set with chattr
|
432
|
+
utility = find_utility_or_error("lsattr")
|
433
|
+
utility_cmd = inspec.command("#{utility} #{file_path}")
|
434
|
+
|
435
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{utility} #{file_path} failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
|
436
|
+
|
437
|
+
# General output for lsattr file_name is:
|
438
|
+
# ----i---------e----- file_name
|
439
|
+
# The fifth char resembles the immutable flag. Total 20 flags are allowed.
|
440
|
+
lsattr_info = utility_cmd.stdout.strip.squeeze(" ")
|
441
|
+
lsattr_info =~ /^.{4}i.{15} .*/
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
class UnixImmutableFlagCheck < ImmutableFlagCheck
|
446
|
+
def is_immutable?
|
447
|
+
# Check if chflags is available on the system. Most unix-like system comes with chflags.
|
448
|
+
# This logic check is valid for immutable flag set with chflags
|
449
|
+
find_utility_or_error("chflags")
|
450
|
+
|
451
|
+
# In general ls -lO is used to check immutable flag set by chflags
|
452
|
+
utility_cmd = inspec.command("ls -lO #{file_path}")
|
453
|
+
|
454
|
+
# But on some bsd system (eg: freebsd) ls -lo is used instead of ls -lO
|
455
|
+
utility_cmd = inspec.command("ls -lo #{file_path}") if utility_cmd.exit_status.to_i != 0
|
456
|
+
|
457
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing ls -lo #{file_path} and ls -lO #{file_path} failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
|
458
|
+
|
459
|
+
# General output for ls -lO file_name is:
|
460
|
+
# -rw-r--r-- 1 current_user 1083951318 uchg 0 Apr 6 12:45 file_name
|
461
|
+
# The schg flag and the uchg flag represents the immutable flags
|
462
|
+
# uchg => user immutable flag, schg => system immutable flag.
|
463
|
+
file_info = utility_cmd.stdout.strip.split
|
464
|
+
file_info.include?("uchg") || file_info.include?("schg")
|
465
|
+
end
|
466
|
+
end
|
376
467
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "inspec/resources/command"
|
2
|
+
module Inspec::Resources
|
3
|
+
class LinuxAuditSystem < Inspec.resource(1)
|
4
|
+
# Resource's internal name.
|
5
|
+
name "linux_audit_system"
|
6
|
+
|
7
|
+
# Restrict to only run on the below platforms (if none were given,
|
8
|
+
# all OS's and cloud API's supported)
|
9
|
+
supports platform: "linux"
|
10
|
+
|
11
|
+
desc "Use the `linux_audit_system` Chef InSpec audit resource to test the configuration of linux audit system."
|
12
|
+
|
13
|
+
example <<~EXAMPLE
|
14
|
+
describe linux_audit_system do
|
15
|
+
it { should be_enabled }
|
16
|
+
it { should be_running }
|
17
|
+
its("rules") { should include "-w /etc -p wa" }
|
18
|
+
its("rules") { should include %r{-w /etc -p wa} }
|
19
|
+
its("rules") { should include %r!-w /etc -p wa! }
|
20
|
+
end
|
21
|
+
EXAMPLE
|
22
|
+
|
23
|
+
attr_reader :auditctl_utility
|
24
|
+
|
25
|
+
# Resource initialization.
|
26
|
+
def initialize
|
27
|
+
skip_resource "The `linux_audit_system` resource is not yet available on your OS." unless inspec.os.linux?
|
28
|
+
@auditctl_utility = find_auditctl_or_error
|
29
|
+
end
|
30
|
+
|
31
|
+
# Resource appearance in test reports.
|
32
|
+
def to_s
|
33
|
+
"linux_audit_system"
|
34
|
+
end
|
35
|
+
|
36
|
+
# The be_enabled matcher checks if the auditing is enabled.
|
37
|
+
# The enabled flag 1 indicates that the auditing is enabled.
|
38
|
+
def enabled?
|
39
|
+
auditctl_cmd = inspec.command("#{auditctl_utility} -s | grep enabled")
|
40
|
+
|
41
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{auditctl_utility} -s | grep enabled failed: #{auditctl_cmd.stderr}" if auditctl_cmd.exit_status.to_i != 0
|
42
|
+
|
43
|
+
# Sample stdout: enabled 1
|
44
|
+
auditctl_enabled_status = auditctl_cmd.stdout.strip.split
|
45
|
+
auditctl_enabled_status[1].to_i == 1
|
46
|
+
end
|
47
|
+
|
48
|
+
# The be_running matcher checks if the audit daemon is running.
|
49
|
+
# A pid of 0 indicates that the audit daemon is not running.
|
50
|
+
def running?
|
51
|
+
auditctl_cmd = inspec.command("#{auditctl_utility} -s | grep pid")
|
52
|
+
|
53
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{auditctl_utility} -s | grep enabled failed: #{auditctl_cmd.stderr}" if auditctl_cmd.exit_status.to_i != 0
|
54
|
+
|
55
|
+
# Sample stdout: pid 682462
|
56
|
+
auditctl_running_status = auditctl_cmd.stdout.strip.split
|
57
|
+
!auditctl_running_status[1].nil? && auditctl_running_status[1].to_i != 0
|
58
|
+
end
|
59
|
+
|
60
|
+
# The rules property returns the array of audit rules obtained on auditctl -l.
|
61
|
+
# The auditctl -l list all rules, 1 per line.
|
62
|
+
def rules
|
63
|
+
auditctl_cmd = inspec.command("#{auditctl_utility} -l")
|
64
|
+
|
65
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{auditctl_utility} -l: #{auditctl_cmd.stderr}" if auditctl_cmd.exit_status.to_i != 0
|
66
|
+
|
67
|
+
auditctl_cmd.stdout.strip.split("\n")
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Check if auditctl is available on the system.
|
73
|
+
def find_auditctl_or_error
|
74
|
+
%w{/usr/sbin/auditctl /sbin/auditctl auditctl}.each do |cmd|
|
75
|
+
return cmd if inspec.command(cmd).exist?
|
76
|
+
end
|
77
|
+
|
78
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find `auditctl`. This resource requires `auditctl` utility to be available on the system."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|