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
@@ -0,0 +1,127 @@
1
+ require "base64" unless defined?(Base64)
2
+ require "openssl" unless defined?(OpenSSL)
3
+ require "set" unless defined?(Set)
4
+ require "uri" unless defined?(URI)
5
+
6
+ module Inspec
7
+ class IafFile
8
+ KEY_ALG = OpenSSL::PKey::RSA
9
+ INSPEC_PROFILE_VERSION_1 = "INSPEC-PROFILE-1".freeze
10
+ INSPEC_PROFILE_VERSION_2 = "INSPEC-PROFILE-2".freeze
11
+
12
+ ARTIFACT_DIGEST = OpenSSL::Digest::SHA512
13
+ ARTIFACT_DIGEST_NAME = "SHA512".freeze
14
+
15
+ VALID_PROFILE_VERSIONS = Set.new [INSPEC_PROFILE_VERSION_1, INSPEC_PROFILE_VERSION_2]
16
+ VALID_PROFILE_DIGESTS = Set.new [ARTIFACT_DIGEST_NAME]
17
+
18
+ def self.find_validation_key(keyname)
19
+ [
20
+ ".",
21
+ File.join(Inspec.config_dir, "keys"),
22
+ File.join(Inspec.src_root, "etc", "keys"),
23
+ ].each do |path|
24
+ filename = File.join(path, "#{keyname}.pem.pub")
25
+ return filename if File.exist?(filename)
26
+ rescue ArgumentError => e
27
+ puts e
28
+ raise Inspec::Exceptions::ProfileValidationKeyNotFound.new("Validation key #{keyname} not found")
29
+ end
30
+
31
+ # Retry if we can fetch it from github
32
+ return find_validation_key(keyname) if fetch_validation_key_from_github(keyname)
33
+
34
+ raise Inspec::Exceptions::ProfileValidationKeyNotFound.new("Validation key #{keyname} not found")
35
+ end
36
+
37
+ def self.find_signing_key(keyname)
38
+ [".", File.join(Inspec.config_dir, "keys")].each do |path|
39
+ filename = File.join(path, "#{keyname}.pem.key")
40
+ return filename if File.exist?(filename)
41
+ end
42
+ raise Inspec::Exceptions::ProfileSigningKeyNotFound.new("Signing key #{keyname} not found")
43
+ end
44
+
45
+ def self.fetch_validation_key_from_github(keyname)
46
+ URI.open("https://raw.githubusercontent.com/inspec/inspec/main/etc/keys/#{keyname}.pem.pub") do |r|
47
+ puts "Fetching validation key '#{keyname}' from github"
48
+ dir = File.join(Inspec.config_dir, "keys")
49
+ FileUtils.mkdir_p dir
50
+ key_file = File.join(dir, "#{keyname}.pem.pub")
51
+ File.open(key_file, "w") do |f|
52
+ r.each_line do |line|
53
+ f.puts line
54
+ end
55
+ end
56
+ end
57
+ true
58
+ rescue OpenURI::HTTPError
59
+ false
60
+ end
61
+
62
+ attr_reader :key_name, :version
63
+
64
+ def initialize(path)
65
+ @path = path
66
+ @key_name = nil
67
+ end
68
+
69
+ def valid?
70
+ header = []
71
+ valid = true
72
+ f = File.open(@path, "rb")
73
+ @version = f.readline.strip!
74
+ if version == INSPEC_PROFILE_VERSION_1
75
+ header << version
76
+ header << f.readline.strip!
77
+ header << f.readline.strip!
78
+
79
+ file_sig = ""
80
+ # the signature is multi-line
81
+ while (line = f.readline) != "\n"
82
+ file_sig += line
83
+ end
84
+ header << file_sig.strip!
85
+ f.close
86
+
87
+ f = File.open(@path, "rb")
88
+ while f.readline != "\n" do end
89
+ content = f.read
90
+ f.close
91
+ elsif version == INSPEC_PROFILE_VERSION_2
92
+ header << version
93
+ header << f.readline.strip!
94
+ content = f.read
95
+ f.close
96
+
97
+ header_content = content.unpack("h*").pack("H*")
98
+ header << header_content.slice(0, 100).rstrip
99
+ header << header_content.slice(100, 20).rstrip
100
+ header << header_content.slice(120, 370).rstrip + "\n" # \n at the end is require in this field
101
+ content = content.slice(490, content.length).lstrip
102
+ else
103
+ valid = false
104
+ end
105
+
106
+ @key_name = header[2]
107
+ validation_key_path = Inspec::IafFile.find_validation_key(@key_name)
108
+
109
+ unless valid_header?(header)
110
+ valid = false
111
+ end
112
+
113
+ verification_key = KEY_ALG.new File.read validation_key_path
114
+ signature = Base64.decode64(header[4])
115
+ digest = ARTIFACT_DIGEST.new
116
+ unless verification_key.verify digest, signature, content
117
+ valid = false
118
+ end
119
+
120
+ valid
121
+ end
122
+
123
+ def valid_header?(header)
124
+ VALID_PROFILE_VERSIONS.member?(header[0]) && VALID_PROFILE_DIGESTS.member?(header[3])
125
+ end
126
+ end
127
+ end
@@ -178,7 +178,19 @@ module Inspec::Plugin::V2
178
178
  # TODO: enforce first-level version pinning
