inspec-core 5.17.4 → 5.21.29

Sign up to get free protection for your applications and to get access to all the features.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +20 -17
  3. data/etc/deprecations.json +4 -0
  4. data/inspec-core.gemspec +23 -23
  5. data/lib/inspec/base_cli.rb +7 -0
  6. data/lib/inspec/cli.rb +68 -11
  7. data/lib/inspec/dependencies/dependency_set.rb +6 -2
  8. data/lib/inspec/dsl.rb +24 -5
  9. data/lib/inspec/enhanced_outcomes.rb +19 -0
  10. data/lib/inspec/env_printer.rb +1 -1
  11. data/lib/inspec/errors.rb +2 -0
  12. data/lib/inspec/exceptions.rb +4 -0
  13. data/lib/inspec/fetcher/url.rb +1 -1
  14. data/lib/inspec/file_provider.rb +36 -0
  15. data/lib/inspec/formatters/base.rb +69 -16
  16. data/lib/inspec/iaf_file.rb +127 -0
  17. data/lib/inspec/plugin/v2/loader.rb +19 -8
  18. data/lib/inspec/plugin/v2/plugin_types/reporter.rb +1 -0
  19. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +54 -0
  20. data/lib/inspec/profile.rb +17 -7
  21. data/lib/inspec/reporters/base.rb +1 -0
  22. data/lib/inspec/reporters/cli.rb +94 -3
  23. data/lib/inspec/reporters/json.rb +3 -1
  24. data/lib/inspec/reporters/yaml.rb +3 -1
  25. data/lib/inspec/reporters.rb +2 -1
  26. data/lib/inspec/resources/aide_conf.rb +4 -0
  27. data/lib/inspec/resources/apache.rb +4 -0
  28. data/lib/inspec/resources/apache_conf.rb +4 -0
  29. data/lib/inspec/resources/apt.rb +6 -1
  30. data/lib/inspec/resources/audit_policy.rb +5 -0
  31. data/lib/inspec/resources/auditd_conf.rb +4 -0
  32. data/lib/inspec/resources/bash.rb +4 -0
  33. data/lib/inspec/resources/bond.rb +4 -0
  34. data/lib/inspec/resources/bridge.rb +4 -0
  35. data/lib/inspec/resources/cassandradb_conf.rb +5 -0
  36. data/lib/inspec/resources/cassandradb_session.rb +8 -3
  37. data/lib/inspec/resources/chocolatey_package.rb +4 -0
  38. data/lib/inspec/resources/chrony_conf.rb +4 -0
  39. data/lib/inspec/resources/command.rb +5 -0
  40. data/lib/inspec/resources/cpan.rb +4 -0
  41. data/lib/inspec/resources/cran.rb +4 -0
  42. data/lib/inspec/resources/cron.rb +5 -0
  43. data/lib/inspec/resources/csv.rb +6 -1
  44. data/lib/inspec/resources/dh_params.rb +4 -0
  45. data/lib/inspec/resources/docker_container.rb +4 -0
  46. data/lib/inspec/resources/docker_image.rb +4 -0
  47. data/lib/inspec/resources/docker_plugin.rb +4 -0
  48. data/lib/inspec/resources/docker_service.rb +4 -0
  49. data/lib/inspec/resources/etc_group.rb +4 -0
  50. data/lib/inspec/resources/etc_hosts_allow_deny.rb +5 -0
  51. data/lib/inspec/resources/file.rb +7 -2
  52. data/lib/inspec/resources/filesystem.rb +4 -0
  53. data/lib/inspec/resources/gem.rb +4 -0
  54. data/lib/inspec/resources/groups.rb +4 -0
  55. data/lib/inspec/resources/grub_conf.rb +4 -0
  56. data/lib/inspec/resources/host.rb +4 -0
  57. data/lib/inspec/resources/http.rb +6 -2
  58. data/lib/inspec/resources/ibmdb2_conf.rb +8 -0
  59. data/lib/inspec/resources/ibmdb2_session.rb +12 -3
  60. data/lib/inspec/resources/iis_app.rb +4 -0
  61. data/lib/inspec/resources/iis_app_pool.rb +4 -0
  62. data/lib/inspec/resources/iis_site.rb +4 -0
  63. data/lib/inspec/resources/inetd_conf.rb +4 -0
  64. data/lib/inspec/resources/interface.rb +4 -0
  65. data/lib/inspec/resources/ip6tables.rb +4 -0
  66. data/lib/inspec/resources/ipfilter.rb +4 -0
  67. data/lib/inspec/resources/ipnat.rb +4 -0
  68. data/lib/inspec/resources/iptables.rb +4 -0
  69. data/lib/inspec/resources/json.rb +4 -0
  70. data/lib/inspec/resources/kernel_module.rb +4 -0
  71. data/lib/inspec/resources/kernel_parameter.rb +4 -0
  72. data/lib/inspec/resources/key_rsa.rb +4 -0
  73. data/lib/inspec/resources/ksh.rb +4 -0
  74. data/lib/inspec/resources/limits_conf.rb +4 -0
  75. data/lib/inspec/resources/login_defs.rb +4 -0
  76. data/lib/inspec/resources/lxc.rb +65 -9
  77. data/lib/inspec/resources/mongodb.rb +4 -0
  78. data/lib/inspec/resources/mongodb_conf.rb +5 -0
  79. data/lib/inspec/resources/mongodb_session.rb +6 -1
  80. data/lib/inspec/resources/mount.rb +4 -0
  81. data/lib/inspec/resources/mssql_session.rb +4 -0
  82. data/lib/inspec/resources/mssql_sys_conf.rb +7 -0
  83. data/lib/inspec/resources/mysql_conf.rb +4 -0
  84. data/lib/inspec/resources/mysql_session.rb +8 -1
  85. data/lib/inspec/resources/nginx.rb +6 -1
  86. data/lib/inspec/resources/nginx_conf.rb +4 -0
  87. data/lib/inspec/resources/noop.rb +4 -0
  88. data/lib/inspec/resources/npm.rb +4 -0
  89. data/lib/inspec/resources/ntp_conf.rb +4 -0
  90. data/lib/inspec/resources/oneget.rb +4 -0
  91. data/lib/inspec/resources/opa_api.rb +10 -0
  92. data/lib/inspec/resources/opa_cli.rb +14 -0
  93. data/lib/inspec/resources/oracledb_conf.rb +5 -0
  94. data/lib/inspec/resources/oracledb_listener_conf.rb +4 -0
  95. data/lib/inspec/resources/oracledb_session.rb +23 -4
  96. data/lib/inspec/resources/os.rb +4 -0
  97. data/lib/inspec/resources/os_env.rb +4 -0
  98. data/lib/inspec/resources/package.rb +4 -0
  99. data/lib/inspec/resources/parse_config.rb +10 -1
  100. data/lib/inspec/resources/pip.rb +4 -0
  101. data/lib/inspec/resources/platform.rb +4 -0
  102. data/lib/inspec/resources/podman.rb +353 -0
  103. data/lib/inspec/resources/podman_container.rb +84 -0
  104. data/lib/inspec/resources/podman_image.rb +108 -0
  105. data/lib/inspec/resources/podman_network.rb +81 -0
  106. data/lib/inspec/resources/podman_pod.rb +101 -0
  107. data/lib/inspec/resources/podman_volume.rb +87 -0
  108. data/lib/inspec/resources/postfix_conf.rb +4 -0
  109. data/lib/inspec/resources/postgres_conf.rb +4 -0
  110. data/lib/inspec/resources/postgres_session.rb +8 -4
  111. data/lib/inspec/resources/powershell.rb +4 -0
  112. data/lib/inspec/resources/processes.rb +6 -4
  113. data/lib/inspec/resources/rabbitmq_config.rb +4 -0
  114. data/lib/inspec/resources/registry_key.rb +4 -0
  115. data/lib/inspec/resources/security_identifier.rb +4 -0
  116. data/lib/inspec/resources/security_policy.rb +4 -0
  117. data/lib/inspec/resources/service.rb +5 -1
  118. data/lib/inspec/resources/ssh_config.rb +4 -0
  119. data/lib/inspec/resources/sybase_conf.rb +4 -0
  120. data/lib/inspec/resources/sybase_session.rb +4 -0
  121. data/lib/inspec/resources/sys_info.rb +4 -0
  122. data/lib/inspec/resources/timezone.rb +4 -0
  123. data/lib/inspec/resources/users.rb +4 -0
  124. data/lib/inspec/resources/vbscript.rb +5 -0
  125. data/lib/inspec/resources/virtualization.rb +4 -0
  126. data/lib/inspec/resources/windows_feature.rb +5 -1
  127. data/lib/inspec/resources/windows_firewall.rb +4 -0
  128. data/lib/inspec/resources/windows_firewall_rule.rb +4 -0
  129. data/lib/inspec/resources/windows_hotfix.rb +4 -0
  130. data/lib/inspec/resources/windows_task.rb +4 -0
  131. data/lib/inspec/resources/wmi.rb +4 -0
  132. data/lib/inspec/resources/x509_certificate.rb +59 -0
  133. data/lib/inspec/resources/yum.rb +4 -0
  134. data/lib/inspec/resources/zfs_dataset.rb +4 -0
  135. data/lib/inspec/resources/zfs_pool.rb +4 -0
  136. data/lib/inspec/rule.rb +55 -18
  137. data/lib/inspec/run_data/control.rb +6 -0
  138. data/lib/inspec/run_data/statistics.rb +8 -2
  139. data/lib/inspec/runner.rb +18 -8
  140. data/lib/inspec/runner_rspec.rb +3 -2
  141. data/lib/inspec/schema/exec_json.rb +78 -2
  142. data/lib/inspec/schema/output_schema.rb +4 -1
  143. data/lib/inspec/schema/profile_json.rb +46 -0
  144. data/lib/inspec/schema.rb +91 -0
  145. data/lib/inspec/secrets/yaml.rb +7 -1
  146. data/lib/inspec/ui.rb +1 -0
  147. data/lib/inspec/utils/convert.rb +8 -0
  148. data/lib/inspec/utils/podman.rb +24 -0
  149. data/lib/inspec/utils/waivers/csv_file_reader.rb +34 -0
  150. data/lib/inspec/utils/waivers/excel_file_reader.rb +39 -0
  151. data/lib/inspec/utils/waivers/json_file_reader.rb +15 -0
  152. data/lib/inspec/utils/yaml_profile_summary.rb +34 -0
  153. data/lib/inspec/version.rb +1 -1
  154. data/lib/inspec/waiver_file_reader.rb +61 -0
  155. data/lib/matchers/matchers.rb +7 -1
  156. data/lib/plugins/inspec-init/templates/profiles/alicloud/README.md +27 -0
  157. data/lib/plugins/inspec-init/templates/profiles/alicloud/controls/example.rb +10 -0
  158. data/lib/plugins/inspec-init/templates/profiles/alicloud/inputs.yml +1 -0
  159. data/lib/plugins/inspec-init/templates/profiles/alicloud/inspec.yml +14 -0
  160. data/lib/plugins/inspec-reporter-html2/README.md +1 -1
  161. data/lib/plugins/inspec-reporter-html2/templates/body.html.erb +11 -5
  162. data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +11 -7
  163. data/lib/plugins/inspec-reporter-html2/templates/default.css +12 -0
  164. data/lib/plugins/inspec-reporter-html2/templates/profile.html.erb +1 -1
  165. data/lib/plugins/inspec-reporter-html2/templates/selector.html.erb +7 -1
  166. data/lib/plugins/{inspec-artifact/inspec-artifact.gemspec → inspec-sign/inspec-sign.gemspec} +2 -2
  167. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +164 -0
  168. data/lib/plugins/{inspec-artifact/lib/inspec-artifact → inspec-sign/lib/inspec-sign}/cli.rb +14 -23
  169. data/lib/plugins/inspec-sign/lib/inspec-sign.rb +12 -0
  170. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +39 -13
  171. data/lib/source_readers/inspec.rb +8 -2
  172. metadata +33 -15
  173. data/lib/plugins/inspec-artifact/lib/inspec-artifact/base.rb +0 -187
  174. data/lib/plugins/inspec-artifact/lib/inspec-artifact.rb +0 -12
