CHEF-6439 Mandatory Profile Signing (Preview) (#6705)

* Updated exec option to allow unsigned profiles run

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Added method to verify signed profile and to check for signed profile

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Invoked logic on each run to verify profiles if signed else raise sig req error

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Tests cases added to validate behaviour of inspec exec with signed and unsigned profiles with --chef-allow-unsigned flag

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Refactored and moved delete_signing_keys to common helper library for tests

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Updated code comments for more information and clarity on security update of signed profiles inspec exec

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Test cases to validate inspec run with combination of signed and unsigned profiles

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Documented usage of flag --chef-allow-unsigned

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Renamed the flag to run unsigned profiles to --allow-unsigned

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Refactored logic on profile level for profile signing verification

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Renaming the argument variable - from runner_call to silent

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Added profile mandate check for other inspec commands running profile evaluation

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Updated error message for profile sign requirement

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Updated test helper to fix inspec json test

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Fixed inspec json ability to use cli options successfully

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Documentation added for signed profiles mandatory usage with CLI commands

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Flow changes of raising exception when unsigned instead of direct exit

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Renamed unsigned profile flags

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Extracted out allow unsigned condition to config and modified comment info

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Doc update on consent of using signed and unsigned profiles

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Fix in signing mandatin check and added additional check on runner for better error UI for exec command

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Removed repeated allow-unsigned-profile defination from exec_options

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Test fixes

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Enabled feature preview flag for mandatory signing

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Test fixes after feature flag usage for mandatory signing

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Doc changes using feature preview flag for mandatory signing feature

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Inspec exec tests fixes for ENV values and parallel test fix using default option --allow-unsigned-profile false

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Kitchen fix while using signed profiles with inspec

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Unit test fix for profile resource exception

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Virtual profile detection improved

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Move mandatory profile sigining info to sigining page

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>

* Renamed flag from --allow-unsigned-profile to --allow-unsigned-profiles

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Typo fix in signing doc

Signed-off-by: Nik08 <nikita.mathur@progress.com>

* Trim note in cli.md about mandatory profile signing

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>

* Docs changes

Signed-off-by: Ian Maddaus <ian.maddaus@progress.com>

* Correct docs regarding exit code 5

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>

---------

Signed-off-by: Nik08 <nikita.mathur@progress.com>
Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
Signed-off-by: Ian Maddaus <ian.maddaus@progress.com>
Co-authored-by: Clinton Wolfe <clintoncwolfe@gmail.com>
Co-authored-by: Ian Maddaus <ian.maddaus@progress.com>
This commit is contained in:
Nikita Mathur 2023-11-09 18:50:43 +05:30 committed by GitHub
parent 1a87a2a588
commit fd4e6d97a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 289 additions and 32 deletions

View file

@ -281,18 +281,22 @@ exit codes:
1 usage or general error 1 usage or general error
2 error in plugin system 2 error in plugin system
3 fatal deprecation encountered 3 fatal deprecation encountered
5 invalid profile signature
6 mandatory profile signing mode enabled and no signature found
100 normal exit, at least one test failed 100 normal exit, at least one test failed
101 normal exit, at least one test skipped but none failed 101 normal exit, at least one test skipped but none failed
172 chef license not accepted 172 chef license not accepted
``` ```
Below are some examples of using `exec` with different test locations: ### Examples
Below are some examples of using `exec` with different test locations.
Chef Automate: Chef Automate:
```ruby ```ruby
inspec automate login inspec automate login
inspec exec compliance://username/linux-baseline inspec exec compliance://username/linux-baselinem
``` ```
`inspec compliance` is a backwards compatible alias for `inspec automate` and works the same way: `inspec compliance` is a backwards compatible alias for `inspec automate` and works the same way:
@ -358,6 +362,12 @@ Web-hosted file with basic authentication (supports .zip):
inspec exec https://username:password@webserver/linux-baseline.tar.gz inspec exec https://username:password@webserver/linux-baseline.tar.gz
``` ```
Web-hosted signed profile:
```bash
inspec exec https://username:password@webserver/linux-baseline.iaf
```
### Syntax ### Syntax
This subcommand has the following syntax: This subcommand has the following syntax:
@ -370,6 +380,15 @@ inspec exec LOCATIONS
This subcommand has the following additional options: This subcommand has the following additional options:
`--allow-unsigned-profiles`
: Allow InSpec to execute unsigned profiles if mandatory profile signing is enabled. Defaults to false.
**Chef InSpec 6** and greater has an optional setting that requires signed profiles.
If you try to execute an unsigned profile with this feature enabled, InSpec won't execute the profile and returns exit code 6.
Use `--allow-unsigned-profiles` to execute unsigned profiles if mandatory profile signing is enabled.
For more information, see [Signed InSpec Profiles](/inspec/signing/).
`--attrs=one two three` `--attrs=one two three`
: Legacy name for --input-file - deprecated. : Legacy name for --input-file - deprecated.
@ -655,6 +674,15 @@ inspec json PATH
This subcommand has the following additional options: This subcommand has the following additional options:
`--allow-unsigned-profiles`
: Allow InSpec to read unsigned profiles if [mandatory profile signing](/inspec/signing/) is enabled. Defaults to false.
**Chef InSpec 6** and greater has an optional setting that requires signed profiles.
If you try to read an unsigned profile with this feature enabled, InSpec won't read the profile and returns exit code 6.
Use `--allow-unsigned-profiles` to read unsigned profiles if mandatory profile signing is enabled.
For more information, see [Signed InSpec Profiles](/inspec/signing/).
`--controls=one two three` `--controls=one two three`
: A list of controls to include. Ignore all other tests. : A list of controls to include. Ignore all other tests.

View file

@ -11,7 +11,7 @@ gh_repo = "inspec"
weight = 60 weight = 60
+++ +++
This page documents the `inspec sign` command introduced in InSpec 5 and details some methods to work with signed profiles. This page documents the `inspec sign` command introduced in InSpec 5, the mandatory profile signing feature introduced in InSpec 6, and details some methods to work with signed profiles.
## Usage ## Usage
@ -19,6 +19,17 @@ This page documents the `inspec sign` command introduced in InSpec 5 and details
A signed profile, or `.iaf` file, is an InSpec profile with a digital signature that attests to its authenticity. Progress Chef authored profiles are available as signed profiles starting from 2022. A signed profile, or `.iaf` file, is an InSpec profile with a digital signature that attests to its authenticity. Progress Chef authored profiles are available as signed profiles starting from 2022.
IAF files are not human-readable, but may be viewed using `inspec export`. Support for IAF v2.0 was added to InSpec 5.
### Mandatory profile signing
**Chef InSpec 6** and above has an optional setting that requires that all profiles are signed.
If mandatory profile signing is enabled, InSpec will not execute functions with an un-signed profile and exits with exit code 6.
To enable mandatory profile signing, set the environment variable `CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING` to any non-empty value.
If you need to bypass mandatory profile signing, use the `--allow-unsigned-profiles` CLI option or set the `CHEF_ALLOW_UNSIGNED_PROFILES` environment variable.
### How does Profile Signing Work? ### How does Profile Signing Work?
Profile signing uses a matched pair of keys. The _signing key_ is secret and is used to sign the profile. The _validation key_ is widely distributed and verifies the signed profile signature. Profile signing uses a matched pair of keys. The _signing key_ is secret and is used to sign the profile. The _validation key_ is widely distributed and verifies the signed profile signature.

View file

@ -11,6 +11,22 @@ gh_repo = "inspec"
weight = 55 weight = 55
+++ +++
## Exit code 5
You tried to execute a function with a signed profile, but the signature is either bad or InSpec couldn't find the validation key.
For more information, see the [profile signing documentation](/inspec/signing/).
## Exit code 6
You enabled mandatory profile signing and tried to execute a function with an unsigned profile.
For more information, see the [profile signing documentation](/inspec/signing/).
## Exit code 174
Exit code 174 comes from running Chef InSpec 6 or greater without setting a Chef License key.
See the [InSpec install documentation](/inspec/install/) for setting a Chef License key.
See the [Chef License documentation](/licensing/) for more information about Chef licensing.
## Undefined Local Variable or Method Error for Cloud Resource ## Undefined Local Variable or Method Error for Cloud Resource
This error is a result of invoking a resource from one of the cloud resource packs without initializing an InSpec profile with that resource pack (AWS, Azure, or GCP) as a dependency. This error is a result of invoking a resource from one of the cloud resource packs without initializing an InSpec profile with that resource pack (AWS, Azure, or GCP) as a dependency.
@ -23,12 +39,6 @@ See the relevant resource pack readme for instructions:
- [inspec-azure README](https://github.com/inspec/inspec-azure#use-the-resources) - [inspec-azure README](https://github.com/inspec/inspec-azure#use-the-resources)
- [inspec-gcp README](https://github.com/inspec/inspec-gcp#use-the-resources) - [inspec-gcp README](https://github.com/inspec/inspec-gcp#use-the-resources)
## Exit Code 174
Exit code 174 comes from running Chef InSpec 6 or greater without setting a Chef License key.
See the [InSpec install documentation](/inspec/install/) for setting a Chef License key.
See the [Chef License documentation](/licensing/) for more information about Chef licensing.
## License is not entitled to use InSpec ## License is not entitled to use InSpec
The license key set with Chef InSpec is not entitled to use Chef InSpec. Each license key is entitled to one or more Chef products. To view the products that your key is entitled to, run the `inspec license list` command, which will list your license entitlements. The license key set with Chef InSpec is not entitled to use Chef InSpec. Each license key is entitled to one or more Chef products. To view the products that your key is entitled to, run the `inspec license list` command, which will list your license entitlements.

View file

@ -1,6 +1,6 @@
wNEzKHmtSf1pIdciEC6DOs5SlOs3IbW1psVFLlmZc0NbnHe6MEahAnKWmHUP ouQqvzkBxmM7J1cDE6/gsw8bE01+o2XFfDQoobG+n91yyCw0zjRzOUZ/NH+j
9YrDv2JMQo1I8MM/cez8XDxpK4O5y4HT66RqoAlfBkg82LmYC7f1Cy34ByCj xZwB07D8+NNZQ4kYXMCkGXRQqZGq9nasIFfgwMjA4tWTWaE9i3jcL2Q3HOUF
LBZg5o/IVBGnY+Ksbhtp0mQYEyU048FnXIfh9uOfbKahU8HkPJssTkIw3fjL aZ55wGtr5gpQc+pzQktFGRnC6ChmUgCzO2xTpl/h7TlNWIFU3INF8e2QnGqW
Vrd5GQ4ssfW1XXFaxx7DjxWlPmWBVhd8c1Y2RlACZyI+w1DQNYimrWvgiFym +2Xe7KDhY+4cTYKu9PSWfo6cBv71N/t2fbRTYyeCBoukav3fOioitO0aGiWv
0VbnndiSX+2x84AZHE9AmsebcAYk9QlqO1N0VeYqBZj45FXLtpsNwYo0amDa RrR8HSomvEQE+9yQanxkyiSjPW93/SCI2rjNq9ouIGXYzTchRkgxH+hZXV1V
D/wyKGxRQLUYXyd2tDVJMWbeHPHy8UIK17RoSctrEg== CJw+H5I+k9sx3I8on5eXK3Oa0Bd3zGetrXFfQLu83A==

View file

@ -85,4 +85,7 @@
inspec-reporter-progress-bar: inspec-reporter-progress-bar:
description: Use progress bar streaming reporter description: Use progress bar streaming reporter
inspec-reporter-child-status: inspec-reporter-child-status:
description: Child status reporter used in inspec parallel reporting. description: Child status reporter used in inspec parallel reporting.
inspec-mandatory-profile-signing:
description: Required to use a signed Inspec profile by default with inspec commands
env_preview: true

View file

@ -171,6 +171,8 @@ module Inspec
desc: "Use the given path for caching dependencies, (default: ~/.inspec/cache)." desc: "Use the given path for caching dependencies, (default: ~/.inspec/cache)."
option :auto_install_gems, type: :boolean, default: false, option :auto_install_gems, type: :boolean, default: false,
desc: "Auto installs gem dependencies of the profile or resource pack." desc: "Auto installs gem dependencies of the profile or resource pack."
option :allow_unsigned_profiles, type: :boolean, default: false,
desc: "Allow unsigned profiles to be used in InSpec command."
end end
def self.supermarket_options def self.supermarket_options
@ -373,6 +375,9 @@ module Inspec
def pretty_handle_exception(exception) def pretty_handle_exception(exception)
case exception case exception
when Inspec::ProfileSignatureRequired
$stderr.puts exception.message
Inspec::UI.new.exit(:signature_required)
when Inspec::InvalidProfileSignature when Inspec::InvalidProfileSignature
$stderr.puts exception.message $stderr.puts exception.message
Inspec::UI.new.exit(:bad_signature) Inspec::UI.new.exit(:bad_signature)

View file

@ -80,6 +80,10 @@ module Inspec
puts puts
end end
def allow_unsigned_profiles?
self["allow_unsigned_profiles"] || ENV["CHEF_ALLOW_UNSIGNED_PROFILES"]
end
# return all telemetry options from config # return all telemetry options from config
# @return [Hash] # @return [Hash]
def telemetry_options def telemetry_options

View file

@ -27,4 +27,6 @@ module Inspec
class FeatureConfigMissingError < Error; end class FeatureConfigMissingError < Error; end
class FeatureConfigTamperedError < Error; end class FeatureConfigTamperedError < Error; end
class ProfileSignatureRequired < Error; end
end end

View file

@ -16,6 +16,7 @@ require "inspec/utils/json_profile_summary"
require "inspec/dependency_loader" require "inspec/dependency_loader"
require "inspec/dependency_installer" require "inspec/dependency_installer"
require "inspec/utils/profile_ast_helpers" require "inspec/utils/profile_ast_helpers"
require "plugins/inspec-sign/lib/inspec-sign/base"
module Inspec module Inspec
class Profile class Profile
@ -82,7 +83,7 @@ module Inspec
end end
attr_reader :source_reader, :backend, :runner_context, :check_mode attr_reader :source_reader, :backend, :runner_context, :check_mode
attr_accessor :parent_profile, :profile_id, :profile_name attr_accessor :parent_profile, :profile_id, :profile_name, :target
def_delegator :@source_reader, :tests def_delegator :@source_reader, :tests
def_delegator :@source_reader, :libraries def_delegator :@source_reader, :libraries
def_delegator :@source_reader, :metadata def_delegator :@source_reader, :metadata
@ -181,6 +182,26 @@ module Inspec
@state == :failed @state == :failed
end end
def verify_if_signed
if signed?
verify_signed_profile
true
else
false
end
end
def signed?
# Signed profiles have .iaf extension
(@source_reader&.target&.parent&.class == Inspec::IafProvider)
end
def verify_signed_profile
# Kitchen inspec send target profile in Hash format in some scenarios. For example: While using local profile with kitchen, {:path => "path/to/kitchen/lcoal-profile"}
target_profile = target.is_a?(Hash) ? target.values[0] : target
InspecPlugins::Sign::Base.profile_verify(target_profile, true) if target_profile
end
# #
# Is this profile is supported on the current platform of the # Is this profile is supported on the current platform of the
# backend machine and the current inspec version. # backend machine and the current inspec version.
@ -215,8 +236,30 @@ module Inspec
@params ||= load_params @params ||= load_params
end end
def virtual_profile?
# A virtual profile is for virtual profile evaluation
# Used by shell & inspec detect command.
(name == "inspec-shell") && (files&.length == 1) && (files[0] == "inspec.yml")
end
def collect_tests def collect_tests
unless @tests_collected || failed? unless @tests_collected || failed?
# This is that one common place in InSpec engine which is used to collect tests of InSpec profile
# One common place used by most of the CLI commands using profile, like exec, export etc
# Checking for profile signature in parent profile only
# Child profiles of a signed profile are extracted to cache dir
# Hence they are not in .iaf format
# Only runs this block when preview flag CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING is set
Inspec.with_feature("inspec-mandatory-profile-signing") {
if !parent_profile && !virtual_profile?
cfg = Inspec::Config.cached
if cfg.is_a?(Inspec::Config) && !cfg.allow_unsigned_profiles?
raise Inspec::ProfileSignatureRequired, "Signature required for profile: #{name}. Please provide a signed profile. Or set CHEF_ALLOW_UNSIGNED_PROFILES in the environment. Or use `--allow-unsigned-profiles` flag with InSpec CLI." unless verify_if_signed
end
end
}
return unless supports_platform? return unless supports_platform?
locked_dependencies.each(&:collect_tests) locked_dependencies.each(&:collect_tests)

View file

@ -163,6 +163,16 @@ module Inspec
def run(with = nil) def run(with = nil)
ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec" ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
# Validate if profiles are signed and verified
# Additional check is required to provide error message in case of inspec exec command (exec command can use multiple profiles as well)
# Only runs this block when preview flag CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING is set
Inspec.with_feature("inspec-mandatory-profile-signing") {
unless @conf.allow_unsigned_profiles?
verify_target_profiles_if_signed(@target_profiles)
end
}
Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}" Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
load load
run_tests(with) run_tests(with)
@ -174,6 +184,14 @@ module Inspec
Inspec::UI.new.exit(:usage_error) Inspec::UI.new.exit(:usage_error)
end end
def verify_target_profiles_if_signed(target_profiles)
unsigned_profiles = []
target_profiles.each do |profile|
unsigned_profiles << profile.name unless profile.verify_if_signed
end
raise Inspec::ProfileSignatureRequired, "Signature required for profile/s: #{unsigned_profiles.join(", ")}. Please provide a signed profile. Or set CHEF_ALLOW_UNSIGNED_PROFILES in the environment. Or use `--allow-unsigned-profiles` flag with InSpec CLI. " unless unsigned_profiles.empty?
end
def render_output(run_data) def render_output(run_data)
return if @conf["reporter"].nil? return if @conf["reporter"].nil?

View file

@ -32,6 +32,7 @@ module Inspec
EXIT_FATAL_DEPRECATION = 3 EXIT_FATAL_DEPRECATION = 3
EXIT_GEM_DEPENDENCY_LOAD_ERROR = 4 EXIT_GEM_DEPENDENCY_LOAD_ERROR = 4
EXIT_BAD_SIGNATURE = 5 EXIT_BAD_SIGNATURE = 5
EXIT_SIGNATURE_REQUIRED = 6
EXIT_LICENSE_NOT_ACCEPTED = 172 EXIT_LICENSE_NOT_ACCEPTED = 172
EXIT_LICENSE_NOT_ENTITLED = 173 EXIT_LICENSE_NOT_ENTITLED = 173
EXIT_LICENSE_NOT_SET = 174 EXIT_LICENSE_NOT_SET = 174

View file

@ -3,6 +3,7 @@ require "fileutils" unless defined?(FileUtils)
require "minitest/autorun" require "minitest/autorun"
require "inspec/backend" require "inspec/backend"
require_relative "../../lib/inspec-habitat/profile" require_relative "../../lib/inspec-habitat/profile"
require "inspec/feature"
class InspecPlugins::Habitat::ProfileTest < Minitest::Test class InspecPlugins::Habitat::ProfileTest < Minitest::Test
def setup def setup

View file

@ -84,8 +84,8 @@ class ParallelCli < Minitest::Test
def test_parallel_dry_run_with_verbose_option def test_parallel_dry_run_with_verbose_option
out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_1} --dry-run --verbose") out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_1} --dry-run --verbose")
stdout = out.stdout stdout = out.stdout
assert_includes stdout, "complete-profile -t ssh://vagrant@127.0.0.1:2201 --reporter child-status cli:myfile.out --no-create-lockfile --no-sudo --winrm-transport negotiate --insecure false --winrm-shell-type powershell --auto-install-gems false --distinct-exit true --diff true --sort-results-by file --filter-empty-profiles false --reporter-include-source false" assert_includes stdout, "complete-profile -t ssh://vagrant@127.0.0.1:2201 --reporter child-status cli:myfile.out --no-create-lockfile --no-sudo --winrm-transport negotiate --insecure false --winrm-shell-type powershell --auto-install-gems false --allow-unsigned-profiles false --distinct-exit true --diff true --sort-results-by file --filter-empty-profiles false --reporter-include-source false"
assert_includes stdout, "control-tags -t ssh://vagrant@127.0.0.1:2201 --reporter child-status cli:myfile.out --winrm-transport negotiate --insecure false --winrm-shell-type powershell --auto-install-gems false --distinct-exit true --diff true --sort-results-by file --filter-empty-profiles false --reporter-include-source false" assert_includes stdout, "control-tags -t ssh://vagrant@127.0.0.1:2201 --reporter child-status cli:myfile.out --winrm-transport negotiate --insecure false --winrm-shell-type powershell --auto-install-gems false --allow-unsigned-profiles false --distinct-exit true --diff true --sort-results-by file --filter-empty-profiles false --reporter-include-source false"
assert_equal stdout.split("\n").count, 6 assert_equal stdout.split("\n").count, 6
assert_exit_code 0, out assert_exit_code 0, out
end end

