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.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/etc/deprecations.json +4 -0
  4. data/etc/keys/progress-2022-05-04.pem.pub +9 -0
  5. data/inspec-core.gemspec +1 -1
  6. data/lib/inspec/base_cli.rb +5 -0
  7. data/lib/inspec/cli.rb +64 -9
  8. data/lib/inspec/dependencies/dependency_set.rb +6 -2
  9. data/lib/inspec/dependency_loader.rb +5 -1
  10. data/lib/inspec/dsl.rb +18 -5
  11. data/lib/inspec/errors.rb +2 -0
  12. data/lib/inspec/exceptions.rb +2 -0
  13. data/lib/inspec/fetcher/url.rb +1 -1
  14. data/lib/inspec/file_provider.rb +36 -0
  15. data/lib/inspec/iaf_file.rb +127 -0
  16. data/lib/inspec/profile.rb +31 -14
  17. data/lib/inspec/resources/aide_conf.rb +4 -0
  18. data/lib/inspec/resources/apache.rb +4 -0
  19. data/lib/inspec/resources/apache_conf.rb +4 -0
  20. data/lib/inspec/resources/apt.rb +6 -1
  21. data/lib/inspec/resources/audit_policy.rb +5 -0
  22. data/lib/inspec/resources/auditd_conf.rb +4 -0
  23. data/lib/inspec/resources/bash.rb +4 -0
  24. data/lib/inspec/resources/bond.rb +4 -0
  25. data/lib/inspec/resources/bridge.rb +4 -0
  26. data/lib/inspec/resources/cassandradb_conf.rb +5 -0
  27. data/lib/inspec/resources/cassandradb_session.rb +8 -3
  28. data/lib/inspec/resources/chocolatey_package.rb +4 -0
  29. data/lib/inspec/resources/chrony_conf.rb +4 -0
  30. data/lib/inspec/resources/command.rb +5 -0
  31. data/lib/inspec/resources/cpan.rb +4 -0
  32. data/lib/inspec/resources/cran.rb +4 -0
  33. data/lib/inspec/resources/cron.rb +5 -0
  34. data/lib/inspec/resources/csv.rb +6 -1
  35. data/lib/inspec/resources/default_gateway.rb +61 -0
  36. data/lib/inspec/resources/dh_params.rb +4 -0
  37. data/lib/inspec/resources/docker_container.rb +4 -0
  38. data/lib/inspec/resources/docker_image.rb +4 -0
  39. data/lib/inspec/resources/docker_plugin.rb +4 -0
  40. data/lib/inspec/resources/docker_service.rb +4 -0
  41. data/lib/inspec/resources/etc_group.rb +4 -0
  42. data/lib/inspec/resources/etc_hosts_allow_deny.rb +5 -0
  43. data/lib/inspec/resources/file.rb +97 -1
  44. data/lib/inspec/resources/filesystem.rb +4 -0
  45. data/lib/inspec/resources/gem.rb +4 -0
  46. data/lib/inspec/resources/groups.rb +9 -0
  47. data/lib/inspec/resources/grub_conf.rb +4 -0
  48. data/lib/inspec/resources/host.rb +46 -3
  49. data/lib/inspec/resources/http.rb +4 -0
  50. data/lib/inspec/resources/ibmdb2_conf.rb +8 -0
  51. data/lib/inspec/resources/ibmdb2_session.rb +12 -3
  52. data/lib/inspec/resources/iis_app.rb +4 -0
  53. data/lib/inspec/resources/iis_app_pool.rb +4 -0
  54. data/lib/inspec/resources/iis_site.rb +4 -0
  55. data/lib/inspec/resources/inetd_conf.rb +4 -0
  56. data/lib/inspec/resources/interface.rb +4 -0
  57. data/lib/inspec/resources/ip6tables.rb +4 -0
  58. data/lib/inspec/resources/ipfilter.rb +4 -0
  59. data/lib/inspec/resources/ipnat.rb +4 -0
  60. data/lib/inspec/resources/iptables.rb +4 -0
  61. data/lib/inspec/resources/json.rb +4 -0
  62. data/lib/inspec/resources/kernel_module.rb +4 -0
  63. data/lib/inspec/resources/kernel_parameter.rb +4 -0
  64. data/lib/inspec/resources/key_rsa.rb +4 -0
  65. data/lib/inspec/resources/ksh.rb +4 -0
  66. data/lib/inspec/resources/limits_conf.rb +4 -0
  67. data/lib/inspec/resources/linux_audit_system.rb +81 -0
  68. data/lib/inspec/resources/login_defs.rb +4 -0
  69. data/lib/inspec/resources/mongodb.rb +4 -0
  70. data/lib/inspec/resources/mongodb_conf.rb +5 -0
  71. data/lib/inspec/resources/mongodb_session.rb +6 -1
  72. data/lib/inspec/resources/mount.rb +4 -0
  73. data/lib/inspec/resources/mssql_session.rb +4 -0
  74. data/lib/inspec/resources/mssql_sys_conf.rb +7 -0
  75. data/lib/inspec/resources/mysql_conf.rb +4 -0
  76. data/lib/inspec/resources/mysql_session.rb +8 -1
  77. data/lib/inspec/resources/nginx.rb +6 -1
  78. data/lib/inspec/resources/nginx_conf.rb +4 -0
  79. data/lib/inspec/resources/noop.rb +4 -0
  80. data/lib/inspec/resources/npm.rb +4 -0
  81. data/lib/inspec/resources/ntp_conf.rb +4 -0
  82. data/lib/inspec/resources/oneget.rb +4 -0
  83. data/lib/inspec/resources/opa_api.rb +10 -0
  84. data/lib/inspec/resources/opa_cli.rb +14 -0
  85. data/lib/inspec/resources/oracledb_conf.rb +5 -0
  86. data/lib/inspec/resources/oracledb_listener_conf.rb +4 -0
  87. data/lib/inspec/resources/oracledb_session.rb +10 -0
  88. data/lib/inspec/resources/os.rb +4 -0
  89. data/lib/inspec/resources/os_env.rb +4 -0
  90. data/lib/inspec/resources/package.rb +4 -0
  91. data/lib/inspec/resources/parse_config.rb +10 -1
  92. data/lib/inspec/resources/php_config.rb +72 -0
  93. data/lib/inspec/resources/pip.rb +4 -0
  94. data/lib/inspec/resources/platform.rb +4 -0
  95. data/lib/inspec/resources/postfix_conf.rb +4 -0
  96. data/lib/inspec/resources/postgres_conf.rb +4 -0
  97. data/lib/inspec/resources/postgres_session.rb +8 -4
  98. data/lib/inspec/resources/powershell.rb +4 -0
  99. data/lib/inspec/resources/processes.rb +17 -4
  100. data/lib/inspec/resources/rabbitmq_config.rb +4 -0
  101. data/lib/inspec/resources/registry_key.rb +4 -0
  102. data/lib/inspec/resources/security_identifier.rb +4 -0
  103. data/lib/inspec/resources/security_policy.rb +4 -0
  104. data/lib/inspec/resources/service.rb +80 -1
  105. data/lib/inspec/resources/ssh_config.rb +4 -0
  106. data/lib/inspec/resources/sybase_conf.rb +4 -0
  107. data/lib/inspec/resources/sybase_session.rb +4 -0
  108. data/lib/inspec/resources/sys_info.rb +4 -0
  109. data/lib/inspec/resources/timezone.rb +4 -0
  110. data/lib/inspec/resources/users.rb +4 -0
  111. data/lib/inspec/resources/vbscript.rb +5 -0
  112. data/lib/inspec/resources/virtualization.rb +4 -0
  113. data/lib/inspec/resources/windows_feature.rb +5 -1
  114. data/lib/inspec/resources/windows_firewall.rb +4 -0
  115. data/lib/inspec/resources/windows_firewall_rule.rb +4 -0
  116. data/lib/inspec/resources/windows_hotfix.rb +4 -0
  117. data/lib/inspec/resources/windows_task.rb +4 -0
  118. data/lib/inspec/resources/wmi.rb +4 -0
  119. data/lib/inspec/resources/x509_certificate.rb +59 -0
  120. data/lib/inspec/resources/x509_private_key.rb +93 -0
  121. data/lib/inspec/resources/yum.rb +4 -0
  122. data/lib/inspec/resources/zfs.rb +48 -0
  123. data/lib/inspec/resources/zfs_dataset.rb +4 -0
  124. data/lib/inspec/resources/zfs_pool.rb +4 -0
  125. data/lib/inspec/rule.rb +1 -1
  126. data/lib/inspec/secrets/yaml.rb +7 -1
  127. data/lib/inspec/ui.rb +1 -0
  128. data/lib/inspec/utils/yaml_profile_summary.rb +34 -0
  129. data/lib/inspec/version.rb +1 -1
  130. data/lib/plugins/inspec-reporter-html2/templates/body.html.erb +4 -4
  131. data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +1 -1
  132. data/lib/plugins/inspec-reporter-html2/templates/profile.html.erb +1 -1
  133. data/lib/plugins/{inspec-artifact/inspec-artifact.gemspec → inspec-sign/inspec-sign.gemspec} +2 -2
  134. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +161 -0
  135. data/lib/plugins/{inspec-artifact/lib/inspec-artifact → inspec-sign/lib/inspec-sign}/cli.rb +14 -23
  136. data/lib/plugins/inspec-sign/lib/inspec-sign.rb +12 -0
  137. data/lib/source_readers/inspec.rb +8 -2
  138. metadata +16 -8
  139. data/lib/plugins/inspec-artifact/lib/inspec-artifact/base.rb +0 -187
  140. 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