data/lib/inspec/rule.rb CHANGED
@@ -8,13 +8,15 @@ require "inspec/impact"
8
8
  require "inspec/resource"
9
9
  require "inspec/resources/os"
10
10
  require "inspec/input_registry"
11
+ require "inspec/waiver_file_reader"
12
+ require "inspec/utils/convert"
11
13
 
12
14
  module Inspec
13
15
  class Rule
14
16
  include ::RSpec::Matchers
15
17
 
16
18
  attr_reader :__waiver_data
17
- attr_accessor :resource_dsl
19
+ attr_accessor :resource_dsl, :na_impact_freeze
18
20
  attr_reader :__profile_id
19
21
 
20
22
  def initialize(id, profile_id, resource_dsl, opts, &block)
@@ -38,6 +40,7 @@ module Inspec
38
40
  @__merge_count = 0
39
41
  @__merge_changes = []
40
42
  @__skip_only_if_eval = opts[:skip_only_if_eval]
43
+ @__na_rule = {}
41
44
 
42
45
  # evaluate the given definition
43
46
  return unless block_given?
@@ -73,10 +76,13 @@ module Inspec
73
76
  end
74
77
 
75
78
  def impact(v = nil)
76
- if v.is_a?(String)
77
- @impact = Inspec::Impact.impact_from_string(v)
78
- elsif !v.nil?
79
- @impact = v
79
+ # N/A impact freeze is required when only_applicable_if block has reset impact value to zero"
80
+ unless na_impact_freeze
81
+ if v.is_a?(String)
82
+ @impact = Inspec::Impact.impact_from_string(v)
83
+ elsif !v.nil?
84
+ @impact = v
85
+ end
80
86
  end