179
179
  plugin_deps = [Gem::Dependency.new(plugin_gem_name.to_s, version_constraint)]
180
180
  managed_gem_set = Gem::Resolver::VendorSet.new
181
- list_managed_gems.each { |spec| managed_gem_set.add_vendor_gem(spec.name, spec.gem_dir) }
181
+
182
+ list_managed_gems.each do |spec|
183
+ if Gem::Specification.load spec.gem_dir
184
+ managed_gem_set.add_vendor_gem(spec.name, spec.gem_dir)
185
+ else
186
+ # In case of invalid gemspec as mentioned in this PR https://github.com/brianmario/yajl-ruby/pull/223
187
+ # the add_vendor_gem breaks. So this is patch to fix the loading issue.
188
+ # Horribly, chdir to gemspec path to honor . in gemspec
189
+ Dir.chdir(spec.gem_dir) do |dir|
190
+ managed_gem_set.add_vendor_gem(spec.name, spec.gem_dir)
191
+ end
192
+ end
193
+ end
182
194
 
183
195
  # TODO: Next two lines merge our managed gems with the other gems available
184
196
  # in our "local universe" - which may be the system, or it could be in a Bundler microcosm,
@@ -278,7 +290,12 @@ module Inspec::Plugin::V2
278
290
  when :user_gem
279
291
  status.entry_point = status.name.to_s
280
292
  status.version = plugin_entry[:version]
281
- status.description = fetch_plugin_specs(status.name.to_s)&.summary
293
+ # Fetch the summary of the gem from local gemspec file instead of remote call using Gem::SpecFetcher.fetcher.
294
+ unless plugin_entry[:version].nil? # safe check very rare case.
295
+ version_string = plugin_entry[:version].gsub(/[=,~,>,<]/, "").strip
296
+ plugin_name_with_version = "#{status.name}-#{version_string}"
297
+ status.description = fetch_gemspec(File.join(plugin_gem_path, "gems", plugin_name_with_version, "/", status.name.to_s + ".gemspec"))&.summary
298
+ end
282
299
  when :path
283
300
  status.entry_point = plugin_entry[:installation_path]
284
301
  end
@@ -287,12 +304,6 @@ module Inspec::Plugin::V2
287
304
  end
288
305
  end
289
306
 
290
- def fetch_plugin_specs(plugin_name)
291
- fetcher = Gem::SpecFetcher.fetcher
292
- plugin_dependency = Gem::Dependency.new(plugin_name)
293
- fetcher.spec_for_dependency(plugin_dependency).flatten.first
294
- end
295
-
296
307
  def fixup_train_plugin_status(status)
297
308
  status.api_generation = :'train-1'
298
309
  if status.installation_type == :user_gem
@@ -7,6 +7,7 @@ module Inspec::Plugin::V2::PluginType
7
7
  include Inspec::Utils::RunDataFilters
8
8
 
9
9
  attr_reader :run_data
10
+ attr_accessor :enhanced_outcomes
10
11
 
11
12
  def initialize(config)
12
13
  @config = config
@@ -11,6 +11,7 @@ module Inspec::Plugin::V2::PluginType
11
11
  @running_controls_list = []
12
12
  @control_checks_count_map = {}
13
13
  @controls_count = nil
14
+ @notifications = {}
14
15
  end
15
16
 
16
17
  private
@@ -49,5 +50,58 @@ module Inspec::Plugin::V2::PluginType
49
50
  @control_checks_count_map = RSpec.configuration.formatters.grep(Inspec::Formatters::Base).first.get_control_checks_count_map
50
51
  end
51
52
  end
