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,34 @@
1
+ require "csv" unless defined?(CSV)
2
+
3
+ module Waivers
4
+ class CSVFileReader
5
+ def self.resolve(path)
6
+ return nil unless File.file?(path)
7
+
8
+ @headers ||= []
9
+ fetch_data(path)
10
+ end
11
+
12
+ def self.fetch_data(path)
13
+ waiver_data_hash = {}
14
+ CSV.foreach(path, headers: true) do |row|
15
+ row_hash = row.to_hash
16
+ @headers = row_hash.keys if @headers.empty?
17
+ control_id = row_hash["control_id"]
18
+ # delete keys and values not required in final hash
19
+ row_hash.delete("control_id")
20
+ row_hash.delete_if { |k, v| k.nil? || v.nil? }
21
+
22
+ waiver_data_hash[control_id] = row_hash if control_id && !row_hash.blank?
23
+ end
24
+
25
+ waiver_data_hash
26
+ rescue CSV::MalformedCSVError => e
27
+ raise "Error reading InSpec waivers in CSV: #{e}"
28
+ end
29
+
30
+ def self.headers
31
+ @headers
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ require "roo"
2
+ require "roo-xls"
3
+
4
+ module Waivers
5
+ class ExcelFileReader
6
+ def self.resolve(path)
7
+ return nil unless File.file?(path)
8
+
9
+ @headers ||= []
10
+ fetch_data(path)
11
+ end
12
+
13
+ def self.fetch_data(path)
14
+ waiver_data_hash = {}
15
+ file_extension = File.extname(path) == ".xlsx" ? :xlsx : :xls
16
+ excel_file = Roo::Spreadsheet.open(path, extension: file_extension)
17
+ excel_file.sheet(0).parse(headers: true).each_with_index do |row, index|
18
+ if index == 0
19
+ @headers = row.keys
20
+ else
21
+ row_hash = row
22
+ control_id = row_hash["control_id"]
23
+ # delete keys and values not required in final hash
24
+ row_hash.delete("control_id")
25
+ row_hash.delete_if { |k, v| k.nil? || v.nil? }
26
+ end
27
+
28
+ waiver_data_hash[control_id] = row_hash if control_id && !row_hash.blank?
29
+ end
30
+ waiver_data_hash
31
+ rescue Exception => e
32
+ raise "Error reading InSpec waivers in Excel: #{e}"
33
+ end
34
+
35
+ def self.headers
36
+ @headers
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module Waivers
2
+ class JSONFileReader
3
+ def self.resolve(path)
4
+ return nil unless File.file?(path)
5
+
6
+ fetch_data(path)
7
+ end
8
+
9
+ def self.fetch_data(path)
10
+ JSON.parse(File.read(path))
11
+ rescue JSON::ParserError => e
12
+ raise "Error reading InSpec waivers in JSON: #{e}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Inspec
4
+ module Utils
5
+ #
6
+ # Inspec::Utils::YamlProfileSummary takes in certain information to identify a
7
+ # profile and then produces a YAML-formatted summary of that profile. It can
8
+ # return the results to STDOUT or a file.
9
+ #
10
+ #
11
+ module YamlProfileSummary
12
+ def self.produce_yaml(info:, write_path: "", suppress_output: false)
13
+ # add in inspec version
14
+ info[:generator] = {
15
+ name: "inspec",
16
+ version: Inspec::VERSION,
17
+ }
18
+ if write_path.empty?
19
+ puts info.to_yaml
20
+ else
21
+ unless suppress_output
22
+ if File.exist? write_path
23
+ Inspec::Log.info "----> updating #{write_path}"
24
+ else
25
+ Inspec::Log.info "----> creating #{write_path}"
26
+ end
27
+ end
28
+ full_write_path = File.expand_path(write_path)
29
+ File.write(full_write_path, info.to_yaml)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "5.17.4".freeze
2
+ VERSION = "5.21.29".freeze
3
3
  end