81
87
 
82
88
  @impact
@@ -133,15 +139,28 @@ module Inspec
133
139
  #
134
140
  # @param [Type] &block returns true if tests are added, false otherwise
135
141
  # @return [nil]
136
- def only_if(message = nil)
142
+ def only_if(message = nil, impact: nil)
137
143
  return unless block_given?
138
144
  return if @__skip_only_if_eval == true
139
145
 
146
+ self.impact(impact) if impact && !yield
140
147
  @__skip_rule[:result] ||= !yield
141
148
  @__skip_rule[:type] = :only_if
142
149
  @__skip_rule[:message] = message
143
150
  end
144
151
 
152
+ def only_applicable_if(message = nil)
153
+ return unless block_given?
154
+ return if yield
155
+
156
+ impact(0.0)
157
+ self.na_impact_freeze = true # this flag prevents impact value to reset to any other value
158
+
159
+ @__na_rule[:result] ||= !yield
160
+ @__na_rule[:type] = :only_applicable_if
161
+ @__na_rule[:message] = message
162
+ end
163
+
145
164
  # Describe will add one or more tests to this control. There is 2 ways
146
165
  # of calling it:
147
166
  #
@@ -252,6 +271,10 @@ module Inspec
252
271
  rule.instance_variable_get(:@__skip_rule)
253
272
  end
254
273
 
274
+ def self.na_status(rule)
275
+ rule.instance_variable_get(:@__na_rule)
276
+ end
277
+
255
278
  def self.set_skip_rule(rule, value, message = nil, type = :only_if)