53
+
54
+ def enhanced_outcomes
55
+ @enhanced_outcomes ||= RSpec.configuration.formatters.grep(Inspec::Formatters::Base).first.enhanced_outcomes
56
+ end
57
+
58
+ def add_enhanced_outcomes(control_id)
59
+ if control_has_error(@notifications[control_id])
60
+ "error"
61
+ elsif control_has_impact_zero(@notifications[control_id])
62
+ "not_applicable"
63
+ elsif control_has_all_tests_skipped(@notifications[control_id])
64
+ "not_reviewed"
65
+ elsif control_has_any_tests_failed(@notifications[control_id])
66
+ "failed"
67
+ else
68
+ "passed"
69
+ end
70
+ end
71
+
72
+ def control_has_error(notifications)
73
+ notifications.any? do |notification_data|
74
+ notification, _status = notification_data
75
+ !notification.example.exception.nil? && !(notification.example.exception.is_a? RSpec::Expectations::ExpectationNotMetError) && !notification.example.exception.backtrace.nil? && (!notification.description.include? "No-op") # No-op exception occurs in case of not_applicable_if
76
+ end
77
+ end
78
+
79
+ def control_has_all_tests_skipped(notifications)
80
+ notifications.all? do |notification_data|
81
+ _notification, status = notification_data
82
+ status == "skipped"
83
+ end
84
+ end
85
+
86
+ def control_has_any_tests_failed(notifications)
87
+ notifications.any? do |notification_data|
88
+ _notification, status = notification_data
89
+ status == "failed"
90
+ end
91
+ end
92
+
93
+ def control_has_impact_zero(notifications)
94
+ notification_data = notifications.first
95
+ notification_impact = notification_data.first.example.metadata[:impact]
96
+ notification_data && !notification_impact.nil? && notification_impact.to_f == 0.0
97
+ end
98
+
99
+ def collect_notifications(notification, control_id, status)
100
+ if @notifications[control_id].nil?
101
+ @notifications[control_id] = [[notification, status]]
102
+ else
103
+ @notifications[control_id].push([notification, status])
104
+ end
105
+ end
52
106
  end
53
107
  end
@@ -350,22 +350,24 @@ module Inspec
350
350
  def load_libraries
351
351
  return @runner_context if @libraries_loaded
352
352
 
353
- locked_dependencies.dep_list.each_with_index do |(_name, dep), i|
353
+ locked_dependencies.dep_list.each_with_index do |(_name, dep), index|
354
354
  d = dep.profile
355
355
  # this will force a dependent profile load so we are only going to add
356
356
  # this metadata if the parent profile is supported.
357
357
  if @supports_platform && !d.supports_platform?
358
358
  # since ruby 1.9 hashes are ordered so we can just use index values here
359
359
  # TODO: NO! this is a violation of encapsulation to an extreme
360
- metadata.dependencies[i][:status] = "skipped"
361
- msg = "Skipping profile: '#{d.name}' on unsupported platform: '#{d.backend.platform.name}/#{d.backend.platform.release}'."
362
- metadata.dependencies[i][:status_message] = msg
363
- metadata.dependencies[i][:skip_message] = msg # Repeat as skip_message for backward compatibility
360
+ if metadata.dependencies[index]
361
+ metadata.dependencies[index][:status] = "skipped"
362
+ msg = "Skipping profile: '#{d.name}' on unsupported platform: '#{d.backend.platform.name}/#{d.backend.platform.release}'."
363
+ metadata.dependencies[index][:status_message] = msg
364
+ metadata.dependencies[index][:skip_message] = msg # Repeat as skip_message for backward compatibility
365
+ end
364
366
  next
365
- elsif metadata.dependencies[i]
367
+ elsif metadata.dependencies[index]
366
368
  # Currently wrapper profiles will load all dependencies, and then we
367
369
  # load them again when we dive down. This needs to be re-done.
368
- metadata.dependencies[i][:status] = "loaded"
370
+ metadata.dependencies[index][:status] = "loaded"
369
371
  end
370
372
 
371
373
  # rubocop:disable Layout/ExtraSpacing
@@ -746,6 +748,14 @@ module Inspec
746
748
  @source_reader.target.files
747
749
  end
748
750
 
751
+ def readme
752
+ @source_reader.readme&.values&.first
753
+ end
754
+
755
+ def metadata_src
756
+ @source_reader.metadata_src
757
+ end
758
+
749
759
  #
750
760
  # TODO(ssd): Relative path handling really needs to be carefully
751
761
  # thought through, especially with respect to relative paths in
