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.
@@ -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
- if os[:release] =~ /^20\d\d/
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 are deprecated in favor for direct value access
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
- def has_authorized_key?(_compare_key)
289
- Inspec.deprecate(:resource_user_serverspec_compat, "The user resource `has_authorized_key?` matcher is deprecated. There is no currently implemented alternative")
290
- raise NotImplementedError
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
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "5.10.5".freeze
2
+ VERSION = "5.17.4".freeze
3
3
  end
@@ -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
- control_id = notification.example.metadata[:id]
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
- control_id = notification.example.metadata[:id]
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
- control_id = notification.example.metadata[:id]
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 show_progress(control_id)
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 Exception => ex
82
- raise "Exception in Progress Bar streaming reporter: #{ex}"
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.lstrip.force_encoding(Encoding::UTF_8)
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