256
279
  rule.instance_variable_set(:@__skip_rule,
257
280
  {
@@ -273,16 +296,26 @@ module Inspec
273
296
  # creates a dummay array of "checks" with a skip outcome
274
297
  def self.prepare_checks(rule)
275
298
  skip_check = skip_status(rule)
276
- return checks(rule) unless skip_check[:result].eql?(true)
299
+ na_check = na_status(rule)
300
+ return checks(rule) unless skip_check[:result].eql?(true) || na_check[:result].eql?(true)
277
301
 
278
- if skip_check[:message]
279
- msg = "Skipped control due to #{skip_check[:type]} condition: #{skip_check[:message]}"
302
+ resource = rule.noop
303
+ if skip_check[:result].eql?(true)
304
+ if skip_check[:message]
305
+ msg = "Skipped control due to #{skip_check[:type]} condition: #{skip_check[:message]}"
306
+ else
307
+ msg = "Skipped control due to #{skip_check[:type]} condition."
308
+ end
309
+ resource.skip_resource(msg)
280
310
  else
281
- msg = "Skipped control due to #{skip_check[:type]} condition."
311
+ if na_check[:message]
312
+ msg = "N/A control due to #{na_check[:type]} condition: #{na_check[:message]}"
313
+ else
314
+ msg = "N/A control due to #{na_check[:type]} condition."
315
+ end
316
+ resource.fail_resource(msg)
282
317
  end
283
318
 
284
- resource = rule.noop
285
- resource.skip_resource(msg)
286
319
  [["describe", [resource], nil]]
287
320
  end
288
321
 
@@ -337,17 +370,20 @@ module Inspec
337
370
  # only_if mechanism)
338
371
  # Double underscore: not intended to be called as part of the DSL
339
372
  def __apply_waivers
340
- input_name = @__rule_id # TODO: control ID slugging
341
- registry = Inspec::InputRegistry.instance
342
- input = registry.inputs_by_profile.dig(__profile_id, input_name)
343
- return unless input && input.has_value? && input.value.is_a?(Hash)
373
+ control_id = @__rule_id # TODO: control ID slugging
374
+ waiver_files = Inspec::Config.cached.final_options["waiver_file"] if Inspec::Config.cached.respond_to?(:final_options)
375
+
376
+ waiver_data_by_profile = Inspec::WaiverFileReader.fetch_waivers_by_profile(__profile_id, waiver_files) unless waiver_files.nil?
377
+
378
+ return unless waiver_data_by_profile && waiver_data_by_profile[control_id] && waiver_data_by_profile[control_id].is_a?(Hash)
344
379
 
345
380
  # An InSpec Input is a datastructure that tracks a profile parameter
346
381
  # over time. Its value can be set by many sources, and it keeps a
347
382
  # log of each "set" event so that when it is collapsed to a value,
348
383
  # it can determine the correct (highest priority) value.
349
384
  # Store in an instance variable for.. later reading???
350
- @__waiver_data = input.value
385
+ @__waiver_data = waiver_data_by_profile[control_id]
386
+
351
387
  __waiver_data["skipped_due_to_waiver"] = false
352
388
  __waiver_data["message"] = ""
353
389
 
@@ -358,7 +394,7 @@ module Inspec
358
394
  # YAML will automagically give us a Date or a Time.
359
395
  # If transcoding YAML between languages (e.g. Go) the date might have also ended up as a String.
360
396
  # A string that does not represent a valid time results in the date 0000-01-01.
361
- if [Date, Time].include?(expiry.class) || (expiry.is_a?(String) && Time.new(expiry).year != 0)
397
+ if [Date, Time].include?(expiry.class) || (expiry.is_a?(String) && Time.parse(expiry).year != 0)
362
398
  expiry = expiry.to_time if expiry.is_a? Date
363
399
  expiry = Time.parse(expiry) if expiry.is_a? String
364
400
  if expiry < Time.now # If the waiver expired, return - no skip applied
@@ -376,6 +412,7 @@ module Inspec
376
412
  # expiration_date. We only care here if it has a "run" key and it
377
413
  # is false-like, since all non-skipped waiver operations are handled
378
414
  # during reporting phase.
415
+ __waiver_data["run"] = Converter.to_boolean(__waiver_data["run"]) if __waiver_data.key?("run")
379
416
  return unless __waiver_data.key?("run") && !__waiver_data["run"]
380
417
 
381
418
  # OK, apply a skip.
@@ -1,3 +1,5 @@
1
+ require "inspec/enhanced_outcomes"
2
+
1
3
  module Inspec
2
4
  class RunData
3
5
  Control = Struct.new(
@@ -31,6 +33,10 @@ module Inspec
31
33
  ].each do |field|
32
34
  self[field] = raw_ctl_data[field]
33
35
  end
36
+
37
+ def status
38
+ Inspec::EnhancedOutcomes.determine_status(results, impact)
39
+ end
34
40
  end
35
41
  end
36
42
 
@@ -16,14 +16,20 @@ module Inspec
16
16
  :total,
17
17
  :passed,
18
18
  :skipped,
19
- :failed
19
+ :failed,
20
+ :not_reviewed,
21
+ :not_applicable,
22
+ :error
20
23
  ) do
21
24
  include HashLikeStruct
22
25
  def initialize(raw_stat_ctl_data)
23
26
  self.total = raw_stat_ctl_data[:total]
24
27
  self.passed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:passed][:total])
25
- self.skipped = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:skipped][:total])
26
28
  self.failed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:failed][:total])
29
+ self.skipped = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:skipped][:total]) if raw_stat_ctl_data[:skipped]
30
+ self.not_reviewed = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:not_reviewed][:total]) if raw_stat_ctl_data[:not_reviewed]
31
+ self.not_applicable = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:not_applicable][:total]) if raw_stat_ctl_data[:not_applicable]
32
+ self.error = Inspec::RunData::Statistics::Controls::Total.new(raw_stat_ctl_data[:error][:total]) if raw_stat_ctl_data[:error]
27
33
  end
28
34
  end
29
35
  class Controls
data/lib/inspec/runner.rb CHANGED
@@ -60,9 +60,11 @@ module Inspec
60
60
  end
61
61
 
62
62
  if @conf[:waiver_file]
63
- waivers = @conf.delete(:waiver_file)
64
- @conf[:input_file] ||= []
65
- @conf[:input_file].concat waivers
63
+ @conf[:waiver_file].each do |file|
64
+ unless File.file?(file)
65
+ raise Inspec::Exceptions::WaiversFileDoesNotExist, "Waiver file #{file} does not exist."
66
+ end
67
+ end
66
68
  end
67
69
 
68
70
  # About reading inputs:
@@ -133,12 +135,20 @@ module Inspec
133
135
  all_controls.each do |rule|
134
136
  unless rule.nil?
135
137
  register_rule(rule)
136
- checks = ::Inspec::Rule.prepare_checks(rule)
137
- unless checks.empty?
138
+ total_checks = 0
139
+ control_describe_checks = ::Inspec::Rule.prepare_checks(rule)
140
+
141
+ examples = control_describe_checks.flat_map do |m, a, b|
142
+ get_check_example(m, a, b)
143
+ end.compact
144
+
145
+ examples.map { |example| total_checks += example.examples.count }
146
+
147
+ unless control_describe_checks.empty?
138
148
  # controls with empty tests are avoided
139
149
  # checks represent tests within control
140
- controls_count += 1
141
- control_checks_count_map[rule.to_s] = checks.count
150
+ controls_count += 1 if control_checks_count_map[rule.to_s].nil?
151
+ control_checks_count_map[rule.to_s] = control_checks_count_map[rule.to_s].to_i + total_checks
142
152
  end
143
153
  end
