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