@@ -5,6 +5,7 @@ module Inspec::Reporters
5
5
  include Inspec::Utils::RunDataFilters
6
6
 
7
7
  attr_reader :run_data
8
+ attr_accessor :enhanced_outcomes
8
9
 
9
10
  def initialize(config)
10
11
  @config = config
@@ -9,6 +9,9 @@ module Inspec::Reporters
9
9
  "passed" => "\033[0;1;32m",
10
10
  "skipped" => "\033[0;37m",
11
11
  "reset" => "\033[0m",
12
+ "error" => "\033[34m",
13
+ "not_applicable" => "\033[36m",
14
+ "not_reviewed" => "\033[33m",
12
15
  }.freeze
13
16
 
14
17
  # Most currently available Windows terminals have poor support
@@ -18,6 +21,9 @@ module Inspec::Reporters
18
21
  "skipped" => "[SKIP]",
19
22
  "passed" => "[PASS]",
20
23
  "unknown" => "[UNKN]",
24
+ "error" => "[ERR]",
25
+ "not_applicable" => "[N/A]",
26
+ "not_reviewed" => "[N/R]",
21
27
  }.freeze
22
28
  else
23
29
  # Extended colors for everyone else
@@ -26,6 +32,9 @@ module Inspec::Reporters
26
32
  "passed" => "\033[38;5;41m",
27
33
  "skipped" => "\033[38;5;247m",
28
34
  "reset" => "\033[0m",
35
+ "error" => "\033[0;38;5;21m",
36
+ "not_applicable" => "\033[0;38;5;117m",
37
+ "not_reviewed" => "\033[0;38;5;214m",
29
38
  }.freeze
30
39
 
31
40
  # Groovy UTF-8 characters for everyone else...
@@ -35,6 +44,9 @@ module Inspec::Reporters
35
44
  "skipped" => "↺",
36
45
  "passed" => "✔",
37
46
  "unknown" => "?",
47
+ "error" => "ERR",
48
+ "not_applicable" => "N/A",
49
+ "not_reviewed" => "N/R",
38
50
  }.freeze
39
51
  end
40
52
 
@@ -63,7 +75,11 @@ module Inspec::Reporters
63
75
  end
64
76
 
65
77
  output("")
66
- print_profile_summary
78
+ if enhanced_outcomes
79
+ print_control_outcomes_summary
80
+ else
81
+ print_profile_summary
82
+ end
67
83
  print_tests_summary
68
84
  end
69
85
 
@@ -88,6 +104,7 @@ module Inspec::Reporters
88
104
  def print_standard_control_results(profile)
89
105
  standard_controls_from_profile(profile).each do |control_from_profile|
90
106
  control = Control.new(control_from_profile)
107
+ control.enhanced_outcomes = enhanced_outcomes
91
108
  next if control.results.nil?
92
109
 
93
110
  output(format_control_header(control))
@@ -122,7 +139,7 @@ module Inspec::Reporters
122
139
  end
123
140
 
124
141
  def format_control_header(control)
125
- impact = control.impact_string
142
+ impact = enhanced_outcomes ? control.impact_string_for_enhanced_outcomes : control.impact_string
126
143
  format_message(
127
144
  color: impact,
128
145
  indicator: impact,
@@ -292,6 +309,68 @@ module Inspec::Reporters
292
309
  }
293
310
  end
294
311
 