144
154
  end
@@ -158,7 +168,7 @@ module Inspec
158
168
  return if @conf["reporter"].nil?
159
169
 
160
170
  @conf["reporter"].each do |reporter|
161
- result = Inspec::Reporters.render(reporter, run_data)
171
+ result = Inspec::Reporters.render(reporter, run_data, @conf["enhanced_outcomes"])
162
172
  raise Inspec::ReporterError, "Error generating reporter '#{reporter[0]}'" if result == false
163
173
  end
164
174
  end
@@ -107,11 +107,11 @@ module Inspec
107
107
  stats = @formatter.results[:statistics][:controls]
108
108
  load_failures = @formatter.results[:profiles]&.select { |p| p[:status] == "failed" }&.any?
109
109
  skipped = @formatter.results.dig(:profiles, 0, :status) == "skipped"
110
- if stats[:failed][:total] == 0 && stats[:skipped][:total] == 0 && !skipped && !load_failures
110
+ if stats[:failed][:total] == 0 && stats[:skipped][:total] == 0 && !skipped && !load_failures && (stats[:error] && stats[:error][:total] == 0) # placed error count condition because of enhanced outcomes
111
111
  0
112
112
  elsif load_failures
113
113
  @conf["distinct_exit"] ? 102 : 1
114
- elsif stats[:failed][:total] > 0
114
+ elsif stats[:failed][:total] > 0 || (stats[:error] && stats[:error][:total] > 0)
115
115
  @conf["distinct_exit"] ? 100 : 1
116
116
  elsif stats[:skipped][:total] > 0 || skipped
117
117
  @conf["distinct_exit"] ? 101 : 0
@@ -196,6 +196,7 @@ module Inspec
196
196
  def configure_output
197
197
  RSpec.configuration.output_stream = $stdout
198
198
  @formatter = RSpec.configuration.add_formatter(Inspec::Formatters::Base)
199
+ @formatter.enhanced_outcomes = @conf.final_options["enhanced_outcomes"]
199
200
  RSpec.configuration.add_formatter(Inspec::Formatters::ShowProgress, $stderr) if @conf[:show_progress]
200
201
  set_optional_formatters
201
202
  RSpec.configuration.color = @conf["color"]
@@ -19,8 +19,8 @@ module Inspec
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
- "enum" => %w{passed failed skipped error},
23
- }, [], "The status of a control. Should be one of 'passed', 'failed', 'skipped', or 'error'.")
22
+ "enum" => %w{passed failed skipped},
23
+ }, [], "The status of a control. Should be one of 'passed', 'failed', or 'skipped'.")
24
24
 
25
25
  # Represents the statistics/result of a control"s execution
26
26
  CONTROL_RESULT = Primitives::SchemaType.new("Control Result", {
@@ -75,6 +75,36 @@ module Inspec
75
75
  },
76
76
  }, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT], "Describes a control and any findings it has.")
77
77
 
78
+ # Represents a control produced with enhanced outcomes option
79
+ ENHANCED_OUTCOME_CONTROL = Primitives::SchemaType.new("Exec JSON Control", {
80
+ "type" => "object",
81
+ "additionalProperties" => true,
82
+ "required" => %w{id title desc impact refs tags code source_location results},
83
+ "properties" => {
84
+ "id" => Primitives.desc(Primitives::STRING, "The id."),
85
+ "title" => Primitives.desc({ "type" => %w{string null} }, "The title - is nullable."), # Nullable string
86
+ "desc" => Primitives.desc({ "type" => %w{string null} }, "The description for the overarching control."),
87
+ "descriptions" => Primitives.desc(Primitives.array(CONTROL_DESCRIPTION.ref), "A set of additional descriptions. Example: the 'fix' text."),
88
+ "impact" => Primitives.desc(Primitives::IMPACT, "The impactfulness or severity."),
89
+ "status" => {
90
+ "enum" => %w{passed failed not_applicable not_reviewed error},
91
+ "description" => Primitives.desc(Primitives::STRING, "The enhanced outcome status of the control"),
92
+ },
93
+ "refs" => Primitives.desc(Primitives.array(Primitives::REFERENCE.ref), "The set of references to external documents."),
94
+ "tags" => Primitives.desc(Primitives::TAGS, "A set of tags - usually metadata."),
95
+ "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."),
96
+ "source_location" => Primitives.desc(Primitives::SOURCE_LOCATION.ref, "The explicit location of the control within the source code."),
97
+ "results" => Primitives.desc(Primitives.array(CONTROL_RESULT.ref), %q(
98
+ The set of all tests within the control and their results and findings. Example:
99
+ For Chef Inspec, if in the control's code we had the following:
100
+ describe sshd_config do
101
+ its('Port') { should cmp 22 }
102
+ end
103
+ The findings from this block would be appended to the results, as well as those of any other blocks within the control.
104
+ )),
105
+ },
106
+ }, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT], "Describes a control and any findings it has.")
107
+
78
108
  # Based loosely on https://docs.chef.io/inspec/profiles/ as of July 3, 2019
79
109
  # However, concessions were made to the reality of current reporters, specifically
80
110
  # with how description is omitted and version/inspec_version aren't as advertised online
@@ -112,6 +142,40 @@ module Inspec
112
142
  },