View file

@ -92,12 +92,16 @@ module InspecPlugins
Inspec::UI.new.exit(:usage_error) Inspec::UI.new.exit(:usage_error)
end end
def self.profile_verify(signed_profile_path) def self.profile_verify(signed_profile_path, silent = false)
file_to_verify = signed_profile_path file_to_verify = signed_profile_path
puts "Verifying #{file_to_verify}" puts "Verifying #{file_to_verify}" unless silent
iaf_file = Inspec::IafFile.new(file_to_verify) iaf_file = Inspec::IafFile.new(file_to_verify)
if iaf_file.valid? if iaf_file.valid?
# Signed profile verification is called from runner and not from CLI
# Do not exit and do not print logs
return if silent
puts "Detected format version '#{iaf_file.version}'" puts "Detected format version '#{iaf_file.version}'"
puts "Attempting to verify using key '#{iaf_file.key_name}'" puts "Attempting to verify using key '#{iaf_file.key_name}'"
puts "Profile is valid." puts "Profile is valid."

View file

@ -1,6 +1,7 @@
require "fileutils" unless defined?(FileUtils) require "fileutils" unless defined?(FileUtils)
require "plugins/shared/core_plugin_test_helper" require "plugins/shared/core_plugin_test_helper"
require "securerandom" unless defined?(SecureRandom) require "securerandom" unless defined?(SecureRandom)
require "functional/helper"
class SignCli < Minitest::Test class SignCli < Minitest::Test
include CorePluginFunctionalHelper include CorePluginFunctionalHelper
@ -17,7 +18,7 @@ class SignCli < Minitest::Test
assert_includes stdout, "Generating validation key" assert_includes stdout, "Generating validation key"
assert_exit_code 0, out assert_exit_code 0, out
delete_keys(unique_key_name) delete_signing_keys(unique_key_name)
end end
end end
@ -42,7 +43,7 @@ class SignCli < Minitest::Test
assert_includes out.stdout.force_encoding(Encoding::UTF_8), "Verifying artifact-profile-0.1.0.iaf" assert_includes out.stdout.force_encoding(Encoding::UTF_8), "Verifying artifact-profile-0.1.0.iaf"
assert_exit_code 0, out assert_exit_code 0, out
delete_keys(unique_key_name) delete_signing_keys(unique_key_name)
end end
end end
@ -67,12 +68,7 @@ class SignCli < Minitest::Test
assert_includes out.stdout.force_encoding(Encoding::UTF_8), "Verifying artifact-profile-5_3-0.1.0.iaf" assert_includes out.stdout.force_encoding(Encoding::UTF_8), "Verifying artifact-profile-5_3-0.1.0.iaf"
assert_exit_code 0, out assert_exit_code 0, out
delete_keys(unique_key_name) delete_signing_keys(unique_key_name)
end end
end end
def delete_keys(unique_key_name)
File.delete("#{Inspec.config_dir}/keys/#{unique_key_name}.pem.key") if File.exist?("#{Inspec.config_dir}/keys/#{unique_key_name}.pem.key")
File.delete("#{Inspec.config_dir}/keys/#{unique_key_name}.pem.pub") if File.exist?("#{Inspec.config_dir}/keys/#{unique_key_name}.pem.pub")
end
end end

