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