312
+ def control_outcomes_summary
313
+ failed = 0
314
+ passed = 0
315
+ error = 0
316
+ not_reviewed = 0
317
+ not_applicable = 0
318
+
319
+ all_unique_controls.each do |control|
320
+ next if control[:status].empty?
321
+
322
+ if control[:status] == "failed"
323
+ failed += 1
324
+ elsif control[:status] == "error"
325
+ error += 1
326
+ elsif control[:status] == "not_reviewed"
327
+ not_reviewed += 1
328
+ elsif control[:status] == "not_applicable"
329
+ not_applicable += 1
330
+ else
331
+ passed += 1
332
+ end
333
+ end
334
+
335
+ total = failed + passed + error + not_reviewed + not_applicable
336
+
337
+ {
338
+ "total" => total,
339
+ "failed" => failed,
340
+ "passed" => passed,
341
+ "error" => error,
342
+ "not_reviewed" => not_reviewed,
343
+ "not_applicable" => not_applicable,
344
+ }
345
+ end
346
+
347
+ def print_control_outcomes_summary
348
+ summary = control_outcomes_summary
349
+ return unless summary["total"] > 0
350
+
351
+ success_str = summary["passed"] == 1 ? "1 successful control" : "#{summary["passed"]} successful controls"
352
+ failed_str = summary["failed"] == 1 ? "1 control failure" : "#{summary["failed"]} control failures"
353
+ error_str = summary["error"] == 1 ? "1 control has error" : "#{summary["error"]} controls have error"
354
+ not_rev_str = summary["not_reviewed"] == 1 ? "1 control not reviewed" : "#{summary["not_reviewed"]} controls not reviewed"
355
+ not_app_str = summary["not_applicable"] == 1 ? "1 control not applicable" : "#{summary["not_applicable"]} controls not applicable"
356
+
357
+ success_color = summary["passed"] > 0 ? "passed" : "no_color"
358
+ failed_color = summary["failed"] > 0 ? "failed" : "no_color"
359
+ error_color = summary["error"] > 0 ? "error" : "no_color"
360
+ not_rev_color = summary["not_reviewed"] > 0 ? "not_reviewed" : "no_color"
361
+ not_app_color = summary["not_applicable"] > 0 ? "not_applicable" : "no_color"
362
+
363
+ s = format(
364
+ "Profile Summary: %s, %s, %s, %s, %s",
365
+ format_with_color(success_color, success_str),
366
+ format_with_color(failed_color, failed_str),
367
+ format_with_color(not_rev_color, not_rev_str),
368
+ format_with_color(not_app_color, not_app_str),
369
+ format_with_color(error_color, error_str)
370
+ )
371
+ output(s) if summary["total"] > 0
372
+ end
373
+
295
374
  def print_profile_summary
296
375
  summary = profile_summary
297
376
  return unless summary["total"] > 0
@@ -350,6 +429,7 @@ module Inspec::Reporters
350
429
 
351
430
  class Control
352
431
  attr_reader :data
432
+ attr_accessor :enhanced_outcomes
353
433
 
354
434
  def initialize(control_hash)
355
435
  @data = control_hash
@@ -379,6 +459,10 @@ module Inspec::Reporters
379
459
  id.start_with?("(generated from ")
380
460
  end
381
461
 
462
+ def status
463
+ data[:status]
464
+ end
465
+
382
466
  def title_for_report
383
467
  # if this is an anonymous control, just grab the resource title from any result entry
384
468
  return results.first[:resource_title] if anonymous?
@@ -392,10 +476,17 @@ module Inspec::Reporters
392
476
  # append a failure summary if appropriate.
393
477
  title_for_report += " (#{failure_count} failed)" if failure_count > 0
394
478
  title_for_report += " (#{skipped_count} skipped)" if skipped_count > 0
395
-
396
479
  title_for_report
397
480
  end
398
481
 
482
+ def impact_string_for_enhanced_outcomes
483
+ if impact.nil?
484
+ "unknown"
485
+ else
486
+ status
487
+ end
488
+ end
489
+
399
490
  def impact_string
400
491
  if anonymous?
401
492
  nil
@@ -114,7 +114,7 @@ module Inspec::Reporters
114
114
 
115
115
  def profile_controls(profile)