@@ -89,6 +89,10 @@ module Inspec::Resources
89
89
  repo(name.to_s) unless name.nil?
90
90
  end
91
91
 
92
+ def resource_id
93
+ "Yum repository"
94
+ end
95
+
92
96
  def to_s
93
97
  "Yum Repository"
94
98
  end
@@ -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
@@ -38,6 +38,10 @@ module Inspec::Resources
38
38
  inspec.mount(@params["mountpoint"]).mounted?
39
39
  end
40
40
 
41
+ def resource_id
42
+ @zfs_dataset || "ZFS Dataset"
43
+ end
44
+
41
45
  def to_s
42
46
  "ZFS Dataset #{@zfs_dataset}"
43
47
  end
@@ -31,6 +31,10 @@ module Inspec::Resources
31
31
  inspec.command("#{@zpool_cmd} get -Hp all #{@zfs_pool}").exit_status == 0
32
32
  end
33
33
 
34
+ def resource_id
35
+ @zfs_pool || "ZFS Pool"
36
+ end
37
+
34
38
  def to_s
35
39
  "ZFS Pool #{@zfs_pool}"
36
40
  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.new(expiry).year != 0)
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
@@ -16,7 +16,13 @@ module Secrets
16
16
 
17
17
  # array of yaml file paths