113
143
  }, [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.")
114
144
 
145
+ ENHANCED_OUTCOME_PROFILE = Primitives::SchemaType.new("Exec JSON Profile", {
146
+ "type" => "object",
147
+ "additionalProperties" => true,
148
+ "required" => %w{name sha256 supports attributes groups controls},
149
+ # Name is mandatory in inspec.yml.
150
+ # supports, controls, groups, and attributes are always present, even if empty
151
+ # sha256, status, status_message
152
+ "properties" => {
153
+ # These are provided in inspec.yml
154
+ "name" => Primitives.desc(Primitives::STRING, "The name - must be unique."),
155
+ "title" => Primitives.desc(Primitives::STRING, "The title - should be human readable."),
156
+ "maintainer" => Primitives.desc(Primitives::STRING, "The maintainer(s)."),
157
+ "copyright" => Primitives.desc(Primitives::STRING, "The copyright holder(s)."),
158
+ "copyright_email" => Primitives.desc(Primitives::STRING, "The email address or other contact information of the copyright holder(s)."),
159
+ "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."),
160
+ "parent_profile" => Primitives.desc(Primitives::STRING, "The name of the parent profile if the profile is a dependency of another."),
161
+ "license" => Primitives.desc(Primitives::STRING, "The copyright license. Example: the full text or the name, such as 'Apache License, Version 2.0'."),
162
+ "summary" => Primitives.desc(Primitives::STRING, "The summary. Example: the Security Technical Implementation Guide (STIG) header."),
163
+ "version" => Primitives.desc(Primitives::STRING, "The version of the profile."),
164
+ "supports" => Primitives.desc(Primitives.array(Primitives::SUPPORT.ref), "The set of supported platform targets."),
165
+ "description" => Primitives.desc(Primitives::STRING, "The description - should be more detailed than the summary."),
166
+ "inspec_version" => Primitives.desc(Primitives::STRING, "The version of Inspec."),
167
+
168
+ # These are generated at runtime, and all except status_message and skip_message are guaranteed
169
+ "sha256" => Primitives.desc(Primitives::STRING, "The checksum of the profile."),
170
+ "status" => Primitives.desc(Primitives::STRING, "The status. Example: loaded."), # enum? loaded, failed, skipped
171
+ "status_message" => Primitives.desc(Primitives::STRING, "The reason for the status. Example: why it was skipped or failed to load."),
172
+ "skip_message" => Primitives.desc(Primitives::STRING, "The reason for skipping if it was skipped."), # Deprecated field - status_message should be used instead.
173
+ "controls" => Primitives.desc(Primitives.array(CONTROL.ref), "The set of controls including any findings."),
174
+ "groups" => Primitives.desc(Primitives.array(Primitives::CONTROL_GROUP.ref), "A set of descriptions for the control groups. Example: the ids of the controls."),
175
+ "attributes" => Primitives.desc(Primitives.array(Primitives::INPUT), "The input(s) or attribute(s) used in the run."),
176
+ },
177
+ }, [ENHANCED_OUTCOME_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.")
178
+
115
179
  # Result of exec json. Top level value
116
180
  # TODO: Include the format of top level controls. This was omitted for lack of sufficient examples
117
181
  OUTPUT = Primitives::SchemaType.new("Exec JSON Output", {
@@ -125,6 +189,18 @@ module Inspec
125
189
  "version" => Primitives.desc(Primitives::STRING, "Version number of the tool that generated the findings. Example: '4.18.108' is a version of Chef InSpec."),
126
190
  },
127
191
  }, [Primitives::PLATFORM, PROFILE, Primitives::STATISTICS], "The top level value containing all of the results.")
192
+
193
+ ENHANCED_OUTCOME_OUTPUT = Primitives::SchemaType.new("Exec JSON Output", {
194
+ "type" => "object",
195
+ "additionalProperties" => true,
196
+ "required" => %w{platform profiles statistics version},
197
+ "properties" => {
198
+ "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."),
199
+ "profiles" => Primitives.desc(Primitives.array(PROFILE.ref), "Information on the run(s) from the tool that generated the findings. Example: the findings."),
200
+ "statistics" => Primitives.desc(Primitives::STATISTICS.ref, "Statistics for the run(s) from the tool that generated the findings. Example: the runtime duration."),
201
+ "version" => Primitives.desc(Primitives::STRING, "Version number of the tool that generated the findings. Example: '4.18.108' is a version of Chef InSpec."),
202
+ },
203
+ }, [Primitives::PLATFORM, ENHANCED_OUTCOME_PROFILE, Primitives::STATISTICS], "The top level value containing all of the results.")
128
204
  end
129
205
  end
130
206
  end
@@ -30,6 +30,8 @@ module Inspec
30
30
  "profile-json" => OutputSchema.finalize(Schema::ProfileJson::PROFILE),
31
31
  "exec-json" => OutputSchema.finalize(Schema::ExecJson::OUTPUT),
32
32
  "exec-jsonmin" => OutputSchema.finalize(Schema::ExecJsonMin::OUTPUT),
33
+ "profile-json-enhanced-outcomes" => OutputSchema.finalize(Schema::ProfileJson::ENHANCED_OUTCOME_PROFILE),
34
+ "exec-json-enhanced-outcomes" => OutputSchema.finalize(Schema::ExecJson::ENHANCED_OUTCOME_OUTPUT),
33
35
  "platforms" => PLATFORMS,
34
36
  }.freeze
35
37
 
@@ -37,7 +39,8 @@ module Inspec
37
39
  LIST.keys
38
40
  end
39
41
 
40
- def self.json(name)
42
+ def self.json(name, opts)
43
+ name += "-enhanced-outcomes" if opts["enhanced_outcomes"]
41
44
  if !LIST.key?(name)
42
45
  raise("Cannot find schema #{name.inspect}.")
43
46
  elsif LIST[name].is_a?(Proc)
@@ -31,6 +31,28 @@ module Inspec
31
31
  },
32
32
  }, [CONTROL_DESCRIPTIONS, Primitives::REFERENCE, Primitives::SOURCE_LOCATION], "The set of all tests within the control.")
33
33
 
