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.
- checksums.yaml +4 -4
- data/Gemfile +20 -17
- data/etc/deprecations.json +4 -0
- data/inspec-core.gemspec +23 -23
- data/lib/inspec/base_cli.rb +7 -0
- data/lib/inspec/cli.rb +68 -11
- data/lib/inspec/dependencies/dependency_set.rb +6 -2
- data/lib/inspec/dsl.rb +24 -5
- data/lib/inspec/enhanced_outcomes.rb +19 -0
- data/lib/inspec/env_printer.rb +1 -1
- data/lib/inspec/errors.rb +2 -0
- data/lib/inspec/exceptions.rb +4 -0
- data/lib/inspec/fetcher/url.rb +1 -1
- data/lib/inspec/file_provider.rb +36 -0
- data/lib/inspec/formatters/base.rb +69 -16
- data/lib/inspec/iaf_file.rb +127 -0
- data/lib/inspec/plugin/v2/loader.rb +19 -8
- data/lib/inspec/plugin/v2/plugin_types/reporter.rb +1 -0
- data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +54 -0
- data/lib/inspec/profile.rb +17 -7
- data/lib/inspec/reporters/base.rb +1 -0
- data/lib/inspec/reporters/cli.rb +94 -3
- data/lib/inspec/reporters/json.rb +3 -1
- data/lib/inspec/reporters/yaml.rb +3 -1
- data/lib/inspec/reporters.rb +2 -1
- data/lib/inspec/resources/aide_conf.rb +4 -0
- data/lib/inspec/resources/apache.rb +4 -0
- data/lib/inspec/resources/apache_conf.rb +4 -0
- data/lib/inspec/resources/apt.rb +6 -1
- data/lib/inspec/resources/audit_policy.rb +5 -0
- data/lib/inspec/resources/auditd_conf.rb +4 -0
- data/lib/inspec/resources/bash.rb +4 -0
- data/lib/inspec/resources/bond.rb +4 -0
- data/lib/inspec/resources/bridge.rb +4 -0
- data/lib/inspec/resources/cassandradb_conf.rb +5 -0
- data/lib/inspec/resources/cassandradb_session.rb +8 -3
- data/lib/inspec/resources/chocolatey_package.rb +4 -0
- data/lib/inspec/resources/chrony_conf.rb +4 -0
- data/lib/inspec/resources/command.rb +5 -0
- data/lib/inspec/resources/cpan.rb +4 -0
- data/lib/inspec/resources/cran.rb +4 -0
- data/lib/inspec/resources/cron.rb +5 -0
- data/lib/inspec/resources/csv.rb +6 -1
- data/lib/inspec/resources/dh_params.rb +4 -0
- data/lib/inspec/resources/docker_container.rb +4 -0
- data/lib/inspec/resources/docker_image.rb +4 -0
- data/lib/inspec/resources/docker_plugin.rb +4 -0
- data/lib/inspec/resources/docker_service.rb +4 -0
- data/lib/inspec/resources/etc_group.rb +4 -0
- data/lib/inspec/resources/etc_hosts_allow_deny.rb +5 -0
- data/lib/inspec/resources/file.rb +7 -2
- data/lib/inspec/resources/filesystem.rb +4 -0
- data/lib/inspec/resources/gem.rb +4 -0
- data/lib/inspec/resources/groups.rb +4 -0
- data/lib/inspec/resources/grub_conf.rb +4 -0
- data/lib/inspec/resources/host.rb +4 -0
- data/lib/inspec/resources/http.rb +6 -2
- data/lib/inspec/resources/ibmdb2_conf.rb +8 -0
- data/lib/inspec/resources/ibmdb2_session.rb +12 -3
- data/lib/inspec/resources/iis_app.rb +4 -0
- data/lib/inspec/resources/iis_app_pool.rb +4 -0
- data/lib/inspec/resources/iis_site.rb +4 -0
- data/lib/inspec/resources/inetd_conf.rb +4 -0
- data/lib/inspec/resources/interface.rb +4 -0
- data/lib/inspec/resources/ip6tables.rb +4 -0
- data/lib/inspec/resources/ipfilter.rb +4 -0
- data/lib/inspec/resources/ipnat.rb +4 -0
- data/lib/inspec/resources/iptables.rb +4 -0
- data/lib/inspec/resources/json.rb +4 -0
- data/lib/inspec/resources/kernel_module.rb +4 -0
- data/lib/inspec/resources/kernel_parameter.rb +4 -0
- data/lib/inspec/resources/key_rsa.rb +4 -0
- data/lib/inspec/resources/ksh.rb +4 -0
- data/lib/inspec/resources/limits_conf.rb +4 -0
- data/lib/inspec/resources/login_defs.rb +4 -0
- data/lib/inspec/resources/lxc.rb +65 -9
- data/lib/inspec/resources/mongodb.rb +4 -0
- data/lib/inspec/resources/mongodb_conf.rb +5 -0
- data/lib/inspec/resources/mongodb_session.rb +6 -1
- data/lib/inspec/resources/mount.rb +4 -0
- data/lib/inspec/resources/mssql_session.rb +4 -0
- data/lib/inspec/resources/mssql_sys_conf.rb +7 -0
- data/lib/inspec/resources/mysql_conf.rb +4 -0
- data/lib/inspec/resources/mysql_session.rb +8 -1
- data/lib/inspec/resources/nginx.rb +6 -1
- data/lib/inspec/resources/nginx_conf.rb +4 -0
- data/lib/inspec/resources/noop.rb +4 -0
- data/lib/inspec/resources/npm.rb +4 -0
- data/lib/inspec/resources/ntp_conf.rb +4 -0
- data/lib/inspec/resources/oneget.rb +4 -0
- data/lib/inspec/resources/opa_api.rb +10 -0
- data/lib/inspec/resources/opa_cli.rb +14 -0
- data/lib/inspec/resources/oracledb_conf.rb +5 -0
- data/lib/inspec/resources/oracledb_listener_conf.rb +4 -0
- data/lib/inspec/resources/oracledb_session.rb +23 -4
- data/lib/inspec/resources/os.rb +4 -0
- data/lib/inspec/resources/os_env.rb +4 -0
- data/lib/inspec/resources/package.rb +4 -0
- data/lib/inspec/resources/parse_config.rb +10 -1
- data/lib/inspec/resources/pip.rb +4 -0
- data/lib/inspec/resources/platform.rb +4 -0
- data/lib/inspec/resources/podman.rb +353 -0
- data/lib/inspec/resources/podman_container.rb +84 -0
- data/lib/inspec/resources/podman_image.rb +108 -0
- data/lib/inspec/resources/podman_network.rb +81 -0
- data/lib/inspec/resources/podman_pod.rb +101 -0
- data/lib/inspec/resources/podman_volume.rb +87 -0
- data/lib/inspec/resources/postfix_conf.rb +4 -0
- data/lib/inspec/resources/postgres_conf.rb +4 -0
- data/lib/inspec/resources/postgres_session.rb +8 -4
- data/lib/inspec/resources/powershell.rb +4 -0
- data/lib/inspec/resources/processes.rb +6 -4
- data/lib/inspec/resources/rabbitmq_config.rb +4 -0
- data/lib/inspec/resources/registry_key.rb +4 -0
- data/lib/inspec/resources/security_identifier.rb +4 -0
- data/lib/inspec/resources/security_policy.rb +4 -0
- data/lib/inspec/resources/service.rb +5 -1
- data/lib/inspec/resources/ssh_config.rb +4 -0
- data/lib/inspec/resources/sybase_conf.rb +4 -0
- data/lib/inspec/resources/sybase_session.rb +4 -0
- data/lib/inspec/resources/sys_info.rb +4 -0
- data/lib/inspec/resources/timezone.rb +4 -0
- data/lib/inspec/resources/users.rb +4 -0
- data/lib/inspec/resources/vbscript.rb +5 -0
- data/lib/inspec/resources/virtualization.rb +4 -0
- data/lib/inspec/resources/windows_feature.rb +5 -1
- data/lib/inspec/resources/windows_firewall.rb +4 -0
- data/lib/inspec/resources/windows_firewall_rule.rb +4 -0
- data/lib/inspec/resources/windows_hotfix.rb +4 -0
- data/lib/inspec/resources/windows_task.rb +4 -0
- data/lib/inspec/resources/wmi.rb +4 -0
- data/lib/inspec/resources/x509_certificate.rb +59 -0
- data/lib/inspec/resources/yum.rb +4 -0
- data/lib/inspec/resources/zfs_dataset.rb +4 -0
- data/lib/inspec/resources/zfs_pool.rb +4 -0
- data/lib/inspec/rule.rb +55 -18
- data/lib/inspec/run_data/control.rb +6 -0
- data/lib/inspec/run_data/statistics.rb +8 -2
- data/lib/inspec/runner.rb +18 -8
- data/lib/inspec/runner_rspec.rb +3 -2
- data/lib/inspec/schema/exec_json.rb +78 -2
- data/lib/inspec/schema/output_schema.rb +4 -1
- data/lib/inspec/schema/profile_json.rb +46 -0
- data/lib/inspec/schema.rb +91 -0
- data/lib/inspec/secrets/yaml.rb +7 -1
- data/lib/inspec/ui.rb +1 -0
- data/lib/inspec/utils/convert.rb +8 -0
- data/lib/inspec/utils/podman.rb +24 -0
- data/lib/inspec/utils/waivers/csv_file_reader.rb +34 -0
- data/lib/inspec/utils/waivers/excel_file_reader.rb +39 -0
- data/lib/inspec/utils/waivers/json_file_reader.rb +15 -0
- data/lib/inspec/utils/yaml_profile_summary.rb +34 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/inspec/waiver_file_reader.rb +61 -0
- data/lib/matchers/matchers.rb +7 -1
- data/lib/plugins/inspec-init/templates/profiles/alicloud/README.md +27 -0
- data/lib/plugins/inspec-init/templates/profiles/alicloud/controls/example.rb +10 -0
- data/lib/plugins/inspec-init/templates/profiles/alicloud/inputs.yml +1 -0
- data/lib/plugins/inspec-init/templates/profiles/alicloud/inspec.yml +14 -0
- data/lib/plugins/inspec-reporter-html2/README.md +1 -1
- data/lib/plugins/inspec-reporter-html2/templates/body.html.erb +11 -5
- data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +11 -7
- data/lib/plugins/inspec-reporter-html2/templates/default.css +12 -0
- data/lib/plugins/inspec-reporter-html2/templates/profile.html.erb +1 -1
- data/lib/plugins/inspec-reporter-html2/templates/selector.html.erb +7 -1
- data/lib/plugins/{inspec-artifact/inspec-artifact.gemspec → inspec-sign/inspec-sign.gemspec} +2 -2
- data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +164 -0
- data/lib/plugins/{inspec-artifact/lib/inspec-artifact → inspec-sign/lib/inspec-sign}/cli.rb +14 -23
- data/lib/plugins/inspec-sign/lib/inspec-sign.rb +12 -0
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +39 -13
- data/lib/source_readers/inspec.rb +8 -2
- metadata +33 -15
- data/lib/plugins/inspec-artifact/lib/inspec-artifact/base.rb +0 -187
- 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
|
data/lib/inspec/version.rb
CHANGED
@@ -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
|
data/lib/matchers/matchers.rb
CHANGED
@@ -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
|
-
|
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 @@
|
|
1
|
+
|
@@ -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/
|
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),
|
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),
|
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"),
|
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"),
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
status
|
7
|
-
|
8
|
-
status
|
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"),
|
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"),
|
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>
|
data/lib/plugins/{inspec-artifact/inspec-artifact.gemspec → inspec-sign/inspec-sign.gemspec}
RENAMED
@@ -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-
|
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
|
76
|
+
module Sign
|
75
77
|
class CLI < Inspec.plugin(2, :cli_command)
|
76
78
|
include Inspec::Dist
|
77
79
|
|
78
|
-
subcommand_desc "
|
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::
|
89
|
+
InspecPlugins::Sign::Base.keygen(options)
|
88
90
|
end
|
89
91
|
|
90
|
-
desc "
|
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
|
-
|
96
|
-
|
97
|
-
|
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 "
|
107
|
-
|
108
|
-
|
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
|