View file

@ -186,8 +186,9 @@ module FunctionalHelper
prefix += opts[:prefix] || "" prefix += opts[:prefix] || ""
prefix += assemble_env_prefix(opts[:env]) prefix += assemble_env_prefix(opts[:env])
command_line += " --reporter json " if opts[:json] && command_line =~ /\bexec\b/ command_line += " --reporter json " if opts[:json] && command_line =~ /\bexec\b/
command_line += " --no-create-lockfile " if (!opts[:lock]) && command_line =~ /\bexec\b/
command_line += " --enhanced_outcomes " if opts[:enhanced_outcomes] && command_line =~ /\bexec\b/ command_line += " --enhanced_outcomes " if opts[:enhanced_outcomes] && command_line =~ /\bexec\b/
command_line += " --allow-unsigned-profiles #{opts[:allow_unsigned_profiles]}" if opts[:allow_unsigned_profiles]
command_line += " --no-create-lockfile" if (!opts[:lock]) && command_line =~ /\bexec\b/
run_result = nil run_result = nil
if opts[:tmpdir] if opts[:tmpdir]
@ -256,6 +257,11 @@ module FunctionalHelper
end end
end end
def delete_signing_keys(unique_key_name)
File.delete("#{Inspec.config_dir}/keys/#{unique_key_name}.pem.key") if File.exist?("#{Inspec.config_dir}/keys/#{unique_key_name}.pem.key")
File.delete("#{Inspec.config_dir}/keys/#{unique_key_name}.pem.pub") if File.exist?("#{Inspec.config_dir}/keys/#{unique_key_name}.pem.pub")
end
private private
def assemble_env_prefix(env = {}) def assemble_env_prefix(env = {})