34
+ # Represents a control with enhanced outcomes status information
35
+ ENHANCED_OUTCOME_CONTROL = Primitives::SchemaType.new("Profile JSON Control", {
36
+ "type" => "object",
37
+ "additionalProperties" => true,
38
+ "required" => %w{id title desc impact tags code},
39
+ "properties" => {
40
+ "id" => Primitives.desc(Primitives::STRING, "The id."),
41
+ "title" => Primitives.desc({ "type" => %w{string null} }, "The title - is nullable."),
42
+ "desc" => Primitives.desc({ "type" => %w{string null} }, "The description for the overarching control."),
43
+ "descriptions" => Primitives.desc(CONTROL_DESCRIPTIONS.ref, "A set of additional descriptions. Example: the 'fix' text."),
44
+ "impact" => Primitives.desc(Primitives::IMPACT, "The impactfulness or severity."),
45
+ "status" => {
46
+ "enum" => %w{passed failed not_applicable not_reviewed error},
47
+ "description" => Primitives.desc(Primitives::STRING, "The enhanced outcome status of the control"),
48
+ },
49
+ "refs" => Primitives.desc(Primitives.array(Primitives::REFERENCE.ref), "The set of references to external documents."),
50
+ "tags" => Primitives.desc(Primitives::TAGS, "A set of tags - usually metadata."),
51
+ "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."),
52
+ "source_location" => Primitives.desc(Primitives::SOURCE_LOCATION.ref, "The explicit location of the control within the source code."),
53
+ },
54
+ }, [CONTROL_DESCRIPTIONS, Primitives::REFERENCE, Primitives::SOURCE_LOCATION], "The set of all tests within the control.")
55
+
34
56
  # A profile that has not been run.
35
57
  PROFILE = Primitives::SchemaType.new("Profile JSON Profile", {
36
58
  "type" => "object",
@@ -55,6 +77,30 @@ module Inspec
55
77
  "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."), # Can have depends, but NOT a parentprofile
56
78
  },
57
79
  }, [Primitives::SUPPORT, CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::GENERATOR], "Information on the set of controls that can be assessed. Example: it can include the name of the Inspec profile.")
80
+
81
+ ENHANCED_OUTCOME_PROFILE = Primitives::SchemaType.new("Profile JSON Profile", {
82
+ "type" => "object",
83
+ "additionalProperties" => true, # Anything in the yaml will be put in here. LTTODO: Make this stricter!
84
+ "required" => %w{name supports controls groups sha256},
85
+ "properties" => {
86
+ "name" => Primitives.desc(Primitives::STRING, "The name - must be unique."),
87
+ "supports" => Primitives.desc(Primitives.array(Primitives::SUPPORT.ref), "The set of supported platform targets."),
88
+ "controls" => Primitives.desc(Primitives.array(CONTROL.ref), "The set of controls - contains no findings as the assessment has not yet occurred."),
89
+ "groups" => Primitives.desc(Primitives.array(Primitives::CONTROL_GROUP.ref), "A set of descriptions for the control groups. Example: the ids of the controls."),
90
+ "inputs" => Primitives.desc(Primitives.array(Primitives::INPUT), "The input(s) or attribute(s) used to be in the run."),
91
+ "sha256" => Primitives.desc(Primitives::STRING, "The checksum of the profile."),
92
+ "status" => Primitives.desc(Primitives::STRING, "The status. Example: skipped."),
93
+ "generator" => Primitives.desc(Primitives::GENERATOR.ref, "The tool that generated this file. Example: Chef Inspec."),
94
+ "version" => Primitives.desc(Primitives::STRING, "The version of the profile."),
95
+
96
+ # Other properties possible in inspec docs, but that aren"t guaranteed
97
+ "title" => Primitives.desc(Primitives::STRING, "The title - should be human readable."),
98
+ "maintainer" => Primitives.desc(Primitives::STRING, "The maintainer(s)."),
99
+ "copyright" => Primitives.desc(Primitives::STRING, "The copyright holder(s)."),
100
+ "copyright_email" => Primitives.desc(Primitives::STRING, "The email address or other contract information of the copyright holder(s)."),
101
+ "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."), # Can have depends, but NOT a parentprofile
102
+ },
103
+ }, [Primitives::SUPPORT, ENHANCED_OUTCOME_CONTROL, Primitives::CONTROL_GROUP, Primitives::DEPENDENCY, Primitives::GENERATOR], "Information on the set of controls that can be assessed. Example: it can include the name of the Inspec profile.")
58
104
  end
59
105
  end
60
106
  end
data/lib/inspec/schema.rb CHANGED
@@ -111,6 +111,43 @@ module Inspec
111
111
  },
112
112
  }.freeze
113
113
 
114
+ CONTROL_ENHANCED_OUTCOME = {
115
+ "type" => "object",
116
+ "additionalProperties" => false,
117
+ "properties" => {
118
+ "id" => { "type" => "string" },
119
+ "title" => { "type" => %w{string null} },
120
+ "desc" => { "type" => %w{string null} },
121
+ "descriptions" => { "type" => %w{array} },
122
+ "impact" => { "type" => "number" },
123
+ "status" => {
124
+ "enum" => %w{passed failed not_applicable not_reviewed error},
125
+ "description" => Primitives.desc(Primitives::STRING, "The enhanced outcome status of the control"),
126
+ },
127
+ "refs" => REFS,
128
+ "tags" => TAGS,
129
+ "code" => { "type" => "string" },
130
+ "source_location" => {
131
+ "type" => "object",
132
+ "properties" => {
133
+ "ref" => { "type" => "string" },
134
+ "line" => { "type" => "number" },
135
+ },
136
+ },
137
+ "results" => { "type" => "array", "items" => RESULT },
138
+ "waiver_data" => {
139
+ "type" => "object",
140
+ "properties" => {
141
+ "skipped_due_to_waiver" => { "type" => "string" },
142
+ "run" => { "type" => "boolean" },
143
+ "message" => { "type" => "string" },
144
+ "expiration_date" => { "type" => "string" },
145
+ "justification" => { "type" => "string" },
146
+ },
147
+ },
148
+ },
149
+ }.freeze
150
+
114
151
  SUPPORTS = {
115
152
  "type" => "object",
116
153
  "additionalProperties" => false,
@@ -173,6 +210,45 @@ module Inspec
173
210
  },