@@ -0,0 +1,61 @@
1
+ require "inspec/secrets/yaml"
2
+ require "inspec/utils/waivers/csv_file_reader"
3
+ require "inspec/utils/waivers/json_file_reader"
4
+
5
+ module Inspec
6
+ class WaiverFileReader
7
+
8
+ def self.fetch_waivers_by_profile(profile_id, files)
9
+ read_waivers_from_file(profile_id, files) if @waivers_data.nil? || @waivers_data[profile_id].nil?
10
+ @waivers_data[profile_id]
11
+ end
12
+
13
+ def self.read_waivers_from_file(profile_id, files)
14
+ @waivers_data ||= {}
15
+ output = {}
16
+
17
+ files.each do |file_path|
18
+ file_extension = File.extname(file_path)
19
+ data = nil
20
+ if [".yaml", ".yml"].include? file_extension
21
+ data = Secrets::YAML.resolve(file_path)
22
+ data = data.inputs unless data.nil?
23
+ validate_json_yaml(data)
24
+ elsif file_extension == ".csv"
25
+ data = Waivers::CSVFileReader.resolve(file_path)
26
+ headers = Waivers::CSVFileReader.headers
27
+ validate_headers(headers)
28
+ elsif file_extension == ".json"
29
+ data = Waivers::JSONFileReader.resolve(file_path)
30
+ validate_json_yaml(data)
31
+ end
32
+ output.merge!(data) if !data.nil? && data.is_a?(Hash)
33
+
34
+ if data.nil?
35
+ raise Inspec::Exceptions::WaiversFileNotReadable,
36
+ "Cannot find parser for waivers file '#{file_path}'. " \
37
+ "Check to make sure file has the appropriate extension."
38
+ end
39
+ end
40
+
41
+ @waivers_data[profile_id] = output
42
+ end
43
+
44
+ def self.validate_headers(headers, json_yaml = false)
45
+ required_fields = json_yaml ? %w{justification} : %w{control_id justification}
46
+ all_fields = %w{control_id justification expiration_date run}
47
+
48
+ Inspec::Log.warn "Missing column headers: #{(required_fields - headers)}" unless (required_fields - headers).empty?
49
+ Inspec::Log.warn "Invalid column header: Column can't be nil" if headers.include? nil
50
+ Inspec::Log.warn "Extra column headers: #{(headers - all_fields)}" unless (headers - all_fields).empty?
51
+ end
52
+
53
+ def self.validate_json_yaml(data)
54
+ headers = []
55
+ data.each_value do |value|
56
+ headers.push value.keys
57
+ end
58
+ validate_headers(headers.flatten.uniq, true)
59
+ end
60
+ end
61
+ end
@@ -225,7 +225,11 @@ RSpec::Matchers.define :cmp do |first_expected| # rubocop:disable Metrics/BlockL
225
225
  end
226
226
 
227
227
  def boolean?(value)
228
- %w{true false}.include?(value.downcase)
228
+ if value.respond_to?("downcase")
229
+ %w{true false}.include?(value.downcase)
230
+ else
231
+ value.is_a?(TrueClass) || value.is_a?(FalseClass)
232
+ end
229
233
  end
230
234
 
231
235
  def version?(value)
@@ -252,6 +256,8 @@ RSpec::Matchers.define :cmp do |first_expected| # rubocop:disable Metrics/BlockL
252
256
  return actual.send(op, expected.to_i)
253
257
  elsif expected.is_a?(String) && boolean?(expected) && [true, false].include?(actual)
254
258
  return actual.send(op, to_boolean(expected))
259
+ elsif boolean?(expected) && %w{true false}.include?(actual)
260
+ return actual.send(op, expected.to_s)
255
261
  elsif expected.is_a?(Integer) && actual.is_a?(String) && integer?(actual)
256
262
  return actual.to_i.send(op, expected)
257
263
  elsif expected.is_a?(Float) && float?(actual)
