inspec-core 5.12.2 → 5.18.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/etc/deprecations.json +4 -0
- data/etc/keys/progress-2022-05-04.pem.pub +9 -0
- data/inspec-core.gemspec +1 -1
- data/lib/inspec/base_cli.rb +5 -0
- data/lib/inspec/cli.rb +64 -9
- data/lib/inspec/dependencies/dependency_set.rb +6 -2
- data/lib/inspec/dependency_loader.rb +5 -1
- data/lib/inspec/dsl.rb +18 -5
- data/lib/inspec/errors.rb +2 -0
- data/lib/inspec/exceptions.rb +2 -0
- data/lib/inspec/fetcher/url.rb +1 -1
- data/lib/inspec/file_provider.rb +36 -0
- data/lib/inspec/iaf_file.rb +127 -0
- data/lib/inspec/profile.rb +31 -14
- 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/default_gateway.rb +61 -0
- 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 +97 -1
- data/lib/inspec/resources/filesystem.rb +4 -0
- data/lib/inspec/resources/gem.rb +4 -0
- data/lib/inspec/resources/groups.rb +9 -0
- data/lib/inspec/resources/grub_conf.rb +4 -0
- data/lib/inspec/resources/host.rb +46 -3
- data/lib/inspec/resources/http.rb +4 -0
- 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/linux_audit_system.rb +81 -0
- data/lib/inspec/resources/login_defs.rb +4 -0
- 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 +10 -0
- 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/php_config.rb +72 -0
- data/lib/inspec/resources/pip.rb +4 -0
- data/lib/inspec/resources/platform.rb +4 -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 +17 -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 +80 -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/x509_private_key.rb +93 -0
- data/lib/inspec/resources/yum.rb +4 -0
- data/lib/inspec/resources/zfs.rb +48 -0
- data/lib/inspec/resources/zfs_dataset.rb +4 -0
- data/lib/inspec/resources/zfs_pool.rb +4 -0
- data/lib/inspec/rule.rb +1 -1
- data/lib/inspec/secrets/yaml.rb +7 -1
- data/lib/inspec/ui.rb +1 -0
- data/lib/inspec/utils/yaml_profile_summary.rb +34 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/plugins/inspec-reporter-html2/templates/body.html.erb +4 -4
- data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +1 -1
- data/lib/plugins/inspec-reporter-html2/templates/profile.html.erb +1 -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 +161 -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/source_readers/inspec.rb +8 -2
- metadata +16 -8
- 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,93 @@
|
|
1
|
+
require "inspec/resources/file"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class X509PrivateKey < Inspec.resource(1)
|
5
|
+
# Resource internal name.
|
6
|
+
name "x509_private_key"
|
7
|
+
|
8
|
+
# Restrict to only run on the below platforms (if none were given,
|
9
|
+
# all OS's and cloud API's supported)
|
10
|
+
supports platform: "unix"
|
11
|
+
supports platform: "windows"
|
12
|
+
|
13
|
+
desc "Use the x509_private_key InSpec audit resource to test the x509 private key"
|
14
|
+
|
15
|
+
example <<~EXAMPLE
|
16
|
+
# With passphrase
|
17
|
+
describe x509_private_key("/home/openssl_activity/alice_private.pem", "password@123") do
|
18
|
+
it { should be_valid }
|
19
|
+
it { should be_encrypted }
|
20
|
+
it { should have_matching_certificate("/home/openssl_activity/alice_certificate.crt") }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Without passphrase
|
24
|
+
describe x509_private_key("/home/openssl_activity/bob_private.pem") do
|
25
|
+
it { should be_valid }
|
26
|
+
it { should_not be_encrypted }
|
27
|
+
it { should have_matching_certificate("/home/openssl_activity/bob_certificate.crt") }
|
28
|
+
end
|
29
|
+
EXAMPLE
|
30
|
+
|
31
|
+
# Resource initialization.
|
32
|
+
attr_reader :secret_key_path, :passphrase, :openssl_utility
|
33
|
+
|
34
|
+
def initialize(secret_key_path, passphrase = nil)
|
35
|
+
@openssl_utility = check_openssl_or_error
|
36
|
+
@secret_key_path = secret_key_path
|
37
|
+
@passphrase = passphrase
|
38
|
+
end
|
39
|
+
|
40
|
+
# Resource appearance in test reports.
|
41
|
+
def to_s
|
42
|
+
"x509_private_key"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Matcher to check if the given key is valid.
|
46
|
+
def valid?
|
47
|
+
# Below is the command to check if the key is valid.
|
48
|
+
openssl_key_validity_cmd = "#{openssl_utility} rsa -in #{secret_key_path} -check -noout"
|
49
|
+
|
50
|
+
# Additionally, if key is password protected, passphrase needs to be given with -passin argument
|
51
|
+
openssl_key_validity_cmd.concat(" -passin pass:#{passphrase}") if passphrase
|
52
|
+
|
53
|
+
openssl_key_validity = inspec.command(openssl_key_validity_cmd)
|
54
|
+
openssl_key_validity.exit_status.to_i == 0
|
55
|
+
end
|
56
|
+
|
57
|
+
# Matcher to check if the given key is encrypted.
|
58
|
+
def encrypted?
|
59
|
+
raise Inspec::Exceptions::ResourceFailed, "The given secret key #{secret_key_path} does not exist." unless inspec.file(secret_key_path).exist?
|
60
|
+
|
61
|
+
# All encrypted keys have the header of Proc-Type: 4,ENCRYPTED
|
62
|
+
key_file = inspec.file(secret_key_path)
|
63
|
+
key_file.content =~ /Proc-Type: 4,ENCRYPTED/
|
64
|
+
end
|
65
|
+
|
66
|
+
# Matcher to verify if the private key maatches the certificate
|
67
|
+
def has_matching_certificate?(cert_file_or_path)
|
68
|
+
cert_hash_cmd = "openssl x509 -noout -modulus -in #{cert_file_or_path} | openssl md5"
|
69
|
+
cert_hash = inspec.command(cert_hash_cmd)
|
70
|
+
|
71
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{cert_hash_cmd} failed: #{cert_hash.stderr}" if cert_hash.exit_status.to_i != 0
|
72
|
+
|
73
|
+
key_hash_cmd = "openssl rsa -noout -modulus -in #{secret_key_path}"
|
74
|
+
passphrase ? key_hash_cmd.concat(" -passin pass:#{passphrase} | openssl md5") : key_hash_cmd.concat(" | openssl md5")
|
75
|
+
key_hash = inspec.command(key_hash_cmd)
|
76
|
+
|
77
|
+
raise Inspec::Exceptions::ResourceFailed, "Executing #{key_hash_cmd} failed: #{key_hash.stderr}" if key_hash.exit_status.to_i != 0
|
78
|
+
|
79
|
+
cert_hash.stdout == key_hash.stdout
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
# This resource requires openssl to be available on the system
|
85
|
+
def check_openssl_or_error
|
86
|
+
%w{/usr/sbin/openssl /usr/bin/openssl /sbin/openssl /bin/openssl openssl}.each do |cmd|
|
87
|
+
return cmd if inspec.command(cmd).exist?
|
88
|
+
end
|
89
|
+
|
90
|
+
raise Inspec::Exceptions::ResourceFailed, "Could not find `openssl` on your system."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/inspec/resources/yum.rb
CHANGED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "inspec/resources/zfs_pool"
|
2
|
+
|
3
|
+
module Inspec::Resources
|
4
|
+
class Zfs < ZfsPool
|
5
|
+
# resource's internal name.
|
6
|
+
name "zfs"
|
7
|
+
|
8
|
+
# Restrict to only run on the below platforms
|
9
|
+
supports platform: "unix"
|
10
|
+
|
11
|
+
desc "Use the zfs InSpec audit resource to test if the named ZFS Pool is present and/or has certain properties."
|
12
|
+
|
13
|
+
example <<~EXAMPLE
|
14
|
+
describe zfs("new-pool") do
|
15
|
+
it { should exist }
|
16
|
+
it { should have_property({ "failmode" => "wait", "capacity" => "0" }) }
|
17
|
+
end
|
18
|
+
EXAMPLE
|
19
|
+
|
20
|
+
# Resource initialization is done in the parent class i.e. ZfsPool
|
21
|
+
|
22
|
+
# Unique identity for the resource.
|
23
|
+
def resource_id
|
24
|
+
# @zfs_pool is the zfs pool name assigned during initialization in the parent class i.e. ZfsPool
|
25
|
+
@zfs_pool
|
26
|
+
end
|
27
|
+
|
28
|
+
# Resource appearance in test reports.
|
29
|
+
def to_s
|
30
|
+
"zfs #{resource_id}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# The below matcher checks if the given properties are valid properties of the zfs pool.
|
34
|
+
def has_property?(properties_hash)
|
35
|
+
raise Inspec::Exceptions::ResourceSkipped, "Provide a valid key-value pair of the zfs properties." if properties_hash.empty?
|
36
|
+
|
37
|
+
# Transform all the key & values provided by user to string,
|
38
|
+
# since hash keys can be symbols or strings & values can be integers or strings.
|
39
|
+
# @params is a hash populated in the parent class with the properties(key-value) of the current zfs pool.
|
40
|
+
# and the key-value in @params are of string type.
|
41
|
+
properties_hash.transform_keys(&:to_s)
|
42
|
+
properties_hash.transform_values(&:to_s)
|
43
|
+
|
44
|
+
# check if the given properties is a subset of @params
|
45
|
+
properties_hash <= @params
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/inspec/rule.rb
CHANGED
@@ -358,7 +358,7 @@ module Inspec
|
|
358
358
|
# YAML will automagically give us a Date or a Time.
|
359
359
|
# If transcoding YAML between languages (e.g. Go) the date might have also ended up as a String.
|
360
360
|
# A string that does not represent a valid time results in the date 0000-01-01.
|
361
|
-
if [Date, Time].include?(expiry.class) || (expiry.is_a?(String) && Time.
|
361
|
+
if [Date, Time].include?(expiry.class) || (expiry.is_a?(String) && Time.parse(expiry).year != 0)
|
362
362
|
expiry = expiry.to_time if expiry.is_a? Date
|
363
363
|
expiry = Time.parse(expiry) if expiry.is_a? String
|
364
364
|
if expiry < Time.now # If the waiver expired, return - no skip applied
|
data/lib/inspec/secrets/yaml.rb
CHANGED
@@ -16,7 +16,13 @@ module Secrets
|
|
16
16
|
|
17
17
|
# array of yaml file paths
|
18
18
|
def initialize(target)
|
19
|
-
|
19
|
+
# Ruby 3.1 treats YAML load as a dangerous operation by default, requiring us to declare date and time classes as permitted
|
20
|
+
# It's not a valid option in 3.0.x
|
21
|
+
if Gem.ruby_version >= Gem::Version.new("3.1.0")
|
22
|
+
@inputs = ::YAML.load_file(target, permitted_classes: [Date, Time])
|
23
|
+
else
|
24
|
+
@inputs = ::YAML.load_file(target)
|
25
|
+
end
|
20
26
|
|
21
27
|
if @inputs == false || !@inputs.is_a?(Hash)
|
22
28
|
Inspec::Log.warn("#{self.class} unable to parse #{target}: invalid YAML or contents is not a Hash")
|
data/lib/inspec/ui.rb
CHANGED
@@ -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
@@ -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">
|
@@ -74,7 +74,7 @@
|
|
74
74
|
</table>
|
75
75
|
|
76
76
|
<% control.results.each do |result| %>
|
77
|
-
<%= ERB.new(File.read(template_path + "/result.html.erb"),
|
77
|
+
<%= ERB.new(File.read(template_path + "/result.html.erb"), eoutvar: "_rslt").result(binding) %>
|
78
78
|
<% end %>
|
79
79
|
|
80
80
|
</div>
|
@@ -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>
|
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,161 @@
|
|
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
|
+
artifact_filename = "#{profile_md["name"]}-#{profile_md["version"]}.#{SIGNED_PROFILE_SUFFIX}"
|
63
|
+
|
64
|
+
# Generating tar.gz file using archive method of Inspec Cli
|
65
|
+
Inspec::InspecCLI.new.archive(profile_path, "error")
|
66
|
+
tarfile = "#{profile_md["name"]}-#{profile_md["version"]}.tar.gz"
|
67
|
+
tar_content = IO.binread(tarfile)
|
68
|
+
FileUtils.rm(tarfile)
|
69
|
+
|
70
|
+
# Generate the signature
|
71
|
+
signing_key = KEY_ALG.new File.read keypath
|
72
|
+
sha = ARTIFACT_DIGEST.new
|
73
|
+
signature = signing_key.sign sha, tar_content
|
74
|
+
# convert the signature to Base64
|
75
|
+
signature_base64 = Base64.encode64(signature)
|
76
|
+
|
77
|
+
content = (format("%-100s", options[:keyname]) +
|
78
|
+
format("%-20s", ARTIFACT_DIGEST_NAME) +
|
79
|
+
format("%-370s", signature_base64)).gsub(" ", "\0").unpack("H*").pack("h*") + "#{tar_content}"
|
80
|
+
|
81
|
+
File.open(artifact_filename, "wb") do |f|
|
82
|
+
f.puts INSPEC_PROFILE_VERSION_2
|
83
|
+
f.puts "Use \"inspec export\" to view this file"
|
84
|
+
f.write(content)
|
85
|
+
end
|
86
|
+
puts "Successfully generated #{artifact_filename}"
|
87
|
+
rescue Inspec::Exceptions::ProfileValidationKeyNotFound => e
|
88
|
+
$stderr.puts e.message
|
89
|
+
Inspec::UI.new.exit(:usage_error)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.profile_verify(signed_profile_path)
|
93
|
+
file_to_verify = signed_profile_path
|
94
|
+
puts "Verifying #{file_to_verify}"
|
95
|
+
|
96
|
+
iaf_file = Inspec::IafFile.new(file_to_verify)
|
97
|
+
if iaf_file.valid?
|
98
|
+
puts "Detected format version '#{iaf_file.version}'"
|
99
|
+
puts "Attempting to verify using key '#{iaf_file.key_name}'"
|
100
|
+
puts "Profile is valid."
|
101
|
+
Inspec::UI.new.exit(:normal)
|
102
|
+
else
|
103
|
+
puts "Detected format version '#{iaf_file.version}'"
|
104
|
+
puts "Attempting to verify using key '#{iaf_file.key_name}'" if iaf_file.key_name
|
105
|
+
puts "Profile is invalid"
|
106
|
+
Inspec::UI.new.exit(:bad_signature)
|
107
|
+
end
|
108
|
+
rescue Inspec::Exceptions::ProfileValidationKeyNotFound => e
|
109
|
+
$stderr.puts e.message
|
110
|
+
Inspec::UI.new.exit(:usage_error)
|
111
|
+
end
|
112
|
+
|
113
|
+
def read_profile_metadata(profile_path)
|
114
|
+
begin
|
115
|
+
p = Pathname.new(profile_path)
|
116
|
+
p = p.join("inspec.yml")
|
117
|
+
unless p.exist?
|
118
|
+
raise "#{profile_path} doesn't appear to be a valid #{PRODUCT_NAME} profile"
|
119
|
+
end
|
120
|
+
|
121
|
+
yaml = YAML.load_file(p.to_s)
|
122
|
+
yaml = yaml.to_hash
|
123
|
+
|
124
|
+
unless yaml.key? "name"
|
125
|
+
raise "Profile is invalid, name is not defined"
|
126
|
+
end
|
127
|
+
|
128
|
+
unless yaml.key? "version"
|
129
|
+
raise "Profile is invalid, version is not defined"
|
130
|
+
end
|
131
|
+
rescue => e
|
132
|
+
# rewrap it and pass it up to the CLI
|
133
|
+
$stderr.puts "Error reading profile metadata file #{e.message}"
|
134
|
+
Inspec::UI.new.exit(:usage_error)
|
135
|
+
end
|
136
|
+
|
137
|
+
yaml
|
138
|
+
end
|
139
|
+
|
140
|
+
def write_profile_content_id(profile_path, profile_content_id)
|
141
|
+
p = Pathname.new(profile_path)
|
142
|
+
p = p.join("inspec.yml")
|
143
|
+
yaml = YAML.load_file(p.to_s)
|
144
|
+
existing_profile_content_id = yaml["profile_content_id"]
|
145
|
+
|
146
|
+
unless existing_profile_content_id.nil?
|
147
|
+
ui = Inspec::UI.new
|
148
|
+
ui.error("Cannot use --profile-content-id when profile_content_id already exists in metadata file.")
|
149
|
+
ui.exit(:usage_error)
|
150
|
+
end
|
151
|
+
|
152
|
+
lines = IO.readlines(p)
|
153
|
+
lines << "\nprofile_content_id: #{profile_content_id}\n"
|
154
|
+
|
155
|
+
File.open("#{p}", "w" ) do |f|
|
156
|
+
f.puts lines
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
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
|
@@ -12,7 +12,7 @@ module SourceReaders
|
|
12
12
|
nil
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_reader :metadata, :tests, :libraries, :data_files, :target
|
15
|
+
attr_reader :metadata, :metadata_src, :tests, :libraries, :data_files, :target, :readme
|
16
16
|
|
17
17
|
# This create a new instance of an InSpec profile source reader
|
18
18
|
#
|
@@ -24,14 +24,16 @@ module SourceReaders
|
|
24
24
|
@tests = load_tests
|
25
25
|
@libraries = load_libs
|
26
26
|
@data_files = load_data_files
|
27
|
+
@readme = load_readme
|
27
28
|
end
|
28
29
|
|
29
30
|
private
|
30
31
|
|
31
32
|
def load_metadata(metadata_source)
|
33
|
+
@metadata_src = @target.read(metadata_source)
|
32
34
|
Inspec::Metadata.from_ref(
|
33
35
|
metadata_source,
|
34
|
-
@
|
36
|
+
@metadata_src,
|
35
37
|
nil
|
36
38
|
)
|
37
39
|
rescue Psych::SyntaxError => e
|
@@ -62,5 +64,9 @@ module SourceReaders
|
|
62
64
|
def load_data_files
|
63
65
|
load_all(%r{^files/})
|
64
66
|
end
|
67
|
+
|
68
|
+
def load_readme
|
69
|
+
load_all(/README.md/)
|
70
|
+
end
|
65
71
|
end
|
66
72
|
end
|