inspec-core 4.56.19 → 5.12.2
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 +12 -16
- data/inspec-core.gemspec +1 -1
- data/lib/inspec/base_cli.rb +14 -2
- data/lib/inspec/cli.rb +15 -0
- data/lib/inspec/dependency_installer.rb +74 -0
- data/lib/inspec/dependency_loader.rb +97 -0
- data/lib/inspec/dsl.rb +11 -2
- data/lib/inspec/errors.rb +7 -0
- data/lib/inspec/formatters/base.rb +23 -0
- data/lib/inspec/metadata.rb +36 -0
- data/lib/inspec/plugin/v2/installer.rb +9 -2
- data/lib/inspec/plugin/v2/loader.rb +13 -0
- data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +44 -1
- data/lib/inspec/plugin/v2/status.rb +2 -1
- data/lib/inspec/profile.rb +63 -0
- data/lib/inspec/reporters/automate.rb +1 -1
- data/lib/inspec/reporters/cli.rb +1 -1
- data/lib/inspec/reporters/json.rb +31 -11
- data/lib/inspec/resource.rb +6 -0
- data/lib/inspec/resources/apt.rb +12 -6
- data/lib/inspec/resources/cgroup.rb +101 -0
- data/lib/inspec/resources/cron.rb +49 -0
- data/lib/inspec/resources/docker_container.rb +21 -0
- data/lib/inspec/resources/docker_image.rb +53 -0
- data/lib/inspec/resources/ipfilter.rb +59 -0
- data/lib/inspec/resources/ipnat.rb +58 -0
- data/lib/inspec/resources/lxc.rb +57 -0
- data/lib/inspec/resources/mail_alias.rb +46 -0
- data/lib/inspec/resources/routing_table.rb +137 -0
- data/lib/inspec/resources/service.rb +14 -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/resources.rb +3 -16
- data/lib/inspec/runner.rb +18 -1
- data/lib/inspec/runner_rspec.rb +15 -0
- data/lib/inspec/schema/exec_json.rb +59 -58
- data/lib/inspec/schema/exec_json_min.rb +16 -16
- data/lib/inspec/schema/primitives.rb +68 -51
- data/lib/inspec/schema/profile_json.rb +27 -27
- data/lib/inspec/schema.rb +1 -0
- data/lib/inspec/ui.rb +10 -0
- data/lib/inspec/utils/deprecated_cloud_resources_list.rb +54 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/inspec.rb +3 -0
- 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-init/lib/inspec-init/cli.rb +1 -0
- data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +9 -0
- data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +126 -0
- data/lib/plugins/inspec-init/lib/inspec-init/renderer.rb +9 -8
- data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/plugin.erb +16 -0
- data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/streaming_reporter.erb +31 -0
- data/lib/plugins/inspec-init/templates/profiles/aws/inspec.yml +1 -1
- data/lib/plugins/inspec-init/templates/resources/basic/docs/resource-doc.erb +77 -0
- data/lib/plugins/inspec-init/templates/resources/basic/libraries/inspec-resource-template.erb +94 -0
- data/lib/plugins/inspec-init/templates/resources/plural/docs/resource-doc.erb +62 -0
- data/lib/plugins/inspec-init/templates/resources/plural/libraries/inspec-resource-template.erb +73 -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-html2/templates/body.html.erb +2 -0
- data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +3 -0
- data/lib/plugins/inspec-reporter-html2/templates/profile.html.erb +1 -0
- data/lib/plugins/inspec-reporter-html2/templates/result.html.erb +1 -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/README.md +5 -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/plugin.rb +13 -0
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +123 -0
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/version.rb +8 -0
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar.rb +15 -0
- metadata +33 -3
@@ -0,0 +1,137 @@
|
|
1
|
+
require "inspec/resources/command"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class Routingtable < Inspec.resource(1)
|
5
|
+
# resource internal name.
|
6
|
+
name "routing_table"
|
7
|
+
|
8
|
+
# Restrict to only run on the below platforms (if none were given,
|
9
|
+
# all OS's and cloud API's supported)
|
10
|
+
supports platform: "unix"
|
11
|
+
supports platform: "windows"
|
12
|
+
|
13
|
+
desc "Use the `routing_table` Chef InSpec audit resource to test the routing information parameters(destination, gateway and interface) present in the routing table."
|
14
|
+
|
15
|
+
example <<~EXAMPLE
|
16
|
+
describe routing_table do
|
17
|
+
it do
|
18
|
+
should have_entry(
|
19
|
+
:destination => '192.168.43.1/32',
|
20
|
+
:interface => 'lxdbr0',
|
21
|
+
:gateway => '172.31.80.1',
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe routing_table do
|
27
|
+
it { should have_entry(destination: '0.0.0.0', interface: 'eth0', gateway: '172.31.80.1') }
|
28
|
+
end
|
29
|
+
EXAMPLE
|
30
|
+
|
31
|
+
def initialize
|
32
|
+
skip_resource "The `routing_table` resource is not yet available on your OS." unless inspec.os.unix? || inspec.os.windows?
|
33
|
+
# fetch the routing information and store it in @routing_info (could be hash, tbd)
|
34
|
+
@routing_info = {}
|
35
|
+
fetch_routing_information
|
36
|
+
end
|
37
|
+
|
38
|
+
# resource appearance in test reports.
|
39
|
+
def to_s
|
40
|
+
"routing_table"
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_entry?(input_route)
|
44
|
+
# check if the destination, gateway, interface exists as part of the routing_info
|
45
|
+
if input_route.key?(:destination) && input_route.key?(:gateway) && input_route.key?(:interface)
|
46
|
+
# check if there is key with destination's value in hash;
|
47
|
+
# if yes, check if destination and gateway is present else return false
|
48
|
+
@routing_info.key?(input_route[:destination]) ? @routing_info[input_route[:destination]].include?([input_route[:gateway], input_route[:interface]]) : false
|
49
|
+
else
|
50
|
+
raise Inspec::Exceptions::ResourceSkipped, "One or more missing key, have_entry? matcher expects a hash with 3 keys: destination, gateway and interface"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# fetches the routing information for the system
|
57
|
+
def fetch_routing_information
|
58
|
+
# check if netstat is available on the system
|
59
|
+
utility = find_netstat_or_error
|
60
|
+
|
61
|
+
# the command to fetch the routing information
|
62
|
+
fetch_route_cmd = "#{utility} -rn"
|
63
|
+
|
64
|
+
# execute the above netstat command
|
65
|
+
cmd = inspec.command(fetch_route_cmd)
|
66
|
+
|
67
|
+
# raise error if the exit status is not zero;
|
68
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing netstat failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
|
69
|
+
|
70
|
+
# Todo:
|
71
|
+
# Improve logic to fetch destination, gateway & interface efficiently.
|
72
|
+
# The below logic assumes the following:
|
73
|
+
# 1. destination, gateway & interface header is present as Destination, Gateway & (Iface or Netif or Interface) respectively.
|
74
|
+
# (Netif on BSD, Darwin,Iface on linux & Interface on Windows)
|
75
|
+
# 2. there is no blank data for any columns or the blank data are present after the interface column.
|
76
|
+
|
77
|
+
# cmd.stdout is the standard out of netstat -rn; split on new line to get the rows
|
78
|
+
raw_route_info = cmd.stdout.split("\n")
|
79
|
+
|
80
|
+
# since raw_route_info contains some row before the header (i.e. Destination Gateway ...); remove those rows
|
81
|
+
raw_route_info.shift until raw_route_info[0] =~ /Destination/i
|
82
|
+
|
83
|
+
# split each rows based on space to get the individual columns
|
84
|
+
# raw_route_info is now array of arrays with the routing information
|
85
|
+
raw_route_info.map! { |info| info.strip.split }
|
86
|
+
|
87
|
+
# these variables will store the indices where destination, gateway and interface are present
|
88
|
+
destination_index, gateway_index, interface_index = -1, -1, -1
|
89
|
+
|
90
|
+
# The headers in windows are as:
|
91
|
+
# Network Destination Netmask Gateway Interface Metric
|
92
|
+
# Splitting on space makes "Network Destination" to be two separate values as "Network" & "Destination"
|
93
|
+
# Remove "Network" value to apply the logic of finding index
|
94
|
+
raw_route_info[0].shift if inspec.os.windows?
|
95
|
+
|
96
|
+
# find the indices of destination, gateway and interface;
|
97
|
+
# because the position of gateway & interface varies with operating system
|
98
|
+
raw_route_info[0].each_with_index do |header, index|
|
99
|
+
if header =~ /Destination/i
|
100
|
+
destination_index = index
|
101
|
+
elsif header =~ /Gateway/i
|
102
|
+
gateway_index = index
|
103
|
+
elsif header =~ /Iface|Netif|Interface/i
|
104
|
+
interface_index = index
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# remove the initial header consisting of Destination, Gateway, Mask, ... since this is of no use
|
109
|
+
raw_route_info.shift
|
110
|
+
|
111
|
+
# check the indices are assigned with some index and not -1
|
112
|
+
if destination_index != -1 && gateway_index != -1 && interface_index != -1
|
113
|
+
# iterate through the route_info; and find destination, gateway and interface from each row
|
114
|
+
raw_route_info.each do |info|
|
115
|
+
# if value exists at the destination_index, gateway_index, and interface_index; store the value in @routing_info
|
116
|
+
if !info[destination_index].nil? && !info[gateway_index].nil? && !info[interface_index].nil?
|
117
|
+
# if the destination_key is already present, append the gateway & interface; else create new array and add them
|
118
|
+
if @routing_info.key?(info[destination_index])
|
119
|
+
@routing_info[info[destination_index]] << [info[gateway_index], info[interface_index]]
|
120
|
+
else
|
121
|
+
@routing_info[info[destination_index]] = [[info[gateway_index], info[interface_index]]]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# check if netstat is available on the system
|
129
|
+
def find_netstat_or_error
|
130
|
+
%w{/usr/sbin/netstat /sbin/netstat /usr/bin/netstat /bin/netstat netstat}.each do |cmd|
|
131
|
+
return cmd if inspec.command(cmd).exist?
|
132
|
+
end
|
133
|
+
|
134
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find `netstat` utility to view routing table information"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -537,9 +537,22 @@ module Inspec::Resources
|
|
537
537
|
end
|
538
538
|
|
539
539
|
def info(service_name)
|
540
|
+
# `service -l` lists all files in /etc/rc.d and the local startup directories
|
541
|
+
# % service -l
|
542
|
+
# accounting
|
543
|
+
# addswap
|
544
|
+
# adjkerntz
|
545
|
+
# apm
|
546
|
+
# archdep
|
547
|
+
cmd = inspec.command("#{service_ctl} -l")
|
548
|
+
return nil if cmd.exit_status != 0
|
549
|
+
|
550
|
+
# search for the service
|
551
|
+
srv = /^#{service_name}$/.match(cmd.stdout)
|
552
|
+
return nil if srv.nil? || srv[0].nil?
|
553
|
+
|
540
554
|
# check if service is enabled
|
541
555
|
cmd = inspec.command("#{service_ctl} #{service_name} enabled")
|
542
|
-
|
543
556
|
enabled = cmd.exit_status == 0
|
544
557
|
|
545
558
|
# check if the service is running
|
@@ -1 +1,13 @@
|
|
1
1
|
require "inspec/resources/users"
|
2
|
+
# user resource belong_to matcher for serverspec compatibility
|
3
|
+
RSpec::Matchers.define :belong_to_primary_group do |group|
|
4
|
+
match do |user|
|
5
|
+
user.belongs_to_primary_group?(group)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
RSpec::Matchers.define :belong_to_group do |group|
|
10
|
+
match do |user|
|
11
|
+
user.belongs_to_group?(group)
|
12
|
+
end
|
13
|
+
end
|
@@ -137,20 +137,17 @@ module Inspec::Resources
|
|
137
137
|
# its('badpasswordattempts') { should eq 0 }
|
138
138
|
# end
|
139
139
|
#
|
140
|
-
# The following Serverspec matchers
|
140
|
+
# The following Serverspec matchers were deprecated in favor for direct value access
|
141
|
+
# but are made available as part of Serverspec compatibility in March, 2022.
|
141
142
|
#
|
142
143
|
# describe user('root') do
|
143
144
|
# it { should belong_to_group 'root' }
|
145
|
+
# it { should belong_to_primary_group 'root' }
|
144
146
|
# it { should have_uid 0 }
|
145
147
|
# it { should have_home_directory '/root' }
|
146
148
|
# it { should have_login_shell '/bin/bash' }
|
147
149
|
# its('minimum_days_between_password_change') { should eq 0 }
|
148
150
|
# its('maximum_days_between_password_change') { should eq 99 }
|
149
|
-
# end
|
150
|
-
#
|
151
|
-
# ServerSpec tests that are not supported:
|
152
|
-
#
|
153
|
-
# describe user('root') do
|
154
151
|
# it { should have_authorized_key 'ssh-rsa ADg54...3434 user@example.local' }
|
155
152
|
# its(:encrypted_password) { should eq 1234 }
|
156
153
|
# end
|
@@ -258,36 +255,56 @@ module Inspec::Resources
|
|
258
255
|
|
259
256
|
# implement 'mindays' method to be compatible with serverspec
|
260
257
|
def minimum_days_between_password_change
|
261
|
-
Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `minimum_days_between_password_change` property is deprecated. Please use `mindays`.")
|
262
258
|
mindays
|
263
259
|
end
|
264
260
|
|
265
261
|
# implement 'maxdays' method to be compatible with serverspec
|
266
262
|
def maximum_days_between_password_change
|
267
|
-
Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `maximum_days_between_password_change` property is deprecated. Please use `maxdays`.")
|
268
263
|
maxdays
|
269
264
|
end
|
270
265
|
|
271
266
|
# implements rspec has matcher, to be compatible with serverspec
|
272
267
|
# @see: https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/built_in/has.rb
|
268
|
+
# has_uid matcher: compatibility with serverspec
|
273
269
|
def has_uid?(compare_uid)
|
274
|
-
Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `has_uid?` matcher is deprecated.")
|
275
270
|
uid == compare_uid
|
276
271
|
end
|
277
272
|
|
273
|
+
# has_home_directory matcher: compatibility with serverspec
|
278
274
|
def has_home_directory?(compare_home)
|
279
|
-
Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `has_home_directory?` matcher is deprecated. Please use `its('home')`.")
|
280
275
|
home == compare_home
|
281
276
|
end
|
282
277
|
|
278
|
+
# has_login_shell matcher: compatibility with serverspec
|
283
279
|
def has_login_shell?(compare_shell)
|
284
|
-
Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `has_login_shell?` matcher is deprecated. Please use `its('shell')`.")
|
285
280
|
shell == compare_shell
|
286
281
|
end
|
287
282
|
|
288
|
-
|
289
|
-
|
290
|
-
|
283
|
+
# has_authorized_key matcher: compatibility with serverspec
|
284
|
+
def has_authorized_key?(compare_key)
|
285
|
+
# get_authorized_keys returns the list of key, check if given key is included.
|
286
|
+
get_authorized_keys.include?(compare_key)
|
287
|
+
end
|
288
|
+
|
289
|
+
# belongs_to_primary_group matcher: compatibility with serverspec
|
290
|
+
def belongs_to_primary_group?(group_name)
|
291
|
+
groupname == group_name
|
292
|
+
end
|
293
|
+
|
294
|
+
# belongs_to_group matcher: compatibility with serverspec
|
295
|
+
def belongs_to_group?(group_name)
|
296
|
+
groups.include?(group_name)
|
297
|
+
end
|
298
|
+
|
299
|
+
# encrypted_password property: compatibility with serverspec
|
300
|
+
# it allows to run test against the hashed passwords of the given user
|
301
|
+
# applicable for unix/linux systems with getent utility.
|
302
|
+
def encrypted_password
|
303
|
+
raise Inspec::Exceptions::ResourceSkipped, "encrypted_password property is not applicable for your system" if inspec.os.windows? || inspec.os.darwin?
|
304
|
+
|
305
|
+
# shadow_information returns array of the information from the shadow file
|
306
|
+
# the value at 1st index is the encrypted_password information
|
307
|
+
shadow_information[1]
|
291
308
|
end
|
292
309
|
|
293
310
|
def to_s
|
@@ -314,6 +331,54 @@ module Inspec::Resources
|
|
314
331
|
|
315
332
|
@cred_cache = @user_provider.credentials(@username) unless @user_provider.nil?
|
316
333
|
end
|
334
|
+
|
335
|
+
# helper method for has_authorized_key matcher
|
336
|
+
# get_authorized_keys return the key/keys stored in the authorized_keys path
|
337
|
+
def get_authorized_keys
|
338
|
+
# cat is used in unix system to display content of file; similarly type is used for windows
|
339
|
+
bin = inspec.os.windows? ? "type" : "cat"
|
340
|
+
|
341
|
+
# auth_path gets assigned with the valid path for authorized_keys
|
342
|
+
auth_path = ""
|
343
|
+
|
344
|
+
# possible paths where authorized_keys are stored
|
345
|
+
# inspec.command is used over inspec.file because inspec.file requires absolute path
|
346
|
+
%w{~/.ssh/authorized_keys ~/.ssh/authorized_keys2}.each do |path|
|
347
|
+
if inspec.command("#{bin} #{path}").exit_status == 0
|
348
|
+
auth_path = path
|
349
|
+
break
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# if auth_path is empty, no valid path was found, hence raise exception
|
354
|
+
raise Inspec::Exceptions::ResourceSkipped, "Can't find any valid path for authorized_keys" if auth_path.empty?
|
355
|
+
|
356
|
+
# authorized_keys are obtained in the standard output;
|
357
|
+
# split keys on newline if more than one keys are part of authorized_keys
|
358
|
+
inspec.command("#{bin} #{auth_path}").stdout.split("\n").map(&:strip)
|
359
|
+
end
|
360
|
+
|
361
|
+
# Helper method for encrypted_password property
|
362
|
+
def shadow_information
|
363
|
+
# check if getent is available on the system
|
364
|
+
bin = find_getent_utility
|
365
|
+
|
366
|
+
# fetch details of the passwd file for the current user using getent
|
367
|
+
cmd = inspec.command("#{bin} shadow #{@username}")
|
368
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{bin} shadow #{@username} failed: #{cmd.stderr}" if cmd.exit_status.to_i != 0
|
369
|
+
|
370
|
+
# shadow information are : separated values, split and return
|
371
|
+
cmd.stdout.split(":").map(&:strip)
|
372
|
+
end
|
373
|
+
|
374
|
+
# check if getent exist in the system
|
375
|
+
def find_getent_utility
|
376
|
+
%w{/usr/bin/getent /bin/getent getent}.each do |cmd|
|
377
|
+
return cmd if inspec.command(cmd).exist?
|
378
|
+
end
|
379
|
+
|
380
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find `getent` on your system."
|
381
|
+
end
|
317
382
|
end
|
318
383
|
|
319
384
|
# Class defined to compare for groups without case-sensitivity
|
@@ -190,7 +190,7 @@ module Inspec::Resources
|
|
190
190
|
true
|
191
191
|
end
|
192
192
|
|
193
|
-
# Detect LXC/Docker
|
193
|
+
# Detect LXC/Docker/k8s/podman
|
194
194
|
#
|
195
195
|
# /proc/self/cgroup will look like this inside a docker container:
|
196
196
|
# <index #>:<subsystem>:/lxc/<hexadecimal container id>
|
@@ -208,7 +208,7 @@ module Inspec::Resources
|
|
208
208
|
#
|
209
209
|
# Full notes, https://tickets.opscode.com/browse/OHAI-551
|
210
210
|
# Kernel docs, https://www.kernel.org/doc/Documentation/cgroups
|
211
|
-
def
|
211
|
+
def detect_container
|
212
212
|
return false unless inspec.file("/proc/self/cgroup").exist?
|
213
213
|
|
214
214
|
cgroup_content = inspec.file("/proc/self/cgroup").content
|
@@ -216,6 +216,12 @@ module Inspec::Resources
|
|
216
216
|
cgroup_content =~ %r{^\d+:[^:]+:/[^/]+/(lxc|docker)-.+$} # rubocop:disable Layout/MultilineOperationIndentation
|
217
217
|
@virtualization_data[:system] = $1 # rubocop:disable Style/PerlBackrefs
|
218
218
|
@virtualization_data[:role] = "guest"
|
219
|
+
elsif cgroup_content =~ %r{^\d+:[^:]+:/(kubepods)/.+$}
|
220
|
+
@virtualization_data[:system] = $1
|
221
|
+
@virtualization_data[:role] = "guest"
|
222
|
+
elsif /container=podman/.match?(file_read("/proc/1/environ"))
|
223
|
+
@virtualization_data[:system] = "podman"
|
224
|
+
@virtualization_data[:role] = "guest"
|
219
225
|
elsif lxc_version_exists? && cgroup_content =~ %r{\d:[^:]+:/$}
|
220
226
|
# lxc-version shouldn't be installed by default
|
221
227
|
# Even so, it is likely we are on an LXC capable host that is not being used as such
|
@@ -297,7 +303,7 @@ module Inspec::Resources
|
|
297
303
|
return if detect_docker
|
298
304
|
return if detect_virtualbox
|
299
305
|
return if detect_lxd
|
300
|
-
return if
|
306
|
+
return if detect_container
|
301
307
|
return if detect_linux_vserver
|
302
308
|
return if detect_kvm_from_cpuinfo
|
303
309
|
return if detect_kvm_from_sys
|
data/lib/inspec/resources.rb
CHANGED
@@ -8,21 +8,6 @@
|
|
8
8
|
# glob so this remains a sort of manifest for our resources.
|
9
9
|
|
10
10
|
require "inspec/resource"
|
11
|
-
|
12
|
-
# Detect if we are running the stripped-down inspec-core
|
13
|
-
# This relies on AWS being stripped from the inspec-core gem
|
14
|
-
inspec_core_only = ENV["NO_AWS"] || !File.exist?(File.join(File.dirname(__FILE__), "..", "resource_support", "aws.rb"))
|
15
|
-
|
16
|
-
# Do not attempt to load cloud resources if we are in inspec-core mode
|
17
|
-
unless inspec_core_only
|
18
|
-
require "resource_support/aws"
|
19
|
-
require "resources/azure/azure_backend"
|
20
|
-
require "resources/azure/azure_generic_resource"
|
21
|
-
require "resources/azure/azure_resource_group"
|
22
|
-
require "resources/azure/azure_virtual_machine"
|
23
|
-
require "resources/azure/azure_virtual_machine_data_disk"
|
24
|
-
end
|
25
|
-
|
26
11
|
require "inspec/resources/aide_conf"
|
27
12
|
require "inspec/resources/apache"
|
28
13
|
require "inspec/resources/apache_conf"
|
@@ -41,6 +26,7 @@ require "inspec/resources/cassandradb_session"
|
|
41
26
|
require "inspec/resources/cassandradb_conf"
|
42
27
|
require "inspec/resources/cassandra"
|
43
28
|
require "inspec/resources/crontab"
|
29
|
+
require "inspec/resources/cron"
|
44
30
|
require "inspec/resources/timezone"
|
45
31
|
require "inspec/resources/dh_params"
|
46
32
|
require "inspec/resources/directory"
|
@@ -138,7 +124,8 @@ require "inspec/resources/xinetd_conf"
|
|
138
124
|
require "inspec/resources/yum"
|
139
125
|
require "inspec/resources/zfs_dataset"
|
140
126
|
require "inspec/resources/zfs_pool"
|
141
|
-
|
127
|
+
require "inspec/resources/ipnat"
|
128
|
+
require "inspec/resources/ipfilter"
|
142
129
|
# file formats, depend on json implementation
|
143
130
|
require "inspec/resources/json"
|
144
131
|
require "inspec/resources/yaml"
|
data/lib/inspec/runner.rb
CHANGED
@@ -105,6 +105,7 @@ module Inspec
|
|
105
105
|
|
106
106
|
write_lockfile(profile) if @create_lockfile
|
107
107
|
profile.locked_dependencies
|
108
|
+
profile.load_gem_dependencies
|
108
109
|
profile_context = profile.load_libraries
|
109
110
|
|
110
111
|
profile_context.dependencies.list.values.each do |requirement|
|
@@ -126,9 +127,25 @@ module Inspec
|
|
126
127
|
end
|
127
128
|
end
|
128
129
|
|
130
|
+
controls_count = 0
|
131
|
+
control_checks_count_map = {}
|
132
|
+
|
129
133
|
all_controls.each do |rule|
|
130
|
-
|
134
|
+
unless rule.nil?
|
135
|
+
register_rule(rule)
|
136
|
+
checks = ::Inspec::Rule.prepare_checks(rule)
|
137
|
+
unless checks.empty?
|
138
|
+
# controls with empty tests are avoided
|
139
|
+
# checks represent tests within control
|
140
|
+
controls_count += 1
|
141
|
+
control_checks_count_map[rule.to_s] = checks.count
|
142
|
+
end
|
143
|
+
end
|
131
144
|
end
|
145
|
+
|
146
|
+
# this sets data via runner-rspec into base RSpec formatter object, which gets used up within streaming plugins
|
147
|
+
@test_collector.set_controls_count(controls_count)
|
148
|
+
@test_collector.set_control_checks_count_map(control_checks_count_map)
|
132
149
|
end
|
133
150
|
|
134
151
|
def run(with = nil)
|
data/lib/inspec/runner_rspec.rb
CHANGED
@@ -42,6 +42,21 @@ module Inspec
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
# These control count related methods are called from load logic of runner library of inspec
|
46
|
+
######### Start of control count related methods
|
47
|
+
def set_controls_count(controls_count)
|
48
|
+
formatters.each do |fmt|
|
49
|
+
fmt.set_controls_count(controls_count)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_control_checks_count_map(mapping)
|
54
|
+
formatters.each do |fmt|
|
55
|
+
fmt.set_control_checks_count_map(mapping)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
######### end of control count related methods
|
59
|
+
|
45
60
|
def backend
|
46
61
|
formatters.first.backend
|
47
62
|
end
|
@@ -11,16 +11,16 @@ module Inspec
|
|
11
11
|
"additionalProperties" => true,
|
12
12
|
"required" => %w{label data},
|
13
13
|
"properties" => {
|
14
|
-
"label" => Primitives::STRING,
|
15
|
-
"data" => Primitives::STRING,
|
14
|
+
"label" => Primitives.desc(Primitives::STRING, "The type of description. Examples: 'fix' or 'check'."),
|
15
|
+
"data" => Primitives.desc(Primitives::STRING, "The text of the description."),
|
16
16
|
},
|
17
|
-
}, [])
|
17
|
+
}, [], "A description for a control.")
|
18
18
|
|
19
19
|
# Lists the potential values for a control result
|
20
20
|
CONTROL_RESULT_STATUS = Primitives::SchemaType.new("Control Result Status", {
|
21
21
|
"type" => "string",
|
22
22
|
"enum" => %w{passed failed skipped error},
|
23
|
-
}, [])
|
23
|
+
}, [], "The status of a control. Should be one of 'passed', 'failed', 'skipped', or 'error'.")
|
24
24
|
|
25
25
|
# Represents the statistics/result of a control"s execution
|
26
26
|
CONTROL_RESULT = Primitives::SchemaType.new("Control Result", {
|
@@ -28,24 +28,26 @@ module Inspec
|
|
28
28
|
"additionalProperties" => true,
|
29
29
|
"required" => %w{code_desc run_time start_time},
|
30
30
|
"properties" => {
|
31
|
-
"status" => CONTROL_RESULT_STATUS.ref,
|
32
|
-
"code_desc" => Primitives::STRING,
|
33
|
-
"run_time" => Primitives::NUMBER,
|
34
|
-
"start_time" => Primitives::STRING,
|
31
|
+
"status" => Primitives.desc(CONTROL_RESULT_STATUS.ref, "The status of this test within the control. Example: 'failed'."),
|
32
|
+
"code_desc" => Primitives.desc(Primitives::STRING, "A description of this test. Example: 'limits.conf * is expected to include ['hard', 'maxlogins', '10']."),
|
33
|
+
"run_time" => Primitives.desc(Primitives::NUMBER, "The execution time in seconds for the test."),
|
34
|
+
"start_time" => Primitives.desc(Primitives::STRING, "The time at which the test started."),
|
35
35
|
|
36
36
|
# All optional
|
37
|
-
"resource" => Primitives::STRING,
|
38
|
-
"message" => Primitives::STRING,
|
39
|
-
"skip_message" => Primitives::STRING,
|
40
|
-
"exception" => Primitives::STRING,
|
37
|
+
"resource" => Primitives.desc(Primitives::STRING, "The resource used in the test. Example: in Inspec, you can use the 'File' resource."),
|
38
|
+
"message" => Primitives.desc(Primitives::STRING, "An explanation of the test status - usually only provided when the test fails."),
|
39
|
+
"skip_message" => Primitives.desc(Primitives::STRING, "An explanation of the test status if the status was 'skipped."),
|
40
|
+
"exception" => Primitives.desc(Primitives::STRING, "The type of exception if an exception was thrown."),
|
41
|
+
"resource_id" => Primitives.desc(Primitives::STRING, "The unique identifier of the resource."),
|
41
42
|
"backtrace" => {
|
42
43
|
"anyOf" => [
|
43
44
|
Primitives.array(Primitives::STRING),
|
44
45
|
Primitives::NULL,
|
45
46
|
],
|
47
|
+
"description" => "The stacktrace/backtrace of the exception if one occurred.",
|
46
48
|
},
|
47
49
|
},
|
48
|
-
}, [CONTROL_RESULT_STATUS])
|
50
|
+
}, [CONTROL_RESULT_STATUS], "A test within a control and its results and findings such as how long it took to run.")
|
49
51
|
|
50
52
|
# Represents a control produced
|
51
53
|
CONTROL = Primitives::SchemaType.new("Exec JSON Control", {
|
@@ -53,26 +55,25 @@ module Inspec
|
|
53
55
|
"additionalProperties" => true,
|
54
56
|
"required" => %w{id title desc impact refs tags code source_location results},
|
55
57
|
"properties" => {
|
56
|
-
"id" => Primitives.desc(Primitives::STRING, "The
|
57
|
-
"title" => { "type" => %w{string null} }, # Nullable string
|
58
|
-
"desc" => { "type" => %w{string null} },
|
59
|
-
"descriptions" => Primitives.array(CONTROL_DESCRIPTION.ref),
|
60
|
-
"impact" => Primitives::IMPACT,
|
61
|
-
"refs" => Primitives.array(Primitives::REFERENCE.ref),
|
62
|
-
"tags" => Primitives::TAGS,
|
63
|
-
"code" => Primitives.desc(Primitives::STRING, "The raw source code of the control. Note that if this is an overlay control, it does not include the underlying source code"),
|
64
|
-
"source_location" => Primitives::SOURCE_LOCATION.ref,
|
65
|
-
"results" => Primitives.desc(Primitives.array(CONTROL_RESULT.ref), %q
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
}),
|
58
|
+
"id" => Primitives.desc(Primitives::STRING, "The id."),
|
59
|
+
"title" => Primitives.desc({ "type" => %w{string null} }, "The title - is nullable."), # Nullable string
|
60
|
+
"desc" => Primitives.desc({ "type" => %w{string null} }, "The description for the overarching control."),
|
61
|
+
"descriptions" => Primitives.desc(Primitives.array(CONTROL_DESCRIPTION.ref), "A set of additional descriptions. Example: the 'fix' text."),
|
62
|
+
"impact" => Primitives.desc(Primitives::IMPACT, "The impactfulness or severity."),
|
63
|
+
"refs" => Primitives.desc(Primitives.array(Primitives::REFERENCE.ref), "The set of references to external documents."),
|
64
|
+
"tags" => Primitives.desc(Primitives::TAGS, "A set of tags - usually metadata."),
|
65
|
+
"code" => Primitives.desc(Primitives::STRING, "The raw source code of the control. Note that if this is an overlay control, it does not include the underlying source code."),
|
66
|
+
"source_location" => Primitives.desc(Primitives::SOURCE_LOCATION.ref, "The explicit location of the control within the source code."),
|
67
|
+
"results" => Primitives.desc(Primitives.array(CONTROL_RESULT.ref), %q(
|
68
|
+
The set of all tests within the control and their results and findings. Example:
|
69
|
+
For Chef Inspec, if in the control's code we had the following:
|
70
|
+
describe sshd_config do
|
71
|
+
its('Port') { should cmp 22 }
|
72
|
+
end
|
73
|
+
The findings from this block would be appended to the results, as well as those of any other blocks within the control.
|
74
|
+
)),
|
74
75
|
},
|
75
|
-
}, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT])
|
76
|
+
}, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT], "Describes a control and any findings it has.")
|
76
77
|
|
77
78
|
# Based loosely on https://docs.chef.io/inspec/profiles/ as of July 3, 2019
|
78
79
|
# However, concessions were made to the reality of current reporters, specifically
|
@@ -86,30 +87,30 @@ module Inspec
|
|
86
87
|
# sha256, status, status_message
|
87
88
|
"properties" => {
|
88
89
|
# These are provided in inspec.yml
|
89
|
-
"name" => Primitives::STRING,
|
90
|
-
"title" => Primitives::STRING,
|
91
|
-
"maintainer" => Primitives::STRING,
|
92
|
-
"copyright" => Primitives::STRING,
|
93
|
-
"copyright_email" => Primitives::STRING,
|
94
|
-
"depends" => Primitives.array(Primitives::DEPENDENCY.ref),
|
95
|
-
"parent_profile" => Primitives::STRING,
|
96
|
-
"license" => Primitives::STRING,
|
97
|
-
"summary" => Primitives::STRING,
|
98
|
-
"version" => Primitives::STRING,
|
99
|
-
"supports" => Primitives.array(Primitives::SUPPORT.ref),
|
100
|
-
"description" => Primitives::STRING,
|
101
|
-
"inspec_version" => Primitives::STRING,
|
90
|
+
"name" => Primitives.desc(Primitives::STRING, "The name - must be unique."),
|
91
|
+
"title" => Primitives.desc(Primitives::STRING, "The title - should be human readable."),
|
92
|
+
"maintainer" => Primitives.desc(Primitives::STRING, "The maintainer(s)."),
|
93
|
+
"copyright" => Primitives.desc(Primitives::STRING, "The copyright holder(s)."),
|
94
|
+
"copyright_email" => Primitives.desc(Primitives::STRING, "The email address or other contact information of the copyright holder(s)."),
|
95
|
+
"depends" => Primitives.desc(Primitives.array(Primitives::DEPENDENCY.ref), "The set of dependencies this profile depends on. Example: an overlay profile is dependent on another profile."),
|
96
|
+
"parent_profile" => Primitives.desc(Primitives::STRING, "The name of the parent profile if the profile is a dependency of another."),
|
97
|
+
"license" => Primitives.desc(Primitives::STRING, "The copyright license. Example: the full text or the name, such as 'Apache License, Version 2.0'."),
|
98
|
+
"summary" => Primitives.desc(Primitives::STRING, "The summary. Example: the Security Technical Implementation Guide (STIG) header."),
|
99
|
+
"version" => Primitives.desc(Primitives::STRING, "The version of the profile."),
|
100
|
+
"supports" => Primitives.desc(Primitives.array(Primitives::SUPPORT.ref), "The set of supported platform targets."),
|
101
|
+
"description" => Primitives.desc(Primitives::STRING, "The description - should be more detailed than the summary."),
|
102
|
+
"inspec_version" => Primitives.desc(Primitives::STRING, "The version of Inspec."),
|
102
103
|
|
103
104
|
# These are generated at runtime, and all except status_message and skip_message are guaranteed
|
104
|
-
"sha256" => Primitives::STRING,
|
105
|
-
"status" => Primitives::STRING,
|
106
|
-
"status_message" => Primitives::STRING,
|
107
|
-
"skip_message" => Primitives::STRING,
|
108
|
-
"controls" => Primitives.array(CONTROL.ref),
|
109
|
-
"groups" => Primitives.array(Primitives::CONTROL_GROUP.ref),
|
110
|
-
"attributes" => Primitives.array(Primitives::INPUT),
|
105
|
+
"sha256" => Primitives.desc(Primitives::STRING, "The checksum of the profile."),
|
106
|
+
"status" => Primitives.desc(Primitives::STRING, "The status. Example: loaded."), # enum? loaded, failed, skipped
|
107
|
+
"status_message" => Primitives.desc(Primitives::STRING, "The reason for the status. Example: why it was skipped or failed to load."),
|
108
|
+
"skip_message" => Primitives.desc(Primitives::STRING, "The reason for skipping if it was skipped."), # Deprecated field - status_message should be used instead.
|
109
|
+
"controls" => Primitives.desc(Primitives.array(CONTROL.ref), "The set of controls including any findings."),
|
110
|
+
"groups" => Primitives.desc(Primitives.array(Primitives::CONTROL_GROUP.ref), "A set of descriptions for the control groups. Example: the ids of the controls."),
|
111
|
+
"attributes" => Primitives.desc(Primitives.array(Primitives::INPUT), "The input(s) or attribute(s) used in the run."),
|
111
112
|
},
|
112
|
-
}, [CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::SUPPORT])
|
113
|
+
}, [CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::SUPPORT], "Information on the set of controls assessed. Example: it can include the name of the Inspec profile and any findings.")
|
113
114
|
|
114
115
|
# Result of exec json. Top level value
|
115
116
|
# TODO: Include the format of top level controls. This was omitted for lack of sufficient examples
|
@@ -118,12 +119,12 @@ module Inspec
|
|
118
119
|
"additionalProperties" => true,
|
119
120
|
"required" => %w{platform profiles statistics version},
|
120
121
|
"properties" => {
|
121
|
-
"platform" => Primitives::PLATFORM.ref,
|
122
|
-
"profiles" => Primitives.array(PROFILE.ref),
|
123
|
-
"statistics" => Primitives::STATISTICS.ref,
|
124
|
-
"version" => Primitives::STRING,
|
122
|
+
"platform" => Primitives.desc(Primitives::PLATFORM.ref, "Information on the platform the run from the tool that generated the findings was from. Example: the name of the operating system."),
|
123
|
+
"profiles" => Primitives.desc(Primitives.array(PROFILE.ref), "Information on the run(s) from the tool that generated the findings. Example: the findings."),
|
124
|
+
"statistics" => Primitives.desc(Primitives::STATISTICS.ref, "Statistics for the run(s) from the tool that generated the findings. Example: the runtime duration."),
|
125
|
+
"version" => Primitives.desc(Primitives::STRING, "Version number of the tool that generated the findings. Example: '4.18.108' is a version of Chef InSpec."),
|
125
126
|
},
|
126
|
-
}, [Primitives::PLATFORM, PROFILE, Primitives::STATISTICS])
|
127
|
+
}, [Primitives::PLATFORM, PROFILE, Primitives::STATISTICS], "The top level value containing all of the results.")
|
127
128
|
end
|
128
129
|
end
|
129
130
|
end
|