@@ -0,0 +1,27 @@
1
+ # Example InSpec Profile For AliCloud
2
+
3
+ This example shows the implementation of an InSpec profile for AliCloud.
4
+
5
+ The related control will simply be skipped if this is not provided. See the [InSpec DSL documentation](https://docs.chef.io/inspec/dsl_inspec/) for more details on conditional execution using `only_if`.
6
+
7
+ ## Run the test
8
+
9
+ ```bash
10
+ $ cd my-alicloud-sample-profile/
11
+ $ inspec exec . -t alicloud://
12
+ ```
13
+
14
+ ```
15
+ Profile: Ali Cloud InSpec Profile (my-alicloud-profile)
16
+ Version: 0.1.0
17
+ Target: alicloud://ap-south-1
18
+ ✔ ali-cloud-instances-1.0: Ensure AliCloud ECS Instances has correct attributes.
19
+ ✔ AliCloud ECS Instances (All) is expected to exist
20
+ ✔ AliCloud ECS Instances (All) entries.count is expected to be >= 1
21
+ Profile: AliCloud Resource Pack (inspec-alicloud)
22
+ Version: 0.10.8
23
+ Target: alicloud://ap-south-1
24
+ No tests executed.
25
+ Profile Summary: 1 successful controls, 0 control failures, 0 controls skipped
26
+ Test Summary: 1 successful, 0 failures, 0 skipped
27
+ ```
@@ -0,0 +1,10 @@
1
+ title "Test AliCloud Instances count"
2
+
3
+ control "ali-cloud-instances-1.0" do
4
+ impact 1.0
5
+ title "Ensure AliCloud ECS Instances Class has correct attributes."
6
+ describe alicloud_ecs_instances do
7
+ it { should exist }
8
+ its("entries.count") { should be >= 1 }
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ name: my-alicloud-sample-profile
2
+ title: Ali Cloud InSpec Profile
3
+ maintainer: The Authors
4
+ copyright: The Authors
5
+ copyright_email: you@example.com
6
+ license: Apache-2.0
7
+ summary: An InSpec Compliance Profile For Ali CLoud
8
+ version: 0.1.0
9
+ inspec_version: '~> 5'
10
+ depends:
11
+ - name: inspec-alicloud
12
+ url: https://github.com/inspec/inspec-alicloud/archive/main.tar.gz
13
+ supports:
14
+ - platform: alicloud
@@ -50,4 +50,4 @@ Specifies the full path to the location of a JavaScript file that will be read a
50
50
 
51
51
  ## Developing This Plugin
52
52
 
53
- This plugin is part of the Chef InSpec source code. While it has its own tests, the general contribution policy is dictated by the Chef InSpec project at https://github.com/inspec/inspec/blob/master/CONTRIBUTING.md
53
+ This plugin is part of the Chef InSpec source code. While it has its own tests, the general contribution policy is dictated by the Chef InSpec project at https://github.com/inspec/inspec/blob/main/CONTRIBUTING.md
@@ -7,21 +7,21 @@
7
7
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
8
8
  <style type="text/css">
9
9
  /* Must inline all CSS files, this is a single-file output that may be airgapped */
10
- <%= ERB.new(File.read(css_path), nil, nil, "_css").result(binding) %>
10
+ <%= ERB.new(File.read(css_path), eoutvar: "_css").result(binding) %>
11
11
  </style>
12
12
  <script type="text/javascript">
13
13
  // <![CDATA[
14
14
  /* Must inline all JavaScript files, this is a single-file output that may be airgapped */
15
- <%= ERB.new(File.read(js_path), nil, nil, "_js").result(binding) %>
15
+ <%= ERB.new(File.read(js_path), eoutvar: "_js").result(binding) %>
16
16
  // ]]>
17
17
  </script>
18
18
  </head>
19
19
  <body onload="pageLoaded()">
20
- <%= ERB.new(File.read(template_path + "/selector.html.erb"), nil, nil, "_select").result(binding) %>
20
+ <%= ERB.new(File.read(template_path + "/selector.html.erb"), eoutvar: "_select").result(binding) %>
21
21
  <div class="inspec-report">
22
22
  <h1><%= Inspec::Dist::PRODUCT_NAME %> Report</h1>
23
23
  <% run_data.profiles.each do |profile| %>
24
- <%= ERB.new(File.read(template_path + "/profile.html.erb"), nil, nil, "_prof").result(binding) %>
24
+ <%= ERB.new(File.read(template_path + "/profile.html.erb"), eoutvar: "_prof").result(binding) %>
25
25
  <% end %>
26
26
 
27
27
  <div class="inspec-summary">
@@ -36,8 +36,14 @@
36
36
  <caption>Control Statistics</caption>
37
37
  <tr><th colspan="2"><h4 id="statistics-label">Control Statistics</h4></th></tr>
38
38
  <tr class= "passed"><th>Passed:</th><td><%= run_data.statistics.controls.passed.total %></td></tr>
39
- <tr class= "skipped"><th>Skipped:</th><td><%= run_data.statistics.controls.skipped.total %></td></tr>
40
39
  <tr class= "failed"><th>Failed:</th><td><%= run_data.statistics.controls.failed.total %></td></tr>
40
+ <% if enhanced_outcomes %>
41
+ <tr class= "not_reviewed"><th>Not Reviewed:</th><td><%= run_data.statistics.controls.not_reviewed.total %></td></tr>
42
+ <tr class= "not_applicable"><th>Not Applicable:</th><td><%= run_data.statistics.controls.not_applicable.total %></td></tr>
43
+ <tr class= "error"><th>Error:</th><td><%= run_data.statistics.controls.error.total %></td></tr>
44
+ <% else %>
45
+ <tr class= "skipped"><th>Skipped:</th><td><%= run_data.statistics.controls.skipped.total %></td></tr>
46
+ <% end %>
41
47
  <tr class= "duration"><th>Duration:</th><td><%= run_data.statistics.duration %> seconds</td></tr>
42
48
  <tr class= "date"><th>Time Finished:</th><td><%= Time.now %></td></tr>
43
49
  </table>
@@ -1,11 +1,15 @@
1
1
  <% slugged_id = control.id.tr(" ", "_") %>
2
2
  <%
3
- # Determine status of control
4
- status = "passed"
5
- if control.results.any? { |r| r.status == "failed" }
6
- status = "failed"
7
- elsif control.results.any? { |r| r.status == "skipped" }
8
- status = "skipped"
3
+ if enhanced_outcomes
4
+ status = control.status
5
+ else
6
+ # Determine status of control
7
+ status = "passed"
8
+ if control.results.any? { |r| r.status == "failed" }
9
+ status = "failed"
10
+ elsif control.results.any? { |r| r.status == "skipped" }
11
+ status = "skipped"
12
+ end
9
13
  end
10
14
  %>
11
15
 
@@ -74,7 +78,7 @@
74
78
  </table>
75
79
 
76
80
  <% control.results.each do |result| %>
77
- <%= ERB.new(File.read(template_path + "/result.html.erb"), nil, nil, "_rslt").result(binding) %>
81
+ <%= ERB.new(File.read(template_path + "/result.html.erb"), eoutvar: "_rslt").result(binding) %>
78
82
  <% end %>
79
83
 
80
84
  </div>
@@ -60,6 +60,18 @@ pre code {
60
60
  .result-metadata .status-skipped div {
61
61
  background-color: grey;
62
62
  }
63
+ .control-metadata .status-error div,
64
+ .result-metadata .status-error div {
65
+ background-color: rgb(63, 15, 183);
66
+ }
67
+ .control-metadata .status-not_applicable div,
68
+ .result-metadata .status-not_applicable div {
69
+ background-color: rgb(135, 206, 250);
70
+ }
71
+ .control-metadata .status-not_reviewed div,
72
+ .result-metadata .status-not_reviewed div {
73
+ background-color: rgb(255, 194, 0);
74
+ }
63
75
  .result-metadata,
64
76
  .control-metadata {
65
77
  margin: 0 0 0 5%;
@@ -18,7 +18,7 @@
18
18
 
19
19
  <% if profile.status == "loaded" %>
20
20
  <% profile.controls.each do |control| %>
21
- <%= ERB.new(File.read(template_path + "/control.html.erb"), nil, nil, "_ctl").result(binding) %>
21
+ <%= ERB.new(File.read(template_path + "/control.html.erb"), eoutvar: "_ctl").result(binding) %>
22
22
  <% end %>
23
23
  <% end %>
24
24
  </div>
@@ -1,8 +1,14 @@
1
1
  <div class="selector-panel">
2
2
  <p id="selector-instructions">Display controls that are:</p>
3
3
  <input class="selector-checkbox" id="passed-checkbox" type="checkbox" checked="checked"/><label for="passed-checkbox">Passed</label>
4
- <input class="selector-checkbox" id="skipped-checkbox" type="checkbox" checked="checked"/><label for="skipped-checkbox">Skipped</label>
5
4
  <input class="selector-checkbox" id="failed-checkbox" type="checkbox" checked="checked"/><label for="failed-checkbox">Failed</label>
5
+ <% if enhanced_outcomes %>
6
+ <input class="selector-checkbox" id="not_reviewed-checkbox" type="checkbox" checked="checked"/><label for="not_reviewed-checkbox">Not Reviewed</label>
7
+ <input class="selector-checkbox" id="not_applicable-checkbox" type="checkbox" checked="checked"/><label for="not_applicable-checkbox">Not Applicable</label>
8
+ <input class="selector-checkbox" id="error-checkbox" type="checkbox" checked="checked"/><label for="error-checkbox">Error</label>
9
+ <% else %>
10
+ <input class="selector-checkbox" id="skipped-checkbox" type="checkbox" checked="checked"/><label for="skipped-checkbox">Skipped</label>
11
+ <% end %>
6
12
  <p id="selector-instructions">Display profiles that are:</p>
7
13
  <input class="profile-selector-checkbox" id="child-profile-checkbox" type="checkbox" /><label for="child-profile-checkbox">Dependent Profiles</label>
8
14
  </div>
@@ -2,8 +2,8 @@
2
2
  # These specs are used in plugin list and search command
3
3
 
4
4
  Gem::Specification.new do |spec|
5
- spec.name = "inspec-artifact"
5
+ spec.name = "inspec-sign"
6
6
  spec.summary = ""
7
7
  spec.description = "Plugin to generate asymmetrical keys that you can use to encrypt profiles"
8
8
  spec.license = "Apache-2.0"
9
- end
9
+ end
@@ -0,0 +1,164 @@
1
+ require "base64" unless defined?(Base64)
2
+ require "openssl" unless defined?(OpenSSL)
3
+ require "pathname" unless defined?(Pathname)
4
+ require "set" unless defined?(Set)
5
+ require "tempfile" unless defined?(Tempfile)
6
+ require "yaml"
7
+ require "inspec/dist"
8
+ require "inspec/utils/json_profile_summary"
9
+ require "inspec/iaf_file"
10
+
11
+ module InspecPlugins
12
+ module Sign
13
+ class Base
14
+ include Inspec::Dist
15
+
16
+ KEY_BITS = 2048
17
+ KEY_ALG = OpenSSL::PKey::RSA
18
+
19
+ INSPEC_PROFILE_VERSION_1 = "INSPEC-PROFILE-1".freeze
20
+ INSPEC_REPORT_VERSION_1 = "INSPEC-REPORT-1".freeze
21
+
22
+ INSPEC_PROFILE_VERSION_2 = "INSPEC-PROFILE-2".freeze
23
+ ARTIFACT_DIGEST = OpenSSL::Digest::SHA512
24
+ ARTIFACT_DIGEST_NAME = "SHA512".freeze
25
+
26
+ VALID_PROFILE_VERSIONS = Set.new [INSPEC_PROFILE_VERSION_1, INSPEC_PROFILE_VERSION_2]
27
+ VALID_PROFILE_DIGESTS = Set.new [ARTIFACT_DIGEST_NAME]
28
+
29
+ SIGNED_PROFILE_SUFFIX = "iaf".freeze
30
+ SIGNED_REPORT_SUFFIX = "iar".freeze
31
+
32
+ def self.keygen(options)
33
+ key = KEY_ALG.new KEY_BITS
34
+
35
+ path = File.join(Inspec.config_dir, "keys")
36
+ FileUtils.mkdir_p(path)
37
+
38
+ puts "Generating signing key in #{path}/#{options["keyname"]}.pem.key"
39
+ open "#{path}/#{options["keyname"]}.pem.key", "w" do |io|
40
+ io.write key.to_pem
41
+ end
42
+ puts "Generating validation key in #{path}/#{options["keyname"]}.pem.pub"
43
+ open "#{path}/#{options["keyname"]}.pem.pub", "w" do |io|
44
+ io.write key.public_key.to_pem
45
+ end
46
+ end
47
+
48
+ def self.profile_sign(profile_path, options)
49
+ artifact = new
50
+
51
+ # Writes the profile content id in the inspec.yml
52
+ if options[:profile_content_id] && !options[:profile_content_id].strip.empty?
53
+ artifact.write_profile_content_id(profile_path, options[:profile_content_id])
54
+ end
55
+
56
+ puts "Signing #{profile_path} with key #{options["keyname"]}"
57
+ keypath = Inspec::IafFile.find_signing_key(options["keyname"])
58
+
59
+ # Read name and version from metadata and use them to form the filename
60
+ profile_md = artifact.read_profile_metadata(profile_path)
61
+
62
+ # Behave same as archive filename for iaf filename
63
+ slug = profile_md["name"].downcase.strip.tr(" ", "-").gsub(/[^\w-]/, "_")
64
+ filename = "#{slug}-#{profile_md["version"]}"
65
+ artifact_filename = "#{filename}.#{SIGNED_PROFILE_SUFFIX}"
66
+
67
+ # Generating tar.gz file using archive method of Inspec Cli
68
+ Inspec::InspecCLI.new.archive(profile_path, "error")
69
+ tarfile = "#{filename}.tar.gz"
70
+ tar_content = IO.binread(tarfile)
71
+ FileUtils.rm(tarfile)
72
+
73
+ # Generate the signature
74
+ signing_key = KEY_ALG.new File.read keypath
75
+ sha = ARTIFACT_DIGEST.new
76
+ signature = signing_key.sign sha, tar_content
77
+ # convert the signature to Base64
78
+ signature_base64 = Base64.encode64(signature)
79
+
80
+ content = (format("%-100s", options[:keyname]) +
81
+ format("%-20s", ARTIFACT_DIGEST_NAME) +
82
+ format("%-370s", signature_base64)).gsub(" ", "\0").unpack("H*").pack("h*") + "#{tar_content}"
83
+
84
+ File.open(artifact_filename, "wb") do |f|
85
+ f.puts INSPEC_PROFILE_VERSION_2
86
+ f.puts "Use \"inspec export\" to view this file"
87
+ f.write(content)
88
+ end
89
+ puts "Successfully generated #{artifact_filename}"
90
+ rescue Inspec::Exceptions::ProfileValidationKeyNotFound => e
91
+ $stderr.puts e.message
92
+ Inspec::UI.new.exit(:usage_error)
93
+ end
94
+
95
+ def self.profile_verify(signed_profile_path)
96
+ file_to_verify = signed_profile_path
97
+ puts "Verifying #{file_to_verify}"
98
+
99
+ iaf_file = Inspec::IafFile.new(file_to_verify)
100
+ if iaf_file.valid?
101
+ puts "Detected format version '#{iaf_file.version}'"
102
+ puts "Attempting to verify using key '#{iaf_file.key_name}'"
103
+ puts "Profile is valid."
104
+ Inspec::UI.new.exit(:normal)
105
+ else
106
+ puts "Detected format version '#{iaf_file.version}'"
107
+ puts "Attempting to verify using key '#{iaf_file.key_name}'" if iaf_file.key_name
108
+ puts "Profile is invalid"
109
+ Inspec::UI.new.exit(:bad_signature)
110
+ end
111
+ rescue Inspec::Exceptions::ProfileValidationKeyNotFound => e
112
+ $stderr.puts e.message
113
+ Inspec::UI.new.exit(:usage_error)
114
+ end
115
+
116
+ def read_profile_metadata(profile_path)
117
+ begin
118
+ p = Pathname.new(profile_path)
119
+ p = p.join("inspec.yml")
120
+ unless p.exist?
121
+ raise "#{profile_path} doesn't appear to be a valid #{PRODUCT_NAME} profile"
122
+ end
123
+
124
+ yaml = YAML.load_file(p.to_s)
125
+ yaml = yaml.to_hash
126
+
127
+ unless yaml.key? "name"
128
+ raise "Profile is invalid, name is not defined"
129
+ end
130
+
131
+ unless yaml.key? "version"
132
+ raise "Profile is invalid, version is not defined"
133
+ end
134
+ rescue => e
135
+ # rewrap it and pass it up to the CLI
136
+ $stderr.puts "Error reading profile metadata file #{e.message}"
137
+ Inspec::UI.new.exit(:usage_error)
138
+ end
139
+
140
+ yaml
141
+ end
142
+
143
+ def write_profile_content_id(profile_path, profile_content_id)
144
+ p = Pathname.new(profile_path)
145
+ p = p.join("inspec.yml")
146
+ yaml = YAML.load_file(p.to_s)
147
+ existing_profile_content_id = yaml["profile_content_id"]
148
+
149
+ unless existing_profile_content_id.nil?
150
+ ui = Inspec::UI.new
151
+ ui.error("Cannot use --profile-content-id when profile_content_id already exists in metadata file.")
152
+ ui.exit(:usage_error)
153
+ end
154
+
155
+ lines = IO.readlines(p)
156
+ lines << "\nprofile_content_id: #{profile_content_id}\n"
157
+
158
+ File.open("#{p}", "w" ) do |f|
159
+ f.puts lines
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -70,46 +70,37 @@ require "inspec/dist"
70
70
  # To extract the raw content from a .iaf:
71
71
  # sed '1,/^$/d' foo.iaf
72
72
 
73
+ # inspec artifact is renamed to inspec sign
74
+
73
75
  module InspecPlugins
74
- module Artifact
76
+ module Sign
75
77
  class CLI < Inspec.plugin(2, :cli_command)
76
78
  include Inspec::Dist
77
79
 
78
- subcommand_desc "artifact SUBCOMMAND", "Manage #{PRODUCT_NAME} Artifacts"
80
+ subcommand_desc "sign SUBCOMMAND", "Manage #{PRODUCT_NAME} profile signing."
79
81
 
80
- desc "generate", "Generate a RSA key pair for signing and verification"
82
+ desc "generate-keys", "Generate a RSA key pair for signing and verification"
81
83
  option :keyname, type: :string, required: true,
82
84
  desc: "Desriptive name of key"
83
85
  option :keydir, type: :string, default: "./",
84
86
  desc: "Directory to search for keys"
85
87
  def generate_keys
86
88
  puts "Generating keys"
87
- InspecPlugins::Artifact::Base.keygen(options)
89
+ InspecPlugins::Sign::Base.keygen(options)
88
90
  end
89
91
 
90
- desc "sign-profile", "Create a signed .iaf artifact"
91
- option :profile, type: :string, required: true,
92
- desc: "Path to profile directory"
92
+ desc "profile PATH", "sign the profile in PATH and generate .iaf artifact."
93
93
  option :keyname, type: :string, required: true,
94
94
  desc: "Desriptive name of key"
95
- def sign_profile
96
- InspecPlugins::Artifact::Base.profile_sign(options)
97
- end
98
-
99
- desc "verify-profile", "Verify a signed .iaf artifact"
100
- option :infile, type: :string, required: true,
101
- desc: ".iaf file to verify"
102
- def verify_profile
103
- InspecPlugins::Artifact::Base.profile_verify(options)
95
+ option :profile_content_id, type: :string,
96
+ desc: "UUID of the profile. This will write the profile_content_id in the metadata file if it does not already exist in the metadata file."
97
+ def profile(profile_path)
98
+ InspecPlugins::Sign::Base.profile_sign(profile_path, options)
104
99
  end
105
100
 
106
- desc "install-profile", "Verify and install a signed .iaf artifact"
107
- option :infile, type: :string, required: true,
108
- desc: ".iaf file to install"
109
- option :destdir, type: :string, required: true,
110
- desc: "Installation directory"
111
- def install_profile
112
- InspecPlugins::Artifact::Base.profile_install(options)
101
+ desc "verify PATH", "Verify a signed profile .iaf artifact at given path."
102
+ def verify(signed_profile_path)
103
+ InspecPlugins::Sign::Base.profile_verify(signed_profile_path)
113
104
  end
114
105
  end
115
106
  end
@@ -0,0 +1,12 @@
1
+ module InspecPlugins
2
+ module Sign
3
+ class Plugin < Inspec.plugin(2)
4
+ plugin_name :'inspec-sign'
5
+
6
+ cli_command :sign do
7
+ require_relative "inspec-sign/cli"
8
+ InspecPlugins::Sign::CLI
9
+ end
10
+ end
11
+ end
12
+ end