inspec-core 5.10.5 → 5.17.4
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/keys/progress-2022-05-04.pem.pub +9 -0
- data/lib/inspec/cli.rb +2 -0
- data/lib/inspec/dependency_loader.rb +5 -1
- data/lib/inspec/profile.rb +14 -7
- 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/host.rb +42 -3
- data/lib/inspec/resources/linux_audit_system.rb +81 -0
- data/lib/inspec/resources/mail_alias.rb +46 -0
- data/lib/inspec/resources/php_config.rb +72 -0
- data/lib/inspec/resources/processes.rb +11 -0
- data/lib/inspec/resources/routing_table.rb +137 -0
- data/lib/inspec/resources/service.rb +90 -2
- data/lib/inspec/resources/user.rb +12 -0
- data/lib/inspec/resources/users.rb +79 -14
- data/lib/inspec/resources/x509_private_key.rb +93 -0
- data/lib/inspec/resources/zfs.rb +48 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +32 -21
- metadata +10 -2
@@ -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
|
@@ -182,7 +182,9 @@ module Inspec::Resources
|
|
182
182
|
when "aix"
|
183
183
|
SrcMstr.new(inspec)
|
184
184
|
when "amazon"
|
185
|
-
|
185
|
+
# If `initctl` exists on the system, use `Upstart`. Else use `Systemd` since all-new Amazon Linux supports `systemctl`.
|
186
|
+
# This way, it is not dependent on the version of Amazon Linux.
|
187
|
+
if inspec.command("initctl").exist? || inspec.command("/sbin/initctl").exist?
|
186
188
|
Upstart.new(inspec, service_ctl)
|
187
189
|
else
|
188
190
|
Systemd.new(inspec, service_ctl)
|
@@ -271,6 +273,30 @@ module Inspec::Resources
|
|
271
273
|
info[:startname]
|
272
274
|
end
|
273
275
|
|
276
|
+
# matcher equivalent to startmode property; compares start-up mode
|
277
|
+
# supported only on windows.
|
278
|
+
def has_start_mode?(mode)
|
279
|
+
raise Inspec::Exceptions::ResourceSkipped, "The `has_start_mode` matcher is not supported on your OS yet." unless inspec.os.windows?
|
280
|
+
|
281
|
+
mode == startmode
|
282
|
+
end
|
283
|
+
|
284
|
+
# matcher to check if the service is monitored by the given monitoring tool/software
|
285
|
+
def monitored_by?(monitoring_tool)
|
286
|
+
# Currently supported monitoring tools are: monit & god
|
287
|
+
# To add support for new monitoring tools, extend the case statement with additional monitoring tool and
|
288
|
+
# add the definition and logic in a new class (inheriting the base class MonitoringTool: optional)
|
289
|
+
case monitoring_tool
|
290
|
+
when "monit"
|
291
|
+
current_monitoring_tool = Monit.new(inspec, @service_name)
|
292
|
+
when "god"
|
293
|
+
current_monitoring_tool = God.new(inspec, @service_name)
|
294
|
+
else
|
295
|
+
puts "The monitoring tool #{monitoring_tool} is not yet supported by InSpec."
|
296
|
+
end
|
297
|
+
current_monitoring_tool.is_service_monitored?
|
298
|
+
end
|
299
|
+
|
274
300
|
def to_s
|
275
301
|
"Service #{@service_name}"
|
276
302
|
end
|
@@ -537,9 +563,22 @@ module Inspec::Resources
|
|
537
563
|
end
|
538
564
|
|
539
565
|
def info(service_name)
|
566
|
+
# `service -l` lists all files in /etc/rc.d and the local startup directories
|
567
|
+
# % service -l
|
568
|
+
# accounting
|
569
|
+
# addswap
|
570
|
+
# adjkerntz
|
571
|
+
# apm
|
572
|
+
# archdep
|
573
|
+
cmd = inspec.command("#{service_ctl} -l")
|
574
|
+
return nil if cmd.exit_status != 0
|
575
|
+
|
576
|
+
# search for the service
|
577
|
+
srv = /^#{service_name}$/.match(cmd.stdout)
|
578
|
+
return nil if srv.nil? || srv[0].nil?
|
579
|
+
|
540
580
|
# check if service is enabled
|
541
581
|
cmd = inspec.command("#{service_ctl} #{service_name} enabled")
|
542
|
-
|
543
582
|
enabled = cmd.exit_status == 0
|
544
583
|
|
545
584
|
# check if the service is running
|
@@ -880,4 +919,53 @@ module Inspec::Resources
|
|
880
919
|
Runit.new(inspec, service_ctl)
|
881
920
|
end
|
882
921
|
end
|
922
|
+
|
923
|
+
# Helper class for monitored_by matcher
|
924
|
+
class MonitoringTool
|
925
|
+
attr_reader :inspec, :service_name
|
926
|
+
def initialize(inspec, service_name)
|
927
|
+
@inspec = inspec
|
928
|
+
@service_name ||= service_name
|
929
|
+
end
|
930
|
+
|
931
|
+
def find_utility_or_error(utility_name)
|
932
|
+
[ "/usr/sbin/#{utility_name}" , "/sbin/#{utility_name}" , "/usr/bin/#{utility_name}" , "/bin/#{utility_name}" , "#{utility_name}" ].each do |cmd|
|
933
|
+
return cmd if inspec.command(cmd).exist?
|
934
|
+
end
|
935
|
+
|
936
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find `#{utility_name}`"
|
937
|
+
end
|
938
|
+
end
|
939
|
+
|
940
|
+
class Monit < MonitoringTool
|
941
|
+
def is_service_monitored?
|
942
|
+
utility = find_utility_or_error("monit")
|
943
|
+
utility_cmd = inspec.command("#{utility} summary")
|
944
|
+
|
945
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{utility} summary failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
|
946
|
+
|
947
|
+
monitoring_info = utility_cmd.stdout.split("\n")
|
948
|
+
monitoring_info.map! { |info| info.strip.squeeze(" ") }
|
949
|
+
is_monitored = false
|
950
|
+
monitoring_info.each do |info|
|
951
|
+
if info =~ /^#{service_name} OK.*/
|
952
|
+
is_monitored = true
|
953
|
+
break
|
954
|
+
end
|
955
|
+
end
|
956
|
+
is_monitored
|
957
|
+
end
|
958
|
+
end
|
959
|
+
|
960
|
+
class God < MonitoringTool
|
961
|
+
def is_service_monitored?
|
962
|
+
utility = find_utility_or_error("god")
|
963
|
+
utility_cmd = inspec.command("#{utility} status #{service_name}")
|
964
|
+
|
965
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{utility} status #{service_name} failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
|
966
|
+
|
967
|
+
monitoring_info = utility_cmd.stdout.strip
|
968
|
+
monitoring_info =~ /^#{service_name}: up/
|
969
|
+
end
|
970
|
+
end
|
883
971
|
end
|
@@ -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
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require "inspec/resources/file"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class X509PrivateKey < Inspec.resource(1)
|
5
|
+
# Resource internal name.
|
6
|
+
name "x509_private_key"
|
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 x509_private_key InSpec audit resource to test the x509 private key"
|
14
|
+
|
15
|
+
example <<~EXAMPLE
|
16
|
+
# With passphrase
|
17
|
+
describe x509_private_key("/home/openssl_activity/alice_private.pem", "password@123") do
|
18
|
+
it { should be_valid }
|
19
|
+
it { should be_encrypted }
|
20
|
+
it { should have_matching_certificate("/home/openssl_activity/alice_certificate.crt") }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Without passphrase
|
24
|
+
describe x509_private_key("/home/openssl_activity/bob_private.pem") do
|
25
|
+
it { should be_valid }
|
26
|
+
it { should_not be_encrypted }
|
27
|
+
it { should have_matching_certificate("/home/openssl_activity/bob_certificate.crt") }
|
28
|
+
end
|
29
|
+
EXAMPLE
|
30
|
+
|
31
|
+
# Resource initialization.
|
32
|
+
attr_reader :secret_key_path, :passphrase, :openssl_utility
|
33
|
+
|
34
|
+
def initialize(secret_key_path, passphrase = nil)
|
35
|
+
@openssl_utility = check_openssl_or_error
|
36
|
+
@secret_key_path = secret_key_path
|
37
|
+
@passphrase = passphrase
|
38
|
+
end
|
39
|
+
|
40
|
+
# Resource appearance in test reports.
|
41
|
+
def to_s
|
42
|
+
"x509_private_key"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Matcher to check if the given key is valid.
|
46
|
+
def valid?
|
47
|
+
# Below is the command to check if the key is valid.
|
48
|
+
openssl_key_validity_cmd = "#{openssl_utility} rsa -in #{secret_key_path} -check -noout"
|
49
|
+
|
50
|
+
# Additionally, if key is password protected, passphrase needs to be given with -passin argument
|
51
|
+
openssl_key_validity_cmd.concat(" -passin pass:#{passphrase}") if passphrase
|
52
|
+
|
53
|
+
openssl_key_validity = inspec.command(openssl_key_validity_cmd)
|
54
|
+
openssl_key_validity.exit_status.to_i == 0
|
55
|
+
end
|
56
|
+
|
57
|
+
# Matcher to check if the given key is encrypted.
|
58
|
+
def encrypted?
|
59
|
+
raise Inspec::Exceptions::ResourceFailed, "The given secret key #{secret_key_path} does not exist." unless inspec.file(secret_key_path).exist?
|
60
|
+
|
61
|
+
# All encrypted keys have the header of Proc-Type: 4,ENCRYPTED
|
62
|
+
key_file = inspec.file(secret_key_path)
|
63
|
+
key_file.content =~ /Proc-Type: 4,ENCRYPTED/
|
64
|
+
end
|
65
|
+
|
66
|
+
# Matcher to verify if the private key maatches the certificate
|
67
|
+
def has_matching_certificate?(cert_file_or_path)
|
68
|
+
cert_hash_cmd = "openssl x509 -noout -modulus -in #{cert_file_or_path} | openssl md5"
|
69
|
+
cert_hash = inspec.command(cert_hash_cmd)
|
70
|
+
|
71
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{cert_hash_cmd} failed: #{cert_hash.stderr}" if cert_hash.exit_status.to_i != 0
|
72
|
+
|
73
|
+
key_hash_cmd = "openssl rsa -noout -modulus -in #{secret_key_path}"
|
74
|
+
passphrase ? key_hash_cmd.concat(" -passin pass:#{passphrase} | openssl md5") : key_hash_cmd.concat(" | openssl md5")
|
75
|
+
key_hash = inspec.command(key_hash_cmd)
|
76
|
+
|
77
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{key_hash_cmd} failed: #{key_hash.stderr}" if key_hash.exit_status.to_i != 0
|
78
|
+
|
79
|
+
cert_hash.stdout == key_hash.stdout
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# This resource requires openssl to be available on the system
|
85
|
+
def check_openssl_or_error
|
86
|
+
%w{/usr/sbin/openssl /usr/bin/openssl /sbin/openssl /bin/openssl openssl}.each do |cmd|
|
87
|
+
return cmd if inspec.command(cmd).exist?
|
88
|
+
end
|
89
|
+
|
90
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find `openssl` on your system."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "inspec/resources/zfs_pool"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class Zfs < ZfsPool
|
5
|
+
# resource's internal name.
|
6
|
+
name "zfs"
|
7
|
+
|
8
|
+
# Restrict to only run on the below platforms
|
9
|
+
supports platform: "unix"
|
10
|
+
|
11
|
+
desc "Use the zfs InSpec audit resource to test if the named ZFS Pool is present and/or has certain properties."
|
12
|
+
|
13
|
+
example <<~EXAMPLE
|
14
|
+
describe zfs("new-pool") do
|
15
|
+
it { should exist }
|
16
|
+
it { should have_property({ "failmode" => "wait", "capacity" => "0" }) }
|
17
|
+
end
|
18
|
+
EXAMPLE
|
19
|
+
|
20
|
+
# Resource initialization is done in the parent class i.e. ZfsPool
|
21
|
+
|
22
|
+
# Unique identity for the resource.
|
23
|
+
def resource_id
|
24
|
+
# @zfs_pool is the zfs pool name assigned during initialization in the parent class i.e. ZfsPool
|
25
|
+
@zfs_pool
|
26
|
+
end
|
27
|
+
|
28
|
+
# Resource appearance in test reports.
|
29
|
+
def to_s
|
30
|
+
"zfs #{resource_id}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# The below matcher checks if the given properties are valid properties of the zfs pool.
|
34
|
+
def has_property?(properties_hash)
|
35
|
+
raise Inspec::Exceptions::ResourceSkipped, "Provide a valid key-value pair of the zfs properties." if properties_hash.empty?
|
36
|
+
|
37
|
+
# Transform all the key & values provided by user to string,
|
38
|
+
# since hash keys can be symbols or strings & values can be integers or strings.
|
39
|
+
# @params is a hash populated in the parent class with the properties(key-value) of the current zfs pool.
|
40
|
+
# and the key-value in @params are of string type.
|
41
|
+
properties_hash.transform_keys(&:to_s)
|
42
|
+
properties_hash.transform_values(&:to_s)
|
43
|
+
|
44
|
+
# check if the given properties is a subset of @params
|
45
|
+
properties_hash <= @params
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/inspec/version.rb
CHANGED
@@ -41,9 +41,9 @@ module InspecPlugins::StreamingReporterProgressBar
|
|
41
41
|
# Groovy UTF-8 characters for everyone else...
|
42
42
|
# ...even though they probably only work on Mac
|
43
43
|
INDICATORS = {
|
44
|
-
"failed" => "×",
|
45
|
-
"skipped" => "↺",
|
46
|
-
"passed" => "✔",
|
44
|
+
"failed" => "× [FAILED] ",
|
45
|
+
"skipped" => "↺ [SKIPPED]",
|
46
|
+
"passed" => "✔ [PASSED] ",
|
47
47
|
}.freeze
|
48
48
|
end
|
49
49
|
|
@@ -54,58 +54,69 @@ module InspecPlugins::StreamingReporterProgressBar
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def example_passed(notification)
|
57
|
-
|
58
|
-
set_status_mapping(control_id, "passed")
|
59
|
-
show_progress(control_id) if control_ended?(control_id)
|
57
|
+
set_example(notification, "passed")
|
60
58
|
end
|
61
59
|
|
62
60
|
def example_failed(notification)
|
63
|
-
|
64
|
-
set_status_mapping(control_id, "failed")
|
65
|
-
show_progress(control_id) if control_ended?(control_id)
|
61
|
+
set_example(notification, "failed")
|
66
62
|
end
|
67
63
|
|
68
64
|
def example_pending(notification)
|
69
|
-
|
70
|
-
set_status_mapping(control_id, "skipped")
|
71
|
-
show_progress(control_id) if control_ended?(control_id)
|
65
|
+
set_example(notification, "skipped")
|
72
66
|
end
|
73
67
|
|
74
68
|
private
|
75
69
|
|
76
|
-
def
|
70
|
+
def set_example(notification, status)
|
71
|
+
control_id = notification.example.metadata[:id]
|
72
|
+
title = notification.example.metadata[:title]
|
73
|
+
full_description = notification.example.metadata[:full_description]
|
74
|
+
control_impact = notification.example.metadata[:impact]
|
75
|
+
set_status_mapping(control_id, status)
|
76
|
+
show_progress(control_id, title, full_description, control_impact) if control_ended?(control_id)
|
77
|
+
end
|
78
|
+
|
79
|
+
def show_progress(control_id, title, full_description, control_impact)
|
77
80
|
@bar ||= ProgressBar.new(controls_count, :bar, :counter, :percentage)
|
78
81
|
sleep 0.1
|
79
82
|
@bar.increment!
|
80
|
-
@bar.puts format_it(control_id)
|
81
|
-
rescue
|
82
|
-
raise "Exception in Progress Bar streaming reporter: #{
|
83
|
+
@bar.puts format_it(control_id, title, full_description, control_impact)
|
84
|
+
rescue StandardError => e
|
85
|
+
raise "Exception in Progress Bar streaming reporter: #{e}"
|
83
86
|
end
|
84
87
|
|
85
|
-
def format_it(control_id)
|
88
|
+
def format_it(control_id, title, full_description, control_impact)
|
86
89
|
control_status = if @status_mapping[control_id].include? "failed"
|
87
90
|
"failed"
|
88
|
-
elsif @status_mapping[control_id].include? "skipped"
|
89
|
-
"skipped"
|
90
91
|
elsif @status_mapping[control_id].include? "passed"
|
91
92
|
"passed"
|
93
|
+
else
|
94
|
+
@status_mapping[control_id].include? "skipped"
|
95
|
+
"skipped"
|
92
96
|
end
|
93
|
-
|
94
97
|
indicator = INDICATORS[control_status]
|
95
98
|
message_to_format = ""
|
96
99
|
message_to_format += "#{indicator} "
|
97
|
-
message_to_format += control_id.to_s.
|
100
|
+
message_to_format += "#{control_id.to_s.strip.dup.force_encoding(Encoding::UTF_8)} "
|
101
|
+
message_to_format += "#{title.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " if title
|
102
|
+
message_to_format += "#{full_description.gsub(/\n*\s+/, " ").to_s.force_encoding(Encoding::UTF_8)} " unless title
|
98
103
|
format_with_color(control_status, message_to_format)
|
104
|
+
rescue Exception => e
|
105
|
+
raise "Exception in show_progress: #{e}"
|
99
106
|
end
|
100
107
|
|
101
108
|
def format_with_color(color_name, text)
|
102
109
|
"#{COLORS[color_name]}#{text}#{COLORS["reset"]}"
|
110
|
+
rescue StandardError => e
|
111
|
+
raise "Exception in format_with_color: #{e}"
|
103
112
|
end
|
104
113
|
|
105
114
|
# status mapping with control id to decide the final state of the control
|
106
115
|
def set_status_mapping(control_id, status)
|
107
116
|
@status_mapping[control_id] = [] if @status_mapping[control_id].nil?
|
108
117
|
@status_mapping[control_id].push(status)
|
118
|
+
rescue StandardError => e
|
119
|
+
raise "Exception in format_with_color: #{e}"
|
109
120
|
end
|
110
121
|
|
111
122
|
end
|