116
116
  (profile[:controls] || []).map { |c|
117
- {
117
+ control_hash = {
118
118
  id: c[:id],
119
119
  title: c[:title],
120
120
  desc: c.dig(:descriptions, :default),
@@ -130,6 +130,8 @@ module Inspec::Reporters
130
130
  waiver_data: c[:waiver_data] || {},
131
131
  results: profile_results(c),
132
132
  }
133
+ control_hash.merge!({ status: c[:status] }) if enhanced_outcomes
134
+ control_hash
133
135
  }
134
136
  end
135
137
 
@@ -3,7 +3,9 @@ require "yaml"
3
3
  module Inspec::Reporters
4
4
  class Yaml < Base
5
5
  def render
6
- output(Inspec::Reporters::Json.new({ run_data: run_data }).report.to_yaml, false)
6
+ json_reporter_obj = Inspec::Reporters::Json.new({ run_data: run_data })
7
+ json_reporter_obj.enhanced_outcomes = enhanced_outcomes
8
+ output(json_reporter_obj.report.to_yaml, false)
7
9
  end
8
10
 
9
11
  def report
@@ -7,7 +7,7 @@ require "inspec/reporters/yaml"
7
7
 
8
8
  module Inspec::Reporters
9
9
  # rubocop:disable Metrics/CyclomaticComplexity
10
- def self.render(reporter, run_data)
10
+ def self.render(reporter, run_data, enhanced_outcomes = false)
11
11
  name, config = reporter.dup
12
12
  config[:run_data] = run_data
13
13
  case name
@@ -29,6 +29,7 @@ module Inspec::Reporters
29
29
  activator.activate!
30
30
  reporter = activator.implementation_class.new(config)
31
31
  end
32
+ reporter.enhanced_outcomes = enhanced_outcomes
32
33
 
33
34
  # optional send_report method on reporter
34
35
  return reporter.send_report if defined?(reporter.send_report)
@@ -48,6 +48,10 @@ module Inspec::Resources
48
48
 
49
49
  filter.install_filter_methods_on_resource(self, :params)
50
50
 
51
+ def resource_id
52
+ @conf_path || "aide_conf"
53
+ end
54
+
51
55
  def to_s
52
56
  "AIDE Config"
53
57
  end
@@ -40,6 +40,10 @@ module Inspec::Resources
40
40
  end
41
41
  end
42
42
 
43
+ def resource_id
44
+ @conf_path || "apache"
45
+ end
46
+
43
47
  def to_s
44
48
  "Apache Environment"
45
49
  end
@@ -124,6 +124,10 @@ module Inspec::Resources
124
124
  @params["ServerRoot"] || @params["serverroot"]
125
125
  end
126
126
 
127
+ def resource_id
128
+ @conf_path || "apache_conf"
129
+ end
130
+
127
131
  def to_s
128
132
  "Apache Config #{conf_path}"
129
133
  end
@@ -39,10 +39,11 @@ module Inspec::Resources
39
39
  EXAMPLE
40
40
 
41
41
  def initialize(ppa_name)
42
+ @ppa_name = ppa_name
42
43
  @deb_url = nil
43
44
  # check if the os is ubuntu or debian
44
45
  if inspec.os.debian?
45
- @deb_url = determine_ppa_url(ppa_name)
46
+ @deb_url = determine_ppa_url(@ppa_name)
46
47
  else
47
48
  # this resource is only supported on ubuntu and debian
48
49
  skip_resource "The `apt` resource is not supported on your OS yet."
@@ -61,6 +62,10 @@ module Inspec::Resources
61
62
  actives.size == 1 && actives[0] = true
62
63
  end
63
64
 
65
+ def resource_id
66
+ @deb_url || @ppa_name || "apt repository"
67
+ end
68
+
64
69
  def to_s
65
70
  "Apt Repository #{@deb_url}"
66
71
  end
@@ -57,6 +57,11 @@ module Inspec::Resources
57
57
  values
58
58
  end
59
59
 
60
+ # Since this resource does not have any unique identifier for resource, sending the Auditpol command as UUID.
61
+ def resource_id
62
+ "audit_policy"
63
+ end
64
+
60
65
  def to_s
61
66
  "Audit Policy"
62
67
  end
@@ -27,6 +27,10 @@ module Inspec::Resources
27
27
  read_params[name.to_s]
28
28
  end
29
29
 
30
+ def resource_id
31
+ @conf_path || "auditd_conf"
32
+ end
33
+
30
34
  def to_s
31
35
  "Audit Daemon Config"
32
36
  end
@@ -28,6 +28,10 @@ module Inspec::Resources
28
28
  super(CommandWrapper.wrap(command, options))
29
29
  end
30
30
 
31
+ def resource_id
32
+ @raw_command || "bash"
33
+ end
34
+
31
35
  def to_s
32
36
  "Bash command #{@raw_command}"
33
37
  end
@@ -63,6 +63,10 @@ module Inspec::Resources
63
63
  params["Bonding Mode"].first
64
64
  end
65
65
 
66
+ def resource_id
67
+ @path || "bond"
68
+ end
69
+
66
70
  def to_s
67
71
  "Bond #{@bond}"
68
72
  end
@@ -45,6 +45,10 @@ module Inspec::Resources
45
45
  bridge_info.nil? ? nil : bridge_info[:interfaces]
46
46
  end
47
47
 
48
+ def resource_id
49
+ @bridge_name || "bridge"
50
+ end
51
+
48
52
  def to_s
49
53
  "Bridge #{@bridge_name}"
50
54
  end
@@ -31,6 +31,11 @@ module Inspec::Resources
31
31
  super(@conf_path)
32
32
  end
33
33
 
34
+ # if system unables to determine the cassandra conf path the @conf_path can be nil so in that case sending "" string as resource_id
35
+ def resource_id
36
+ @conf_path || "cassandradb_conf"
37
+ end
38
+
34
39
  private
35
40
 
36
41
  def parse(content)