174
211
  }.freeze
175
212
 
213
+ PROFILE_ENHANCED_OUTCOME = {
214
+ "type" => "object",
215
+ "additionalProperties" => false,
216
+ "properties" => {
217
+ "name" => { "type" => "string" },
218
+ "version" => { "type" => "string", "optional" => true },
219
+ "sha256" => { "type" => "string", "optional" => false },
220
+
221
+ "title" => { "type" => "string", "optional" => true },
222
+ "maintainer" => { "type" => "string", "optional" => true },
223
+ "copyright" => { "type" => "string", "optional" => true },
224
+ "copyright_email" => { "type" => "string", "optional" => true },
225
+ "license" => { "type" => "string", "optional" => true },
226
+ "summary" => { "type" => "string", "optional" => true },
227
+ "status" => { "type" => "string", "optional" => false },
228
+ "status_message" => { "type" => "string", "optional" => true },
229
+ # skip_message is deprecated, status_message should be used to store the reason for skipping
230
+ "skip_message" => { "type" => "string", "optional" => true },
231
+
232
+ "supports" => {
233
+ "type" => "array",
234
+ "items" => SUPPORTS,
235
+ "optional" => true,
236
+ },
237
+ "controls" => {
238
+ "type" => "array",
239
+ "items" => CONTROL_ENHANCED_OUTCOME,
240
+ },
241
+ "groups" => {
242
+ "type" => "array",
243
+ "items" => CONTROL_GROUP,
244
+ },
245
+ "attributes" => { # TODO: rename to inputs, refs #3802
246
+ "type" => "array",
247
+ # TODO: more detailed specification needed
248
+ },
249
+ },
250
+ }.freeze
251
+
176
252
  EXEC_JSON = {
177
253
  "type" => "object",
178
254
  "additionalProperties" => false,
@@ -187,6 +263,20 @@ module Inspec
187
263
  },
188
264
  }.freeze
189
265
 
266
+ EXEC_JSON_ENHANCED_OUTCOME = {
267
+ "type" => "object",
268
+ "additionalProperties" => false,
269
+ "properties" => {
270
+ "platform" => PLATFORM,
271
+ "profiles" => {
272
+ "type" => "array",
273
+ "items" => PROFILE_ENHANCED_OUTCOME,
274
+ },
275
+ "statistics" => STATISTICS,
276
+ "version" => { "type" => "string" },
277
+ },
278
+ }.freeze
279
+
190
280
  MIN_CONTROL = {
191
281
  "type" => "object",
192
282
  "additionalProperties" => false,
@@ -228,6 +318,7 @@ module Inspec
228
318
  LIST = {
229
319
  "exec-json" => EXEC_JSON,
230
320
  "exec-jsonmin" => EXEC_JSONMIN,
321
+ "exec-json-enhanced-outcome" => EXEC_JSON_ENHANCED_OUTCOME,
231
322
  "platforms" => PLATFORMS,
232
323
  }.freeze
233
324
 
@@ -16,7 +16,13 @@ module Secrets
16
16
 
17
17
  # array of yaml file paths
18
18
  def initialize(target)
19
- @inputs = ::YAML.load_file(target)
19
+ # Ruby 3.1 treats YAML load as a dangerous operation by default, requiring us to declare date and time classes as permitted
20
+ # It's not a valid option in 3.0.x
21
+ if Gem.ruby_version >= Gem::Version.new("3.1.0")
22
+ @inputs = ::YAML.load_file(target, permitted_classes: [Date, Time])
23
+ else
24
+ @inputs = ::YAML.load_file(target)
25
+ end
20
26
 
21
27
  if @inputs == false || !@inputs.is_a?(Hash)
22
28
  Inspec::Log.warn("#{self.class} unable to parse #{target}: invalid YAML or contents is not a Hash")
data/lib/inspec/ui.rb CHANGED
@@ -31,6 +31,7 @@ module Inspec
31
31
  EXIT_PLUGIN_ERROR = 2
32
32
  EXIT_FATAL_DEPRECATION = 3
33
33
  EXIT_GEM_DEPENDENCY_LOAD_ERROR = 4
34
+ EXIT_BAD_SIGNATURE = 5
34
35
  EXIT_LICENSE_NOT_ACCEPTED = 172
35
36
  EXIT_FAILED_TESTS = 100
36
37
  EXIT_SKIPPED_TESTS = 101
@@ -5,4 +5,12 @@ module Converter
5
5
  val = val.to_i if val =~ /^\d+$/
6
6
  val
7
7
  end
8
+
9
+ def self.to_boolean(value)
10
+ if ["true", "True", "TRUE", true, "yes", "y", "YES", "Y"].include? value
11
+ true
12
+ elsif ["false", "False", "FALSE", false, "no", "n", "NO", "N"].include? value
13
+ false
14
+ end
15
+ end
8
16
  end
@@ -0,0 +1,24 @@
1
+ require "inspec/resources/command"
2
+
3
+ module Inspec
4
+ module Utils
5
+ module Podman
6
+ def podman_running?
7
+ inspec.command("podman version").exit_status == 0
8
+ end
9
+
10
+ # Generates the template in this format using labels hash: "\"id\": {{json .ID}}, \"name\": {{json .Name}}",
11
+ def generate_go_template(labels)
12
+ (labels.map { |k, v| "\"#{k}\": {{json .#{v}}}" }).join(", ")
13
+ end
14
+
15
+ def parse_command_output(output)
16
+ require "json" unless defined?(JSON)
17
+ JSON.parse(output)
18
+ rescue JSON::ParserError => _e
19
+ warn "Could not parse the command output"
20
+ {}
21
+ end
22
+ end
23
+ end
24
+ end