View file

@ -1479,4 +1479,127 @@ EOT
_(run_result.stdout).must_include ":status: passed" _(run_result.stdout).must_include ":status: passed"
end end
end end
describe "Signed profile mandatory feature" do
it "should be able to run a single signed profile successfully" do
prepare_examples do |dir|
skip_windows! # Breakage confirmed, only on CI: https://buildkite.com/chef-oss/inspec-inspec-master-verify/builds/2355#2c9d032e-4a24-4e7c-aef2-1c9e2317d9e2
unique_key_name = SecureRandom.uuid
# Create profile
profile = File.join(dir, "artifact-profile")
run_inspec_process("init profile artifact-profile", prefix: "cd #{dir};")
# Generate key to sign profile
run_inspec_process("sign generate-keys --keyname #{unique_key_name}", prefix: "cd #{dir};")
# Sign profile
run_inspec_process("sign profile #{profile} --keyname #{unique_key_name}", prefix: "cd #{dir};")
# Run inspec exec on signed profiles with allow_unsigned_profiles false (default behaviour)
run_result = run_inspec_process("exec artifact-profile-0.1.0.iaf", prefix: "cd #{dir};", allow_unsigned_profiles: false, env: { CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING: "1" })
_(run_result.stdout).must_include "1 successful control"
_(run_result.exit_status).must_equal 0
delete_signing_keys(unique_key_name)
end
end
it "should be able to run multiple signed profiles successfully" do
prepare_examples do |dir|
skip_windows! # Breakage confirmed, only on CI: https://buildkite.com/chef-oss/inspec-inspec-master-verify/builds/2355#2c9d032e-4a24-4e7c-aef2-1c9e2317d9e2
unique_key_name = SecureRandom.uuid
# Create 2 profiles
profile_1 = File.join(dir, "artifact-profile-1")
run_inspec_process("init profile artifact-profile-1", prefix: "cd #{dir};")
profile_2 = File.join(dir, "artifact-profile-2")
run_inspec_process("init profile artifact-profile-2", prefix: "cd #{dir};")
# Generate key to sign profile
run_inspec_process("sign generate-keys --keyname #{unique_key_name}", prefix: "cd #{dir};")
# Sign both the profiles
run_inspec_process("sign profile #{profile_1} --keyname #{unique_key_name}", prefix: "cd #{dir};")
run_inspec_process("sign profile #{profile_2} --keyname #{unique_key_name}", prefix: "cd #{dir};")
# Run inspec exec on both the signed profiles with allow_unsigned_profiles false (default behaviour)
run_result = run_inspec_process("exec artifact-profile-1-0.1.0.iaf artifact-profile-2-0.1.0.iaf", prefix: "cd #{dir};", allow_unsigned_profiles: false, env: { CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING: "1" })
_(run_result.stdout).must_include "2 successful controls"
_(run_result.exit_status).must_equal 0
delete_signing_keys(unique_key_name)
end
end
it "should raise signature required error for single unsigned profile without flag --allow-unsigned-profiles" do
run_result = run_inspec_process("exec #{complete_profile} --no-create-lockfile", allow_unsigned_profiles: false, env: { CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING: "1" })
_(run_result.stderr).must_include "Signature required"
_(run_result.stderr).must_include "profile/s: complete"
_(run_result.exit_status).must_equal 6
end
it "should raise signature required error for multiple unsigned profiles without flag --allow-unsigned-profiles" do
run_result = run_inspec_process("exec #{complete_profile} #{inheritance_profile} --no-create-lockfile", allow_unsigned_profiles: false, env: { CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING: "1" })
_(run_result.stderr).must_include "Signature required"
_(run_result.stderr).must_include "profile/s: complete, inheritance"
_(run_result.exit_status).must_equal 6
end
it "when running combination of signed and unsigned profile without flag --allow-unsigned-profiles should raise signature required error and exit" do
prepare_examples do |dir|
skip_windows! # Breakage confirmed, only on CI: https://buildkite.com/chef-oss/inspec-inspec-master-verify/builds/2355#2c9d032e-4a24-4e7c-aef2-1c9e2317d9e2
unique_key_name = SecureRandom.uuid
# Create profile
profile = File.join(dir, "artifact-profile")
run_inspec_process("init profile artifact-profile", prefix: "cd #{dir};")
# Generate key to sign profile
run_inspec_process("sign generate-keys --keyname #{unique_key_name}", prefix: "cd #{dir};")
# Sign profile
run_inspec_process("sign profile #{profile} --keyname #{unique_key_name}", prefix: "cd #{dir};")
# Run inspec exec on combination of a signed profile and an unsigned profile with allow_unsigned_profiles false (default behaviour)
run_result = run_inspec_process("exec #{complete_profile} artifact-profile-0.1.0.iaf", prefix: "cd #{dir};", allow_unsigned_profiles: false, env: { CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING: "1" })
_(run_result.stderr).must_include "Signature required"
_(run_result.stderr).must_include "profile/s: complete"
_(run_result.exit_status).must_equal 6
delete_signing_keys(unique_key_name)
end
end
it "when running combination of signed and unsigned profile with flag --allow-unsigned-profiles should run successfully without raising signature required error" do
prepare_examples do |dir|
skip_windows! # Breakage confirmed, only on CI: https://buildkite.com/chef-oss/inspec-inspec-master-verify/builds/2355#2c9d032e-4a24-4e7c-aef2-1c9e2317d9e2
unique_key_name = SecureRandom.uuid
# Create profile
profile = File.join(dir, "artifact-profile")
run_inspec_process("init profile artifact-profile", prefix: "cd #{dir};")
# Generate key to sign profile
run_inspec_process("sign generate-keys --keyname #{unique_key_name}", prefix: "cd #{dir};")
# Sign profile
run_inspec_process("sign profile #{profile} --keyname #{unique_key_name}", prefix: "cd #{dir};")
# Run inspec exec on combination of a signed profile and an unsigned profile with allow_unsigned_profiles true
run_result = run_inspec_process("exec #{complete_profile} artifact-profile-0.1.0.iaf", prefix: "cd #{dir};", allow_unsigned_profiles: true, env: { CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING: "1" })
_(run_result.stdout).must_include "2 successful controls"
_(run_result.exit_status).must_equal 0
delete_signing_keys(unique_key_name)
end
end
end
end end