18
18
  def initialize(target)
19
- @inputs = ::YAML.load_file(target)
19
+ # Ruby 3.1 treats YAML load as a dangerous operation by default, requiring us to declare date and time classes as permitted
20
+ # It's not a valid option in 3.0.x
21
+ if Gem.ruby_version >= Gem::Version.new("3.1.0")
22
+ @inputs = ::YAML.load_file(target, permitted_classes: [Date, Time])
23
+ else
24
+ @inputs = ::YAML.load_file(target)
25
+ end
20
26
 
21
27
  if @inputs == false || !@inputs.is_a?(Hash)
22
28
  Inspec::Log.warn("#{self.class} unable to parse #{target}: invalid YAML or contents is not a Hash")
data/lib/inspec/ui.rb CHANGED
@@ -31,6 +31,7 @@ module Inspec
31
31
  EXIT_PLUGIN_ERROR = 2
32
32
  EXIT_FATAL_DEPRECATION = 3
33
33
  EXIT_GEM_DEPENDENCY_LOAD_ERROR = 4
34
+ EXIT_BAD_SIGNATURE = 5
34
35
  EXIT_LICENSE_NOT_ACCEPTED = 172
35
36
  EXIT_FAILED_TESTS = 100
36
37
  EXIT_SKIPPED_TESTS = 101
@@ -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.12.2".freeze
2
+ VERSION = "5.18.14".freeze
3
3
  end
@@ -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">
@@ -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"), nil, nil, "_rslt").result(binding) %>
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"), 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>
@@ -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,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 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
@@ -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
- @target.read(metadata_source),
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