From fd4e6d97a6422e1f8700e9fb98fb2aa15333a9c9 Mon Sep 17 00:00:00 2001 From: Nikita Mathur Date: Thu, 9 Nov 2023 18:50:43 +0530 Subject: [PATCH] CHEF-6439 Mandatory Profile Signing (Preview) (#6705) * Updated exec option to allow unsigned profiles run Signed-off-by: Nik08 * Added method to verify signed profile and to check for signed profile Signed-off-by: Nik08 * Invoked logic on each run to verify profiles if signed else raise sig req error Signed-off-by: Nik08 * Tests cases added to validate behaviour of inspec exec with signed and unsigned profiles with --chef-allow-unsigned flag Signed-off-by: Nik08 * Refactored and moved delete_signing_keys to common helper library for tests Signed-off-by: Nik08 * Updated code comments for more information and clarity on security update of signed profiles inspec exec Signed-off-by: Nik08 * Test cases to validate inspec run with combination of signed and unsigned profiles Signed-off-by: Nik08 * Documented usage of flag --chef-allow-unsigned Signed-off-by: Nik08 * Renamed the flag to run unsigned profiles to --allow-unsigned Signed-off-by: Nik08 * Refactored logic on profile level for profile signing verification Signed-off-by: Nik08 * Renaming the argument variable - from runner_call to silent Signed-off-by: Nik08 * Added profile mandate check for other inspec commands running profile evaluation Signed-off-by: Nik08 * Updated error message for profile sign requirement Signed-off-by: Nik08 * Updated test helper to fix inspec json test Signed-off-by: Nik08 * Fixed inspec json ability to use cli options successfully Signed-off-by: Nik08 * Documentation added for signed profiles mandatory usage with CLI commands Signed-off-by: Nik08 * Flow changes of raising exception when unsigned instead of direct exit Signed-off-by: Nik08 * Renamed unsigned profile flags Signed-off-by: Nik08 * Extracted out allow unsigned condition to config and modified comment info Signed-off-by: Nik08 * Doc update on consent of using signed and unsigned profiles Signed-off-by: Nik08 * Fix in signing mandatin check and added additional check on runner for better error UI for exec command Signed-off-by: Nik08 * Removed repeated allow-unsigned-profile defination from exec_options Signed-off-by: Nik08 * Test fixes Signed-off-by: Nik08 * Enabled feature preview flag for mandatory signing Signed-off-by: Nik08 * Test fixes after feature flag usage for mandatory signing Signed-off-by: Nik08 * Doc changes using feature preview flag for mandatory signing feature Signed-off-by: Nik08 * Inspec exec tests fixes for ENV values and parallel test fix using default option --allow-unsigned-profile false Signed-off-by: Nik08 * Kitchen fix while using signed profiles with inspec Signed-off-by: Nik08 * Unit test fix for profile resource exception Signed-off-by: Nik08 * Virtual profile detection improved Signed-off-by: Nik08 * Move mandatory profile sigining info to sigining page Signed-off-by: Clinton Wolfe * Renamed flag from --allow-unsigned-profile to --allow-unsigned-profiles Signed-off-by: Nik08 * Typo fix in signing doc Signed-off-by: Nik08 * Trim note in cli.md about mandatory profile signing Signed-off-by: Clinton Wolfe * Docs changes Signed-off-by: Ian Maddaus * Correct docs regarding exit code 5 Signed-off-by: Clinton Wolfe --------- Signed-off-by: Nik08 Signed-off-by: Clinton Wolfe Signed-off-by: Ian Maddaus Co-authored-by: Clinton Wolfe Co-authored-by: Ian Maddaus --- docs-chef-io/content/inspec/cli.md | 32 ++++- docs-chef-io/content/inspec/signing.md | 13 +- .../content/inspec/troubleshooting.md | 22 +++- etc/features.sig | 12 +- etc/features.yaml | 5 +- lib/inspec/base_cli.rb | 5 + lib/inspec/config.rb | 4 + lib/inspec/errors.rb | 2 + lib/inspec/profile.rb | 45 ++++++- lib/inspec/runner.rb | 18 +++ lib/inspec/ui.rb | 1 + .../inspec-habitat/test/unit/profile_test.rb | 1 + .../test/functional/inspec_parallel_test.rb | 4 +- .../inspec-sign/lib/inspec-sign/base.rb | 8 +- .../test/functional/inspec_sign_test.rb | 12 +- test/functional/helper.rb | 8 +- test/functional/inspec_exec_test.rb | 123 ++++++++++++++++++ test/unit/dsl/control_test.rb | 4 +- .../profile_resource_exceptions_test.rb | 1 + test/unit/profiles/profile_test.rb | 1 + 20 files changed, 289 insertions(+), 32 deletions(-) diff --git a/docs-chef-io/content/inspec/cli.md b/docs-chef-io/content/inspec/cli.md index f1ca0944e..3bde96309 100644 --- a/docs-chef-io/content/inspec/cli.md +++ b/docs-chef-io/content/inspec/cli.md @@ -281,18 +281,22 @@ exit codes: 1 usage or general error 2 error in plugin system 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 101 normal exit, at least one test skipped but none failed 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: ```ruby 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: @@ -358,6 +362,12 @@ Web-hosted file with basic authentication (supports .zip): 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 This subcommand has the following syntax: @@ -370,6 +380,15 @@ inspec exec LOCATIONS 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` : Legacy name for --input-file - deprecated. @@ -655,6 +674,15 @@ inspec json PATH 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` : A list of controls to include. Ignore all other tests. diff --git a/docs-chef-io/content/inspec/signing.md b/docs-chef-io/content/inspec/signing.md index c0b5db587..bc005734a 100644 --- a/docs-chef-io/content/inspec/signing.md +++ b/docs-chef-io/content/inspec/signing.md @@ -11,7 +11,7 @@ gh_repo = "inspec" 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 @@ -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. +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? 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. diff --git a/docs-chef-io/content/inspec/troubleshooting.md b/docs-chef-io/content/inspec/troubleshooting.md index e0b583e68..6fe947ec9 100644 --- a/docs-chef-io/content/inspec/troubleshooting.md +++ b/docs-chef-io/content/inspec/troubleshooting.md @@ -11,6 +11,22 @@ gh_repo = "inspec" 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 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-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 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. diff --git a/etc/features.sig b/etc/features.sig index 509637895..426e70b73 100644 --- a/etc/features.sig +++ b/etc/features.sig @@ -1,6 +1,6 @@ -wNEzKHmtSf1pIdciEC6DOs5SlOs3IbW1psVFLlmZc0NbnHe6MEahAnKWmHUP -9YrDv2JMQo1I8MM/cez8XDxpK4O5y4HT66RqoAlfBkg82LmYC7f1Cy34ByCj -LBZg5o/IVBGnY+Ksbhtp0mQYEyU048FnXIfh9uOfbKahU8HkPJssTkIw3fjL -Vrd5GQ4ssfW1XXFaxx7DjxWlPmWBVhd8c1Y2RlACZyI+w1DQNYimrWvgiFym -0VbnndiSX+2x84AZHE9AmsebcAYk9QlqO1N0VeYqBZj45FXLtpsNwYo0amDa -D/wyKGxRQLUYXyd2tDVJMWbeHPHy8UIK17RoSctrEg== +ouQqvzkBxmM7J1cDE6/gsw8bE01+o2XFfDQoobG+n91yyCw0zjRzOUZ/NH+j +xZwB07D8+NNZQ4kYXMCkGXRQqZGq9nasIFfgwMjA4tWTWaE9i3jcL2Q3HOUF +aZ55wGtr5gpQc+pzQktFGRnC6ChmUgCzO2xTpl/h7TlNWIFU3INF8e2QnGqW ++2Xe7KDhY+4cTYKu9PSWfo6cBv71N/t2fbRTYyeCBoukav3fOioitO0aGiWv +RrR8HSomvEQE+9yQanxkyiSjPW93/SCI2rjNq9ouIGXYzTchRkgxH+hZXV1V +CJw+H5I+k9sx3I8on5eXK3Oa0Bd3zGetrXFfQLu83A== diff --git a/etc/features.yaml b/etc/features.yaml index 098f41c15..58a728fe8 100644 --- a/etc/features.yaml +++ b/etc/features.yaml @@ -85,4 +85,7 @@ inspec-reporter-progress-bar: description: Use progress bar streaming reporter inspec-reporter-child-status: - description: Child status reporter used in inspec parallel reporting. \ No newline at end of file + 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 \ No newline at end of file diff --git a/lib/inspec/base_cli.rb b/lib/inspec/base_cli.rb index 48a85e128..7a20a780a 100644 --- a/lib/inspec/base_cli.rb +++ b/lib/inspec/base_cli.rb @@ -171,6 +171,8 @@ module Inspec desc: "Use the given path for caching dependencies, (default: ~/.inspec/cache)." option :auto_install_gems, type: :boolean, default: false, 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 def self.supermarket_options @@ -373,6 +375,9 @@ module Inspec def pretty_handle_exception(exception) case exception + when Inspec::ProfileSignatureRequired + $stderr.puts exception.message + Inspec::UI.new.exit(:signature_required) when Inspec::InvalidProfileSignature $stderr.puts exception.message Inspec::UI.new.exit(:bad_signature) diff --git a/lib/inspec/config.rb b/lib/inspec/config.rb index c9314ee4e..86b3ccf98 100644 --- a/lib/inspec/config.rb +++ b/lib/inspec/config.rb @@ -80,6 +80,10 @@ module Inspec puts end + def allow_unsigned_profiles? + self["allow_unsigned_profiles"] || ENV["CHEF_ALLOW_UNSIGNED_PROFILES"] + end + # return all telemetry options from config # @return [Hash] def telemetry_options diff --git a/lib/inspec/errors.rb b/lib/inspec/errors.rb index 2abadaf2f..781a930a6 100644 --- a/lib/inspec/errors.rb +++ b/lib/inspec/errors.rb @@ -27,4 +27,6 @@ module Inspec class FeatureConfigMissingError < Error; end class FeatureConfigTamperedError < Error; end + + class ProfileSignatureRequired < Error; end end diff --git a/lib/inspec/profile.rb b/lib/inspec/profile.rb index 0d79a228b..244e8c923 100644 --- a/lib/inspec/profile.rb +++ b/lib/inspec/profile.rb @@ -16,6 +16,7 @@ require "inspec/utils/json_profile_summary" require "inspec/dependency_loader" require "inspec/dependency_installer" require "inspec/utils/profile_ast_helpers" +require "plugins/inspec-sign/lib/inspec-sign/base" module Inspec class Profile @@ -82,7 +83,7 @@ module Inspec end 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, :libraries def_delegator :@source_reader, :metadata @@ -181,6 +182,26 @@ module Inspec @state == :failed 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 # backend machine and the current inspec version. @@ -215,8 +236,30 @@ module Inspec @params ||= load_params 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 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? locked_dependencies.each(&:collect_tests) diff --git a/lib/inspec/runner.rb b/lib/inspec/runner.rb index b2bb5e604..3272eeacf 100644 --- a/lib/inspec/runner.rb +++ b/lib/inspec/runner.rb @@ -163,6 +163,16 @@ module Inspec def run(with = nil) 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)}" load run_tests(with) @@ -174,6 +184,14 @@ module Inspec Inspec::UI.new.exit(:usage_error) 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) return if @conf["reporter"].nil? diff --git a/lib/inspec/ui.rb b/lib/inspec/ui.rb index a56f02feb..0a8805bc7 100644 --- a/lib/inspec/ui.rb +++ b/lib/inspec/ui.rb @@ -32,6 +32,7 @@ module Inspec EXIT_FATAL_DEPRECATION = 3 EXIT_GEM_DEPENDENCY_LOAD_ERROR = 4 EXIT_BAD_SIGNATURE = 5 + EXIT_SIGNATURE_REQUIRED = 6 EXIT_LICENSE_NOT_ACCEPTED = 172 EXIT_LICENSE_NOT_ENTITLED = 173 EXIT_LICENSE_NOT_SET = 174 diff --git a/lib/plugins/inspec-habitat/test/unit/profile_test.rb b/lib/plugins/inspec-habitat/test/unit/profile_test.rb index 6feea0028..d577e4169 100644 --- a/lib/plugins/inspec-habitat/test/unit/profile_test.rb +++ b/lib/plugins/inspec-habitat/test/unit/profile_test.rb @@ -3,6 +3,7 @@ require "fileutils" unless defined?(FileUtils) require "minitest/autorun" require "inspec/backend" require_relative "../../lib/inspec-habitat/profile" +require "inspec/feature" class InspecPlugins::Habitat::ProfileTest < Minitest::Test def setup diff --git a/lib/plugins/inspec-parallel/test/functional/inspec_parallel_test.rb b/lib/plugins/inspec-parallel/test/functional/inspec_parallel_test.rb index 77563e6a3..db2dab99e 100644 --- a/lib/plugins/inspec-parallel/test/functional/inspec_parallel_test.rb +++ b/lib/plugins/inspec-parallel/test/functional/inspec_parallel_test.rb @@ -84,8 +84,8 @@ class ParallelCli < Minitest::Test def test_parallel_dry_run_with_verbose_option out = run_inspec_process("parallel exec #{complete_profile} -o #{options_file_1} --dry-run --verbose") 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, "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, "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 --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_exit_code 0, out end diff --git a/lib/plugins/inspec-sign/lib/inspec-sign/base.rb b/lib/plugins/inspec-sign/lib/inspec-sign/base.rb index a3e39d3f0..c8a3d56e5 100644 --- a/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +++ b/lib/plugins/inspec-sign/lib/inspec-sign/base.rb @@ -92,12 +92,16 @@ module InspecPlugins Inspec::UI.new.exit(:usage_error) end - def self.profile_verify(signed_profile_path) + def self.profile_verify(signed_profile_path, silent = false) 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) 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 "Attempting to verify using key '#{iaf_file.key_name}'" puts "Profile is valid." diff --git a/lib/plugins/inspec-sign/test/functional/inspec_sign_test.rb b/lib/plugins/inspec-sign/test/functional/inspec_sign_test.rb index 3ea2cd2de..3eb6ab499 100644 --- a/lib/plugins/inspec-sign/test/functional/inspec_sign_test.rb +++ b/lib/plugins/inspec-sign/test/functional/inspec_sign_test.rb @@ -1,6 +1,7 @@ require "fileutils" unless defined?(FileUtils) require "plugins/shared/core_plugin_test_helper" require "securerandom" unless defined?(SecureRandom) +require "functional/helper" class SignCli < Minitest::Test include CorePluginFunctionalHelper @@ -17,7 +18,7 @@ class SignCli < Minitest::Test assert_includes stdout, "Generating validation key" assert_exit_code 0, out - delete_keys(unique_key_name) + delete_signing_keys(unique_key_name) 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_exit_code 0, out - delete_keys(unique_key_name) + delete_signing_keys(unique_key_name) 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_exit_code 0, out - delete_keys(unique_key_name) + delete_signing_keys(unique_key_name) 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 diff --git a/test/functional/helper.rb b/test/functional/helper.rb index 87591611e..cb9fa8c63 100644 --- a/test/functional/helper.rb +++ b/test/functional/helper.rb @@ -186,8 +186,9 @@ module FunctionalHelper prefix += opts[:prefix] || "" prefix += assemble_env_prefix(opts[:env]) 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 += " --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 if opts[:tmpdir] @@ -256,6 +257,11 @@ module FunctionalHelper 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 def assemble_env_prefix(env = {}) diff --git a/test/functional/inspec_exec_test.rb b/test/functional/inspec_exec_test.rb index 7093f8bc3..60caa0b4c 100644 --- a/test/functional/inspec_exec_test.rb +++ b/test/functional/inspec_exec_test.rb @@ -1479,4 +1479,127 @@ EOT _(run_result.stdout).must_include ":status: passed" 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 diff --git a/test/unit/dsl/control_test.rb b/test/unit/dsl/control_test.rb index 454bc16ff..937b62304 100644 --- a/test/unit/dsl/control_test.rb +++ b/test/unit/dsl/control_test.rb @@ -3,6 +3,7 @@ require "inspec/config" require "inspec/profile" require "inspec/runner_mock" require "inspec/fetcher/mock" +require "inspec/feature" describe "controls" do def load(content) @@ -15,8 +16,7 @@ describe "controls" do backend: Inspec::Backend.create(Inspec::Config.mock), } - Inspec::Profile.for_target(data, opts) - .params[:controls]["1"] + Inspec::Profile.for_target(data, opts).params[:controls]["1"] end let(:rand_string) { rand.to_s } diff --git a/test/unit/profiles/profile_resource_exceptions_test.rb b/test/unit/profiles/profile_resource_exceptions_test.rb index 089e44e40..f82508c45 100644 --- a/test/unit/profiles/profile_resource_exceptions_test.rb +++ b/test/unit/profiles/profile_resource_exceptions_test.rb @@ -1,6 +1,7 @@ require "helper" require "inspec/profile_context" require "inspec/runner_mock" +require "inspec/feature" describe "resource exception" do let(:profile) do diff --git a/test/unit/profiles/profile_test.rb b/test/unit/profiles/profile_test.rb index 9d45732cd..5e7d8c500 100644 --- a/test/unit/profiles/profile_test.rb +++ b/test/unit/profiles/profile_test.rb @@ -4,6 +4,7 @@ require "inspec/runner_mock" require "inspec/resource" require "inspec/resources/command" require "inspec/profile" +require "inspec/feature" describe Inspec::Profile do let(:logger) { Minitest::Mock.new }