View file

@ -3,6 +3,7 @@ require "inspec/config"
require "inspec/profile" require "inspec/profile"
require "inspec/runner_mock" require "inspec/runner_mock"
require "inspec/fetcher/mock" require "inspec/fetcher/mock"
require "inspec/feature"
describe "controls" do describe "controls" do
def load(content) def load(content)
@ -15,8 +16,7 @@ describe "controls" do
backend: Inspec::Backend.create(Inspec::Config.mock), backend: Inspec::Backend.create(Inspec::Config.mock),
} }
Inspec::Profile.for_target(data, opts) Inspec::Profile.for_target(data, opts).params[:controls]["1"]
.params[:controls]["1"]
end end
let(:rand_string) { rand.to_s } let(:rand_string) { rand.to_s }

View file

@ -1,6 +1,7 @@
require "helper" require "helper"
require "inspec/profile_context" require "inspec/profile_context"
require "inspec/runner_mock" require "inspec/runner_mock"
require "inspec/feature"
describe "resource exception" do describe "resource exception" do
let(:profile) do let(:profile) do

View file

@ -4,6 +4,7 @@ require "inspec/runner_mock"
require "inspec/resource" require "inspec/resource"
require "inspec/resources/command" require "inspec/resources/command"
require "inspec/profile" require "inspec/profile"
require "inspec/feature"
describe Inspec::Profile do describe Inspec::Profile do
let(:logger) { Minitest::Mock.new } let(:logger) { Minitest::Mock.new }