From 2bcbaff7fb8ac828c29718183be00e2bb2322215 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 7 Jan 2025 11:38:37 -0600 Subject: [PATCH 01/22] Add compliance_engine::enforcement lookup_key function Add a Hiera backend and Puppet module skeleton. Fixes #33 --- .devcontainer/Dockerfile | 14 +- Gemfile | 5 + Rakefile | 5 +- compliance_engine.gemspec | 4 +- lib/compliance_engine/data.rb | 26 +- lib/compliance_engine/version.rb | 2 +- .../compliance_engine/enforcement.rb | 95 +++ metadata.json | 52 ++ spec/data/10_enforce_spec.yaml | 43 + spec/data/common.yaml | 4 + .../data/compliance_engine-tolerance-100.yaml | 6 + spec/data/compliance_engine-tolerance-25.yaml | 6 + spec/data/compliance_engine-tolerance-60.yaml | 6 + spec/data/compliance_engine-tolerance.yaml | 9 + spec/data/compliance_engine.yaml | 19 + .../compliance_profiles/escaped_knockout.yaml | 31 + spec/data/hiera.yaml | 14 + .../compliance_profiles/passing_checks.yaml | 95 +++ spec/data/profile-merging.yaml | 9 + .../compliance_profiles/test1_deviation.yaml | 45 ++ .../test2_3_deviation.yaml | 35 + .../compliance_profiles/undefined_values.yaml | 85 ++ spec/fixtures/hieradata/10_enforce_spec.yaml | 32 + spec/fixtures/hieradata/profile-merging.yaml | 4 + spec/fixtures/modules/compliance_engine | 1 + .../compliance_engine/enforcement_spec.rb | 755 ++++++++++++++++++ spec/functions/lookup/00_enforcement_spec.rb | 135 ++++ .../lookup/01_enforcement_confine_spec.rb | 237 ++++++ .../lookup/02_enforcement_merge_spec.rb | 163 ++++ .../03_enforcement_profile_merge_spec.rb | 218 +++++ .../04_enforcement_profile_merge_spec.rb | 486 +++++++++++ .../lookup/05_enforcement_override_spec.rb | 135 ++++ .../lookup/06_enforcement_debug_spec.rb | 132 +++ .../lookup/07_enforcement_tolerance_spec.rb | 237 ++++++ spec/functions/lookup/10_enforce_spec.rb | 118 +++ spec/hiera.yaml | 16 + spec/spec_helper.rb | 2 + spec/spec_helper_puppet.rb | 79 ++ spec/support/compliance_module_mocks.rb | 56 ++ 39 files changed, 3404 insertions(+), 12 deletions(-) create mode 100644 lib/puppet/functions/compliance_engine/enforcement.rb create mode 100644 metadata.json create mode 100644 spec/data/10_enforce_spec.yaml create mode 100644 spec/data/common.yaml create mode 100644 spec/data/compliance_engine-tolerance-100.yaml create mode 100644 spec/data/compliance_engine-tolerance-25.yaml create mode 100644 spec/data/compliance_engine-tolerance-60.yaml create mode 100644 spec/data/compliance_engine-tolerance.yaml create mode 100644 spec/data/compliance_engine.yaml create mode 100644 spec/data/escaped_knockout/SIMP/compliance_profiles/escaped_knockout.yaml create mode 100644 spec/data/hiera.yaml create mode 100644 spec/data/passing_checks/SIMP/compliance_profiles/passing_checks.yaml create mode 100644 spec/data/profile-merging.yaml create mode 100644 spec/data/test1_deviation/SIMP/compliance_profiles/test1_deviation.yaml create mode 100644 spec/data/test2_3_deviation/SIMP/compliance_profiles/test2_3_deviation.yaml create mode 100644 spec/data/undefined_values/SIMP/compliance_profiles/undefined_values.yaml create mode 100644 spec/fixtures/hieradata/10_enforce_spec.yaml create mode 100644 spec/fixtures/hieradata/profile-merging.yaml create mode 120000 spec/fixtures/modules/compliance_engine create mode 100644 spec/functions/compliance_engine/enforcement_spec.rb create mode 100755 spec/functions/lookup/00_enforcement_spec.rb create mode 100755 spec/functions/lookup/01_enforcement_confine_spec.rb create mode 100755 spec/functions/lookup/02_enforcement_merge_spec.rb create mode 100755 spec/functions/lookup/03_enforcement_profile_merge_spec.rb create mode 100755 spec/functions/lookup/04_enforcement_profile_merge_spec.rb create mode 100755 spec/functions/lookup/05_enforcement_override_spec.rb create mode 100755 spec/functions/lookup/06_enforcement_debug_spec.rb create mode 100755 spec/functions/lookup/07_enforcement_tolerance_spec.rb create mode 100644 spec/functions/lookup/10_enforce_spec.rb create mode 100644 spec/hiera.yaml create mode 100644 spec/spec_helper_puppet.rb create mode 100644 spec/support/compliance_module_mocks.rb diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 98ce378..9308de8 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -40,14 +40,14 @@ RUN useradd -ms /bin/bash vscode \ USER vscode # Set up bash-git-prompt -RUN git clone https://github.com/magicmonty/bash-git-prompt.git /home/vscode/.bash-git-prompt --depth 1 \ - && cat >> /home/vscode/.bashrc <> /home/vscode/.bashrc < 13.3.0' group :tests do + gem 'puppet', ENV.fetch('PUPPET_VERSION', '~> 8.0') + gem 'puppetlabs_spec_helper', '~> 8.0' gem 'rspec', '~> 3.12' + gem 'rspec-puppet', '~> 5.0.0' + gem 'rspec-puppet-facts', '~> 3.0' gem 'rubocop', '~> 1.81.0' gem 'rubocop-performance', '~> 1.26.0' gem 'rubocop-rake', '~> 0.7.0' gem 'rubocop-rspec', '~> 3.9.0' + gem 'syslog', require: false end group :development do diff --git a/Rakefile b/Rakefile index 20d8a80..967a329 100644 --- a/Rakefile +++ b/Rakefile @@ -3,7 +3,10 @@ require 'bundler/gem_tasks' require 'rspec/core/rake_task' -RSpec::Core::RakeTask.new(:spec) +RSpec::Core::RakeTask.new(:spec) do |t| + t.pattern = 'spec/**/*_spec.rb' + t.exclude_pattern = 'spec/{data,fixtures,support/**/*_spec.rb' +end require 'rubocop/rake_task' diff --git a/compliance_engine.gemspec b/compliance_engine.gemspec index edaa79e..e4f27c6 100644 --- a/compliance_engine.gemspec +++ b/compliance_engine.gemspec @@ -19,14 +19,14 @@ Gem::Specification.new do |spec| spec.metadata['bug_tracker_uri'] = 'https://github.com/simp/rubygem-simp-compliance_engine/issues' # Specify which files should be added to the gem when it is released. - spec.files = Dir.glob(['*.gemspec', '*.md', 'LICENSE', 'exe/*', 'lib/**/*.rb']) + spec.files = Dir.glob(['*.gemspec', '*.md', 'LICENSE', 'exe/*', 'lib/**/*.rb']).reject { |f| f.start_with?('lib/puppet/') } spec.bindir = 'exe' spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] spec.add_dependency 'deep_merge', '~> 1.2' spec.add_dependency 'irb', '~> 1.14' - spec.add_dependency 'logger', '>= 1.4', '< 2.0' + spec.add_dependency 'logger', '~> 1.4' spec.add_dependency 'observer', '~> 0.1' spec.add_dependency 'rubyzip', '>= 2.3', '< 4' spec.add_dependency 'semantic_puppet', '~> 1.1' diff --git a/lib/compliance_engine/data.rb b/lib/compliance_engine/data.rb index 5733d0d..c036e83 100644 --- a/lib/compliance_engine/data.rb +++ b/lib/compliance_engine/data.rb @@ -326,12 +326,36 @@ def check_mapping(profile_or_ce) return @check_mapping[cache_key] if @check_mapping.key?(cache_key) @check_mapping[cache_key] = checks.select do |_, check| - mapping?(check, profile_or_ce) + mapping?(check, profile_or_ce) && !filtered_by_tolerance?(check) end end private + # Check if a check should be filtered out based on enforcement tolerance + # + # @param check [ComplianceEngine::Check] The check to evaluate + # @return [TrueClass, FalseClass] true if check should be filtered out + def filtered_by_tolerance?(check) + return false if enforcement_tolerance.nil? + + remediation = check.remediation + return false if remediation.nil? + + # Filter out disabled checks + return true if remediation['disabled'] + + # Filter based on risk level + if remediation['risk']&.is_a?(Array) && !remediation['risk'].empty? + risk_level = remediation['risk'][0]['level'] + if risk_level && enforcement_tolerance.to_i > 0 + return risk_level.to_i > enforcement_tolerance.to_i + end + end + + false + end + # Get the collection variables # # @return [Array] diff --git a/lib/compliance_engine/version.rb b/lib/compliance_engine/version.rb index f5f030a..d07af78 100644 --- a/lib/compliance_engine/version.rb +++ b/lib/compliance_engine/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module ComplianceEngine - VERSION = '0.1.6' + VERSION = '0.2.0' # Handle supported compliance data versions class Version diff --git a/lib/puppet/functions/compliance_engine/enforcement.rb b/lib/puppet/functions/compliance_engine/enforcement.rb new file mode 100644 index 0000000..a0d03b3 --- /dev/null +++ b/lib/puppet/functions/compliance_engine/enforcement.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +# @summary Hiera entry point for Compliance Engine +Puppet::Functions.create_function(:'compliance_engine::enforcement') do + # @param key String The key to lookup in the Hiera data + # @return [String] The value of the key in the Hiera data + dispatch :enforcement do + param 'String[1]', :key + param 'Hash[String[1], Any]', :options + param 'Puppet::LookupContext', :context + end + + require 'compliance_engine' + + def enforcement(key, options, context) + ComplianceEngine.log.level = Logger::DEBUG + + @compat = options['compliance_markup_compatibility'] + + case key + when 'lookup_options' + return context.not_found + when %r{^compliance_(?:engine|markup)::} + return context.not_found + else + return context.interpolate(context.cached_value(key)) if context.cache_has_key(key) + end + + # If we have no profiles to work with, we can't do anything. + return context.not_found if profiles.empty? + + if context.cache_has_key(:compliance_engine) + ComplianceEngine.log.debug('Using cached ComplianceEngine::Data object') + data = context.cached_value(:compliance_engine) + else + data = ComplianceEngine::Data.new + data.facts = closure_scope.lookupvar('facts') + data.enforcement_tolerance = enforcement_tolerance || options['enforcement_tolerance'] + data.open(ComplianceEngine::EnvironmentLoader.new(*closure_scope.environment.full_modulepath.select { |path| File.directory?(path) })) + + unless compliance_map.empty? + data.open(ComplianceEngine::DataLoader.new(compliance_map)) + end + context.cache(:compliance_engine, data) + end + + context.cache_all(data.hiera(profiles)) + + return context.interpolate(context.cached_value(key)) if context.cache_has_key(key) + # if data.hiera(profiles).key?(key) + # context.cache(key, data.hiera(profiles)[key]) + # return context.interpolate(data.hiera(profiles)[key]) + # end + + context.not_found + rescue StandardError => e + # Log any exceptions that occur + ComplianceEngine.log.error("Error in compliance_engine::enforcement: #{e.message}") + ComplianceEngine.log.error(e.backtrace.join("\n")) + raise + end + + def profiles + profile_list = call_function('lookup', 'compliance_engine::enforcement', { 'default_value' => [] }) + + # For backwards compatibility with compliance_markup. + if @compat + profile_list += call_function('lookup', 'compliance_markup::enforcement', { 'default_value' => [] }) + end + + profile_list.uniq + end + + def compliance_map + hiera_compliance_map = call_function('lookup', 'compliance_engine::compliance_map', { 'default_value' => {} }) + + # For backwards compatibility with compliance_markup. + if @compat + hiera_compliance_map = DeepMerge.deep_merge!(call_function('lookup', 'compliance_markup::compliance_map', { 'default_value' => {} }), hiera_compliance_map) + end + + hiera_compliance_map + end + + def enforcement_tolerance + tolerance = call_function('lookup', 'compliance_engine::enforcement_tolerance', { 'default_value' => nil }) + + # For backwards compatibility with compliance_markup. + if @compat + tolerance = call_function('lookup', 'compliance_markup::enforcement_tolerance_level', { 'default_value' => nil }) if tolerance.nil? + end + + tolerance + end +end diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..574f58d --- /dev/null +++ b/metadata.json @@ -0,0 +1,52 @@ +{ + "name": "simp-compliance_engine", + "version": "0.2.0", + "author": "Sicura", + "summary": "Hiera backend for Sicura Compliance Engine data", + "license": "Apache-2.0", + "source": "https://github.com/simp/rubygem-simp-compliance_engine", + "dependencies": [ + ], + "operatingsystem_support": [ + { + "operatingsystem": "CentOS", + "operatingsystemrelease": [ + "9" + ] + }, + { + "operatingsystem": "OracleLinux", + "operatingsystemrelease": [ + "8", + "9" + ] + }, + { + "operatingsystem": "RedHat", + "operatingsystemrelease": [ + "8", + "9" + ] + }, + { + "operatingsystem": "Rocky", + "operatingsystemrelease": [ + "8", + "9" + ] + }, + { + "operatingsystem": "AlmaLinux", + "operatingsystemrelease": [ + "8", + "9" + ] + } + ], + "requirements": [ + { + "name": "puppet", + "version_requirement": ">= 7.0.0 < 9.0.0" + } + ] +} diff --git a/spec/data/10_enforce_spec.yaml b/spec/data/10_enforce_spec.yaml new file mode 100644 index 0000000..14e3df1 --- /dev/null +++ b/spec/data/10_enforce_spec.yaml @@ -0,0 +1,43 @@ +--- +compliance_engine::enforcement: +- disa_stig +- nist_800_53:rev4 +compliance_engine::compliance_map: + version: 2.0.0 + profiles: + disa_stig: + controls: + disa_stig: true + nist_800_53:rev4: + controls: + nist_800_53:rev4: true + controls: + disa_stig: {} + nist_800_53:rev4: {} + checks: + oval:com.puppet.test.disa.useradd_shells: + type: puppet-class-parameter + controls: + disa_stig: true + identifiers: + FOO2: + - FOO2 + BAR2: + - BAR2 + settings: + parameter: useradd::shells + value: + - "/bin/disa" + oval:com.puppet.test.nist.useradd_shells: + type: puppet-class-parameter + controls: + nist_800_53:rev4: true + identifiers: + FOO2: + - FOO2 + BAR2: + - BAR2 + settings: + parameter: useradd::shells + value: + - "/bin/nist" diff --git a/spec/data/common.yaml b/spec/data/common.yaml new file mode 100644 index 0000000..b77128c --- /dev/null +++ b/spec/data/common.yaml @@ -0,0 +1,4 @@ +--- +compliance_engine::enforcement: + # - test_profile + - "%{facts.target_compliance_profile}" diff --git a/spec/data/compliance_engine-tolerance-100.yaml b/spec/data/compliance_engine-tolerance-100.yaml new file mode 100644 index 0000000..e21127c --- /dev/null +++ b/spec/data/compliance_engine-tolerance-100.yaml @@ -0,0 +1,6 @@ +--- +compliance_engine::enforcement: + - "%{facts.target_compliance_profile}" + +# Hardcoded integer value for tolerance - Hiera interpolation would produce a string +compliance_engine::enforcement_tolerance: 100 diff --git a/spec/data/compliance_engine-tolerance-25.yaml b/spec/data/compliance_engine-tolerance-25.yaml new file mode 100644 index 0000000..293ed00 --- /dev/null +++ b/spec/data/compliance_engine-tolerance-25.yaml @@ -0,0 +1,6 @@ +--- +compliance_engine::enforcement: + - "%{facts.target_compliance_profile}" + +# Hardcoded integer value for tolerance - Hiera interpolation would produce a string +compliance_engine::enforcement_tolerance: 25 diff --git a/spec/data/compliance_engine-tolerance-60.yaml b/spec/data/compliance_engine-tolerance-60.yaml new file mode 100644 index 0000000..4323da9 --- /dev/null +++ b/spec/data/compliance_engine-tolerance-60.yaml @@ -0,0 +1,6 @@ +--- +compliance_engine::enforcement: + - "%{facts.target_compliance_profile}" + +# Hardcoded integer value for tolerance - Hiera interpolation would produce a string +compliance_engine::enforcement_tolerance: 60 diff --git a/spec/data/compliance_engine-tolerance.yaml b/spec/data/compliance_engine-tolerance.yaml new file mode 100644 index 0000000..ed732a9 --- /dev/null +++ b/spec/data/compliance_engine-tolerance.yaml @@ -0,0 +1,9 @@ +--- +compliance_engine::enforcement: + - "%{facts.target_compliance_profile}" + +# Note: Hiera interpolation always produces strings, so for testing enforcement +# tolerance (which requires Integer), use the dedicated hieradata files: +# - compliance-engine-tolerance-25.yaml +# - compliance-engine-tolerance-60.yaml +# - compliance-engine-tolerance-100.yaml diff --git a/spec/data/compliance_engine.yaml b/spec/data/compliance_engine.yaml new file mode 100644 index 0000000..c2c562c --- /dev/null +++ b/spec/data/compliance_engine.yaml @@ -0,0 +1,19 @@ +--- +compliance_engine::validate_profiles: + - "%{facts.target_compliance_profile}" + +# Needed for catalog inspection to ensure valid data +compliance_engine::report_on_client: true +compliance_engine::report_on_server: false +compliance_engine::report_types: + - 'compliant' + - 'non_compliant' + - 'unknown_parameters' + - 'unknown_resources' + +# Ideally, this would be the same as the validation array but you may want to +# do something different based on your test requirements +compliance_engine::enforcement: + - "%{facts.target_compliance_profile}" + +compliance_engine::enforcement_tolerance: "%{facts.target_enforcement_tolerance}" \ No newline at end of file diff --git a/spec/data/escaped_knockout/SIMP/compliance_profiles/escaped_knockout.yaml b/spec/data/escaped_knockout/SIMP/compliance_profiles/escaped_knockout.yaml new file mode 100644 index 0000000..82580a0 --- /dev/null +++ b/spec/data/escaped_knockout/SIMP/compliance_profiles/escaped_knockout.yaml @@ -0,0 +1,31 @@ +--- +# Some very basic compliance checks designed for the tests +# +# These should all pass + +version: 2.0.0 + +compliance_markup::enforcement: + - test_profile + +compliance_markup::enforcement_tolerance_level: 40 + +profiles: + test_profile: + controls: + test_control: true + +controls: + test_control: {} + +checks: + oval:test4: + type: puppet-class-parameter + settings: + parameter: test4::list1 + value: + - '\\-- not_a_knockout' + controls: + test_control: true + identifiers: + - 'ESC_KNOCKOUT' diff --git a/spec/data/hiera.yaml b/spec/data/hiera.yaml new file mode 100644 index 0000000..c5fd199 --- /dev/null +++ b/spec/data/hiera.yaml @@ -0,0 +1,14 @@ +--- +version: 5 +hierarchy: +- name: SIMP Compliance Engine + lookup_key: compliance_markup::enforcement +- name: Custom Test Hiera + path: "%{custom_hiera}.yaml" +- name: "%{module_name}" + path: "%{module_name}.yaml" +- name: Common + path: default.yaml +defaults: + data_hash: yaml_data + datadir: "/home/steve/src/simp/pupmod-simp-compliance_markup/spec/fixtures/hieradata" diff --git a/spec/data/passing_checks/SIMP/compliance_profiles/passing_checks.yaml b/spec/data/passing_checks/SIMP/compliance_profiles/passing_checks.yaml new file mode 100644 index 0000000..59a3f8b --- /dev/null +++ b/spec/data/passing_checks/SIMP/compliance_profiles/passing_checks.yaml @@ -0,0 +1,95 @@ +--- +# Some very basic compliance checks designed for the tests +# +# These should all pass + +version: 2.0.0 + +profiles: + test_profile: + controls: + test_control: true + +controls: + test_control: {} + +checks: + oval:test1: + type: puppet-class-parameter + settings: + parameter: test1::arg1_1 + value: foo1_1 + controls: + test_control: true + identifiers: + - 'ID1.1' + + oval:test2: + type: puppet-class-parameter + settings: + parameter: test2::test3::arg3_1 + value: 're:foo3_.*' + controls: + test_control: true + identifiers: + - 'ID1.2' + + oval:test3: + type: puppet-class-parameter + settings: + parameter: test2::test3::ref_miss1 + value: missing1 + controls: + test_control: true + identifiers: + - 'MISS1' + + oval:test4: + type: puppet-class-parameter + settings: + parameter: testdef1::defarg1_1 + value: deffoo1_1 + controls: + test_control: true + identifiers: + - 'DEF1.1' + + oval:test5: + type: puppet-class-parameter + settings: + parameter: testdef2::defarg1_2 + value: deffoo1_2 + controls: + test_control: true + identifiers: + - 'DEF1.2' + + oval:test6: + type: puppet-class-parameter + settings: + parameter: unmapped1::arg1_1 + value: um1_1 + controls: + test_control: true + identifiers: + - 'UNK001' + + oval:test7: + type: puppet-class-parameter + settings: + parameter: unmapped1::arg1_2 + value: um1_2 + controls: + test_control: true + identifiers: + - 'UNK002' + + oval:test8: + type: puppet-class-parameter + settings: + parameter: unmapped1::subclass::arg1_2 + value: um1_3 + controls: + test_control: true + identifiers: + - 'UNK003' diff --git a/spec/data/profile-merging.yaml b/spec/data/profile-merging.yaml new file mode 100644 index 0000000..01569e2 --- /dev/null +++ b/spec/data/profile-merging.yaml @@ -0,0 +1,9 @@ +--- +compliance_engine::enforcement: +- profile_test1 +compliance_engine::compliance_map: + version: 2.0.0 + profiles: + profile_test1: + ces: + 05_profile_test2: false diff --git a/spec/data/test1_deviation/SIMP/compliance_profiles/test1_deviation.yaml b/spec/data/test1_deviation/SIMP/compliance_profiles/test1_deviation.yaml new file mode 100644 index 0000000..2558a67 --- /dev/null +++ b/spec/data/test1_deviation/SIMP/compliance_profiles/test1_deviation.yaml @@ -0,0 +1,45 @@ +--- +# Some very basic compliance checks designed for the tests +# +# These should all pass + +version: 2.0.0 + +profiles: + test_profile: + controls: + test_control: true + +controls: + test_control: {} + +checks: + oval:test1: + type: puppet-class-parameter + settings: + parameter: test1::arg1_1 + value: bar1_1 + controls: + test_control: true + identifiers: + - 'ID1.1' + + oval:test2: + type: puppet-class-parameter + settings: + parameter: test1::arg1_2 + value: foo1_2 + controls: + test_control: true + identifiers: + - 'ID1.2' + + oval:test3: + type: puppet-class-parameter + settings: + parameter: test2::test3::arg3_1 + value: foo3_1 + controls: + test_control: true + identifiers: + - 'ID1.2' diff --git a/spec/data/test2_3_deviation/SIMP/compliance_profiles/test2_3_deviation.yaml b/spec/data/test2_3_deviation/SIMP/compliance_profiles/test2_3_deviation.yaml new file mode 100644 index 0000000..71d5ae1 --- /dev/null +++ b/spec/data/test2_3_deviation/SIMP/compliance_profiles/test2_3_deviation.yaml @@ -0,0 +1,35 @@ +--- +# Some very basic compliance checks designed for the tests +# +# These should all pass + +version: 2.0.0 + +profiles: + test_profile: + controls: + test_control: true + +controls: + test_control: {} + +checks: + oval:test1: + type: puppet-class-parameter + settings: + parameter: test1::arg1_1 + value: foo1_1 + controls: + test_control: true + identifiers: + - 'ID1.1' + + oval:test2: + type: puppet-class-parameter + settings: + parameter: test2::test3::arg3_1 + value: bar3_1 + controls: + test_control: true + identifiers: + - 'ID1.2' diff --git a/spec/data/undefined_values/SIMP/compliance_profiles/undefined_values.yaml b/spec/data/undefined_values/SIMP/compliance_profiles/undefined_values.yaml new file mode 100644 index 0000000..31a8ef6 --- /dev/null +++ b/spec/data/undefined_values/SIMP/compliance_profiles/undefined_values.yaml @@ -0,0 +1,85 @@ +--- +# Some very basic compliance checks designed for the tests +# +# These should all pass + +version: 2.0.0 + +profiles: + test_profile: + controls: + test_control: true + +controls: + test_control: {} + +checks: + oval:test1: + type: puppet-class-parameter + settings: + parameter: test1::arg1_1 + value: null + controls: + test_control: true + identifiers : + - 'ID1.1' + + oval:test2: + type: puppet-class-parameter + settings: + parameter: test3::arg3_1 + value: 're:foo3_.*' + controls: + test_control: true + identifiers : + - 'ID1.2' + + oval:test3: + type: puppet-class-parameter + settings: + parameter: test3::ref_miss1 + value: missing1 + controls: + test_control: true + identifiers : + - 'MISS1' + + oval:test4: + type: puppet-class-parameter + settings: + parameter: testdef1::defarg1_1 + value: deffoo1_1 + controls: + test_control: true + identifiers : + - 'DEF1.1' + + oval:test5: + type: puppet-class-parameter + settings: + parameter: unmapped1::arg1_1 + value: um1_1 + controls: + test_control: true + identifiers : + - 'UNK001' + + oval:test6: + type: puppet-class-parameter + settings: + parameter: unmapped1::arg1_2 + value: um1_2 + controls: + test_control: true + identifiers : + - 'UNK002' + + oval:test7: + type: puppet-class-parameter + settings: + parameter: unmapped1::subclass::arg1_2 + value: um1_3 + controls: + test_control: true + identifiers : + - 'UNK003' diff --git a/spec/fixtures/hieradata/10_enforce_spec.yaml b/spec/fixtures/hieradata/10_enforce_spec.yaml new file mode 100644 index 0000000..bd004e3 --- /dev/null +++ b/spec/fixtures/hieradata/10_enforce_spec.yaml @@ -0,0 +1,32 @@ +--- +compliance_engine::enforcement: +- disa_stig +compliance_engine::compliance_map: + version: 2.0.0 + checks: + oval:com.puppet.test.disa.useradd_shells: + type: puppet-class-parameter + controls: + disa_stig: true + identifiers: + FOO2: + - FOO2 + BAR2: + - BAR2 + settings: + parameter: useradd::shells + value: + - "/bin/disa" + oval:com.puppet.test.nist.useradd_shells: + type: puppet-class-parameter + controls: + nist_800_53:rev4: true + identifiers: + FOO2: + - FOO2 + BAR2: + - BAR2 + settings: + parameter: useradd::shells + value: + - "/bin/nist" diff --git a/spec/fixtures/hieradata/profile-merging.yaml b/spec/fixtures/hieradata/profile-merging.yaml new file mode 100644 index 0000000..3615608 --- /dev/null +++ b/spec/fixtures/hieradata/profile-merging.yaml @@ -0,0 +1,4 @@ +--- +compliance_engine::enforcement: +- profile_test1 +- profile_test2 diff --git a/spec/fixtures/modules/compliance_engine b/spec/fixtures/modules/compliance_engine new file mode 120000 index 0000000..a8a4f8c --- /dev/null +++ b/spec/fixtures/modules/compliance_engine @@ -0,0 +1 @@ +../../.. \ No newline at end of file diff --git a/spec/functions/compliance_engine/enforcement_spec.rb b/spec/functions/compliance_engine/enforcement_spec.rb new file mode 100644 index 0000000..6d06f22 --- /dev/null +++ b/spec/functions/compliance_engine/enforcement_spec.rb @@ -0,0 +1,755 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'spec_helper_puppet' +require 'yaml' +require 'fileutils' +require 'tmpdir' + +# Tests for the compliance_engine::enforcement Hiera backend. +# Since this is a lookup_key backend, we test it through the `lookup` function. +# +# These tests create temporary fixture files to test the enforcement backend +# without relying on complex mocking of the file system. +RSpec.describe 'lookup' do + # Create a temporary directory structure for test modules + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test') } + let(:test_module_path) { File.join(tmpdir, 'test_enforcement_module') } + let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } + + # Default test data - can be overridden in specific contexts + let(:profile_data) do + { + 'version' => '2.0.0', + 'profiles' => { + 'enforcement_spec_profile' => { + 'controls' => { + 'enforcement_spec_control' => true, + }, + }, + }, + } + end + + let(:ces_data) do + { + 'version' => '2.0.0', + 'ce' => { + 'enforcement_spec_ce' => { + 'controls' => { + 'enforcement_spec_control' => true, + }, + }, + }, + } + end + + let(:checks_data) do + { + 'version' => '2.0.0', + 'checks' => { + 'enforcement_spec_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'enforcement_spec::test_param', + 'value' => 'test_enforced_value', + }, + 'ces' => [ + 'enforcement_spec_ce', + ], + }, + 'enforcement_spec_check2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'enforcement_spec::another_param', + 'value' => 42, + }, + 'ces' => [ + 'enforcement_spec_ce', + ], + }, + }, + } + end + + before(:each) do + # Create the directory structure + FileUtils.mkdir_p(compliance_dir) + + # Write the test data files + File.write(File.join(compliance_dir, 'profiles.yaml'), profile_data.to_yaml) + File.write(File.join(compliance_dir, 'ces.yaml'), ces_data.to_yaml) + File.write(File.join(compliance_dir, 'checks.yaml'), checks_data.to_yaml) + + # Mock the Puppet environment's modulepath to include our temp directory + # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(Puppet::Node::Environment).to receive(:full_modulepath).and_return([tmpdir]) + # rubocop:enable RSpec/AnyInstance + end + + after(:each) do + # Clean up temporary directory + FileUtils.rm_rf(tmpdir) + end + + context 'with compliance_engine::enforcement backend and no profile configured' do + let(:hieradata) { 'common' } + let(:facts) { {} } + + it 'returns not_found for any key when no profiles are set' do + is_expected.to run.with_params('enforcement_spec::test_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + end + + context 'with compliance_engine::enforcement backend and a valid profile' do + let(:facts) do + { 'target_compliance_profile' => 'enforcement_spec_profile' } + end + let(:hieradata) { 'compliance-engine' } + + it 'returns the enforced string value for a matching parameter' do + is_expected.to run.with_params('enforcement_spec::test_param').and_return('test_enforced_value') + end + + it 'returns the enforced integer value for a matching parameter' do + is_expected.to run.with_params('enforcement_spec::another_param').and_return(42) + end + + it 'returns not_found for keys not in compliance data' do + is_expected.to run.with_params('unknown::param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + + it 'returns not_found for lookup_options key' do + is_expected.to run.with_params('lookup_options') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + + it 'returns not_found for compliance_engine:: prefixed keys' do + is_expected.to run.with_params('compliance_engine::some_internal_key') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + + it 'returns not_found for compliance_markup:: prefixed keys' do + is_expected.to run.with_params('compliance_markup::some_internal_key') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + end + + context 'with compliance_engine::enforcement backend and a non-existent profile' do + let(:facts) do + { 'target_compliance_profile' => 'nonexistent_profile' } + end + let(:hieradata) { 'compliance-engine' } + + it 'returns not_found when the profile does not exist' do + is_expected.to run.with_params('enforcement_spec::test_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + end + + context 'with multiple profiles configured' do + let(:profile_data) do + { + 'version' => '2.0.0', + 'profiles' => { + 'profile_one' => { + 'controls' => { + 'control_one' => true, + }, + }, + 'profile_two' => { + 'controls' => { + 'control_two' => true, + }, + }, + }, + } + end + + let(:ces_data) do + { + 'version' => '2.0.0', + 'ce' => { + 'ce_one' => { + 'controls' => { + 'control_one' => true, + }, + }, + 'ce_two' => { + 'controls' => { + 'control_two' => true, + }, + }, + }, + } + end + + let(:checks_data) do + { + 'version' => '2.0.0', + 'checks' => { + 'check_from_profile_one' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'multi_profile::param_one', + 'value' => 'value_from_profile_one', + }, + 'ces' => ['ce_one'], + }, + 'check_from_profile_two' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'multi_profile::param_two', + 'value' => 'value_from_profile_two', + }, + 'ces' => ['ce_two'], + }, + }, + } + end + + let(:facts) do + { 'target_compliance_profile' => 'profile_one' } + end + let(:hieradata) { 'compliance-engine' } + + it 'returns value from the first profile' do + is_expected.to run.with_params('multi_profile::param_one').and_return('value_from_profile_one') + end + + it 'returns not_found for value only in second profile when first profile is selected' do + is_expected.to run.with_params('multi_profile::param_two') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + end + + context 'with fact-based confine on checks' do + let(:profile_data) do + { + 'version' => '2.0.0', + 'profiles' => { + 'confine_test_profile' => { + 'controls' => { + 'confine_control' => true, + }, + }, + }, + } + end + + let(:ces_data) do + { + 'version' => '2.0.0', + 'ce' => { + 'confine_ce' => { + 'controls' => { + 'confine_control' => true, + }, + }, + }, + } + end + + let(:checks_data) do + { + 'version' => '2.0.0', + 'checks' => { + 'redhat_only_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'confine_test::redhat_param', + 'value' => 'redhat_value', + }, + 'ces' => ['confine_ce'], + 'confine' => { + 'os.family' => 'RedHat', + }, + }, + 'debian_only_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'confine_test::debian_param', + 'value' => 'debian_value', + }, + 'ces' => ['confine_ce'], + 'confine' => { + 'os.family' => 'Debian', + }, + }, + 'any_os_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'confine_test::any_os_param', + 'value' => 'any_os_value', + }, + 'ces' => ['confine_ce'], + }, + }, + } + end + + let(:hieradata) { 'compliance-engine' } + + context 'on RedHat family' do + let(:facts) do + { + 'target_compliance_profile' => 'confine_test_profile', + 'os' => { 'family' => 'RedHat' }, + } + end + + it 'returns value for RedHat-confined check' do + is_expected.to run.with_params('confine_test::redhat_param').and_return('redhat_value') + end + + it 'returns not_found for Debian-confined check' do + is_expected.to run.with_params('confine_test::debian_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + + it 'returns value for non-confined check' do + is_expected.to run.with_params('confine_test::any_os_param').and_return('any_os_value') + end + end + + context 'on Debian family' do + let(:facts) do + { + 'target_compliance_profile' => 'confine_test_profile', + 'os' => { 'family' => 'Debian' }, + } + end + + it 'returns not_found for RedHat-confined check' do + is_expected.to run.with_params('confine_test::redhat_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + + it 'returns value for Debian-confined check' do + is_expected.to run.with_params('confine_test::debian_param').and_return('debian_value') + end + + it 'returns value for non-confined check' do + is_expected.to run.with_params('confine_test::any_os_param').and_return('any_os_value') + end + end + end + + context 'with disabled remediation on checks' do + let(:profile_data) do + { + 'version' => '2.0.0', + 'profiles' => { + 'remediation_test_profile' => { + 'controls' => { + 'remediation_control' => true, + }, + }, + }, + } + end + + let(:ces_data) do + { + 'version' => '2.0.0', + 'ce' => { + 'remediation_ce' => { + 'controls' => { + 'remediation_control' => true, + }, + }, + }, + } + end + + let(:checks_data) do + { + 'version' => '2.0.0', + 'checks' => { + 'enabled_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'remediation_test::enabled_param', + 'value' => 'enabled_value', + }, + 'ces' => ['remediation_ce'], + }, + 'disabled_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'remediation_test::disabled_param', + 'value' => 'disabled_value', + }, + 'ces' => ['remediation_ce'], + 'remediation' => { + 'disabled' => [ + { 'reason' => 'This check is disabled for testing.' }, + ], + }, + }, + }, + } + end + + let(:facts) do + { 'target_compliance_profile' => 'remediation_test_profile' } + end + let(:hieradata) { 'compliance-engine' } + + it 'returns value for enabled check' do + is_expected.to run.with_params('remediation_test::enabled_param').and_return('enabled_value') + end + + # NOTE: Disabled checks are still returned by default - they are informational + # and filtering them requires explicit configuration + it 'still returns value for disabled check (disabled is informational only)' do + is_expected.to run.with_params('remediation_test::disabled_param').and_return('disabled_value') + end + end + + context 'with risk-level remediation and enforcement tolerance' do + let(:profile_data) do + { + 'version' => '2.0.0', + 'profiles' => { + 'tolerance_test_profile' => { + 'controls' => { + 'tolerance_control' => true, + }, + }, + }, + } + end + + let(:ces_data) do + { + 'version' => '2.0.0', + 'ce' => { + 'tolerance_ce' => { + 'controls' => { + 'tolerance_control' => true, + }, + }, + }, + } + end + + let(:checks_data) do + { + 'version' => '2.0.0', + 'checks' => { + 'no_risk_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'tolerance_test::no_risk_param', + 'value' => 'no_risk_value', + }, + 'ces' => ['tolerance_ce'], + }, + 'low_risk_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'tolerance_test::low_risk_param', + 'value' => 'low_risk_value', + }, + 'ces' => ['tolerance_ce'], + 'remediation' => { + 'risk' => [ + { 'level' => 20, 'reason' => 'Low risk check' }, + ], + }, + }, + 'medium_risk_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'tolerance_test::medium_risk_param', + 'value' => 'medium_risk_value', + }, + 'ces' => ['tolerance_ce'], + 'remediation' => { + 'risk' => [ + { 'level' => 50, 'reason' => 'Medium risk check' }, + ], + }, + }, + 'high_risk_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'tolerance_test::high_risk_param', + 'value' => 'high_risk_value', + }, + 'ces' => ['tolerance_ce'], + 'remediation' => { + 'risk' => [ + { 'level' => 80, 'reason' => 'High risk check' }, + ], + }, + }, + }, + } + end + + # NOTE: When no enforcement tolerance is set (nil), all risk levels are accepted + context 'with no enforcement tolerance set (default accepts all)' do + let(:facts) do + { 'target_compliance_profile' => 'tolerance_test_profile' } + end + let(:hieradata) { 'compliance-engine' } + + it 'returns value for no-risk check' do + is_expected.to run.with_params('tolerance_test::no_risk_param').and_return('no_risk_value') + end + + it 'returns value for low-risk check (no filtering when tolerance is nil)' do + is_expected.to run.with_params('tolerance_test::low_risk_param').and_return('low_risk_value') + end + + it 'returns value for medium-risk check (no filtering when tolerance is nil)' do + is_expected.to run.with_params('tolerance_test::medium_risk_param').and_return('medium_risk_value') + end + + it 'returns value for high-risk check (no filtering when tolerance is nil)' do + is_expected.to run.with_params('tolerance_test::high_risk_param').and_return('high_risk_value') + end + end + + # Use dedicated hieradata file with hardcoded integer tolerance + # (Hiera interpolation from facts would produce a string, but the code requires Integer) + context 'with enforcement tolerance set to 25' do + let(:facts) do + { + 'target_compliance_profile' => 'tolerance_test_profile', + 'custom_hiera' => 'compliance_engine-tolerance-25', + } + end + let(:hieradata) { 'compliance_engine-tolerance-25' } + + it 'returns value for no-risk check' do + is_expected.to run.with_params('tolerance_test::no_risk_param').and_return('no_risk_value') + end + + it 'returns value for low-risk check (level 20 < tolerance 25)' do + is_expected.to run.with_params('tolerance_test::low_risk_param').and_return('low_risk_value') + end + + it 'returns not_found for medium-risk check (level 50 >= tolerance 25)' do + is_expected.to run.with_params('tolerance_test::medium_risk_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + + it 'returns not_found for high-risk check (level 80 >= tolerance 25)' do + is_expected.to run.with_params('tolerance_test::high_risk_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + end + + context 'with enforcement tolerance set to 60' do + let(:facts) do + { + 'target_compliance_profile' => 'tolerance_test_profile', + 'custom_hiera' => 'compliance_engine-tolerance-60', + } + end + let(:hieradata) { 'compliance_engine-tolerance-60' } + + it 'returns value for no-risk check' do + is_expected.to run.with_params('tolerance_test::no_risk_param').and_return('no_risk_value') + end + + it 'returns value for low-risk check' do + is_expected.to run.with_params('tolerance_test::low_risk_param').and_return('low_risk_value') + end + + it 'returns value for medium-risk check (level 50 < tolerance 60)' do + is_expected.to run.with_params('tolerance_test::medium_risk_param').and_return('medium_risk_value') + end + + it 'returns not_found for high-risk check (level 80 >= tolerance 60)' do + is_expected.to run.with_params('tolerance_test::high_risk_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + end + end + + context 'with enforcement tolerance set to 100' do + let(:facts) do + { + 'target_compliance_profile' => 'tolerance_test_profile', + 'custom_hiera' => 'compliance_engine-tolerance-100', + } + end + let(:hieradata) { 'compliance_engine-tolerance-100' } + + it 'returns value for all checks including high-risk' do + is_expected.to run.with_params('tolerance_test::high_risk_param').and_return('high_risk_value') + end + end + end + + context 'with different value types' do + let(:profile_data) do + { + 'version' => '2.0.0', + 'profiles' => { + 'types_test_profile' => { + 'controls' => { + 'types_control' => true, + }, + }, + }, + } + end + + let(:ces_data) do + { + 'version' => '2.0.0', + 'ce' => { + 'types_ce' => { + 'controls' => { + 'types_control' => true, + }, + }, + }, + } + end + + let(:checks_data) do + { + 'version' => '2.0.0', + 'checks' => { + 'string_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'types_test::string_param', + 'value' => 'a string value', + }, + 'ces' => ['types_ce'], + }, + 'integer_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'types_test::integer_param', + 'value' => 42, + }, + 'ces' => ['types_ce'], + }, + 'boolean_true_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'types_test::bool_true_param', + 'value' => true, + }, + 'ces' => ['types_ce'], + }, + 'boolean_false_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'types_test::bool_false_param', + 'value' => false, + }, + 'ces' => ['types_ce'], + }, + 'array_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'types_test::array_param', + 'value' => ['item1', 'item2', 'item3'], + }, + 'ces' => ['types_ce'], + }, + 'hash_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'types_test::hash_param', + 'value' => { 'key1' => 'value1', 'key2' => 'value2' }, + }, + 'ces' => ['types_ce'], + }, + }, + } + end + + let(:facts) do + { 'target_compliance_profile' => 'types_test_profile' } + end + let(:hieradata) { 'compliance-engine' } + + it 'returns string value' do + is_expected.to run.with_params('types_test::string_param').and_return('a string value') + end + + it 'returns integer value' do + is_expected.to run.with_params('types_test::integer_param').and_return(42) + end + + it 'returns boolean true value' do + is_expected.to run.with_params('types_test::bool_true_param').and_return(true) + end + + it 'returns boolean false value' do + is_expected.to run.with_params('types_test::bool_false_param').and_return(false) + end + + it 'returns array value' do + is_expected.to run.with_params('types_test::array_param').and_return(['item1', 'item2', 'item3']) + end + + it 'returns hash value' do + is_expected.to run.with_params('types_test::hash_param').and_return({ 'key1' => 'value1', 'key2' => 'value2' }) + end + end + + context 'with controls referenced directly in profile' do + let(:profile_data) do + { + 'version' => '2.0.0', + 'profiles' => { + 'direct_control_profile' => { + 'controls' => { + 'direct_control' => true, + }, + }, + }, + } + end + + let(:ces_data) do + { + 'version' => '2.0.0', + 'ce' => { + 'direct_ce' => { + 'controls' => { + 'direct_control' => true, + }, + }, + }, + } + end + + let(:checks_data) do + { + 'version' => '2.0.0', + 'checks' => { + 'direct_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'direct_test::param', + 'value' => 'direct_value', + }, + 'ces' => ['direct_ce'], + }, + }, + } + end + + let(:facts) do + { 'target_compliance_profile' => 'direct_control_profile' } + end + let(:hieradata) { 'compliance-engine' } + + it 'returns value for check linked through control -> ce -> check' do + is_expected.to run.with_params('direct_test::param').and_return('direct_value') + end + end +end diff --git a/spec/functions/lookup/00_enforcement_spec.rb b/spec/functions/lookup/00_enforcement_spec.rb new file mode 100755 index 0000000..4c827cd --- /dev/null +++ b/spec/functions/lookup/00_enforcement_spec.rb @@ -0,0 +1,135 @@ +#!/usr/bin/env ruby -S rspec + +require 'spec_helper' +require 'spec_helper_puppet' +require 'yaml' +require 'fileutils' +require 'tmpdir' + +RSpec.describe 'lookup' do + # Generate a fake module with dummy data for lookup(). + let(:profile_yaml) do + { + 'version' => '2.0.0', + 'profiles' => { + '00_profile_test' => { + 'controls' => { + '00_control1' => true, + }, + }, + '00_profile_with_check_reference' => { + 'checks' => { + '00_check2' => true, + }, + }, + }, + }.to_yaml + end + + let(:ces_yaml) do + { + 'version' => '2.0.0', + 'ce' => { + '00_ce1' => { + 'controls' => { + '00_control1' => true, + }, + }, + }, + }.to_yaml + end + + let(:checks_yaml) do + { + 'version' => '2.0.0', + 'checks' => { + '00_check1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_00::test_param', + 'value' => 'a string', + }, + 'ces' => [ + '00_ce1', + ], + }, + '00_check2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_00::test_param2', + 'value' => 'another string', + }, + 'ces' => [ + '00_ce1', + ], + }, + }, + }.to_yaml + end + + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test') } + let(:test_module_path) { File.join(tmpdir, 'test_module_00') } + let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } + + before(:each) do + # Create the directory structure + FileUtils.mkdir_p(compliance_dir) + + # Write the test data files + File.write(File.join(compliance_dir, 'profiles.yaml'), profile_yaml) + File.write(File.join(compliance_dir, 'ces.yaml'), ces_yaml) + File.write(File.join(compliance_dir, 'checks.yaml'), checks_yaml) + + # Mock the Puppet environment's modulepath to include our temp directory + # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(Puppet::Node::Environment).to receive(:full_modulepath).and_return([tmpdir]) + # rubocop:enable RSpec/AnyInstance + end + + after(:each) do + # Clean up temporary directory + FileUtils.rm_rf(tmpdir) + end + + on_supported_os.each do |os, os_facts| + context "on #{os} with compliance_engine::enforcement and a non-existent profile" do + let(:facts) do + os_facts.merge('target_compliance_profile' => 'not_a_profile') + end + + let(:hieradata) { 'compliance-engine' } + + it { + is_expected.to run.with_params('test_module_00::test_param') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") + } + end + + context "on #{os} with compliance_engine::enforcement and an existing profile" do + let(:facts) do + os_facts.merge('target_compliance_profile' => '00_profile_test') + end + + let(:hieradata) { 'compliance-engine' } + + # Test unconfined data. + it { is_expected.to run.with_params('test_module_00::test_param').and_return('a string') } + it { is_expected.to run.with_params('test_module_00::test_param2').and_return('another string') } + end + + context "on #{os} with compliance_engine::enforcement and a profile directly referencing a check" do + let(:facts) do + os_facts.merge('target_compliance_profile' => '00_profile_with_check_reference') + end + + let(:hieradata) { 'compliance-engine' } + + # Test unconfined data. + it { + is_expected.to run.with_params('test_module_00::test_param') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") + } + it { is_expected.to run.with_params('test_module_00::test_param2').and_return('another string') } + end + end +end diff --git a/spec/functions/lookup/01_enforcement_confine_spec.rb b/spec/functions/lookup/01_enforcement_confine_spec.rb new file mode 100755 index 0000000..add27f4 --- /dev/null +++ b/spec/functions/lookup/01_enforcement_confine_spec.rb @@ -0,0 +1,237 @@ +#!/usr/bin/env ruby -S rspec + +require 'spec_helper' +require 'spec_helper_puppet' +require 'yaml' +require 'fileutils' +require 'tmpdir' + +RSpec.describe 'lookup' do + # Generate a fake module with dummy data for lookup(). + let(:profile_yaml) do + { + 'version' => '2.0.0', + 'profiles' => { + '01_profile_test' => { + 'controls' => { + '01_control1' => true, + '01_os_control' => true, + }, + }, + }, + }.to_yaml + end + + let(:ces_yaml) do + { + 'version' => '2.0.0', + 'ce' => { + '01_ce1' => { + 'controls' => { + '01_control1' => true, + }, + }, + '01_ce2' => { + 'controls' => { + '01_os_control' => true, + }, + }, + '01_ce3' => { + 'controls' => { + '01_control1' => true, + }, + 'confine' => { + 'module_name' => 'simp-compliance_engine', + 'module_version' => '< 3.1.0', + }, + }, + }, + }.to_yaml + end + + let(:checks_yaml) do + { + 'version' => '2.0.0', + 'checks' => { + '01_el_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_01::is_el', + 'value' => true, + }, + 'ces' => [ + '01_ce2', + ], + 'confine' => { + 'os.family' => 'RedHat', + }, + }, + '01_el_negative_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_01::is_not_el', + 'value' => true, + }, + 'ces' => [ + '01_ce2', + ], + 'confine' => { + 'os.family' => '!RedHat', + }, + }, + '01_el7_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_01::el_version', + 'value' => '7', + }, + 'ces' => [ + '01_ce2', + ], + 'confine' => { + 'os.name' => [ + 'RedHat', + 'CentOS', + ], + 'os.release.major' => '7', + }, + }, + '01_el7_negative_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_01::not_el_version', + 'value' => '7', + }, + 'ces' => [ + '01_ce2', + ], + 'confine' => { + 'os.name' => [ + '!RedHat', + ], + 'os.release.major' => '7', + }, + }, + '01_el7_negative_mixed_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_01::not_el_centos_version', + 'value' => '7', + }, + 'ces' => [ + '01_ce2', + ], + 'confine' => { + 'os.name' => [ + '!RedHat', + 'CentOS', + ], + 'os.release.major' => '7', + }, + }, + '01_confine_in_ces' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_01::fixed_confines', + 'value' => false, + }, + 'ces' => [ + '01_ce3', + ], + }, + }, + }.to_yaml + end + + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test') } + let(:test_module_path) { File.join(tmpdir, 'test_module_01') } + let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } + + before(:each) do + # Create the directory structure + FileUtils.mkdir_p(compliance_dir) + + # Write the test data files + File.write(File.join(compliance_dir, 'profiles.yaml'), profile_yaml) + File.write(File.join(compliance_dir, 'ces.yaml'), ces_yaml) + File.write(File.join(compliance_dir, 'checks.yaml'), checks_yaml) + + # Mock the Puppet environment's modulepath to include our temp directory + # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(Puppet::Node::Environment).to receive(:full_modulepath).and_return([tmpdir]) + # rubocop:enable RSpec/AnyInstance + end + + after(:each) do + # Clean up temporary directory + FileUtils.rm_rf(tmpdir) + end + + on_supported_os.each do |os, os_facts| + context "on #{os} with compliance_engine::enforcement and an existing profile" do + let(:facts) do + os_facts.merge('target_compliance_profile' => '01_profile_test') + end + + let(:hieradata) { 'compliance_engine' } + + # Test for confine on a single fact in checks. + if os_facts[:os]['family'] == 'RedHat' + it { is_expected.to run.with_params('test_module_01::is_el').and_return(true) } + else + it { is_expected.to run.with_params('test_module_01::is_el').and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::is_el'") } + end + + # Test for confine on a single fact in checks. + if os_facts[:os]['family'] != 'RedHat' + it { is_expected.to run.with_params('test_module_01::is_not_el').and_return(true) } + else + it do + is_expected.to run.with_params('test_module_01::is_not_el') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::is_not_el'") + end + end + + # Test for confine on multiple facts and an array of facts in checks. + if (os_facts[:os]['name'] == 'RedHat' || os_facts[:os]['name'] == 'CentOS') && os_facts[:os]['release']['major'] == '7' + it { is_expected.to run.with_params('test_module_01::el_version').and_return('7') } + else + it do + is_expected.to run.with_params('test_module_01::el_version') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::el_version'") + end + end + + # Test for confine on multiple facts and a negative fact match. + if (os_facts[:os]['name'] != 'RedHat') && os_facts[:os]['release']['major'] == '7' + it { is_expected.to run.with_params('test_module_01::not_el_version').and_return('7') } + else + it do + is_expected.to run.with_params('test_module_01::not_el_version') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_version'") + end + end + + # Test for confine on multiple facts and a negative fact match mixed with a positive one. + # TODO: This does not currently work as one might expect. This will still positively match OracleLinux even though + # we ask for OS names that aren't RedHat but are CentOS. The array we're confining can only do an OR operation rather + # than an AND with a negative lookup. + if (os_facts[:os]['name'] != 'RedHat') && (os_facts[:os]['name'] == 'CentOS') && os_facts[:os]['release']['major'] == '7' + it { is_expected.to run.with_params('test_module_01::not_el_centos_version').and_return('7') } # FIXME: # rubocop:disable RSpec/RepeatedExample + elsif (os_facts[:os]['name'] != 'RedHat') && (os_facts[:os]['name'] != 'CentOS') && os_facts[:os]['release']['major'] == '7' + it { is_expected.to run.with_params('test_module_01::not_el_centos_version').and_return('7') } # FIXME: # rubocop:disable RSpec/RepeatedExample + else + it do + is_expected.to run.with_params('test_module_01::not_el_centos_version') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_centos_version'") + end + end + + # Test for confine on module name & module version in ce. + it do + is_expected.to run.with_params('test_module_01::fixed_confines') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::fixed_confines'") + end + end + end +end diff --git a/spec/functions/lookup/02_enforcement_merge_spec.rb b/spec/functions/lookup/02_enforcement_merge_spec.rb new file mode 100755 index 0000000..87b32c2 --- /dev/null +++ b/spec/functions/lookup/02_enforcement_merge_spec.rb @@ -0,0 +1,163 @@ +#!/usr/bin/env ruby -S rspec + +require 'spec_helper' +require 'spec_helper_puppet' +require 'yaml' +require 'fileutils' +require 'tmpdir' + +RSpec.describe 'lookup' do + # Generate a fake module with dummy data for lookup(). + let(:profile_yaml) do + { + 'version' => '2.0.0', + 'profiles' => { + '02_profile_test' => { + 'controls' => { + '02_control1' => true, + }, + }, + }, + }.to_yaml + end + + let(:ces_yaml) do + { + 'version' => '2.0.0', + 'ce' => { + '02_ce1' => { + 'controls' => { + '02_control1' => true, + }, + }, + }, + }.to_yaml + end + + let(:checks_yaml) do + { + 'version' => '2.0.0', + 'checks' => { + '02_array check1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_02::array_param', + 'value' => [ + 'array value 1', + ], + }, + 'ces' => [ + '02_ce1', + ], + }, + '02_array check2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_02::array_param', + 'value' => [ + 'array value 2', + ], + }, + 'ces' => [ + '02_ce1', + ], + }, + '02_hash check1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_02::hash_param', + 'value' => { + 'hash key 1' => 'hash value 1', + }, + }, + 'ces' => [ + '02_ce1', + ], + }, + '02_hash check2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_02::hash_param', + 'value' => { + 'hash key 2' => 'hash value 2', + }, + }, + 'ces' => [ + '02_ce1', + ], + }, + '02_nested hash1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_02::nested_hash', + 'value' => { + 'key' => { + 'key1' => 'value1', + }, + }, + }, + 'ces' => [ + '02_ce1', + ], + }, + '02_nested hash2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_02::nested_hash', + 'value' => { + 'key' => { + 'key2' => 'value2', + }, + }, + }, + 'ces' => [ + '02_ce1', + ], + }, + }, + }.to_yaml + end + + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test') } + let(:test_module_path) { File.join(tmpdir, 'test_module_02') } + let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } + + before(:each) do + # Create the directory structure + FileUtils.mkdir_p(compliance_dir) + + # Write the test data files + File.write(File.join(compliance_dir, 'profiles.yaml'), profile_yaml) + File.write(File.join(compliance_dir, 'ces.yaml'), ces_yaml) + File.write(File.join(compliance_dir, 'checks.yaml'), checks_yaml) + + # Mock the Puppet environment's modulepath to include our temp directory + # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(Puppet::Node::Environment).to receive(:full_modulepath).and_return([tmpdir]) + # rubocop:enable RSpec/AnyInstance + end + + after(:each) do + # Clean up temporary directory + FileUtils.rm_rf(tmpdir) + end + + on_supported_os.each do |os, os_facts| + context "on #{os} with compliance_engine::enforcement and an existing profile" do + let(:facts) do + os_facts.merge('target_compliance_profile' => '02_profile_test') + end + + let(:hieradata) { 'compliance-engine' } + + # Test a simple array. + it { is_expected.to run.with_params('test_module_02::array_param').and_return(['array value 1', 'array value 2']) } + + # Test a simple hash. + it { is_expected.to run.with_params('test_module_02::hash_param').and_return({ 'hash key 1' => 'hash value 1', 'hash key 2' => 'hash value 2' }) } + + # Test a nested hash. + it { is_expected.to run.with_params('test_module_02::nested_hash').and_return({ 'key' => { 'key1' => 'value1', 'key2' => 'value2' } }) } + end + end +end diff --git a/spec/functions/lookup/03_enforcement_profile_merge_spec.rb b/spec/functions/lookup/03_enforcement_profile_merge_spec.rb new file mode 100755 index 0000000..84e34a1 --- /dev/null +++ b/spec/functions/lookup/03_enforcement_profile_merge_spec.rb @@ -0,0 +1,218 @@ +#!/usr/bin/env ruby -S rspec + +require 'spec_helper' +require 'spec_helper_puppet' +require 'yaml' +require 'fileutils' +require 'tmpdir' + +RSpec.describe 'lookup' do + # Generate a fake module with dummy data for lookup(). + let(:profile_yaml) do + { + 'version' => '2.0.0', + 'profiles' => { + 'profile_test1' => { + 'ces' => { + '03_profile_test1' => true, + }, + }, + 'profile_test2' => { + 'ces' => { + '03_profile_test2' => true, + }, + }, + }, + }.to_yaml + end + + let(:ces_yaml) do + { + 'version' => '2.0.0', + 'ce' => { + '03_profile_test1' => {}, + '03_profile_test2' => {}, + }, + }.to_yaml + end + + let(:checks_yaml) do + { + 'version' => '2.0.0', + 'checks' => { + '03_string check1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_03::string_param', + 'value' => 'string value 1', + }, + 'ces' => [ + '03_profile_test1', + ], + }, + '03_string check2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_03::string_param', + 'value' => 'string value 2', + }, + 'ces' => [ + '03_profile_test2', + ], + }, + '03_array check1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_03::array_param', + 'value' => [ + 'array value 1', + ], + }, + 'ces' => [ + '03_profile_test1', + ], + }, + '03_array check2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_03::array_param', + 'value' => [ + 'array value 2', + ], + }, + 'ces' => [ + '03_profile_test2', + ], + }, + '03_hash check1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_03::hash_param', + 'value' => { + 'hash key 1' => 'hash value 1', + }, + }, + 'ces' => [ + '03_profile_test1', + ], + }, + '03_hash check2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_03::hash_param', + 'value' => { + 'hash key 2' => '\-- hash value 2', + }, + }, + 'ces' => [ + '03_profile_test2', + ], + }, + '03_nested hash1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_03::nested_hash', + 'value' => { + 'key' => { + 'key1' => 'value1', + }, + }, + }, + 'ces' => [ + '03_profile_test1', + ], + }, + '03_nested hash2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_03::nested_hash', + 'value' => { + 'key' => { + 'key1' => 'value2', + 'key2' => 'value2', + }, + }, + }, + 'ces' => [ + '03_profile_test2', + ], + }, + }, + }.to_yaml + end + + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test') } + let(:test_module_path) { File.join(tmpdir, 'test_module_03') } + let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } + let(:hieradata_dir) { File.expand_path('../../data', __dir__) } + + before(:each) do + # Create the directory structure + FileUtils.mkdir_p(compliance_dir) + + # Write the test data files + File.write(File.join(compliance_dir, 'profiles.yaml'), profile_yaml) + File.write(File.join(compliance_dir, 'ces.yaml'), ces_yaml) + File.write(File.join(compliance_dir, 'checks.yaml'), checks_yaml) + + # Mock the Puppet environment's modulepath to include our temp directory + # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(Puppet::Node::Environment).to receive(:full_modulepath).and_return([tmpdir]) + # rubocop:enable RSpec/AnyInstance + end + + after(:each) do + # Clean up temporary directory + FileUtils.rm_rf(tmpdir) + end + + on_supported_os.each do |os, os_facts| + context "on #{os} with compliance_engine::enforcement merging profiles" do + let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } + let(:hieradata) { 'profile-merging' } + + before(:each) do + File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + test_hiera = { 'compliance_engine::enforcement' => ['profile_test1', 'profile_test2'] }.to_yaml + fh.puts test_hiera + end + end + + # Test a string. + it { is_expected.to run.with_params('test_module_03::string_param').and_return('string value 1') } + + # Test a simple array. + it { is_expected.to run.with_params('test_module_03::array_param').and_return(['array value 2', 'array value 1']) } + + # Test a simple hash. + it { is_expected.to run.with_params('test_module_03::hash_param').and_return({ 'hash key 1' => 'hash value 1', 'hash key 2' => '\-- hash value 2' }) } + + # Test a nested hash. + it { is_expected.to run.with_params('test_module_03::nested_hash').and_return({ 'key' => { 'key1' => 'value1', 'key2' => 'value2' } }) } + end + + context "on #{os} with compliance_engine::enforcement merging profiles in reverse order" do + let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } + let(:hieradata) { 'profile-merging' } + + before(:each) do + File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + test_hiera = { 'compliance_engine::enforcement' => ['profile_test2', 'profile_test1'] }.to_yaml + fh.puts test_hiera + end + end + + # Test a string. + it { is_expected.to run.with_params('test_module_03::string_param').and_return('string value 2') } + + # Test a simple array. + it { is_expected.to run.with_params('test_module_03::array_param').and_return(['array value 1', 'array value 2']) } + + # Test a simple hash. + it { is_expected.to run.with_params('test_module_03::hash_param').and_return({ 'hash key 2' => '\-- hash value 2', 'hash key 1' => 'hash value 1' }) } + + # Test a nested hash. + it { is_expected.to run.with_params('test_module_03::nested_hash').and_return({ 'key' => { 'key1' => 'value2', 'key2' => 'value2' } }) } + end + end +end diff --git a/spec/functions/lookup/04_enforcement_profile_merge_spec.rb b/spec/functions/lookup/04_enforcement_profile_merge_spec.rb new file mode 100755 index 0000000..b39e1ee --- /dev/null +++ b/spec/functions/lookup/04_enforcement_profile_merge_spec.rb @@ -0,0 +1,486 @@ +#!/usr/bin/env ruby -S rspec + +require 'spec_helper' +require 'spec_helper_puppet' +require 'yaml' +require 'fileutils' +require 'tmpdir' + +RSpec.describe 'lookup' do + # Generate a fake module with dummy data for lookup(). + # Break it up between multiple files to validate merging. + let(:files) do + { + 'profile_00' => { + 'version' => '2.0.0', + 'profiles' => { + 'profile_test1' => { + 'ces' => { + '04_profile_test1' => true, + }, + }, + }, + }, + 'profile_01' => { + 'version' => '2.0.0', + 'profiles' => { + 'profile_test2' => { + 'ces' => { + '04_profile_test2' => true, + }, + }, + }, + }, + 'ces_00' => { + 'version' => '2.0.0', + 'ce' => { + '04_profile_test1' => { + 'controls' => { + '04_control1' => true, + }, + 'oval-ids' => [ + - '04_oval_id1', + ], + }, + }, + }, + 'ces_01' => { + 'version' => '2.0.0', + 'ce' => { + '04_profile_test2' => { + 'identifiers' => { + '04_identifier1' => [], + }, + }, + }, + }, + 'checks_00' => { + 'version' => '2.0.0', + 'checks' => { + '04_string check1' => { + 'ces' => [ + '04_profile_test1', + ], + 'controls' => { + '04_control2' => true, + }, + }, + }, + }, + 'checks_01' => { + 'version' => '2.0.0', + 'checks' => { + '04_string check1' => { + 'type' => 'puppet-class-parameter', + }, + }, + }, + 'checks_02' => { + 'version' => '2.0.0', + 'checks' => { + '04_string check1' => { + 'settings' => { + 'value' => 'string value 1', + }, + }, + }, + }, + 'checks_03' => { + 'version' => '2.0.0', + 'checks' => { + '04_string check1' => { + 'settings' => { + 'parameter' => 'test_module_04::string_param', + }, + }, + }, + }, + 'checks_10' => { + 'version' => '2.0.0', + 'checks' => { + '04_string check2' => { + 'settings' => { + 'parameter' => 'test_module_04::string_param', + }, + }, + }, + }, + 'checks_11' => { + 'version' => '2.0.0', + 'checks' => { + '04_string check2' => { + 'settings' => { + 'value' => 'string value 2', + }, + 'identifiers' => { + '04_identifier2' => [], + }, + }, + }, + }, + 'checks_12' => { + 'version' => '2.0.0', + 'checks' => { + '04_string check2' => { + 'type' => 'puppet-class-parameter', + }, + }, + }, + 'checks_13' => { + 'version' => '2.0.0', + 'checks' => { + '04_string check2' => { + 'ces' => [ + '04_profile_test2', + ], + }, + }, + }, + 'checks_20' => { + 'version' => '2.0.0', + 'checks' => { + '04_array check1' => { + 'type' => 'puppet-class-parameter', + }, + }, + }, + 'checks_21' => { + 'version' => '2.0.0', + 'checks' => { + '04_array check1' => { + 'ces' => [ + '04_profile_test1', + ], + }, + }, + }, + 'checks_22' => { + 'version' => '2.0.0', + 'checks' => { + '04_array check1' => { + 'settings' => { + 'value' => [ + 'array value 1', + ], + }, + 'oval-ids' => [ + - '04_oval_id2', + ], + }, + }, + }, + 'checks_23' => { + 'version' => '2.0.0', + 'checks' => { + '04_array check1' => { + 'settings' => { + 'parameter' => 'test_module_04::array_param', + }, + }, + }, + }, + 'checks_30' => { + 'version' => '2.0.0', + 'checks' => { + '04_array check2' => { + 'settings' => { + 'value' => [ + 'array value 2', + ], + }, + }, + }, + }, + 'checks_31' => { + 'version' => '2.0.0', + 'checks' => { + '04_array check2' => { + 'type' => 'puppet-class-parameter', + }, + }, + }, + 'checks_32' => { + 'version' => '2.0.0', + 'checks' => { + '04_array check2' => { + 'ces' => [ + '04_profile_test2', + ], + }, + }, + }, + 'checks_33' => { + 'version' => '2.0.0', + 'checks' => { + '04_array check2' => { + 'settings' => { + 'parameter' => 'test_module_04::array_param', + }, + 'controls' => { + '04_control3' => true, + }, + }, + }, + }, + 'checks_40' => { + 'version' => '2.0.0', + 'checks' => { + '04_hash check1' => { + 'settings' => { + 'value' => { + 'hash key 1' => 'hash value 1', + }, + }, + 'identifiers' => { + '04_identifier3' => [], + }, + }, + }, + }, + 'checks_41' => { + 'version' => '2.0.0', + 'checks' => { + '04_hash check1' => { + 'settings' => { + 'parameter' => 'test_module_04::hash_param', + }, + }, + }, + }, + 'checks_42' => { + 'version' => '2.0.0', + 'checks' => { + '04_hash check1' => { + 'ces' => [ + '04_profile_test1', + ], + }, + }, + }, + 'checks_43' => { + 'version' => '2.0.0', + 'checks' => { + '04_hash check1' => { + 'type' => 'puppet-class-parameter', + }, + }, + }, + 'checks_50' => { + 'version' => '2.0.0', + 'checks' => { + '04_hash check2' => { + 'ces' => [ + '04_profile_test2', + ], + }, + }, + }, + 'checks_51' => { + 'version' => '2.0.0', + 'checks' => { + '04_hash check2' => { + 'type' => 'puppet-class-parameter', + 'oval-ids' => [ + - '04_oval_id3', + ], + }, + }, + }, + 'checks_52' => { + 'version' => '2.0.0', + 'checks' => { + '04_hash check2' => { + 'settings' => { + 'parameter' => 'test_module_04::hash_param', + }, + }, + }, + }, + 'checks_53' => { + 'version' => '2.0.0', + 'checks' => { + '04_hash check2' => { + 'settings' => { + 'value' => { + 'hash key 2' => 'hash value 2', + }, + }, + }, + }, + }, + 'checks_60' => { + 'version' => '2.0.0', + 'checks' => { + '04_nested hash1' => { + 'settings' => { + 'parameter' => 'test_module_04::nested_hash', + }, + }, + }, + }, + 'checks_61' => { + 'version' => '2.0.0', + 'checks' => { + '04_nested hash1' => { + 'ces' => [ + '04_profile_test1', + ], + }, + }, + }, + 'checks_62' => { + 'version' => '2.0.0', + 'checks' => { + '04_nested hash1' => { + 'settings' => { + 'value' => { + 'key' => { + 'key1' => 'value1', + }, + }, + }, + }, + }, + }, + 'checks_63' => { + 'version' => '2.0.0', + 'checks' => { + '04_nested hash1' => { + 'type' => 'puppet-class-parameter', + }, + }, + }, + 'checks_70' => { + 'version' => '2.0.0', + 'checks' => { + '04_nested hash2' => { + 'type' => 'puppet-class-parameter', + }, + }, + }, + 'checks_71' => { + 'version' => '2.0.0', + 'checks' => { + '04_nested hash2' => { + 'settings' => { + 'parameter' => 'test_module_04::nested_hash', + }, + }, + }, + }, + 'checks_72' => { + 'version' => '2.0.0', + 'checks' => { + '04_nested hash2' => { + 'settings' => { + 'value' => { + 'key' => { + 'key1' => 'value2', + }, + }, + }, + }, + }, + }, + 'checks_73' => { + 'version' => '2.0.0', + 'checks' => { + '04_nested hash2' => { + 'settings' => { + 'value' => { + 'key' => { + 'key2' => 'value2', + }, + }, + }, + }, + }, + }, + 'checks_74' => { + 'version' => '2.0.0', + 'checks' => { + '04_nested hash2' => { + 'ces' => [ + '04_profile_test2', + ], + }, + }, + }, + } + end + + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test') } + let(:test_module_path) { File.join(tmpdir, 'test_module_04') } + let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } + let(:hieradata_dir) { File.expand_path('../../data', __dir__) } + + before(:each) do + # Create the directory structure + FileUtils.mkdir_p(compliance_dir) + + # Write the test data files + files.each do |file, data| + File.write(File.join(compliance_dir, "#{file}.yaml"), data.to_yaml) + end + + # Mock the Puppet environment's modulepath to include our temp directory + # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(Puppet::Node::Environment).to receive(:full_modulepath).and_return([tmpdir]) + # rubocop:enable RSpec/AnyInstance + end + + after(:each) do + # Clean up temporary directory + FileUtils.rm_rf(tmpdir) + end + + on_supported_os.each do |os, os_facts| + context "on #{os} with compliance_engine::enforcement merging profiles" do + let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } + let(:hieradata) { 'profile-merging' } + + before(:each) do + File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + test_hiera = { 'compliance_engine::enforcement' => ['profile_test1', 'profile_test2'] }.to_yaml + fh.puts test_hiera + end + end + + # Test a string. + it { is_expected.to run.with_params('test_module_04::string_param').and_return('string value 1') } + + # Test a simple array. + it { is_expected.to run.with_params('test_module_04::array_param').and_return(['array value 2', 'array value 1']) } + + # Test a simple hash. + it { is_expected.to run.with_params('test_module_04::hash_param').and_return({ 'hash key 1' => 'hash value 1', 'hash key 2' => 'hash value 2' }) } + + # Test a nested hash. + it { is_expected.to run.with_params('test_module_04::nested_hash').and_return({ 'key' => { 'key1' => 'value1', 'key2' => 'value2' } }) } + end + + context "on #{os} with compliance_engine::enforcement merging profiles in reverse order" do + let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } + let(:hieradata) { 'profile-merging' } + + before(:each) do + File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + test_hiera = { 'compliance_engine::enforcement' => ['profile_test2', 'profile_test1'] }.to_yaml + fh.puts test_hiera + end + end + + # Test a string. + it { is_expected.to run.with_params('test_module_04::string_param').and_return('string value 2') } + + # Test a simple array. + it { is_expected.to run.with_params('test_module_04::array_param').and_return(['array value 1', 'array value 2']) } + + # Test a simple hash. + it { is_expected.to run.with_params('test_module_04::hash_param').and_return({ 'hash key 2' => 'hash value 2', 'hash key 1' => 'hash value 1' }) } + + # Test a nested hash. + it { is_expected.to run.with_params('test_module_04::nested_hash').and_return({ 'key' => { 'key1' => 'value2', 'key2' => 'value2' } }) } + end + end +end diff --git a/spec/functions/lookup/05_enforcement_override_spec.rb b/spec/functions/lookup/05_enforcement_override_spec.rb new file mode 100755 index 0000000..3ed1ae2 --- /dev/null +++ b/spec/functions/lookup/05_enforcement_override_spec.rb @@ -0,0 +1,135 @@ +#!/usr/bin/env ruby -S rspec + +require 'spec_helper' +require 'spec_helper_puppet' +require 'yaml' +require 'fileutils' +require 'tmpdir' + +RSpec.describe 'lookup' do + # Generate a fake module with dummy data for lookup(). + let(:profile_yaml) do + { + 'version' => '2.0.0', + 'profiles' => { + 'profile_test1' => { + 'ces' => { + '05_profile_test1' => true, + '05_profile_test2' => true, + }, + }, + }, + }.to_yaml + end + + let(:ces_yaml) do + { + 'version' => '2.0.0', + 'ce' => { + '05_profile_test1' => {}, + '05_profile_test2' => {}, + }, + }.to_yaml + end + + let(:checks_yaml) do + { + 'version' => '2.0.0', + 'checks' => { + '05_hash check1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_05::hash_param', + 'value' => { + 'hash key 1' => 'hash value 1', + }, + }, + 'ces' => [ + '05_profile_test1', + ], + }, + '05_hash check2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_05::hash_param', + 'value' => { + 'hash key 2' => 'hash value 2', + }, + }, + 'ces' => [ + '05_profile_test2', + ], + }, + }, + }.to_yaml + end + + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test') } + let(:test_module_path) { File.join(tmpdir, 'test_module_05') } + let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } + let(:hieradata_dir) { File.expand_path('../../data', __dir__) } + + before(:each) do + # Create the directory structure + FileUtils.mkdir_p(compliance_dir) + + # Write the test data files + File.write(File.join(compliance_dir, 'profiles.yaml'), profile_yaml) + File.write(File.join(compliance_dir, 'ces.yaml'), ces_yaml) + File.write(File.join(compliance_dir, 'checks.yaml'), checks_yaml) + + # Mock the Puppet environment's modulepath to include our temp directory + # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(Puppet::Node::Environment).to receive(:full_modulepath).and_return([tmpdir]) + # rubocop:enable RSpec/AnyInstance + end + + after(:each) do + # Clean up temporary directory + FileUtils.rm_rf(tmpdir) + end + + on_supported_os.each do |os, os_facts| + context "on #{os} with compliance data in modules" do + let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } + let(:hieradata) { 'profile-merging' } + + before(:each) do + File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + test_hiera = { 'compliance_engine::enforcement' => ['profile_test1'] }.to_yaml + fh.puts test_hiera + end + end + + # Test a simple hash. + it { is_expected.to run.with_params('test_module_05::hash_param').and_return({ 'hash key 1' => 'hash value 1', 'hash key 2' => 'hash value 2' }) } + end + + context "on #{os} with compliance_engine::compliance_map override" do + let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } + let(:hieradata) { 'profile-merging' } + + before(:each) do + File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + test_hiera = { + 'compliance_engine::enforcement' => ['profile_test1'], + 'compliance_engine::compliance_map' => { + 'version' => '2.0.0', + 'profiles' => { + 'profile_test1' => { + 'ces' => { + '05_profile_test2' => false, + }, + }, + }, + }, + }.to_yaml + fh.puts test_hiera + end + end + + # Test a simple hash. + it { is_expected.to run.with_params('test_module_05::hash_param').and_return({ 'hash key 1' => 'hash value 1' }) } + end + end +end diff --git a/spec/functions/lookup/06_enforcement_debug_spec.rb b/spec/functions/lookup/06_enforcement_debug_spec.rb new file mode 100755 index 0000000..f6a010c --- /dev/null +++ b/spec/functions/lookup/06_enforcement_debug_spec.rb @@ -0,0 +1,132 @@ +#!/usr/bin/env ruby -S rspec + +require 'spec_helper' +require 'spec_helper_puppet' +require 'yaml' +require 'fileutils' +require 'tmpdir' + +RSpec.describe 'lookup', skip: 'Debug features not yet implemented in compliance_engine' do + # Generate a fake module with dummy data for lookup(). + let(:profile) do + { + 'version' => '2.0.0', + 'profiles' => { + '06_profile_test' => { + 'controls' => { + '06_control1' => true, + }, + }, + }, + } + end + + let(:ces) do + { + 'version' => '2.0.0', + 'ce' => { + '06_ce1' => { + 'controls' => { + '06_control1' => true, + }, + }, + }, + } + end + + let(:checks) do + { + 'version' => '2.0.0', + 'checks' => { + '06_check1' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_06::test_param', + 'value' => 'a string', + }, + 'ces' => [ + '06_ce1', + ], + }, + '06_check2' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_06::test_param2', + 'value' => 'another string', + }, + 'ces' => [ + '06_ce1', + ], + }, + }, + } + end + + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test') } + let(:test_module_path) { File.join(tmpdir, 'test_module_06') } + let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } + + before(:each) do + # Create the directory structure + FileUtils.mkdir_p(compliance_dir) + + # Write the test data files + File.write(File.join(compliance_dir, 'profiles.yaml'), profile.to_yaml) + File.write(File.join(compliance_dir, 'ces.yaml'), ces.to_yaml) + File.write(File.join(compliance_dir, 'checks.yaml'), checks.to_yaml) + + # Mock the Puppet environment's modulepath to include our temp directory + # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(Puppet::Node::Environment).to receive(:full_modulepath).and_return([tmpdir]) + # rubocop:enable RSpec/AnyInstance + end + + after(:each) do + # Clean up temporary directory + FileUtils.rm_rf(tmpdir) + end + + on_supported_os.each do |os, os_facts| + context "on #{os} compliance_engine::debug values" do + let(:lookup) { subject } + let(:facts) do + os_facts.merge('target_compliance_profile' => '06_profile_test') + end + + let(:hieradata) { 'compliance-engine' } + + it do + result = lookup.execute('compliance_engine::debug::hiera_backend_compile_time') + expect(result).to be_a(Float) + expect(result).to be > 0 + end + + it do + result = lookup.execute('compliance_engine::debug::dump') + expect(result).to be_a(Hash) + expect(result['test_module_06::test_param']).to eq('a string') + expect(result['test_module_06::test_param2']).to eq('another string') + expect(result.keys).to eq([ + 'test_module_06::test_param', + 'test_module_06::test_param2', + 'compliance_engine::debug::hiera_backend_compile_time', + ]) + end + + it do + result = lookup.execute('compliance_engine::debug::profiles') + expect(result).to be_a(Array) + expect(result).to include('06_profile_test') + end + + it do + result = lookup.execute('compliance_engine::debug::compliance_data') + expect(result).to be_a(Hash) + expect(result.keys).to eq(['version', 'profiles', 'ce', 'checks']) + expect(result['profiles']).to include(profile['profiles']) + expect(result['ce']).to include(ces['ce']) + expect(result['checks']).to include(checks['checks']) + end + end + end +end diff --git a/spec/functions/lookup/07_enforcement_tolerance_spec.rb b/spec/functions/lookup/07_enforcement_tolerance_spec.rb new file mode 100755 index 0000000..17bf8e5 --- /dev/null +++ b/spec/functions/lookup/07_enforcement_tolerance_spec.rb @@ -0,0 +1,237 @@ +#!/usr/bin/env ruby -S rspec + +require 'spec_helper' +require 'spec_helper_puppet' +require 'yaml' +require 'fileutils' +require 'tmpdir' + +RSpec.describe 'lookup' do + # Generate a fake module with dummy data for lookup(). + let(:profile_yaml) do + { + 'version' => '2.0.0', + 'profiles' => { + '07_profile_test' => { + 'controls' => { + '07_control1' => true, + '07_os_control' => true, + }, + }, + }, + }.to_yaml + end + + let(:ces_yaml) do + { + 'version' => '2.0.0', + 'ce' => { + '07_ce1' => { + 'controls' => { + '07_control1' => true, + }, + }, + '07_ce2' => { + 'controls' => { + '07_os_control' => true, + }, + }, + }, + }.to_yaml + end + + let(:checks_yaml) do + { + 'version' => '2.0.0', + 'checks' => { + '07_disabled_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_07::is_disabled', + 'value' => true, + }, + 'ces' => [ + '07_ce2', + ], + 'remediation' => { + 'disabled' => [ + { 'reason' => 'This is the reason this check is disabled.' }, + ] + }, + }, + '07_level_21_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_07::is_level_21', + 'value' => true, + }, + 'ces' => [ + '07_ce2', + ], + 'remediation' => { + 'risk' => [ + { 'level' => 21 }, + ] + }, + }, + '07_level_41_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_07::is_level_41', + 'value' => true, + }, + 'ces' => [ + '07_ce2', + ], + 'remediation' => { + 'risk' => [ + { 'level' => 41, 'reason' => 'this is the reason for level 41' }, + ] + }, + }, + '07_level_61_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_07::is_level_61', + 'value' => true, + }, + 'ces' => [ + '07_ce2', + ], + 'remediation' => { + 'risk' => [ + { 'level' => 61, 'reason' => 'this is the reason for level 61' }, + ] + }, + }, + '07_level_81_check' => { + 'type' => 'puppet-class-parameter', + 'settings' => { + 'parameter' => 'test_module_07::is_level_81', + 'value' => true, + }, + 'ces' => [ + '07_ce2', + ], + 'remediation' => { + 'risk' => [ + { 'level' => 81, 'reason' => 'this is the reason for level 81' }, + ] + }, + }, + }, + }.to_yaml + end + + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test_07') } + let(:test_module_path) { File.join(tmpdir, 'test_module_07') } + let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } + + before(:each) do + # Create the directory structure + FileUtils.mkdir_p(compliance_dir) + + # Write the test data files + File.write(File.join(compliance_dir, 'profiles.yaml'), profile_yaml) + File.write(File.join(compliance_dir, 'ces.yaml'), ces_yaml) + File.write(File.join(compliance_dir, 'checks.yaml'), checks_yaml) + + # Mock the Puppet environment's modulepath to include our temp directory + # rubocop:disable RSpec/AnyInstance + allow_any_instance_of(Puppet::Node::Environment).to receive(:full_modulepath).and_return([tmpdir]) + # rubocop:enable RSpec/AnyInstance + end + + after(:each) do + # Clean up temporary directory + FileUtils.rm_rf(tmpdir) if tmpdir && File.exist?(tmpdir) + end + + on_supported_os.each do |os, os_facts| + context "on #{os} with compliance_engine::::enforcement and an existing profile using tolerance above level 21" do + let(:facts) do + os_facts.merge( + 'custom_hiera' => 'compliance_engine', + 'target_compliance_profile' => '07_profile_test', + 'target_enforcement_tolerance' => '22', + ) + end + let(:hieradata) { 'compliance_engine' } + + it do + is_expected.to run.with_params('test_module_07::is_disabled') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + end + it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } + it do + is_expected.to run.with_params('test_module_07::is_level_41') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_41'") + end + it do + is_expected.to run.with_params('test_module_07::is_level_61') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") + end + it do + is_expected.to run.with_params('test_module_07::is_level_81') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") + end + end + + context "on #{os} with compliance_engine::::enforcement and an existing profile using tolerance above level 41" do + let(:facts) do + os_facts.merge('custom_hiera' => 'compliance_engine', 'target_compliance_profile' => '07_profile_test', 'target_enforcement_tolerance' => '42') + end + let(:hieradata) { 'compliance_engine' } + + it do + is_expected.to run.with_params('test_module_07::is_disabled') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + end + it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } + it { is_expected.to run.with_params('test_module_07::is_level_41').and_return(true) } + it do + is_expected.to run.with_params('test_module_07::is_level_61') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") + end + it do + is_expected.to run.with_params('test_module_07::is_level_81') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") + end + end + + context "on #{os} with compliance_engine::::enforcement and an existing profile using tolerance above level 61" do + let(:facts) do + os_facts.merge('custom_hiera' => 'compliance_engine', 'target_compliance_profile' => '07_profile_test', 'target_enforcement_tolerance' => '62') + end + let(:hieradata) { 'compliance_engine' } + + it do + is_expected.to run.with_params('test_module_07::is_disabled') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + end + it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } + it { is_expected.to run.with_params('test_module_07::is_level_41').and_return(true) } + it { is_expected.to run.with_params('test_module_07::is_level_61').and_return(true) } + it do + is_expected.to run.with_params('test_module_07::is_level_81') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") + end + end + + context "on #{os} with compliance_engine::::enforcement and an existing profile using tolerance above level 81" do + let(:facts) do + os_facts.merge('custom_hiera' => 'compliance_engine', 'target_compliance_profile' => '07_profile_test', 'target_enforcement_tolerance' => '82') + end + let(:hieradata) { 'compliance_engine' } + + it do + is_expected.to run.with_params('test_module_07::is_disabled') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + end + it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } + it { is_expected.to run.with_params('test_module_07::is_level_41').and_return(true) } + it { is_expected.to run.with_params('test_module_07::is_level_61').and_return(true) } + it { is_expected.to run.with_params('test_module_07::is_level_81').and_return(true) } + end + end +end diff --git a/spec/functions/lookup/10_enforce_spec.rb b/spec/functions/lookup/10_enforce_spec.rb new file mode 100644 index 0000000..0dfb9bb --- /dev/null +++ b/spec/functions/lookup/10_enforce_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' +require 'spec_helper_puppet' +# require 'fileutils' + +def write_hieradata(policy_order) + data = { + 'compliance_engine::enforcement' => policy_order, + 'compliance_engine::compliance_map' => { + 'version' => '2.0.0', + 'profiles' => { + 'disa_stig' => { + 'controls' => { + 'disa_stig' => true, + }, + }, + 'nist_800_53:rev4' => { + 'controls' => { + 'nist_800_53:rev4' => true, + }, + }, + }, + 'controls' => { + 'disa_stig' => {}, + 'nist_800_53:rev4' => {}, + }, + 'checks' => { + 'oval:com.puppet.test.disa.useradd_shells' => { + 'type' => 'puppet-class-parameter', + 'controls' => { + 'disa_stig' => true, + }, + 'identifiers' => { + 'FOO2' => ['FOO2'], + 'BAR2' => ['BAR2'] + }, + 'settings' => { + 'parameter' => 'useradd::shells', + 'value' => ['/bin/disa'] + } + }, + 'oval:com.puppet.test.nist.useradd_shells' => { + 'type' => 'puppet-class-parameter', + 'controls' => { + 'nist_800_53:rev4' => true + }, + 'identifiers' => { + 'FOO2' => ['FOO2'], + 'BAR2' => ['BAR2'] + }, + 'settings' => { + 'parameter' => 'useradd::shells', + 'value' => ['/bin/nist'] + } + } + } + } + } + + # fixtures = File.expand_path('../../fixtures', __dir__) + + # FileUtils.mkdir_p(File.join(fixtures, 'hieradata')) + # File.open(File.join(fixtures, 'hieradata', '10_enforce_spec.yaml'), 'w') do |fh| + File.open(File.join(File.expand_path('../..', __dir__), 'data', '10_enforce_spec.yaml'), 'w') do |fh| + fh.puts data.to_yaml + end +end + +RSpec.describe 'lookup' do + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts.merge('custom_hiera' => '10_enforce_spec') } + + context 'with a single compliance map' do + let(:lookup) { subject } + let(:hieradata) { '10_enforce_spec' } + let(:policy_order) { ['disa_stig'] } + + before(:each) do + write_hieradata(policy_order) + end + + it 'returns /bin/disa' do + result = lookup.execute('useradd::shells') + expect(result).to be_instance_of(Array) + expect(result).to include('/bin/disa') + end + + context 'with a String compliance map' do + let(:policy_order) { 'disa_stig' } + + it 'returns /bin/disa' do + skip('String value for compliance_engine::enforcement not supported, must be Array') + result = lookup.execute('useradd::shells') + expect(result).to be_instance_of(Array) + expect(result).to include('/bin/disa') + end + end + end + + context 'when disa is higher priority' do + let(:lookup) { subject } + let(:hieradata) { '10_enforce_spec' } + let(:policy_order) { ['disa_stig', 'nist_800_53:rev4'] } + + before(:each) do + write_hieradata(policy_order) + end + + it 'returns /bin/disa and /bin/nist' do + result = lookup.execute('useradd::shells') + expect(result).to be_instance_of(Array) + expect(result).to include('/bin/disa') + expect(result).to include('/bin/nist') + end + end + end + end +end diff --git a/spec/hiera.yaml b/spec/hiera.yaml new file mode 100644 index 0000000..a5d02fd --- /dev/null +++ b/spec/hiera.yaml @@ -0,0 +1,16 @@ +--- +version: 5 +defaults: + datadir: data + data_hash: yaml_data +hierarchy: + - name: Custom Test Hiera + path: "%{custom_hiera}.yaml" + - name: "%{module_name}" + path: "%{module_name}.yaml" + - name: Common + paths: + - default.yaml + - common.yaml + - name: "Compliance Engine" + lookup_key: "compliance_engine::enforcement" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b26d6f8..7f349d3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,4 +12,6 @@ config.expect_with :rspec do |c| c.syntax = :expect end + + config.mock_with :rspec end diff --git a/spec/spec_helper_puppet.rb b/spec/spec_helper_puppet.rb new file mode 100644 index 0000000..9e98c89 --- /dev/null +++ b/spec/spec_helper_puppet.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +RSpec.configure do |c| + c.mock_with :rspec +end + +require 'puppetlabs_spec_helper/module_spec_helper' +require 'rspec-puppet-facts' + +require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb')) + +# Load support files +Dir[File.join(__dir__, 'support', '**', '*.rb')].sort.each { |f| require f } + +include RspecPuppetFacts + +default_facts = { + puppetversion: Puppet.version, + facterversion: Facter.version, +} + +default_fact_files = [ + File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml')), + File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml')), +] + +default_fact_files.each do |f| + next unless File.exist?(f) && File.readable?(f) && File.size?(f) + + begin + require 'deep_merge' + default_facts.deep_merge!(YAML.safe_load(File.read(f), permitted_classes: [], permitted_symbols: [], aliases: true)) + rescue StandardError => e + RSpec.configuration.reporter.message "WARNING: Unable to load #{f}: #{e}" + end +end + +# read default_facts and merge them over what is provided by facterdb +default_facts.each do |fact, value| + add_custom_fact fact, value, merge_facts: true +end + +RSpec.configure do |c| + c.default_facts = default_facts + c.hiera_config = 'spec/hiera.yaml' + c.before :each do + # set to strictest setting for testing + # by default Puppet runs at warning level + Puppet.settings[:strict] = :warning + Puppet.settings[:strict_variables] = true + end + c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT'] + c.after(:suite) do + RSpec::Puppet::Coverage.report!(0) + end + + # Filter backtrace noise + backtrace_exclusion_patterns = [ + %r{spec_helper}, + %r{gems}, + ] + + if c.respond_to?(:backtrace_exclusion_patterns) + c.backtrace_exclusion_patterns = backtrace_exclusion_patterns + elsif c.respond_to?(:backtrace_clean_patterns) + c.backtrace_clean_patterns = backtrace_exclusion_patterns + end +end + +# Ensures that a module is defined +# @param module_name Name of the module +def ensure_module_defined(module_name) + module_name.split('::').reduce(Object) do |last_module, next_module| + last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module, false) + last_module.const_get(next_module, false) + end +end + +# 'spec_overrides' from sync.yml will appear below this line diff --git a/spec/support/compliance_module_mocks.rb b/spec/support/compliance_module_mocks.rb new file mode 100644 index 0000000..64d9339 --- /dev/null +++ b/spec/support/compliance_module_mocks.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# Shared context for mocking compliance module file system operations +RSpec.shared_context 'compliance module mocks' do |module_name| + let(:fixtures) { File.expand_path('../../fixtures', __dir__) } + let(:module_path) { File.join(fixtures, 'modules', module_name) } + let(:compliance_dir) { File.join(module_path, 'SIMP', 'compliance_profiles') } + let(:compliance_files) { ['profile.yaml', 'ces.yaml', 'checks.yaml'].map { |f| File.join(compliance_dir, f) } } + + before(:each) do + allow(Dir).to receive(:glob).and_call_original + allow(Dir).to receive(:entries).and_call_original + allow(File).to receive(:directory?).and_call_original + allow(File).to receive(:exist?).and_call_original + + # Mock the modulepath directory entries to include the test module + allow(Dir).to receive(:entries).with(File.join(fixtures, 'modules')).and_return(['.', '..', 'compliance_engine', module_name]) + + allow(File).to receive(:directory?).with(Pathname.new(File.join(fixtures, 'modules'))).and_return(true) + allow(File).to receive(:directory?).with(File.join(fixtures, 'modules', module_name)).and_return(true) + allow(File).to receive(:directory?).with(module_path).and_return(true) + allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) + allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) + + # Mock metadata.json existence check (default to not existing unless overridden) + metadata_path = File.join(module_path, 'metadata.json') + allow(File).to receive(:exist?).with(metadata_path).and_return(defined?(metadata_json) && !metadata_json.nil?) + if defined?(metadata_json) && metadata_json + allow(File).to receive(:read).with(metadata_path).and_return(metadata_json) + end + + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return(compliance_files) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) + + allow(File).to receive(:size).and_call_original + allow(File).to receive(:mtime).and_call_original + allow(File).to receive(:read).and_call_original + + # Mock compliance data files + allow(File).to receive(:size).with(File.join(compliance_dir, 'profile.yaml')).and_return(profile_yaml.length) + allow(File).to receive(:mtime).with(File.join(compliance_dir, 'profile.yaml')).and_return(Time.now) + allow(File).to receive(:read).with(File.join(compliance_dir, 'profile.yaml')).and_return(profile_yaml) + + allow(File).to receive(:size).with(File.join(compliance_dir, 'ces.yaml')).and_return(ces_yaml.length) + allow(File).to receive(:mtime).with(File.join(compliance_dir, 'ces.yaml')).and_return(Time.now) + allow(File).to receive(:read).with(File.join(compliance_dir, 'ces.yaml')).and_return(ces_yaml) + + allow(File).to receive(:size).with(File.join(compliance_dir, 'checks.yaml')).and_return(checks_yaml.length) + allow(File).to receive(:mtime).with(File.join(compliance_dir, 'checks.yaml')).and_return(Time.now) + allow(File).to receive(:read).with(File.join(compliance_dir, 'checks.yaml')).and_return(checks_yaml) + end +end From 1f4856541f8219fbbfccf178183d97bdb974b663 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Mon, 12 Jan 2026 17:36:21 +0000 Subject: [PATCH 02/22] Use the openvox gem --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 34f1f33..d1534cd 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gemspec gem 'rake', '~> 13.3.0' group :tests do - gem 'puppet', ENV.fetch('PUPPET_VERSION', '~> 8.0') + gem 'openvox', ENV.fetch('OPENVOX_VERSION', ENV.fetch('PUPPET_VERSION', '~> 8.0')) gem 'puppetlabs_spec_helper', '~> 8.0' gem 'rspec', '~> 3.12' gem 'rspec-puppet', '~> 5.0.0' From 20ee7dfa36322e8e86ae99e238d6c8db8d8f5560 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Mon, 12 Jan 2026 17:39:16 +0000 Subject: [PATCH 03/22] Cleanup for rubocop --- spec/functions/lookup/01_enforcement_confine_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/functions/lookup/01_enforcement_confine_spec.rb b/spec/functions/lookup/01_enforcement_confine_spec.rb index add27f4..3bbcde5 100755 --- a/spec/functions/lookup/01_enforcement_confine_spec.rb +++ b/spec/functions/lookup/01_enforcement_confine_spec.rb @@ -193,7 +193,7 @@ end # Test for confine on multiple facts and an array of facts in checks. - if (os_facts[:os]['name'] == 'RedHat' || os_facts[:os]['name'] == 'CentOS') && os_facts[:os]['release']['major'] == '7' + if ['RedHat', 'CentOS'].include?(os_facts[:os]['name']) && os_facts[:os]['release']['major'] == '7' it { is_expected.to run.with_params('test_module_01::el_version').and_return('7') } else it do From 5cf763658063a632f804d9ab359205990aff94c3 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Mon, 12 Jan 2026 17:55:59 +0000 Subject: [PATCH 04/22] Minor fixes --- Gemfile | 4 ++-- spec/functions/lookup/07_enforcement_tolerance_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index d1534cd..b204ca3 100644 --- a/Gemfile +++ b/Gemfile @@ -11,8 +11,8 @@ group :tests do gem 'openvox', ENV.fetch('OPENVOX_VERSION', ENV.fetch('PUPPET_VERSION', '~> 8.0')) gem 'puppetlabs_spec_helper', '~> 8.0' gem 'rspec', '~> 3.12' - gem 'rspec-puppet', '~> 5.0.0' - gem 'rspec-puppet-facts', '~> 3.0' + gem 'rspec-puppet', '~> 5.0' + gem 'rspec-puppet-facts', '~> 6.0' gem 'rubocop', '~> 1.81.0' gem 'rubocop-performance', '~> 1.26.0' gem 'rubocop-rake', '~> 0.7.0' diff --git a/spec/functions/lookup/07_enforcement_tolerance_spec.rb b/spec/functions/lookup/07_enforcement_tolerance_spec.rb index 17bf8e5..823ba9c 100755 --- a/spec/functions/lookup/07_enforcement_tolerance_spec.rb +++ b/spec/functions/lookup/07_enforcement_tolerance_spec.rb @@ -148,7 +148,7 @@ end on_supported_os.each do |os, os_facts| - context "on #{os} with compliance_engine::::enforcement and an existing profile using tolerance above level 21" do + context "on #{os} with compliance_engine::enforcement and an existing profile using tolerance above level 21" do let(:facts) do os_facts.merge( 'custom_hiera' => 'compliance_engine', @@ -177,7 +177,7 @@ end end - context "on #{os} with compliance_engine::::enforcement and an existing profile using tolerance above level 41" do + context "on #{os} with compliance_engine::enforcement and an existing profile using tolerance above level 41" do let(:facts) do os_facts.merge('custom_hiera' => 'compliance_engine', 'target_compliance_profile' => '07_profile_test', 'target_enforcement_tolerance' => '42') end @@ -199,7 +199,7 @@ end end - context "on #{os} with compliance_engine::::enforcement and an existing profile using tolerance above level 61" do + context "on #{os} with compliance_engine::enforcement and an existing profile using tolerance above level 61" do let(:facts) do os_facts.merge('custom_hiera' => 'compliance_engine', 'target_compliance_profile' => '07_profile_test', 'target_enforcement_tolerance' => '62') end @@ -218,7 +218,7 @@ end end - context "on #{os} with compliance_engine::::enforcement and an existing profile using tolerance above level 81" do + context "on #{os} with compliance_engine::enforcement and an existing profile using tolerance above level 81" do let(:facts) do os_facts.merge('custom_hiera' => 'compliance_engine', 'target_compliance_profile' => '07_profile_test', 'target_enforcement_tolerance' => '82') end From b452a7386cdc69e303850ed211c00dd4faacaa69 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Mon, 12 Jan 2026 21:32:44 +0000 Subject: [PATCH 05/22] Refactor to use voxpupuli-test and clean up to use its rubocop config where possible --- .rubocop.yml | 759 +----------------- Gemfile | 9 +- Rakefile | 21 +- lib/compliance_engine.rb | 2 +- lib/compliance_engine/collection.rb | 2 +- lib/compliance_engine/component.rb | 17 +- lib/compliance_engine/data.rb | 22 +- lib/compliance_engine/data_loader.rb | 1 + lib/compliance_engine/environment_loader.rb | 13 +- lib/compliance_engine/module_loader.rb | 12 +- lib/compliance_engine/version.rb | 1 + .../compliance_engine/enforcement.rb | 17 +- spec/classes/compliance_engine/check_spec.rb | 6 +- .../compliance_engine/data_loader_spec.rb | 2 +- spec/classes/compliance_engine/data_spec.rb | 154 ++-- .../compliance_engine/module_loader_spec.rb | 24 +- spec/fixtures/modules/compliance_engine | 1 - .../compliance_engine/enforcement_spec.rb | 52 +- spec/functions/lookup/00_enforcement_spec.rb | 10 +- .../lookup/01_enforcement_confine_spec.rb | 51 +- .../lookup/02_enforcement_merge_spec.rb | 1 + .../03_enforcement_profile_merge_spec.rb | 5 +- .../04_enforcement_profile_merge_spec.rb | 41 +- .../lookup/05_enforcement_override_spec.rb | 3 +- .../lookup/06_enforcement_debug_spec.rb | 3 +- .../lookup/07_enforcement_tolerance_spec.rb | 63 +- spec/functions/lookup/10_enforce_spec.rb | 2 + spec/spec_helper_puppet.rb | 2 +- spec/support/compliance_module_mocks.rb | 12 +- 29 files changed, 300 insertions(+), 1008 deletions(-) delete mode 120000 spec/fixtures/modules/compliance_engine diff --git a/.rubocop.yml b/.rubocop.yml index 81fff6e..eed30a0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,11 +1,9 @@ --- -plugins: - - rubocop-performance - - rubocop-rspec - - rubocop-rake +inherit_gem: + voxpupuli-test: rubocop.yml + AllCops: - DisplayCopNames: true - TargetRubyVersion: "2.7" + TargetRubyVersion: 2.7 Include: - "**/*.rb" - Gemfile @@ -17,776 +15,59 @@ AllCops: - spec/fixtures/**/* - vendor/**/* - dist/**/* + Layout/LineLength: Description: People have wide screens, use them. Max: 200 -RSpec/BeforeAfterAll: - Description: - Beware of using after(:all) as it may cause state to leak between tests. - A necessary evil in acceptance testing. - Exclude: - - spec/acceptance/**/*.rb + RSpec/HookArgument: Description: Prefer explicit :each argument, matching existing module's style EnforcedStyle: each -RSpec/DescribeSymbol: - Exclude: - - spec/unit/facter/**/*.rb + Style/BlockDelimiters: Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to be consistent then. EnforcedStyle: braces_for_chaining + Style/ClassAndModuleChildren: Description: Compact style reduces the required amount of indentation. EnforcedStyle: compact -Style/EmptyElse: - Description: Enforce against empty else clauses, but allow `nil` for clarity. - EnforcedStyle: empty -Style/FormatString: - Description: Following the main puppet project's style, prefer the % format format. - EnforcedStyle: percent -Style/FormatStringToken: - Description: - Following the main puppet project's style, prefer the simpler template - tokens over annotated ones. - EnforcedStyle: template -Style/Lambda: - Description: Prefer the keyword for easier discoverability. - EnforcedStyle: literal -Style/RegexpLiteral: - Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168 - EnforcedStyle: percent_r -Style/TernaryParentheses: - Description: - Checks for use of parentheses around ternary conditions. Enforce parentheses - on complex expressions for better readability, but seriously consider breaking - it up. - EnforcedStyle: require_parentheses_when_complex -Style/TrailingCommaInArguments: - Description: - Prefer always trailing comma on multiline argument lists. This makes - diffs, and re-ordering nicer. - EnforcedStyleForMultiline: comma -Style/TrailingCommaInArrayLiteral: - Description: - Prefer always trailing comma on multiline literals. This makes diffs, - and re-ordering nicer. - EnforcedStyleForMultiline: comma -Style/SymbolArray: - Description: Using percent style obscures symbolic intent of array's contents. - EnforcedStyle: brackets -RSpec/MessageSpies: - EnforcedStyle: receive + Style/Documentation: Exclude: - lib/puppet/parser/functions/**/* - spec/**/* -Style/WordArray: - EnforcedStyle: brackets -Performance/AncestorsInclude: - Enabled: true -Performance/BigDecimalWithNumericArgument: - Enabled: true -Performance/BlockGivenWithExplicitBlock: - Enabled: true -Performance/CaseWhenSplat: - Enabled: true -Performance/ConstantRegexp: - Enabled: true -Performance/MethodObjectAsBlock: - Enabled: true -Performance/RedundantSortBlock: - Enabled: true -Performance/RedundantStringChars: - Enabled: true -Performance/ReverseFirst: - Enabled: true -Performance/SortReverse: - Enabled: true -Performance/Squeeze: - Enabled: true -Performance/StringInclude: - Enabled: true -Performance/Sum: - Enabled: true -Style/CollectionMethods: - Enabled: true -Style/MethodCalledOnDoEndBlock: - Enabled: true -Style/StringMethods: - Enabled: true -Bundler/InsecureProtocolSource: - Enabled: false -Gemspec/DuplicatedAssignment: - Enabled: false -Gemspec/OrderedDependencies: - Enabled: false -Gemspec/RequiredRubyVersion: - Enabled: false -Gemspec/RubyVersionGlobalsUsage: - Enabled: false -Layout/ArgumentAlignment: - Enabled: false -Layout/BeginEndAlignment: - Enabled: false -Layout/ClosingHeredocIndentation: - Enabled: false -Layout/EmptyComment: - Enabled: false -Layout/EmptyLineAfterGuardClause: - Enabled: false -Layout/EmptyLinesAroundArguments: - Enabled: false -Layout/EmptyLinesAroundAttributeAccessor: - Enabled: false -Layout/EndOfLine: - Enabled: false -Layout/FirstArgumentIndentation: - Enabled: false + Layout/HashAlignment: Enabled: false -Layout/HeredocIndentation: - Enabled: false -Layout/LeadingEmptyLines: - Enabled: false -Layout/SpaceAroundMethodCallOperator: - Enabled: false -Layout/SpaceInsideArrayLiteralBrackets: - Enabled: false -Layout/SpaceInsideReferenceBrackets: - Enabled: false -Lint/BigDecimalNew: - Enabled: false -Lint/BooleanSymbol: - Enabled: false + Lint/ConstantDefinitionInBlock: Enabled: false -Lint/DeprecatedOpenSSLConstant: - Enabled: false -Lint/DisjunctiveAssignmentInConstructor: - Enabled: false -Lint/DuplicateElsifCondition: - Enabled: false -Lint/DuplicateRequire: - Enabled: false -Lint/DuplicateRescueException: - Enabled: false -Lint/EmptyConditionalBody: - Enabled: false -Lint/EmptyFile: - Enabled: false -Lint/ErbNewArguments: - Enabled: false -Lint/FloatComparison: - Enabled: false -Lint/HashCompareByIdentity: - Enabled: false -Lint/IdentityComparison: - Enabled: false -Lint/InterpolationCheck: - Enabled: false -Lint/MissingCopEnableDirective: - Enabled: false -Lint/MixedRegexpCaptureTypes: - Enabled: false -Lint/NestedPercentLiteral: - Enabled: false -Lint/NonDeterministicRequireOrder: - Enabled: false -Lint/OrderedMagicComments: - Enabled: false -Lint/OutOfRangeRegexpRef: - Enabled: false -Lint/RaiseException: - Enabled: false -Lint/RedundantCopEnableDirective: - Enabled: false -Lint/RedundantRequireStatement: - Enabled: false -Lint/RedundantSafeNavigation: - Enabled: false -Lint/RedundantWithIndex: - Enabled: false -Lint/RedundantWithObject: - Enabled: false -Lint/RegexpAsCondition: - Enabled: false -Lint/ReturnInVoidContext: - Enabled: false + Lint/SafeNavigationConsistency: Enabled: false -Lint/SafeNavigationWithEmpty: - Enabled: false -Lint/SelfAssignment: - Enabled: false -Lint/SendWithMixinArgument: - Enabled: false -Lint/ShadowedArgument: - Enabled: false -Lint/StructNewOverride: - Enabled: false -Lint/ToJSON: - Enabled: false -Lint/TopLevelReturnWithArgument: - Enabled: false -Lint/TrailingCommaInAttributeDeclaration: - Enabled: false -Lint/UnreachableLoop: - Enabled: false -Lint/UriEscapeUnescape: - Enabled: false -Lint/UriRegexp: - Enabled: false -Lint/UselessMethodDefinition: - Enabled: false -Lint/UselessTimes: - Enabled: false -Metrics/AbcSize: - Enabled: false -Metrics/BlockLength: - Enabled: false + Metrics/BlockNesting: Enabled: false -Metrics/ClassLength: - Enabled: false -Metrics/CyclomaticComplexity: - Enabled: false -Metrics/MethodLength: - Enabled: false -Metrics/ModuleLength: - Enabled: false -Metrics/ParameterLists: - Enabled: false -Metrics/PerceivedComplexity: - Enabled: false -Migration/DepartmentName: - Enabled: false -Naming/AccessorMethodName: - Enabled: false -Naming/BlockParameterName: - Enabled: false -Naming/HeredocDelimiterCase: - Enabled: false -Naming/HeredocDelimiterNaming: - Enabled: false -Naming/MemoizedInstanceVariableName: - Enabled: false + Naming/MethodParameterName: Enabled: false -Naming/RescuedExceptionsVariableName: - Enabled: false -Naming/VariableNumber: - Enabled: false -Performance/BindCall: - Enabled: false -Performance/DeletePrefix: - Enabled: false -Performance/DeleteSuffix: - Enabled: false -Performance/InefficientHashSearch: - Enabled: false -Performance/UnfreezeString: - Enabled: false -Performance/UriDefaultParser: - Enabled: false -RSpec/Be: - Enabled: false -RSpec/ContextMethod: - Enabled: false -RSpec/ContextWording: - Enabled: false -RSpec/DescribeClass: - Enabled: false -RSpec/EmptyHook: - Enabled: false -RSpec/EmptyLineAfterExample: - Enabled: false -RSpec/EmptyLineAfterExampleGroup: - Enabled: false -RSpec/EmptyLineAfterHook: - Enabled: false -RSpec/ExampleLength: - Enabled: false -RSpec/ExampleWithoutDescription: - Enabled: false -RSpec/ExpectChange: - Enabled: false -RSpec/ExpectInHook: - Enabled: false -RSpec/HooksBeforeExamples: - Enabled: false -RSpec/ImplicitBlockExpectation: - Enabled: false -RSpec/ImplicitSubject: - Enabled: false + RSpec/LeakyConstantDeclaration: Enabled: false -RSpec/LetBeforeExamples: - Enabled: false -RSpec/MissingExampleGroupArgument: - Enabled: false -RSpec/MultipleExpectations: - Enabled: false + RSpec/MultipleMemoizedHelpers: Enabled: false -RSpec/MultipleSubjects: - Enabled: false -RSpec/NestedGroups: - Enabled: false -RSpec/PredicateMatcher: - Enabled: false -RSpec/ReceiveCounts: - Enabled: false -RSpec/ReceiveNever: - Enabled: false + RSpec/RepeatedExampleGroupBody: Enabled: false -RSpec/RepeatedExampleGroupDescription: - Enabled: false -RSpec/RepeatedIncludeExample: - Enabled: false -RSpec/ReturnFromStub: - Enabled: false -RSpec/SharedExamples: - Enabled: false -RSpec/StubbedMock: - Enabled: false -RSpec/UnspecifiedException: - Enabled: false -RSpec/VariableDefinition: - Enabled: false -RSpec/VoidExpect: - Enabled: false -RSpec/Yield: - Enabled: false + Security/Open: Enabled: false -Style/AccessModifierDeclarations: - Enabled: false -Style/AccessorGrouping: - Enabled: false -Style/AsciiComments: - Enabled: false -Style/BisectedAttrAccessor: - Enabled: false -Style/CaseLikeIf: - Enabled: false -Style/ClassEqualityComparison: - Enabled: false -Style/ColonMethodDefinition: - Enabled: false -Style/CombinableLoops: - Enabled: false -Style/CommentedKeyword: - Enabled: false -Style/Dir: - Enabled: false -Style/DoubleCopDisableDirective: - Enabled: false -Style/EmptyBlockParameter: - Enabled: false -Style/EmptyLambdaParameter: - Enabled: false -Style/Encoding: - Enabled: false -Style/EvalWithLocation: - Enabled: false -Style/ExpandPathArguments: - Enabled: false -Style/ExplicitBlockArgument: - Enabled: false -Style/ExponentialNotation: - Enabled: false -Style/FloatDivision: - Enabled: false -Style/FrozenStringLiteralComment: - Enabled: false -Style/GlobalStdStream: - Enabled: false -Style/HashAsLastArrayItem: - Enabled: false -Style/HashLikeCase: - Enabled: false -Style/HashTransformKeys: - Enabled: false -Style/HashTransformValues: - Enabled: false -Style/IfUnlessModifier: - Enabled: false -Style/KeywordParametersOrder: - Enabled: false -Style/MinMax: - Enabled: false + Style/MixinUsage: Enabled: false -Style/MultilineWhenThen: - Enabled: false -Style/NegatedUnless: - Enabled: false -Style/NumericPredicate: - Enabled: false -Style/OptionalBooleanParameter: - Enabled: false -Style/OrAssignment: - Enabled: false -Style/RandomWithOffset: - Enabled: false -Style/RedundantAssignment: - Enabled: false -Style/RedundantCondition: - Enabled: false -Style/RedundantConditional: - Enabled: false -Style/RedundantFetchBlock: - Enabled: false -Style/RedundantFileExtensionInRequire: - Enabled: false -Style/RedundantRegexpCharacterClass: - Enabled: false -Style/RedundantRegexpEscape: - Enabled: false -Style/RedundantSelfAssignment: - Enabled: false -Style/RedundantSort: - Enabled: false -Style/RescueStandardError: - Enabled: false -Style/SingleArgumentDig: - Enabled: false -Style/SlicingWithRange: - Enabled: false -Style/SoleNestedConditional: - Enabled: false -Style/StderrPuts: - Enabled: false -Style/StringConcatenation: - Enabled: false -Style/Strip: - Enabled: false -Style/SymbolProc: - Enabled: false -Style/TrailingBodyOnClass: - Enabled: false -Style/TrailingBodyOnMethodDefinition: - Enabled: false -Style/TrailingBodyOnModule: - Enabled: false -Style/TrailingCommaInHashLiteral: - Enabled: false -Style/TrailingMethodEndStatement: - Enabled: false -Style/UnpackFirst: - Enabled: false + Lint/DuplicateBranch: Enabled: false -Lint/DuplicateRegexpCharacterClassElement: - Enabled: false -Lint/EmptyBlock: - Enabled: false -Lint/EmptyClass: - Enabled: false -Lint/NoReturnInBeginEndBlocks: - Enabled: false -Lint/ToEnumArguments: - Enabled: false -Lint/UnexpectedBlockArity: - Enabled: false -Lint/UnmodifiedReduceAccumulator: - Enabled: false -Performance/CollectionLiteralInLoop: - Enabled: false -Style/ArgumentsForwarding: - Enabled: false -Style/CollectionCompact: - Enabled: false -Style/DocumentDynamicEvalDefinition: - Enabled: false -Style/NegatedIfElseCondition: - Enabled: false -Style/NilLambda: - Enabled: false -Style/RedundantArgument: - Enabled: false -Style/SwapValues: - Enabled: false -Gemspec/RequireMFA: - Enabled: false -Layout/LineEndStringConcatenationIndentation: - Enabled: false -Layout/SpaceBeforeBrackets: - Enabled: false -Lint/AmbiguousAssignment: - Enabled: true -Lint/AmbiguousOperatorPrecedence: - Enabled: true -Lint/AmbiguousRange: - Enabled: true -Lint/DeprecatedConstants: - Enabled: true -Lint/EmptyInPattern: - Enabled: true -Lint/IncompatibleIoSelectWithFiberScheduler: - Enabled: true -Lint/LambdaWithoutLiteralBlock: - Enabled: true -Lint/NumberedParameterAssignment: - Enabled: true -Lint/OrAssignmentToConstant: - Enabled: true -Lint/RedundantDirGlobSort: - Enabled: true -Lint/RefinementImportMethods: - Enabled: true -Lint/RequireRelativeSelfPath: - Enabled: true -Lint/SymbolConversion: - Enabled: true -Lint/TripleQuotes: - Enabled: true -Lint/UselessRuby2Keywords: - Enabled: true -Naming/BlockForwarding: - Enabled: true -Security/CompoundHash: - Enabled: true -Security/IoMethods: - Enabled: true -Style/EndlessMethod: - Enabled: true -Style/FetchEnvVar: - Enabled: true -Style/FileRead: - Enabled: true -Style/FileWrite: - Enabled: true -Style/HashConversion: - Enabled: true -Style/HashExcept: - Enabled: true -Style/IfWithBooleanLiteralBranches: - Enabled: true -Style/InPatternThen: - Enabled: true -Style/MapToHash: - Enabled: true -Style/MultilineInPatternThen: - Enabled: true -Style/NestedFileDirname: - Enabled: true -Style/NumberedParameters: - Enabled: true -Style/NumberedParametersLimit: - Enabled: true -Style/ObjectThen: - Enabled: true -Style/OpenStructUse: - Enabled: true -Style/QuotedSymbols: - Enabled: true -Style/RedundantInitialize: - Enabled: true -Style/RedundantSelfAssignmentBranch: - Enabled: true -Style/SelectByRegexp: - Enabled: true -Style/StringChars: - Enabled: true -Performance/ConcurrentMonotonicTime: - Enabled: true -Performance/MapCompact: - Enabled: true -Performance/RedundantEqualityComparisonBlock: - Enabled: true -Performance/RedundantSplitRegexpArgument: - Enabled: true -Performance/StringIdentifierArgument: - Enabled: true -RSpec/BeEq: - Enabled: true -RSpec/BeNil: - Enabled: true -RSpec/ExcessiveDocstringSpacing: - Enabled: true -RSpec/IdenticalEqualityAssertion: - Enabled: true -RSpec/SubjectDeclaration: - Enabled: true -RSpec/VerifiedDoubleReference: - Enabled: true -Gemspec/AddRuntimeDependency: - Enabled: true -Gemspec/DeprecatedAttributeAssignment: - Enabled: true -Gemspec/DevelopmentDependencies: - Enabled: true -Layout/LineContinuationLeadingSpace: - Enabled: true -Layout/LineContinuationSpacing: - Enabled: true -Lint/ConstantOverwrittenInRescue: - Enabled: true -Lint/DuplicateMagicComment: - Enabled: true -Lint/DuplicateMatchPattern: - Enabled: true -Lint/DuplicateSetElement: - Enabled: true -Lint/HashNewWithKeywordArgumentsAsDefault: - Enabled: true -Lint/ItWithoutArgumentsInBlock: - Enabled: true -Lint/LiteralAssignmentInCondition: - Enabled: true -Lint/MixedCaseRange: - Enabled: true -Lint/NonAtomicFileOperation: - Enabled: true -Lint/NumericOperationWithConstantResult: - Enabled: true -Lint/RedundantRegexpQuantifiers: - Enabled: true -Lint/RequireRangeParentheses: - Enabled: true -Lint/UnescapedBracketInRegexp: - Enabled: true -Lint/UselessDefined: - Enabled: true -Lint/UselessNumericOperation: - Enabled: true -Lint/UselessRescue: - Enabled: true -Metrics/CollectionLiteralLength: - Enabled: true -Style/AmbiguousEndlessMethodDefinition: - Enabled: true -Style/ArrayIntersect: - Enabled: true -Style/BitwisePredicate: - Enabled: true -Style/CombinableDefined: - Enabled: true -Style/ComparableClamp: - Enabled: true -Style/ConcatArrayLiterals: - Enabled: true -Style/DataInheritance: - Enabled: true -Style/DigChain: - Enabled: true -Style/DirEmpty: - Enabled: true -Style/EmptyHeredoc: - Enabled: true -Style/EnvHome: - Enabled: true -Style/ExactRegexpMatch: - Enabled: true -Style/FileEmpty: - Enabled: true -Style/FileNull: - Enabled: true -Style/FileTouch: - Enabled: true -Style/KeywordArgumentsMerging: - Enabled: true -Style/MagicCommentFormat: - Enabled: true -Style/MapCompactWithConditionalBlock: - Enabled: true -Style/MapIntoArray: - Enabled: true -Style/MapToSet: - Enabled: true -Style/MinMaxComparison: - Enabled: true -Style/OperatorMethodCall: - Enabled: true -Style/RedundantArrayConstructor: - Enabled: true -Style/RedundantConstantBase: - Enabled: true -Style/RedundantCurrentDirectoryInPath: - Enabled: true -Style/RedundantDoubleSplatHashBraces: - Enabled: true -Style/RedundantEach: - Enabled: true -Style/RedundantFilterChain: - Enabled: true -Style/RedundantHeredocDelimiterQuotes: - Enabled: true -Style/RedundantInterpolationUnfreeze: - Enabled: true -Style/RedundantLineContinuation: - Enabled: true -Style/RedundantRegexpArgument: - Enabled: true -Style/RedundantRegexpConstructor: - Enabled: true -Style/RedundantStringEscape: - Enabled: true -Style/ReturnNilInPredicateMethodDefinition: - Enabled: true -Style/SafeNavigationChainLength: - Enabled: true -Style/SendWithLiteralMethodName: - Enabled: true -Style/SingleLineDoEndBlock: - Enabled: true -Style/SuperArguments: - Enabled: true -Style/SuperWithArgsParentheses: - Enabled: true -Style/YAMLFileRead: - Enabled: true -Performance/MapMethodChain: - Enabled: true -Performance/StringBytesize: - Enabled: true -Gemspec/AttributeAssignment: - Enabled: true -Layout/EmptyLinesAfterModuleInclusion: - Enabled: true -Lint/ArrayLiteralInRegexp: - Enabled: true -Lint/ConstantReassignment: - Enabled: true -Lint/CopDirectiveSyntax: - Enabled: true -Lint/RedundantTypeConversion: - Enabled: true -Lint/SharedMutableDefault: - Enabled: true -Lint/SuppressedExceptionInNumberConversion: - Enabled: true -Lint/UselessConstantScoping: - Enabled: true -Lint/UselessDefaultValueArgument: - Enabled: true -Lint/UselessOr: - Enabled: true -Naming/PredicateMethod: - Enabled: true -Style/CollectionQuerying: - Enabled: true -Style/ComparableBetween: - Enabled: true -Style/EmptyStringInsideInterpolation: - Enabled: true -Style/HashFetchChain: - Enabled: true -Style/HashSlice: - Enabled: true -Style/ItAssignment: - Enabled: true -Style/ItBlockParameter: - Enabled: true -Style/RedundantArrayFlatten: - Enabled: true -Style/RedundantFormat: - Enabled: true -Performance/ZipWithoutBlock: - Enabled: true -RSpec/IncludeExamples: - Enabled: true diff --git a/Gemfile b/Gemfile index b204ca3..2a086ff 100644 --- a/Gemfile +++ b/Gemfile @@ -9,15 +9,8 @@ gem 'rake', '~> 13.3.0' group :tests do gem 'openvox', ENV.fetch('OPENVOX_VERSION', ENV.fetch('PUPPET_VERSION', '~> 8.0')) - gem 'puppetlabs_spec_helper', '~> 8.0' - gem 'rspec', '~> 3.12' - gem 'rspec-puppet', '~> 5.0' - gem 'rspec-puppet-facts', '~> 6.0' - gem 'rubocop', '~> 1.81.0' - gem 'rubocop-performance', '~> 1.26.0' - gem 'rubocop-rake', '~> 0.7.0' - gem 'rubocop-rspec', '~> 3.9.0' gem 'syslog', require: false + gem 'voxpupuli-test', '~> 13.0' end group :development do diff --git a/Rakefile b/Rakefile index 967a329..b780f68 100644 --- a/Rakefile +++ b/Rakefile @@ -1,15 +1,20 @@ # frozen_string_literal: true require 'bundler/gem_tasks' -require 'rspec/core/rake_task' +require 'voxpupuli/test/rake' -RSpec::Core::RakeTask.new(:spec) do |t| - t.pattern = 'spec/**/*_spec.rb' - t.exclude_pattern = 'spec/{data,fixtures,support/**/*_spec.rb' -end +# Override the default spec task from voxpupuli-test to exclude spec/data and spec/fixtures +Rake::Task[:spec].clear +Rake::Task['spec:standalone'].clear -require 'rubocop/rake_task' +RSpec::Core::RakeTask.new('spec:standalone') do |t| + t.pattern = 'spec/{classes,functions}/**/*_spec.rb' +end -RuboCop::RakeTask.new +desc 'Run spec tests and clean the fixtures directory if successful' +task spec: :'fixtures:prep' do |_t, args| + Rake::Task['spec:standalone'].invoke(*args.extras) + Rake::Task['fixtures:clean'].invoke +end -task default: [:spec, :rubocop] +task default: %i[spec rubocop] diff --git a/lib/compliance_engine.rb b/lib/compliance_engine.rb index d7fd01d..7738992 100644 --- a/lib/compliance_engine.rb +++ b/lib/compliance_engine.rb @@ -30,7 +30,7 @@ def self.new(*paths) def self.log return @log unless @log.nil? - @log = Logger.new(STDERR) + @log = Logger.new($stderr) @log.level = Logger::WARN @log end diff --git a/lib/compliance_engine/collection.rb b/lib/compliance_engine/collection.rb index e001854..280110c 100644 --- a/lib/compliance_engine/collection.rb +++ b/lib/compliance_engine/collection.rb @@ -113,7 +113,7 @@ def reject(&block) # # @return [Array] def context_variables - [:@enforcement_tolerance, :@environment_data, :@facts] + %i[@enforcement_tolerance @environment_data @facts] end # Returns the key of the object diff --git a/lib/compliance_engine/component.rb b/lib/compliance_engine/component.rb index d925659..ffe6dea 100644 --- a/lib/compliance_engine/component.rb +++ b/lib/compliance_engine/component.rb @@ -112,7 +112,7 @@ def [](key) # # @return [Array] def context_variables - [:@enforcement_tolerance, :@environment_data, :@facts] + %i[@enforcement_tolerance @environment_data @facts] end # Get the cache variables @@ -134,7 +134,7 @@ def fact_match?(fact, confine, depth = 0) fact == confine elsif confine.is_a?(Array) - if depth == 0 + if depth.zero? confine.any? { |value| fact_match?(fact, value, depth + 1) } else fact == confine @@ -155,12 +155,13 @@ def confine_away?(fragment) if k == 'module_name' unless environment_data.nil? return true unless environment_data.key?(v) + module_version = fragment['confine']['module_version'] unless module_version.nil? require 'semantic_puppet' begin return true unless SemanticPuppet::VersionRange.parse(module_version).include?(SemanticPuppet::Version.parse(environment_data[v])) - rescue => e + rescue StandardError => e ComplianceEngine.log.error "Failed to compare #{v} #{environment_data[v]} with version confinement #{module_version}: #{e.message}" return true end @@ -172,12 +173,8 @@ def confine_away?(fragment) # Confinement based on Puppet facts unless facts.nil? fact = facts.dig(*k.split('.')) - if fact.nil? - return true - end - unless fact_match?(fact, v) - return true - end + return true if fact.nil? + return true unless fact_match?(fact, v) end end end @@ -213,7 +210,7 @@ def fragments if enforcement_tolerance.is_a?(Integer) && is_a?(ComplianceEngine::Check) && fragment.key?('remediation') if fragment['remediation'].key?('disabled') message = "Remediation disabled for #{fragment}" - reason = fragment['remediation']['disabled']&.map { |value| value['reason'] }&.reject { |value| value.nil? }&.join("\n") + reason = fragment['remediation']['disabled']&.map { |value| value['reason'] }&.reject(&:nil?)&.join("\n") message += "\n#{reason}" unless reason.nil? ComplianceEngine.log.info message next diff --git a/lib/compliance_engine/data.rb b/lib/compliance_engine/data.rb index c036e83..32e8ff7 100644 --- a/lib/compliance_engine/data.rb +++ b/lib/compliance_engine/data.rb @@ -28,7 +28,7 @@ class ComplianceEngine::Data # @param facts [Hash] The facts to use while evaluating the data # @param enforcement_tolerance [Integer] The tolerance to use while evaluating the data def initialize(*paths, facts: nil, enforcement_tolerance: nil) - @data ||= {} + @data = {} @facts = facts @enforcement_tolerance = enforcement_tolerance open(*paths) unless paths.nil? || paths.empty? @@ -201,7 +201,7 @@ def update( end reset_collection - rescue => e + rescue StandardError => e ComplianceEngine.log.error e.message end @@ -210,6 +210,7 @@ def update( # @return [Array] def files return @files unless @files.nil? + @files = data.select { |_, file| file.key?(:content) }.keys end @@ -219,7 +220,7 @@ def files # @return [Hash] def get(file) data[file][:content] - rescue + rescue StandardError nil end @@ -263,6 +264,7 @@ def confines collection.each_value do |v| v.to_a.each do |component| next unless component.key?('confine') + @confines = DeepMerge.deep_merge!(component['confine'], @confines) end end @@ -346,11 +348,9 @@ def filtered_by_tolerance?(check) return true if remediation['disabled'] # Filter based on risk level - if remediation['risk']&.is_a?(Array) && !remediation['risk'].empty? + if remediation['risk'].is_a?(Array) && !remediation['risk'].empty? risk_level = remediation['risk'][0]['level'] - if risk_level && enforcement_tolerance.to_i > 0 - return risk_level.to_i > enforcement_tolerance.to_i - end + return risk_level.to_i > enforcement_tolerance.to_i if risk_level && enforcement_tolerance.to_i.positive? end false @@ -360,7 +360,7 @@ def filtered_by_tolerance?(check) # # @return [Array] def collection_variables - [:@profiles, :@checks, :@controls, :@ces] + %i[@profiles @checks @controls @ces] end # Get the data variables @@ -374,7 +374,7 @@ def data_variables # # @return [Array] def context_variables - [:@enforcement_tolerance, :@environment_data, :@facts, :@modulepath] + %i[@enforcement_tolerance @environment_data @facts @modulepath] end # Get the cache variables @@ -428,9 +428,7 @@ def mapping?(check, profile_or_ce) # @return [TrueClass, FalseClass] def correlate(a, b) return false if a.nil? || b.nil? - unless a.is_a?(Array) && b.is_a?(Hash) - raise ComplianceEngine::Error, "Expected array and hash, got #{a.class} and #{b.class}" - end + raise ComplianceEngine::Error, "Expected array and hash, got #{a.class} and #{b.class}" unless a.is_a?(Array) && b.is_a?(Hash) return false if a.empty? || b.empty? a.any? { |item| b[item] } diff --git a/lib/compliance_engine/data_loader.rb b/lib/compliance_engine/data_loader.rb index c7f81b8..034fe29 100644 --- a/lib/compliance_engine/data_loader.rb +++ b/lib/compliance_engine/data_loader.rb @@ -25,6 +25,7 @@ def initialize(value = {}, key: nil) # @raise [ComplianceEngine::Error] If the value is not a Hash def data=(value) raise ComplianceEngine::Error, 'Data must be a hash' unless value.is_a?(Hash) + @data = value changed notify_observers(self) diff --git a/lib/compliance_engine/environment_loader.rb b/lib/compliance_engine/environment_loader.rb index 36c8c64..f89da17 100644 --- a/lib/compliance_engine/environment_loader.rb +++ b/lib/compliance_engine/environment_loader.rb @@ -13,15 +13,16 @@ class ComplianceEngine::EnvironmentLoader # @param zipfile_path [String, nil] the path to the zip file if loading from a zip archive def initialize(*paths, fileclass: File, dirclass: Dir, zipfile_path: nil) raise ArgumentError, 'No paths specified' if paths.empty? + @modulepath ||= paths @zipfile_path = zipfile_path modules = paths.map do |path| - dirclass.entries(path) - .grep(%r{\A[a-z][a-z0-9_]*\Z}) - .select { |child| fileclass.directory?(File.join(path, child)) } - .map { |child| File.join(path, child) } - .sort - rescue + dirclass.entries(path). + grep(%r{\A[a-z][a-z0-9_]*\Z}). + select { |child| fileclass.directory?(File.join(path, child)) }. + map { |child| File.join(path, child) }. + sort + rescue StandardError [] end modules.flatten! diff --git a/lib/compliance_engine/module_loader.rb b/lib/compliance_engine/module_loader.rb index cf78a21..2abd0dd 100644 --- a/lib/compliance_engine/module_loader.rb +++ b/lib/compliance_engine/module_loader.rb @@ -27,17 +27,17 @@ def initialize(path, fileclass: File, dirclass: Dir, zipfile_path: nil) metadata = ComplianceEngine::DataLoader::Json.new(metadata_json, fileclass: fileclass) @name = metadata.data['name'] @version = metadata.data['version'] - rescue => e + rescue StandardError => e ComplianceEngine.log.warn "Could not parse #{metadata_json}: #{e.message}" end end # In this directory, we want to look for all yaml and json files # under SIMP/compliance_profiles and simp/compliance_profiles. - globs = ['SIMP/compliance_profiles', 'simp/compliance_profiles'] - .select { |dir| fileclass.directory?(File.join(path, dir)) } - .map { |dir| - ['yaml', 'json'].map { |type| File.join(path, dir, '**', "*.#{type}") } + globs = ['SIMP/compliance_profiles', 'simp/compliance_profiles']. + select { |dir| fileclass.directory?(File.join(path, dir)) }. + map { |dir| + %w[yaml json].map { |type| File.join(path, dir, '**', "*.#{type}") } }.flatten # Using .each here to make mocking with rspec easier. globs.each do |glob| @@ -53,7 +53,7 @@ def initialize(path, fileclass: File, dirclass: Dir, zipfile_path: nil) ComplianceEngine::DataLoader::Yaml.new(file.to_s, fileclass: fileclass, key: key) end @files << loader - rescue => e + rescue StandardError => e ComplianceEngine.log.warn "Could not load #{file}: #{e.message}" end end diff --git a/lib/compliance_engine/version.rb b/lib/compliance_engine/version.rb index d07af78..b5cd864 100644 --- a/lib/compliance_engine/version.rb +++ b/lib/compliance_engine/version.rb @@ -11,6 +11,7 @@ class Version def initialize(version) raise 'Missing version' if version.nil? raise "Unsupported version '#{version}'" unless version == '2.0.0' + @version = version end diff --git a/lib/puppet/functions/compliance_engine/enforcement.rb b/lib/puppet/functions/compliance_engine/enforcement.rb index a0d03b3..d808dc5 100644 --- a/lib/puppet/functions/compliance_engine/enforcement.rb +++ b/lib/puppet/functions/compliance_engine/enforcement.rb @@ -38,15 +38,14 @@ def enforcement(key, options, context) data.enforcement_tolerance = enforcement_tolerance || options['enforcement_tolerance'] data.open(ComplianceEngine::EnvironmentLoader.new(*closure_scope.environment.full_modulepath.select { |path| File.directory?(path) })) - unless compliance_map.empty? - data.open(ComplianceEngine::DataLoader.new(compliance_map)) - end + data.open(ComplianceEngine::DataLoader.new(compliance_map)) unless compliance_map.empty? context.cache(:compliance_engine, data) end context.cache_all(data.hiera(profiles)) return context.interpolate(context.cached_value(key)) if context.cache_has_key(key) + # if data.hiera(profiles).key?(key) # context.cache(key, data.hiera(profiles)[key]) # return context.interpolate(data.hiera(profiles)[key]) @@ -64,9 +63,7 @@ def profiles profile_list = call_function('lookup', 'compliance_engine::enforcement', { 'default_value' => [] }) # For backwards compatibility with compliance_markup. - if @compat - profile_list += call_function('lookup', 'compliance_markup::enforcement', { 'default_value' => [] }) - end + profile_list += call_function('lookup', 'compliance_markup::enforcement', { 'default_value' => [] }) if @compat profile_list.uniq end @@ -75,9 +72,7 @@ def compliance_map hiera_compliance_map = call_function('lookup', 'compliance_engine::compliance_map', { 'default_value' => {} }) # For backwards compatibility with compliance_markup. - if @compat - hiera_compliance_map = DeepMerge.deep_merge!(call_function('lookup', 'compliance_markup::compliance_map', { 'default_value' => {} }), hiera_compliance_map) - end + hiera_compliance_map = DeepMerge.deep_merge!(call_function('lookup', 'compliance_markup::compliance_map', { 'default_value' => {} }), hiera_compliance_map) if @compat hiera_compliance_map end @@ -86,9 +81,7 @@ def enforcement_tolerance tolerance = call_function('lookup', 'compliance_engine::enforcement_tolerance', { 'default_value' => nil }) # For backwards compatibility with compliance_markup. - if @compat - tolerance = call_function('lookup', 'compliance_markup::enforcement_tolerance_level', { 'default_value' => nil }) if tolerance.nil? - end + tolerance = call_function('lookup', 'compliance_markup::enforcement_tolerance_level', { 'default_value' => nil }) if @compat && tolerance.nil? tolerance end diff --git a/spec/classes/compliance_engine/check_spec.rb b/spec/classes/compliance_engine/check_spec.rb index 330e91d..807de88 100644 --- a/spec/classes/compliance_engine/check_spec.rb +++ b/spec/classes/compliance_engine/check_spec.rb @@ -19,9 +19,9 @@ 'file2' => { 'merge_key' => ['value2'], 'confine' => { 'kernel' => ['windows'] } }, 'file3' => { 'merge_key' => ['value3'], 'confine' => { 'module_name' => 'author-module' } }, 'file4' => { 'merge_key' => ['value4'], 'confine' => { 'module_name' => 'author-module', 'module_version' => '>= 1.0.0 < 2.0.0' } }, - 'file5' => { 'merge_key' => ['value5'], 'remediation' => { 'disabled' => [ { 'reason' => 'anything' } ], 'risk' => [ { 'level' => 1 } ] } }, - 'file6' => { 'merge_key' => ['value6'], 'remediation' => { 'risk' => [ { 'level' => 21 } ] } }, - 'file7' => { 'merge_key' => ['value7'], 'remediation' => { 'risk' => [ { 'level' => 41 } ] } }, + 'file5' => { 'merge_key' => ['value5'], 'remediation' => { 'disabled' => [{ 'reason' => 'anything' }], 'risk' => [{ 'level' => 1 }] } }, + 'file6' => { 'merge_key' => ['value6'], 'remediation' => { 'risk' => [{ 'level' => 21 }] } }, + 'file7' => { 'merge_key' => ['value7'], 'remediation' => { 'risk' => [{ 'level' => 41 }] } }, } end diff --git a/spec/classes/compliance_engine/data_loader_spec.rb b/spec/classes/compliance_engine/data_loader_spec.rb index f967bd3..e407edf 100644 --- a/spec/classes/compliance_engine/data_loader_spec.rb +++ b/spec/classes/compliance_engine/data_loader_spec.rb @@ -19,7 +19,7 @@ shared_examples 'an observable' do it 'updates the data' do - expect { data_loader.data = updated_data }.to change { data_loader.data }.from(initial_data).to(updated_data) + expect { data_loader.data = updated_data }.to change(data_loader, :data).from(initial_data).to(updated_data) end it 'notifies observers' do diff --git a/spec/classes/compliance_engine/data_spec.rb b/spec/classes/compliance_engine/data_spec.rb index 1f296d8..624f0e3 100644 --- a/spec/classes/compliance_engine/data_spec.rb +++ b/spec/classes/compliance_engine/data_spec.rb @@ -205,7 +205,7 @@ def test_data ce: ce_00: {} ce_01: {} - A_YAML + A_YAML 'b/file.yaml' => <<~B_YAML, --- version: '2.0.0' @@ -215,7 +215,7 @@ def test_data ce_02: true ce: ce_02: {} - B_YAML + B_YAML }, 'test_module_01' => { 'c/file.yaml' => <<~C_YAML, @@ -227,7 +227,7 @@ def test_data ce_03: true ce: ce_03: {} - C_YAML + C_YAML }, } end @@ -239,14 +239,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") - .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). + and_return( + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.json") - .and_return([]) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.json"). + and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -268,12 +268,12 @@ def test_data it 'returns a list of profiles' do expect(compliance_engine.profiles).to be_instance_of(ComplianceEngine::Profiles) - expect(compliance_engine.profiles.keys).to eq(['test_profile_00', 'test_profile_01', 'test_profile_02']) + expect(compliance_engine.profiles.keys).to eq(%w[test_profile_00 test_profile_01 test_profile_02]) end it 'returns a list of ces' do expect(compliance_engine.ces).to be_instance_of(ComplianceEngine::Ces) - expect(compliance_engine.ces.keys).to eq(['ce_00', 'ce_01', 'ce_02', 'ce_03']) + expect(compliance_engine.ces.keys).to eq(%w[ce_00 ce_01 ce_02 ce_03]) end end @@ -319,7 +319,7 @@ def test_data value: true ces: - enable_widget_spinner_audit_logging - A_YAML + A_YAML }, } end @@ -331,14 +331,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") - .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). + and_return( + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.json") - .and_return([]) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.json"). + and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -449,7 +449,7 @@ def test_data value: ['no'] ces: - enable_widget_spinner_audit_logging.el7 - A_YAML + A_YAML 'b/file.yaml' => <<~B_YAML, --- version: 2.0.0 @@ -485,7 +485,7 @@ def test_data value: ['yes'] ces: - enable_widget_spinner_audit_logging.el8 - B_YAML + B_YAML 'c/file.yaml' => <<~C_YAML, --- version: 2.0.0 @@ -521,7 +521,7 @@ def test_data value: ['maybe'] ces: - enable_widget_spinner_audit_logging.el9 - C_YAML + C_YAML }, } end @@ -533,14 +533,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") - .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). + and_return( + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.json") - .and_return([]) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.json"). + and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -608,7 +608,7 @@ def test_data compliance_engine.facts = nil hiera = compliance_engine.hiera(['custom_profile_1']) expect(hiera).to be_instance_of(Hash) - expect(hiera).to eq({ 'widget_spinner::audit_logging' => ['no', 'yes', 'maybe'] }) + expect(hiera).to eq({ 'widget_spinner::audit_logging' => %w[no yes maybe] }) end it 'correctly invalidates cached data' do @@ -625,7 +625,7 @@ def test_data compliance_engine.facts = nil hiera = compliance_engine.hiera(['custom_profile_1']) expect(hiera).to be_instance_of(Hash) - expect(hiera).to eq({ 'widget_spinner::audit_logging' => ['no', 'yes', 'maybe'] }) + expect(hiera).to eq({ 'widget_spinner::audit_logging' => %w[no yes maybe] }) compliance_engine.facts = { 'os' => { 'release' => { 'major' => '9' }, 'name' => 'RedHat' } } hiera = compliance_engine.hiera(['custom_profile_1']) @@ -675,7 +675,7 @@ def test_data value: true ces: - enable_widget_spinner_audit_logging - A_YAML + A_YAML }, } end @@ -687,14 +687,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") - .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). + and_return( + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.json") - .and_return([]) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.json"). + and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -741,7 +741,7 @@ def test_data it 'returns checks for a profile' do checks = compliance_engine.check_mapping(compliance_engine.profiles['custom_profile_1']) checks.each_value { |check| expect(check).to be_instance_of(ComplianceEngine::Check) } - keys = checks.values.map { |check| check.key } + keys = checks.values.map(&:key) expect(keys).to be_instance_of(Array) expect(keys).to eq(['widget_spinner_audit_logging']) end @@ -749,7 +749,7 @@ def test_data it 'returns checks for a ce' do checks = compliance_engine.check_mapping(compliance_engine.ces['enable_widget_spinner_audit_logging']) checks.each_value { |check| expect(check).to be_instance_of(ComplianceEngine::Check) } - keys = checks.values.map { |check| check.key } + keys = checks.values.map(&:key) expect(keys).to be_instance_of(Array) expect(keys).to eq(['widget_spinner_audit_logging']) end @@ -774,7 +774,7 @@ def test_data value: true controls: nist_800_53:rev4:AU-2: true - A_YAML + A_YAML }, } end @@ -786,14 +786,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") - .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). + and_return( + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.json") - .and_return([]) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.json"). + and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -855,7 +855,7 @@ def test_data settings: parameter: test_module_00::test_param value: a string - A_YAML + A_YAML }, } end @@ -867,14 +867,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") - .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). + and_return( + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.json") - .and_return([]) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.json"). + and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -942,7 +942,7 @@ def test_data value: true controls: nist_800_53:rev4:AU-2: true - A_YAML + A_YAML 'b/file.yaml' => <<~B_YAML, --- version: 2.0.0 @@ -962,7 +962,7 @@ def test_data value: 'a string' ces: - 00_ce1 - B_YAML + B_YAML }, } end @@ -974,14 +974,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") - .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). + and_return( + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.json") - .and_return([]) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.json"). + and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -1004,13 +1004,13 @@ def test_data it 'returns a list of profiles' do profiles = compliance_engine.profiles expect(profiles).to be_instance_of(ComplianceEngine::Profiles) - expect(profiles.keys).to eq(['custom_profile_1', '00_profile_test']) + expect(profiles.keys).to eq(%w[custom_profile_1 00_profile_test]) end it 'returns a list of ces' do ces = compliance_engine.ces expect(ces).to be_instance_of(ComplianceEngine::Ces) - expect(ces.keys).to eq(['enable_widget_spinner_audit_logging', '00_ce1']) + expect(ces.keys).to eq(%w[enable_widget_spinner_audit_logging 00_ce1]) end it 'returns no hiera data when there are no profiles' do @@ -1040,7 +1040,7 @@ def test_data it 'returns checks for custom_profile_1' do checks = compliance_engine.check_mapping(compliance_engine.profiles['custom_profile_1']) checks.each_value { |check| expect(check).to be_instance_of(ComplianceEngine::Check) } - keys = checks.values.map { |check| check.key } + keys = checks.values.map(&:key) expect(keys).to be_instance_of(Array) expect(keys).to eq(['widget_spinner_audit_logging']) end @@ -1048,7 +1048,7 @@ def test_data it 'returns checks for 00_profile_test' do checks = compliance_engine.check_mapping(compliance_engine.profiles['00_profile_test']) checks.each_value { |check| expect(check).to be_instance_of(ComplianceEngine::Check) } - keys = checks.values.map { |check| check.key } + keys = checks.values.map(&:key) expect(keys).to be_instance_of(Array) expect(keys).to eq(['00_check1']) end @@ -1056,7 +1056,7 @@ def test_data it 'returns checks for enable_widget_spinner_audit_logging' do checks = compliance_engine.check_mapping(compliance_engine.ces['enable_widget_spinner_audit_logging']) checks.each_value { |check| expect(check).to be_instance_of(ComplianceEngine::Check) } - keys = checks.values.map { |check| check.key } + keys = checks.values.map(&:key) expect(keys).to be_instance_of(Array) expect(keys).to eq(['widget_spinner_audit_logging']) end @@ -1064,7 +1064,7 @@ def test_data it 'returns checks for 00_ce1' do checks = compliance_engine.check_mapping(compliance_engine.ces['00_ce1']) checks.each_value { |check| expect(check).to be_instance_of(ComplianceEngine::Check) } - keys = checks.values.map { |check| check.key } + keys = checks.values.map(&:key) expect(keys).to be_instance_of(Array) expect(keys).to eq(['00_check1']) end @@ -1101,12 +1101,12 @@ def test_data it 'returns a list of profiles' do expect(compliance_engine.profiles).to be_instance_of(ComplianceEngine::Profiles) - expect(compliance_engine.profiles.keys).to eq(['test_profile_00', 'test_profile_01', 'test_profile_02']) + expect(compliance_engine.profiles.keys).to eq(%w[test_profile_00 test_profile_01 test_profile_02]) end it 'returns a list of ces' do expect(compliance_engine.ces).to be_instance_of(ComplianceEngine::Ces) - expect(compliance_engine.ces.keys).to eq(['ce_00', 'ce_01', 'ce_02', 'ce_03']) + expect(compliance_engine.ces.keys).to eq(%w[ce_00 ce_01 ce_02 ce_03]) end end @@ -1141,12 +1141,12 @@ def test_data it 'returns a list of profiles' do expect(compliance_engine.profiles).to be_instance_of(ComplianceEngine::Profiles) - expect(compliance_engine.profiles.keys).to eq(['test_profile_00', 'test_profile_01', 'test_profile_02']) + expect(compliance_engine.profiles.keys).to eq(%w[test_profile_00 test_profile_01 test_profile_02]) end it 'returns a list of ces' do expect(compliance_engine.ces).to be_instance_of(ComplianceEngine::Ces) - expect(compliance_engine.ces.keys).to eq(['ce_00', 'ce_01', 'ce_02', 'ce_03']) + expect(compliance_engine.ces.keys).to eq(%w[ce_00 ce_01 ce_02 ce_03]) end end end diff --git a/spec/classes/compliance_engine/module_loader_spec.rb b/spec/classes/compliance_engine/module_loader_spec.rb index bb40a5a..0164fce 100644 --- a/spec/classes/compliance_engine/module_loader_spec.rb +++ b/spec/classes/compliance_engine/module_loader_spec.rb @@ -71,7 +71,7 @@ ce: ce_00: {} ce_01: {} - A_YAML + A_YAML 'b/file.yaml' => <<~B_YAML, --- version: '2.0.0' @@ -81,7 +81,7 @@ ce_02: true ce: ce_02: {} - B_YAML + B_YAML 'c/file.yaml' => <<~C_YAML, --- version: '2.0.0' @@ -91,7 +91,7 @@ ce_03: true ce: ce_03: {} - C_YAML + C_YAML }, } end @@ -101,14 +101,14 @@ allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") - .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). + and_return( + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.json") - .and_return([]) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.json"). + and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -135,7 +135,7 @@ end it 'returns a list of file loader objects' do - expect(module_loader.files.map { |loader| loader.key }).to eq(test_data.map { |module_path, files| files.map { |name, _| "#{module_path}/SIMP/compliance_profiles/#{name}" } }.flatten) + expect(module_loader.files.map(&:key)).to eq(test_data.map { |module_path, files| files.map { |name, _| "#{module_path}/SIMP/compliance_profiles/#{name}" } }.flatten) end end @@ -161,7 +161,7 @@ end it 'returns a list of file loader objects' do - expect(module_loader.files.map { |loader| loader.key }).to eq(test_data.map { |module_path, files| files.map { |name, _| "#{module_path}/SIMP/compliance_profiles/#{name}" } }.flatten) + expect(module_loader.files.map(&:key)).to eq(test_data.map { |module_path, files| files.map { |name, _| "#{module_path}/SIMP/compliance_profiles/#{name}" } }.flatten) end end end diff --git a/spec/fixtures/modules/compliance_engine b/spec/fixtures/modules/compliance_engine deleted file mode 120000 index a8a4f8c..0000000 --- a/spec/fixtures/modules/compliance_engine +++ /dev/null @@ -1 +0,0 @@ -../../.. \ No newline at end of file diff --git a/spec/functions/compliance_engine/enforcement_spec.rb b/spec/functions/compliance_engine/enforcement_spec.rb index 6d06f22..7ca1765 100644 --- a/spec/functions/compliance_engine/enforcement_spec.rb +++ b/spec/functions/compliance_engine/enforcement_spec.rb @@ -97,8 +97,8 @@ let(:facts) { {} } it 'returns not_found for any key when no profiles are set' do - is_expected.to run.with_params('enforcement_spec::test_param') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('enforcement_spec::test_param'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -117,23 +117,23 @@ end it 'returns not_found for keys not in compliance data' do - is_expected.to run.with_params('unknown::param') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('unknown::param'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns not_found for lookup_options key' do - is_expected.to run.with_params('lookup_options') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('lookup_options'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns not_found for compliance_engine:: prefixed keys' do - is_expected.to run.with_params('compliance_engine::some_internal_key') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('compliance_engine::some_internal_key'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns not_found for compliance_markup:: prefixed keys' do - is_expected.to run.with_params('compliance_markup::some_internal_key') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('compliance_markup::some_internal_key'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -144,8 +144,8 @@ let(:hieradata) { 'compliance-engine' } it 'returns not_found when the profile does not exist' do - is_expected.to run.with_params('enforcement_spec::test_param') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('enforcement_spec::test_param'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -220,8 +220,8 @@ end it 'returns not_found for value only in second profile when first profile is selected' do - is_expected.to run.with_params('multi_profile::param_two') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('multi_profile::param_two'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -305,8 +305,8 @@ end it 'returns not_found for Debian-confined check' do - is_expected.to run.with_params('confine_test::debian_param') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('confine_test::debian_param'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns value for non-confined check' do @@ -323,8 +323,8 @@ end it 'returns not_found for RedHat-confined check' do - is_expected.to run.with_params('confine_test::redhat_param') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('confine_test::redhat_param'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns value for Debian-confined check' do @@ -535,13 +535,13 @@ end it 'returns not_found for medium-risk check (level 50 >= tolerance 25)' do - is_expected.to run.with_params('tolerance_test::medium_risk_param') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('tolerance_test::medium_risk_param'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns not_found for high-risk check (level 80 >= tolerance 25)' do - is_expected.to run.with_params('tolerance_test::high_risk_param') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('tolerance_test::high_risk_param'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -567,8 +567,8 @@ end it 'returns not_found for high-risk check (level 80 >= tolerance 60)' do - is_expected.to run.with_params('tolerance_test::high_risk_param') - .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('tolerance_test::high_risk_param'). + and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -654,7 +654,7 @@ 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'types_test::array_param', - 'value' => ['item1', 'item2', 'item3'], + 'value' => %w[item1 item2 item3], }, 'ces' => ['types_ce'], }, @@ -692,7 +692,7 @@ end it 'returns array value' do - is_expected.to run.with_params('types_test::array_param').and_return(['item1', 'item2', 'item3']) + is_expected.to run.with_params('types_test::array_param').and_return(%w[item1 item2 item3]) end it 'returns hash value' do diff --git a/spec/functions/lookup/00_enforcement_spec.rb b/spec/functions/lookup/00_enforcement_spec.rb index 4c827cd..be45180 100755 --- a/spec/functions/lookup/00_enforcement_spec.rb +++ b/spec/functions/lookup/00_enforcement_spec.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby -S rspec +# frozen_string_literal: true require 'spec_helper' require 'spec_helper_puppet' @@ -100,8 +101,8 @@ let(:hieradata) { 'compliance-engine' } it { - is_expected.to run.with_params('test_module_00::test_param') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") + is_expected.to run.with_params('test_module_00::test_param'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") } end @@ -126,9 +127,10 @@ # Test unconfined data. it { - is_expected.to run.with_params('test_module_00::test_param') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") + is_expected.to run.with_params('test_module_00::test_param'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") } + it { is_expected.to run.with_params('test_module_00::test_param2').and_return('another string') } end end diff --git a/spec/functions/lookup/01_enforcement_confine_spec.rb b/spec/functions/lookup/01_enforcement_confine_spec.rb index 3bbcde5..d1d3ab0 100755 --- a/spec/functions/lookup/01_enforcement_confine_spec.rb +++ b/spec/functions/lookup/01_enforcement_confine_spec.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby -S rspec +# frozen_string_literal: true require 'spec_helper' require 'spec_helper_puppet' @@ -54,7 +55,7 @@ 'version' => '2.0.0', 'checks' => { '01_el_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_01::is_el', 'value' => true, @@ -67,7 +68,7 @@ }, }, '01_el_negative_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_01::is_not_el', 'value' => true, @@ -80,7 +81,7 @@ }, }, '01_el7_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_01::el_version', 'value' => '7', @@ -89,15 +90,15 @@ '01_ce2', ], 'confine' => { - 'os.name' => [ - 'RedHat', - 'CentOS', + 'os.name' => %w[ + RedHat + CentOS ], 'os.release.major' => '7', }, }, '01_el7_negative_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_01::not_el_version', 'value' => '7', @@ -113,7 +114,7 @@ }, }, '01_el7_negative_mixed_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_01::not_el_centos_version', 'value' => '7', @@ -183,22 +184,22 @@ end # Test for confine on a single fact in checks. - if os_facts[:os]['family'] != 'RedHat' - it { is_expected.to run.with_params('test_module_01::is_not_el').and_return(true) } - else + if os_facts[:os]['family'] == 'RedHat' it do - is_expected.to run.with_params('test_module_01::is_not_el') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::is_not_el'") + is_expected.to run.with_params('test_module_01::is_not_el'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::is_not_el'") end + else + it { is_expected.to run.with_params('test_module_01::is_not_el').and_return(true) } end # Test for confine on multiple facts and an array of facts in checks. - if ['RedHat', 'CentOS'].include?(os_facts[:os]['name']) && os_facts[:os]['release']['major'] == '7' + if %w[RedHat CentOS].include?(os_facts[:os]['name']) && os_facts[:os]['release']['major'] == '7' it { is_expected.to run.with_params('test_module_01::el_version').and_return('7') } else it do - is_expected.to run.with_params('test_module_01::el_version') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::el_version'") + is_expected.to run.with_params('test_module_01::el_version'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::el_version'") end end @@ -207,8 +208,8 @@ it { is_expected.to run.with_params('test_module_01::not_el_version').and_return('7') } else it do - is_expected.to run.with_params('test_module_01::not_el_version') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_version'") + is_expected.to run.with_params('test_module_01::not_el_version'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_version'") end end @@ -216,21 +217,23 @@ # TODO: This does not currently work as one might expect. This will still positively match OracleLinux even though # we ask for OS names that aren't RedHat but are CentOS. The array we're confining can only do an OR operation rather # than an AND with a negative lookup. + # rubocop:disable RSpec/RepeatedExample if (os_facts[:os]['name'] != 'RedHat') && (os_facts[:os]['name'] == 'CentOS') && os_facts[:os]['release']['major'] == '7' - it { is_expected.to run.with_params('test_module_01::not_el_centos_version').and_return('7') } # FIXME: # rubocop:disable RSpec/RepeatedExample + it { is_expected.to run.with_params('test_module_01::not_el_centos_version').and_return('7') } elsif (os_facts[:os]['name'] != 'RedHat') && (os_facts[:os]['name'] != 'CentOS') && os_facts[:os]['release']['major'] == '7' - it { is_expected.to run.with_params('test_module_01::not_el_centos_version').and_return('7') } # FIXME: # rubocop:disable RSpec/RepeatedExample + it { is_expected.to run.with_params('test_module_01::not_el_centos_version').and_return('7') } else it do - is_expected.to run.with_params('test_module_01::not_el_centos_version') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_centos_version'") + is_expected.to run.with_params('test_module_01::not_el_centos_version'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_centos_version'") end end + # rubocop:enable RSpec/RepeatedExample # Test for confine on module name & module version in ce. it do - is_expected.to run.with_params('test_module_01::fixed_confines') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::fixed_confines'") + is_expected.to run.with_params('test_module_01::fixed_confines'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::fixed_confines'") end end end diff --git a/spec/functions/lookup/02_enforcement_merge_spec.rb b/spec/functions/lookup/02_enforcement_merge_spec.rb index 87b32c2..d025dec 100755 --- a/spec/functions/lookup/02_enforcement_merge_spec.rb +++ b/spec/functions/lookup/02_enforcement_merge_spec.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby -S rspec +# frozen_string_literal: true require 'spec_helper' require 'spec_helper_puppet' diff --git a/spec/functions/lookup/03_enforcement_profile_merge_spec.rb b/spec/functions/lookup/03_enforcement_profile_merge_spec.rb index 84e34a1..ba0f409 100755 --- a/spec/functions/lookup/03_enforcement_profile_merge_spec.rb +++ b/spec/functions/lookup/03_enforcement_profile_merge_spec.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby -S rspec +# frozen_string_literal: true require 'spec_helper' require 'spec_helper_puppet' @@ -173,7 +174,7 @@ before(:each) do File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| - test_hiera = { 'compliance_engine::enforcement' => ['profile_test1', 'profile_test2'] }.to_yaml + test_hiera = { 'compliance_engine::enforcement' => %w[profile_test1 profile_test2] }.to_yaml fh.puts test_hiera end end @@ -197,7 +198,7 @@ before(:each) do File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| - test_hiera = { 'compliance_engine::enforcement' => ['profile_test2', 'profile_test1'] }.to_yaml + test_hiera = { 'compliance_engine::enforcement' => %w[profile_test2 profile_test1] }.to_yaml fh.puts test_hiera end end diff --git a/spec/functions/lookup/04_enforcement_profile_merge_spec.rb b/spec/functions/lookup/04_enforcement_profile_merge_spec.rb index b39e1ee..100fe7d 100755 --- a/spec/functions/lookup/04_enforcement_profile_merge_spec.rb +++ b/spec/functions/lookup/04_enforcement_profile_merge_spec.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby -S rspec +# frozen_string_literal: true require 'spec_helper' require 'spec_helper_puppet' @@ -31,7 +32,7 @@ }, }, }, - 'ces_00' => { + 'ces_00' => { 'version' => '2.0.0', 'ce' => { '04_profile_test1' => { @@ -44,7 +45,7 @@ }, }, }, - 'ces_01' => { + 'ces_01' => { 'version' => '2.0.0', 'ce' => { '04_profile_test2' => { @@ -71,7 +72,7 @@ 'version' => '2.0.0', 'checks' => { '04_string check1' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', }, }, }, @@ -80,7 +81,7 @@ 'checks' => { '04_string check1' => { 'settings' => { - 'value' => 'string value 1', + 'value' => 'string value 1', }, }, }, @@ -110,7 +111,7 @@ 'checks' => { '04_string check2' => { 'settings' => { - 'value' => 'string value 2', + 'value' => 'string value 2', }, 'identifiers' => { '04_identifier2' => [], @@ -122,7 +123,7 @@ 'version' => '2.0.0', 'checks' => { '04_string check2' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', }, }, }, @@ -130,7 +131,7 @@ 'version' => '2.0.0', 'checks' => { '04_string check2' => { - 'ces' => [ + 'ces' => [ '04_profile_test2', ], }, @@ -140,7 +141,7 @@ 'version' => '2.0.0', 'checks' => { '04_array check1' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', }, }, }, @@ -148,7 +149,7 @@ 'version' => '2.0.0', 'checks' => { '04_array check1' => { - 'ces' => [ + 'ces' => [ '04_profile_test1', ], }, @@ -184,7 +185,7 @@ 'checks' => { '04_array check2' => { 'settings' => { - 'value' => [ + 'value' => [ 'array value 2', ], }, @@ -195,7 +196,7 @@ 'version' => '2.0.0', 'checks' => { '04_array check2' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', }, }, }, @@ -203,7 +204,7 @@ 'version' => '2.0.0', 'checks' => { '04_array check2' => { - 'ces' => [ + 'ces' => [ '04_profile_test2', ], }, @@ -261,7 +262,7 @@ 'version' => '2.0.0', 'checks' => { '04_hash check1' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', }, }, }, @@ -269,7 +270,7 @@ 'version' => '2.0.0', 'checks' => { '04_hash check2' => { - 'ces' => [ + 'ces' => [ '04_profile_test2', ], }, @@ -301,7 +302,7 @@ 'checks' => { '04_hash check2' => { 'settings' => { - 'value' => { + 'value' => { 'hash key 2' => 'hash value 2', }, }, @@ -346,7 +347,7 @@ 'version' => '2.0.0', 'checks' => { '04_nested hash1' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', }, }, }, @@ -354,7 +355,7 @@ 'version' => '2.0.0', 'checks' => { '04_nested hash2' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', }, }, }, @@ -373,7 +374,7 @@ 'checks' => { '04_nested hash2' => { 'settings' => { - 'value' => { + 'value' => { 'key' => { 'key1' => 'value2', }, @@ -441,7 +442,7 @@ before(:each) do File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| - test_hiera = { 'compliance_engine::enforcement' => ['profile_test1', 'profile_test2'] }.to_yaml + test_hiera = { 'compliance_engine::enforcement' => %w[profile_test1 profile_test2] }.to_yaml fh.puts test_hiera end end @@ -465,7 +466,7 @@ before(:each) do File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| - test_hiera = { 'compliance_engine::enforcement' => ['profile_test2', 'profile_test1'] }.to_yaml + test_hiera = { 'compliance_engine::enforcement' => %w[profile_test2 profile_test1] }.to_yaml fh.puts test_hiera end end diff --git a/spec/functions/lookup/05_enforcement_override_spec.rb b/spec/functions/lookup/05_enforcement_override_spec.rb index 3ed1ae2..38ff350 100755 --- a/spec/functions/lookup/05_enforcement_override_spec.rb +++ b/spec/functions/lookup/05_enforcement_override_spec.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby -S rspec +# frozen_string_literal: true require 'spec_helper' require 'spec_helper_puppet' @@ -36,7 +37,7 @@ { 'version' => '2.0.0', 'checks' => { - '05_hash check1' => { + '05_hash check1' => { 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_05::hash_param', diff --git a/spec/functions/lookup/06_enforcement_debug_spec.rb b/spec/functions/lookup/06_enforcement_debug_spec.rb index f6a010c..d615ddf 100755 --- a/spec/functions/lookup/06_enforcement_debug_spec.rb +++ b/spec/functions/lookup/06_enforcement_debug_spec.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby -S rspec +# frozen_string_literal: true require 'spec_helper' require 'spec_helper_puppet' @@ -122,7 +123,7 @@ it do result = lookup.execute('compliance_engine::debug::compliance_data') expect(result).to be_a(Hash) - expect(result.keys).to eq(['version', 'profiles', 'ce', 'checks']) + expect(result.keys).to eq(%w[version profiles ce checks]) expect(result['profiles']).to include(profile['profiles']) expect(result['ce']).to include(ces['ce']) expect(result['checks']).to include(checks['checks']) diff --git a/spec/functions/lookup/07_enforcement_tolerance_spec.rb b/spec/functions/lookup/07_enforcement_tolerance_spec.rb index 823ba9c..214e18f 100755 --- a/spec/functions/lookup/07_enforcement_tolerance_spec.rb +++ b/spec/functions/lookup/07_enforcement_tolerance_spec.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby -S rspec +# frozen_string_literal: true require 'spec_helper' require 'spec_helper_puppet' @@ -45,7 +46,7 @@ 'version' => '2.0.0', 'checks' => { '07_disabled_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_07::is_disabled', 'value' => true, @@ -60,7 +61,7 @@ }, }, '07_level_21_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_07::is_level_21', 'value' => true, @@ -75,7 +76,7 @@ }, }, '07_level_41_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_07::is_level_41', 'value' => true, @@ -90,7 +91,7 @@ }, }, '07_level_61_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_07::is_level_61', 'value' => true, @@ -105,7 +106,7 @@ }, }, '07_level_81_check' => { - 'type' => 'puppet-class-parameter', + 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'test_module_07::is_level_81', 'value' => true, @@ -153,27 +154,31 @@ os_facts.merge( 'custom_hiera' => 'compliance_engine', 'target_compliance_profile' => '07_profile_test', - 'target_enforcement_tolerance' => '22', + 'target_enforcement_tolerance' => '22' ) end let(:hieradata) { 'compliance_engine' } it do - is_expected.to run.with_params('test_module_07::is_disabled') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + is_expected.to run.with_params('test_module_07::is_disabled'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") end + it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } + it do - is_expected.to run.with_params('test_module_07::is_level_41') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_41'") + is_expected.to run.with_params('test_module_07::is_level_41'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_41'") end + it do - is_expected.to run.with_params('test_module_07::is_level_61') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") + is_expected.to run.with_params('test_module_07::is_level_61'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") end + it do - is_expected.to run.with_params('test_module_07::is_level_81') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") + is_expected.to run.with_params('test_module_07::is_level_81'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") end end @@ -184,18 +189,21 @@ let(:hieradata) { 'compliance_engine' } it do - is_expected.to run.with_params('test_module_07::is_disabled') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + is_expected.to run.with_params('test_module_07::is_disabled'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") end + it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } it { is_expected.to run.with_params('test_module_07::is_level_41').and_return(true) } + it do - is_expected.to run.with_params('test_module_07::is_level_61') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") + is_expected.to run.with_params('test_module_07::is_level_61'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") end + it do - is_expected.to run.with_params('test_module_07::is_level_81') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") + is_expected.to run.with_params('test_module_07::is_level_81'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") end end @@ -206,15 +214,17 @@ let(:hieradata) { 'compliance_engine' } it do - is_expected.to run.with_params('test_module_07::is_disabled') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + is_expected.to run.with_params('test_module_07::is_disabled'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") end + it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } it { is_expected.to run.with_params('test_module_07::is_level_41').and_return(true) } it { is_expected.to run.with_params('test_module_07::is_level_61').and_return(true) } + it do - is_expected.to run.with_params('test_module_07::is_level_81') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") + is_expected.to run.with_params('test_module_07::is_level_81'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") end end @@ -225,9 +235,10 @@ let(:hieradata) { 'compliance_engine' } it do - is_expected.to run.with_params('test_module_07::is_disabled') - .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + is_expected.to run.with_params('test_module_07::is_disabled'). + and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") end + it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } it { is_expected.to run.with_params('test_module_07::is_level_41').and_return(true) } it { is_expected.to run.with_params('test_module_07::is_level_61').and_return(true) } diff --git a/spec/functions/lookup/10_enforce_spec.rb b/spec/functions/lookup/10_enforce_spec.rb index 0dfb9bb..8a8c2fc 100644 --- a/spec/functions/lookup/10_enforce_spec.rb +++ b/spec/functions/lookup/10_enforce_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' require 'spec_helper_puppet' # require 'fileutils' diff --git a/spec/spec_helper_puppet.rb b/spec/spec_helper_puppet.rb index 9e98c89..963db03 100644 --- a/spec/spec_helper_puppet.rb +++ b/spec/spec_helper_puppet.rb @@ -4,7 +4,7 @@ c.mock_with :rspec end -require 'puppetlabs_spec_helper/module_spec_helper' +require 'voxpupuli/test/spec_helper' require 'rspec-puppet-facts' require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb')) diff --git a/spec/support/compliance_module_mocks.rb b/spec/support/compliance_module_mocks.rb index 64d9339..f746697 100644 --- a/spec/support/compliance_module_mocks.rb +++ b/spec/support/compliance_module_mocks.rb @@ -29,12 +29,12 @@ allow(File).to receive(:read).with(metadata_path).and_return(metadata_json) end - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") - .and_return(compliance_files) - allow(Dir).to receive(:glob) - .with("#{module_path}/SIMP/compliance_profiles/**/*.json") - .and_return([]) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). + and_return(compliance_files) + allow(Dir).to receive(:glob). + with("#{module_path}/SIMP/compliance_profiles/**/*.json"). + and_return([]) allow(File).to receive(:size).and_call_original allow(File).to receive(:mtime).and_call_original From 9212b77867a82644ae4d0a0672f298d4ddff0c63 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Mon, 12 Jan 2026 21:38:02 +0000 Subject: [PATCH 06/22] Add one more rubocop override --- .rubocop.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index eed30a0..a157a60 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -71,3 +71,6 @@ Style/MixinUsage: Lint/DuplicateBranch: Enabled: false + +Gemspec/RequireMFA: + Enabled: false From 9aac134d9746ff422c0eaf41bfe9c3cde160239a Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Mon, 12 Jan 2026 21:46:37 +0000 Subject: [PATCH 07/22] Skip Hiera tests on Ruby 4 for now --- .github/workflows/pr_tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index 813f839..df18835 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -41,4 +41,9 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Spec tests - run: bundle exec rake spec + run: | + if [ "${{ matrix.ruby }}" = "4.0" ]; then + bundle exec rspec spec/classes + else + bundle exec rake spec + fi From b1732110c1337b5099df1f0a3ef6120279a13404 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Mon, 12 Jan 2026 21:51:58 +0000 Subject: [PATCH 08/22] Drop Ruby 2.7 from the GHA test matrix --- .github/workflows/pr_tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index df18835..e815bad 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -27,7 +27,6 @@ jobs: strategy: matrix: ruby: - - '2.7' # Puppet 7 - '3.2' # Puppet 8 - '3.3' - '3.4' From 7e236abc5e52ee4ca5b434771c4a6d97839e7258 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 16:04:10 +0000 Subject: [PATCH 09/22] Move back to leading dots on multi-line method calls --- .rubocop.yml | 3 + lib/compliance_engine/environment_loader.rb | 10 +-- lib/compliance_engine/module_loader.rb | 6 +- spec/classes/compliance_engine/data_spec.rb | 84 +++++++++---------- .../compliance_engine/module_loader_spec.rb | 12 +-- .../compliance_engine/enforcement_spec.rb | 48 +++++------ spec/functions/lookup/00_enforcement_spec.rb | 8 +- .../lookup/01_enforcement_confine_spec.rb | 20 ++--- .../lookup/07_enforcement_tolerance_spec.rb | 40 ++++----- spec/support/compliance_module_mocks.rb | 12 +-- 10 files changed, 123 insertions(+), 120 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index a157a60..894f754 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -39,6 +39,9 @@ Style/Documentation: - lib/puppet/parser/functions/**/* - spec/**/* +Layout/DotPosition: + EnforcedStyle: leading + Layout/HashAlignment: Enabled: false diff --git a/lib/compliance_engine/environment_loader.rb b/lib/compliance_engine/environment_loader.rb index f89da17..f82e51b 100644 --- a/lib/compliance_engine/environment_loader.rb +++ b/lib/compliance_engine/environment_loader.rb @@ -17,11 +17,11 @@ def initialize(*paths, fileclass: File, dirclass: Dir, zipfile_path: nil) @modulepath ||= paths @zipfile_path = zipfile_path modules = paths.map do |path| - dirclass.entries(path). - grep(%r{\A[a-z][a-z0-9_]*\Z}). - select { |child| fileclass.directory?(File.join(path, child)) }. - map { |child| File.join(path, child) }. - sort + dirclass.entries(path) + .grep(%r{\A[a-z][a-z0-9_]*\Z}) + .select { |child| fileclass.directory?(File.join(path, child)) } + .map { |child| File.join(path, child) } + .sort rescue StandardError [] end diff --git a/lib/compliance_engine/module_loader.rb b/lib/compliance_engine/module_loader.rb index 2abd0dd..ecf1783 100644 --- a/lib/compliance_engine/module_loader.rb +++ b/lib/compliance_engine/module_loader.rb @@ -34,9 +34,9 @@ def initialize(path, fileclass: File, dirclass: Dir, zipfile_path: nil) # In this directory, we want to look for all yaml and json files # under SIMP/compliance_profiles and simp/compliance_profiles. - globs = ['SIMP/compliance_profiles', 'simp/compliance_profiles']. - select { |dir| fileclass.directory?(File.join(path, dir)) }. - map { |dir| + globs = ['SIMP/compliance_profiles', 'simp/compliance_profiles'] + .select { |dir| fileclass.directory?(File.join(path, dir)) } + .map { |dir| %w[yaml json].map { |type| File.join(path, dir, '**', "*.#{type}") } }.flatten # Using .each here to make mocking with rspec easier. diff --git a/spec/classes/compliance_engine/data_spec.rb b/spec/classes/compliance_engine/data_spec.rb index 624f0e3..c5fbd07 100644 --- a/spec/classes/compliance_engine/data_spec.rb +++ b/spec/classes/compliance_engine/data_spec.rb @@ -239,14 +239,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). - and_return( + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return( file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.json"). - and_return([]) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -331,14 +331,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). - and_return( + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return( file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.json"). - and_return([]) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -533,14 +533,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). - and_return( + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return( file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.json"). - and_return([]) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -687,14 +687,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). - and_return( + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return( file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.json"). - and_return([]) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -786,14 +786,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). - and_return( + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return( file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.json"). - and_return([]) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -867,14 +867,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). - and_return( + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return( file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.json"). - and_return([]) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" @@ -974,14 +974,14 @@ def test_data allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). - and_return( + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return( file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.json"). - and_return([]) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" diff --git a/spec/classes/compliance_engine/module_loader_spec.rb b/spec/classes/compliance_engine/module_loader_spec.rb index 0164fce..f2cc0d8 100644 --- a/spec/classes/compliance_engine/module_loader_spec.rb +++ b/spec/classes/compliance_engine/module_loader_spec.rb @@ -101,14 +101,14 @@ allow(File).to receive(:directory?).with(module_path).and_return(true) allow(File).to receive(:directory?).with("#{module_path}/SIMP/compliance_profiles").and_return(true) allow(File).to receive(:directory?).with("#{module_path}/simp/compliance_profiles").and_return(false) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). - and_return( + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return( file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } ) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.json"). - and_return([]) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) file_data.each do |name, contents| filename = "#{module_path}/SIMP/compliance_profiles/#{name}" diff --git a/spec/functions/compliance_engine/enforcement_spec.rb b/spec/functions/compliance_engine/enforcement_spec.rb index 7ca1765..18eee7a 100644 --- a/spec/functions/compliance_engine/enforcement_spec.rb +++ b/spec/functions/compliance_engine/enforcement_spec.rb @@ -97,8 +97,8 @@ let(:facts) { {} } it 'returns not_found for any key when no profiles are set' do - is_expected.to run.with_params('enforcement_spec::test_param'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('enforcement_spec::test_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -117,23 +117,23 @@ end it 'returns not_found for keys not in compliance data' do - is_expected.to run.with_params('unknown::param'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('unknown::param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns not_found for lookup_options key' do - is_expected.to run.with_params('lookup_options'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('lookup_options') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns not_found for compliance_engine:: prefixed keys' do - is_expected.to run.with_params('compliance_engine::some_internal_key'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('compliance_engine::some_internal_key') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns not_found for compliance_markup:: prefixed keys' do - is_expected.to run.with_params('compliance_markup::some_internal_key'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('compliance_markup::some_internal_key') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -144,8 +144,8 @@ let(:hieradata) { 'compliance-engine' } it 'returns not_found when the profile does not exist' do - is_expected.to run.with_params('enforcement_spec::test_param'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('enforcement_spec::test_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -220,8 +220,8 @@ end it 'returns not_found for value only in second profile when first profile is selected' do - is_expected.to run.with_params('multi_profile::param_two'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('multi_profile::param_two') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -305,8 +305,8 @@ end it 'returns not_found for Debian-confined check' do - is_expected.to run.with_params('confine_test::debian_param'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('confine_test::debian_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns value for non-confined check' do @@ -323,8 +323,8 @@ end it 'returns not_found for RedHat-confined check' do - is_expected.to run.with_params('confine_test::redhat_param'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('confine_test::redhat_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns value for Debian-confined check' do @@ -535,13 +535,13 @@ end it 'returns not_found for medium-risk check (level 50 >= tolerance 25)' do - is_expected.to run.with_params('tolerance_test::medium_risk_param'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('tolerance_test::medium_risk_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end it 'returns not_found for high-risk check (level 80 >= tolerance 25)' do - is_expected.to run.with_params('tolerance_test::high_risk_param'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('tolerance_test::high_risk_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end @@ -567,8 +567,8 @@ end it 'returns not_found for high-risk check (level 80 >= tolerance 60)' do - is_expected.to run.with_params('tolerance_test::high_risk_param'). - and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) + is_expected.to run.with_params('tolerance_test::high_risk_param') + .and_raise_error(Puppet::DataBinding::LookupError, %r{did not find a value}) end end diff --git a/spec/functions/lookup/00_enforcement_spec.rb b/spec/functions/lookup/00_enforcement_spec.rb index be45180..127f789 100755 --- a/spec/functions/lookup/00_enforcement_spec.rb +++ b/spec/functions/lookup/00_enforcement_spec.rb @@ -101,8 +101,8 @@ let(:hieradata) { 'compliance-engine' } it { - is_expected.to run.with_params('test_module_00::test_param'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") + is_expected.to run.with_params('test_module_00::test_param') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") } end @@ -127,8 +127,8 @@ # Test unconfined data. it { - is_expected.to run.with_params('test_module_00::test_param'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") + is_expected.to run.with_params('test_module_00::test_param') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_00::test_param'") } it { is_expected.to run.with_params('test_module_00::test_param2').and_return('another string') } diff --git a/spec/functions/lookup/01_enforcement_confine_spec.rb b/spec/functions/lookup/01_enforcement_confine_spec.rb index d1d3ab0..8616405 100755 --- a/spec/functions/lookup/01_enforcement_confine_spec.rb +++ b/spec/functions/lookup/01_enforcement_confine_spec.rb @@ -186,8 +186,8 @@ # Test for confine on a single fact in checks. if os_facts[:os]['family'] == 'RedHat' it do - is_expected.to run.with_params('test_module_01::is_not_el'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::is_not_el'") + is_expected.to run.with_params('test_module_01::is_not_el') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::is_not_el'") end else it { is_expected.to run.with_params('test_module_01::is_not_el').and_return(true) } @@ -198,8 +198,8 @@ it { is_expected.to run.with_params('test_module_01::el_version').and_return('7') } else it do - is_expected.to run.with_params('test_module_01::el_version'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::el_version'") + is_expected.to run.with_params('test_module_01::el_version') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::el_version'") end end @@ -208,8 +208,8 @@ it { is_expected.to run.with_params('test_module_01::not_el_version').and_return('7') } else it do - is_expected.to run.with_params('test_module_01::not_el_version'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_version'") + is_expected.to run.with_params('test_module_01::not_el_version') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_version'") end end @@ -224,16 +224,16 @@ it { is_expected.to run.with_params('test_module_01::not_el_centos_version').and_return('7') } else it do - is_expected.to run.with_params('test_module_01::not_el_centos_version'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_centos_version'") + is_expected.to run.with_params('test_module_01::not_el_centos_version') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::not_el_centos_version'") end end # rubocop:enable RSpec/RepeatedExample # Test for confine on module name & module version in ce. it do - is_expected.to run.with_params('test_module_01::fixed_confines'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::fixed_confines'") + is_expected.to run.with_params('test_module_01::fixed_confines') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_01::fixed_confines'") end end end diff --git a/spec/functions/lookup/07_enforcement_tolerance_spec.rb b/spec/functions/lookup/07_enforcement_tolerance_spec.rb index 214e18f..a6d73b6 100755 --- a/spec/functions/lookup/07_enforcement_tolerance_spec.rb +++ b/spec/functions/lookup/07_enforcement_tolerance_spec.rb @@ -160,25 +160,25 @@ let(:hieradata) { 'compliance_engine' } it do - is_expected.to run.with_params('test_module_07::is_disabled'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + is_expected.to run.with_params('test_module_07::is_disabled') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") end it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } it do - is_expected.to run.with_params('test_module_07::is_level_41'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_41'") + is_expected.to run.with_params('test_module_07::is_level_41') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_41'") end it do - is_expected.to run.with_params('test_module_07::is_level_61'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") + is_expected.to run.with_params('test_module_07::is_level_61') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") end it do - is_expected.to run.with_params('test_module_07::is_level_81'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") + is_expected.to run.with_params('test_module_07::is_level_81') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") end end @@ -189,21 +189,21 @@ let(:hieradata) { 'compliance_engine' } it do - is_expected.to run.with_params('test_module_07::is_disabled'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + is_expected.to run.with_params('test_module_07::is_disabled') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") end it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } it { is_expected.to run.with_params('test_module_07::is_level_41').and_return(true) } it do - is_expected.to run.with_params('test_module_07::is_level_61'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") + is_expected.to run.with_params('test_module_07::is_level_61') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_61'") end it do - is_expected.to run.with_params('test_module_07::is_level_81'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") + is_expected.to run.with_params('test_module_07::is_level_81') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") end end @@ -214,8 +214,8 @@ let(:hieradata) { 'compliance_engine' } it do - is_expected.to run.with_params('test_module_07::is_disabled'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + is_expected.to run.with_params('test_module_07::is_disabled') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") end it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } @@ -223,8 +223,8 @@ it { is_expected.to run.with_params('test_module_07::is_level_61').and_return(true) } it do - is_expected.to run.with_params('test_module_07::is_level_81'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") + is_expected.to run.with_params('test_module_07::is_level_81') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_level_81'") end end @@ -235,8 +235,8 @@ let(:hieradata) { 'compliance_engine' } it do - is_expected.to run.with_params('test_module_07::is_disabled'). - and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") + is_expected.to run.with_params('test_module_07::is_disabled') + .and_raise_error(Puppet::DataBinding::LookupError, "Function lookup() did not find a value for the name 'test_module_07::is_disabled'") end it { is_expected.to run.with_params('test_module_07::is_level_21').and_return(true) } diff --git a/spec/support/compliance_module_mocks.rb b/spec/support/compliance_module_mocks.rb index f746697..64d9339 100644 --- a/spec/support/compliance_module_mocks.rb +++ b/spec/support/compliance_module_mocks.rb @@ -29,12 +29,12 @@ allow(File).to receive(:read).with(metadata_path).and_return(metadata_json) end - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.yaml"). - and_return(compliance_files) - allow(Dir).to receive(:glob). - with("#{module_path}/SIMP/compliance_profiles/**/*.json"). - and_return([]) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") + .and_return(compliance_files) + allow(Dir).to receive(:glob) + .with("#{module_path}/SIMP/compliance_profiles/**/*.json") + .and_return([]) allow(File).to receive(:size).and_call_original allow(File).to receive(:mtime).and_call_original From f005c51782e9a46087de534e16b99648d42ed787 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 16:18:08 +0000 Subject: [PATCH 10/22] Revert more rubocop changes --- .rubocop.yml | 18 +++++++++++++++--- spec/classes/compliance_engine/data_spec.rb | 14 +++++++------- .../compliance_engine/module_loader_spec.rb | 2 +- .../lookup/07_enforcement_tolerance_spec.rb | 2 +- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 894f754..441e47c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -34,14 +34,26 @@ Style/ClassAndModuleChildren: Description: Compact style reduces the required amount of indentation. EnforcedStyle: compact +Layout/DotPosition: + EnforcedStyle: leading + +Style/TrailingCommaInArguments: + Description: + Prefer always trailing comma on multiline argument lists. This makes + diffs, and re-ordering nicer. + EnforcedStyleForMultiline: comma + +Style/TrailingCommaInArrayLiteral: + Description: + Prefer always trailing comma on multiline literals. This makes diffs, + and re-ordering nicer. + EnforcedStyleForMultiline: comma + Style/Documentation: Exclude: - lib/puppet/parser/functions/**/* - spec/**/* -Layout/DotPosition: - EnforcedStyle: leading - Layout/HashAlignment: Enabled: false diff --git a/spec/classes/compliance_engine/data_spec.rb b/spec/classes/compliance_engine/data_spec.rb index c5fbd07..661812b 100644 --- a/spec/classes/compliance_engine/data_spec.rb +++ b/spec/classes/compliance_engine/data_spec.rb @@ -242,7 +242,7 @@ def test_data allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, ) allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.json") @@ -334,7 +334,7 @@ def test_data allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, ) allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.json") @@ -536,7 +536,7 @@ def test_data allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, ) allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.json") @@ -690,7 +690,7 @@ def test_data allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, ) allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.json") @@ -789,7 +789,7 @@ def test_data allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, ) allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.json") @@ -870,7 +870,7 @@ def test_data allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, ) allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.json") @@ -977,7 +977,7 @@ def test_data allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, ) allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.json") diff --git a/spec/classes/compliance_engine/module_loader_spec.rb b/spec/classes/compliance_engine/module_loader_spec.rb index f2cc0d8..c0dbc26 100644 --- a/spec/classes/compliance_engine/module_loader_spec.rb +++ b/spec/classes/compliance_engine/module_loader_spec.rb @@ -104,7 +104,7 @@ allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.yaml") .and_return( - file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" } + file_data.map { |name, _contents| "#{module_path}/SIMP/compliance_profiles/#{name}" }, ) allow(Dir).to receive(:glob) .with("#{module_path}/SIMP/compliance_profiles/**/*.json") diff --git a/spec/functions/lookup/07_enforcement_tolerance_spec.rb b/spec/functions/lookup/07_enforcement_tolerance_spec.rb index a6d73b6..bf08323 100755 --- a/spec/functions/lookup/07_enforcement_tolerance_spec.rb +++ b/spec/functions/lookup/07_enforcement_tolerance_spec.rb @@ -154,7 +154,7 @@ os_facts.merge( 'custom_hiera' => 'compliance_engine', 'target_compliance_profile' => '07_profile_test', - 'target_enforcement_tolerance' => '22' + 'target_enforcement_tolerance' => '22', ) end let(:hieradata) { 'compliance_engine' } From 5324e8edb1c6a61cb10663f5dc47e9e5279951cc Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 16:22:13 +0000 Subject: [PATCH 11/22] Revert more rubocop changes --- .rubocop.yml | 4 ++++ Rakefile | 2 +- lib/compliance_engine/collection.rb | 2 +- lib/compliance_engine/component.rb | 2 +- lib/compliance_engine/data.rb | 4 ++-- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 441e47c..abbf87c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -49,6 +49,10 @@ Style/TrailingCommaInArrayLiteral: and re-ordering nicer. EnforcedStyleForMultiline: comma +Style/SymbolArray: + Description: Using percent style obscures symbolic intent of array's contents. + EnforcedStyle: brackets + Style/Documentation: Exclude: - lib/puppet/parser/functions/**/* diff --git a/Rakefile b/Rakefile index b780f68..54216f5 100644 --- a/Rakefile +++ b/Rakefile @@ -17,4 +17,4 @@ task spec: :'fixtures:prep' do |_t, args| Rake::Task['fixtures:clean'].invoke end -task default: %i[spec rubocop] +task default: [:spec, :rubocop] diff --git a/lib/compliance_engine/collection.rb b/lib/compliance_engine/collection.rb index 280110c..e001854 100644 --- a/lib/compliance_engine/collection.rb +++ b/lib/compliance_engine/collection.rb @@ -113,7 +113,7 @@ def reject(&block) # # @return [Array] def context_variables - %i[@enforcement_tolerance @environment_data @facts] + [:@enforcement_tolerance, :@environment_data, :@facts] end # Returns the key of the object diff --git a/lib/compliance_engine/component.rb b/lib/compliance_engine/component.rb index ffe6dea..fdbc652 100644 --- a/lib/compliance_engine/component.rb +++ b/lib/compliance_engine/component.rb @@ -112,7 +112,7 @@ def [](key) # # @return [Array] def context_variables - %i[@enforcement_tolerance @environment_data @facts] + [:@enforcement_tolerance, :@environment_data, :@facts] end # Get the cache variables diff --git a/lib/compliance_engine/data.rb b/lib/compliance_engine/data.rb index 32e8ff7..da13622 100644 --- a/lib/compliance_engine/data.rb +++ b/lib/compliance_engine/data.rb @@ -360,7 +360,7 @@ def filtered_by_tolerance?(check) # # @return [Array] def collection_variables - %i[@profiles @checks @controls @ces] + [:@profiles, :@checks, :@controls, :@ces] end # Get the data variables @@ -374,7 +374,7 @@ def data_variables # # @return [Array] def context_variables - %i[@enforcement_tolerance @environment_data @facts @modulepath] + [:@enforcement_tolerance, :@environment_data, :@facts, :@modulepath] end # Get the cache variables From 26bd085db4c2fb3d8e9729766d9576783b313ffd Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 16:35:56 +0000 Subject: [PATCH 12/22] Fix a bug in enforcement_tolerance handling --- lib/compliance_engine/component.rb | 7 ++++--- lib/compliance_engine/data.rb | 24 +----------------------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/lib/compliance_engine/component.rb b/lib/compliance_engine/component.rb index fdbc652..333b02c 100644 --- a/lib/compliance_engine/component.rb +++ b/lib/compliance_engine/component.rb @@ -207,7 +207,8 @@ def fragments next if confine_away?(fragment) # Confinement based on remediation risk - if enforcement_tolerance.is_a?(Integer) && is_a?(ComplianceEngine::Check) && fragment.key?('remediation') + tolerance = enforcement_tolerance.is_a?(String) ? enforcement_tolerance.to_i : enforcement_tolerance + if tolerance.is_a?(Integer) && tolerance.positive? && is_a?(ComplianceEngine::Check) && fragment.key?('remediation') if fragment['remediation'].key?('disabled') message = "Remediation disabled for #{fragment}" reason = fragment['remediation']['disabled']&.map { |value| value['reason'] }&.reject(&:nil?)&.join("\n") @@ -218,8 +219,8 @@ def fragments if fragment['remediation'].key?('risk') risk_level = fragment['remediation']['risk']&.map { |value| value['level'] }&.select { |value| value.is_a?(Integer) }&.max - if risk_level.is_a?(Integer) && risk_level >= enforcement_tolerance - ComplianceEngine.log.info "Remediation risk #{risk_level} exceeds enforcement tolerance #{enforcement_tolerance} for #{fragment}" + if risk_level.is_a?(Integer) && risk_level >= tolerance + ComplianceEngine.log.info "Remediation risk #{risk_level} exceeds enforcement tolerance #{tolerance} for #{fragment}" next end end diff --git a/lib/compliance_engine/data.rb b/lib/compliance_engine/data.rb index da13622..f0eb1be 100644 --- a/lib/compliance_engine/data.rb +++ b/lib/compliance_engine/data.rb @@ -328,34 +328,12 @@ def check_mapping(profile_or_ce) return @check_mapping[cache_key] if @check_mapping.key?(cache_key) @check_mapping[cache_key] = checks.select do |_, check| - mapping?(check, profile_or_ce) && !filtered_by_tolerance?(check) + mapping?(check, profile_or_ce) end end private - # Check if a check should be filtered out based on enforcement tolerance - # - # @param check [ComplianceEngine::Check] The check to evaluate - # @return [TrueClass, FalseClass] true if check should be filtered out - def filtered_by_tolerance?(check) - return false if enforcement_tolerance.nil? - - remediation = check.remediation - return false if remediation.nil? - - # Filter out disabled checks - return true if remediation['disabled'] - - # Filter based on risk level - if remediation['risk'].is_a?(Array) && !remediation['risk'].empty? - risk_level = remediation['risk'][0]['level'] - return risk_level.to_i > enforcement_tolerance.to_i if risk_level && enforcement_tolerance.to_i.positive? - end - - false - end - # Get the collection variables # # @return [Array] From 4f938438713968ea204a5bf1219797d734dc8a74 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 16:38:11 +0000 Subject: [PATCH 13/22] Revert more rubocop changes --- .rubocop.yml | 3 +++ lib/compliance_engine/module_loader.rb | 2 +- spec/classes/compliance_engine/data_spec.rb | 20 +++++++++---------- .../compliance_engine/enforcement_spec.rb | 4 ++-- .../lookup/01_enforcement_confine_spec.rb | 8 ++++---- .../03_enforcement_profile_merge_spec.rb | 4 ++-- .../04_enforcement_profile_merge_spec.rb | 4 ++-- .../lookup/06_enforcement_debug_spec.rb | 2 +- 8 files changed, 25 insertions(+), 22 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index abbf87c..a8aa687 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -58,6 +58,9 @@ Style/Documentation: - lib/puppet/parser/functions/**/* - spec/**/* +Style/WordArray: + EnforcedStyle: brackets + Layout/HashAlignment: Enabled: false diff --git a/lib/compliance_engine/module_loader.rb b/lib/compliance_engine/module_loader.rb index ecf1783..74adc69 100644 --- a/lib/compliance_engine/module_loader.rb +++ b/lib/compliance_engine/module_loader.rb @@ -37,7 +37,7 @@ def initialize(path, fileclass: File, dirclass: Dir, zipfile_path: nil) globs = ['SIMP/compliance_profiles', 'simp/compliance_profiles'] .select { |dir| fileclass.directory?(File.join(path, dir)) } .map { |dir| - %w[yaml json].map { |type| File.join(path, dir, '**', "*.#{type}") } + ['yaml', 'json'].map { |type| File.join(path, dir, '**', "*.#{type}") } }.flatten # Using .each here to make mocking with rspec easier. globs.each do |glob| diff --git a/spec/classes/compliance_engine/data_spec.rb b/spec/classes/compliance_engine/data_spec.rb index 661812b..7537015 100644 --- a/spec/classes/compliance_engine/data_spec.rb +++ b/spec/classes/compliance_engine/data_spec.rb @@ -268,12 +268,12 @@ def test_data it 'returns a list of profiles' do expect(compliance_engine.profiles).to be_instance_of(ComplianceEngine::Profiles) - expect(compliance_engine.profiles.keys).to eq(%w[test_profile_00 test_profile_01 test_profile_02]) + expect(compliance_engine.profiles.keys).to eq(['test_profile_00', 'test_profile_01', 'test_profile_02']) end it 'returns a list of ces' do expect(compliance_engine.ces).to be_instance_of(ComplianceEngine::Ces) - expect(compliance_engine.ces.keys).to eq(%w[ce_00 ce_01 ce_02 ce_03]) + expect(compliance_engine.ces.keys).to eq(['ce_00', 'ce_01', 'ce_02', 'ce_03']) end end @@ -608,7 +608,7 @@ def test_data compliance_engine.facts = nil hiera = compliance_engine.hiera(['custom_profile_1']) expect(hiera).to be_instance_of(Hash) - expect(hiera).to eq({ 'widget_spinner::audit_logging' => %w[no yes maybe] }) + expect(hiera).to eq({ 'widget_spinner::audit_logging' => ['no', 'yes', 'maybe'] }) end it 'correctly invalidates cached data' do @@ -625,7 +625,7 @@ def test_data compliance_engine.facts = nil hiera = compliance_engine.hiera(['custom_profile_1']) expect(hiera).to be_instance_of(Hash) - expect(hiera).to eq({ 'widget_spinner::audit_logging' => %w[no yes maybe] }) + expect(hiera).to eq({ 'widget_spinner::audit_logging' => ['no', 'yes', 'maybe'] }) compliance_engine.facts = { 'os' => { 'release' => { 'major' => '9' }, 'name' => 'RedHat' } } hiera = compliance_engine.hiera(['custom_profile_1']) @@ -1004,13 +1004,13 @@ def test_data it 'returns a list of profiles' do profiles = compliance_engine.profiles expect(profiles).to be_instance_of(ComplianceEngine::Profiles) - expect(profiles.keys).to eq(%w[custom_profile_1 00_profile_test]) + expect(profiles.keys).to eq(['custom_profile_1', '00_profile_test']) end it 'returns a list of ces' do ces = compliance_engine.ces expect(ces).to be_instance_of(ComplianceEngine::Ces) - expect(ces.keys).to eq(%w[enable_widget_spinner_audit_logging 00_ce1]) + expect(ces.keys).to eq(['enable_widget_spinner_audit_logging', '00_ce1']) end it 'returns no hiera data when there are no profiles' do @@ -1101,12 +1101,12 @@ def test_data it 'returns a list of profiles' do expect(compliance_engine.profiles).to be_instance_of(ComplianceEngine::Profiles) - expect(compliance_engine.profiles.keys).to eq(%w[test_profile_00 test_profile_01 test_profile_02]) + expect(compliance_engine.profiles.keys).to eq(['test_profile_00', 'test_profile_01', 'test_profile_02']) end it 'returns a list of ces' do expect(compliance_engine.ces).to be_instance_of(ComplianceEngine::Ces) - expect(compliance_engine.ces.keys).to eq(%w[ce_00 ce_01 ce_02 ce_03]) + expect(compliance_engine.ces.keys).to eq(['ce_00', 'ce_01', 'ce_02', 'ce_03']) end end @@ -1141,12 +1141,12 @@ def test_data it 'returns a list of profiles' do expect(compliance_engine.profiles).to be_instance_of(ComplianceEngine::Profiles) - expect(compliance_engine.profiles.keys).to eq(%w[test_profile_00 test_profile_01 test_profile_02]) + expect(compliance_engine.profiles.keys).to eq(['test_profile_00', 'test_profile_01', 'test_profile_02']) end it 'returns a list of ces' do expect(compliance_engine.ces).to be_instance_of(ComplianceEngine::Ces) - expect(compliance_engine.ces.keys).to eq(%w[ce_00 ce_01 ce_02 ce_03]) + expect(compliance_engine.ces.keys).to eq(['ce_00', 'ce_01', 'ce_02', 'ce_03']) end end end diff --git a/spec/functions/compliance_engine/enforcement_spec.rb b/spec/functions/compliance_engine/enforcement_spec.rb index 18eee7a..6d06f22 100644 --- a/spec/functions/compliance_engine/enforcement_spec.rb +++ b/spec/functions/compliance_engine/enforcement_spec.rb @@ -654,7 +654,7 @@ 'type' => 'puppet-class-parameter', 'settings' => { 'parameter' => 'types_test::array_param', - 'value' => %w[item1 item2 item3], + 'value' => ['item1', 'item2', 'item3'], }, 'ces' => ['types_ce'], }, @@ -692,7 +692,7 @@ end it 'returns array value' do - is_expected.to run.with_params('types_test::array_param').and_return(%w[item1 item2 item3]) + is_expected.to run.with_params('types_test::array_param').and_return(['item1', 'item2', 'item3']) end it 'returns hash value' do diff --git a/spec/functions/lookup/01_enforcement_confine_spec.rb b/spec/functions/lookup/01_enforcement_confine_spec.rb index 8616405..e9be5c7 100755 --- a/spec/functions/lookup/01_enforcement_confine_spec.rb +++ b/spec/functions/lookup/01_enforcement_confine_spec.rb @@ -90,9 +90,9 @@ '01_ce2', ], 'confine' => { - 'os.name' => %w[ - RedHat - CentOS + 'os.name' => [ + 'RedHat', + 'CentOS' ], 'os.release.major' => '7', }, @@ -194,7 +194,7 @@ end # Test for confine on multiple facts and an array of facts in checks. - if %w[RedHat CentOS].include?(os_facts[:os]['name']) && os_facts[:os]['release']['major'] == '7' + if ['RedHat', 'CentOS'].include?(os_facts[:os]['name']) && os_facts[:os]['release']['major'] == '7' it { is_expected.to run.with_params('test_module_01::el_version').and_return('7') } else it do diff --git a/spec/functions/lookup/03_enforcement_profile_merge_spec.rb b/spec/functions/lookup/03_enforcement_profile_merge_spec.rb index ba0f409..eefc5cc 100755 --- a/spec/functions/lookup/03_enforcement_profile_merge_spec.rb +++ b/spec/functions/lookup/03_enforcement_profile_merge_spec.rb @@ -174,7 +174,7 @@ before(:each) do File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| - test_hiera = { 'compliance_engine::enforcement' => %w[profile_test1 profile_test2] }.to_yaml + test_hiera = { 'compliance_engine::enforcement' => ['profile_test1', 'profile_test2'] }.to_yaml fh.puts test_hiera end end @@ -198,7 +198,7 @@ before(:each) do File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| - test_hiera = { 'compliance_engine::enforcement' => %w[profile_test2 profile_test1] }.to_yaml + test_hiera = { 'compliance_engine::enforcement' => ['profile_test2', 'profile_test1'] }.to_yaml fh.puts test_hiera end end diff --git a/spec/functions/lookup/04_enforcement_profile_merge_spec.rb b/spec/functions/lookup/04_enforcement_profile_merge_spec.rb index 100fe7d..26eeb92 100755 --- a/spec/functions/lookup/04_enforcement_profile_merge_spec.rb +++ b/spec/functions/lookup/04_enforcement_profile_merge_spec.rb @@ -442,7 +442,7 @@ before(:each) do File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| - test_hiera = { 'compliance_engine::enforcement' => %w[profile_test1 profile_test2] }.to_yaml + test_hiera = { 'compliance_engine::enforcement' => ['profile_test1', 'profile_test2'] }.to_yaml fh.puts test_hiera end end @@ -466,7 +466,7 @@ before(:each) do File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| - test_hiera = { 'compliance_engine::enforcement' => %w[profile_test2 profile_test1] }.to_yaml + test_hiera = { 'compliance_engine::enforcement' => ['profile_test2', 'profile_test1'] }.to_yaml fh.puts test_hiera end end diff --git a/spec/functions/lookup/06_enforcement_debug_spec.rb b/spec/functions/lookup/06_enforcement_debug_spec.rb index d615ddf..641342a 100755 --- a/spec/functions/lookup/06_enforcement_debug_spec.rb +++ b/spec/functions/lookup/06_enforcement_debug_spec.rb @@ -123,7 +123,7 @@ it do result = lookup.execute('compliance_engine::debug::compliance_data') expect(result).to be_a(Hash) - expect(result.keys).to eq(%w[version profiles ce checks]) + expect(result.keys).to eq(['version', 'profiles', 'ce', 'checks']) expect(result['profiles']).to include(profile['profiles']) expect(result['ce']).to include(ces['ce']) expect(result['checks']).to include(checks['checks']) From 85a57023df0e11c7d8c21b5318d9579c8f209c01 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 16:41:24 +0000 Subject: [PATCH 14/22] Add EL10 and openvox to metadata.json --- metadata.json | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/metadata.json b/metadata.json index 574f58d..562e362 100644 --- a/metadata.json +++ b/metadata.json @@ -9,44 +9,53 @@ ], "operatingsystem_support": [ { - "operatingsystem": "CentOS", + "operatingsystem": "AlmaLinux", "operatingsystemrelease": [ - "9" + "8", + "9", + "10" ] }, { - "operatingsystem": "OracleLinux", + "operatingsystem": "CentOS", "operatingsystemrelease": [ - "8", - "9" + "9", + "10" ] }, { - "operatingsystem": "RedHat", + "operatingsystem": "OracleLinux", "operatingsystemrelease": [ "8", - "9" + "9", + "10" ] }, { - "operatingsystem": "Rocky", + "operatingsystem": "RedHat", "operatingsystemrelease": [ "8", - "9" + "9", + "10" ] }, { - "operatingsystem": "AlmaLinux", + "operatingsystem": "Rocky", "operatingsystemrelease": [ "8", - "9" + "9", + "10" ] } ], "requirements": [ { "name": "puppet", - "version_requirement": ">= 7.0.0 < 9.0.0" + "version_requirement": ">= 8.0.0 < 9.0.0" + }, + { + "name": "openvox", + "version_requirement": ">= 8.0.0 < 9.0.0" } ] } From 9a91339bbd3cee2049eae02746490ffc4bbfa47c Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 18:12:50 +0000 Subject: [PATCH 15/22] Fixes for parallel test execution --- .gitignore | 4 + spec/data/10_enforce_spec.yaml | 43 ------ spec/data/profile-merging.yaml | 9 -- .../03_enforcement_profile_merge_spec.rb | 21 ++- .../04_enforcement_profile_merge_spec.rb | 21 ++- .../lookup/05_enforcement_override_spec.rb | 21 ++- spec/functions/lookup/10_enforce_spec.rb | 122 +++++++++--------- 7 files changed, 113 insertions(+), 128 deletions(-) delete mode 100644 spec/data/10_enforce_spec.yaml delete mode 100644 spec/data/profile-merging.yaml diff --git a/.gitignore b/.gitignore index 8a66977..1a5b32a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,9 @@ # rspec failure tracking .rspec_status +# Test-generated hiera data files (process-specific, cleaned up automatically) +/spec/data/profile-merging-*.yaml +/spec/data/10_enforce_spec-*.yaml + /Gemfile.lock /vendor/ diff --git a/spec/data/10_enforce_spec.yaml b/spec/data/10_enforce_spec.yaml deleted file mode 100644 index 14e3df1..0000000 --- a/spec/data/10_enforce_spec.yaml +++ /dev/null @@ -1,43 +0,0 @@ ---- -compliance_engine::enforcement: -- disa_stig -- nist_800_53:rev4 -compliance_engine::compliance_map: - version: 2.0.0 - profiles: - disa_stig: - controls: - disa_stig: true - nist_800_53:rev4: - controls: - nist_800_53:rev4: true - controls: - disa_stig: {} - nist_800_53:rev4: {} - checks: - oval:com.puppet.test.disa.useradd_shells: - type: puppet-class-parameter - controls: - disa_stig: true - identifiers: - FOO2: - - FOO2 - BAR2: - - BAR2 - settings: - parameter: useradd::shells - value: - - "/bin/disa" - oval:com.puppet.test.nist.useradd_shells: - type: puppet-class-parameter - controls: - nist_800_53:rev4: true - identifiers: - FOO2: - - FOO2 - BAR2: - - BAR2 - settings: - parameter: useradd::shells - value: - - "/bin/nist" diff --git a/spec/data/profile-merging.yaml b/spec/data/profile-merging.yaml deleted file mode 100644 index 01569e2..0000000 --- a/spec/data/profile-merging.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- -compliance_engine::enforcement: -- profile_test1 -compliance_engine::compliance_map: - version: 2.0.0 - profiles: - profile_test1: - ces: - 05_profile_test2: false diff --git a/spec/functions/lookup/03_enforcement_profile_merge_spec.rb b/spec/functions/lookup/03_enforcement_profile_merge_spec.rb index eefc5cc..925d21b 100755 --- a/spec/functions/lookup/03_enforcement_profile_merge_spec.rb +++ b/spec/functions/lookup/03_enforcement_profile_merge_spec.rb @@ -146,6 +146,7 @@ let(:test_module_path) { File.join(tmpdir, 'test_module_03') } let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } let(:hieradata_dir) { File.expand_path('../../data', __dir__) } + let(:hieradata_file) { "profile-merging-#{Process.pid}" } before(:each) do # Create the directory structure @@ -169,16 +170,20 @@ on_supported_os.each do |os, os_facts| context "on #{os} with compliance_engine::enforcement merging profiles" do - let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } - let(:hieradata) { 'profile-merging' } + let(:facts) { os_facts.merge('custom_hiera' => hieradata_file) } + let(:hieradata) { hieradata_file } before(:each) do - File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + File.open(File.join(hieradata_dir, "#{hieradata_file}.yaml"), 'w') do |fh| test_hiera = { 'compliance_engine::enforcement' => ['profile_test1', 'profile_test2'] }.to_yaml fh.puts test_hiera end end + after(:each) do + FileUtils.rm_f(File.join(hieradata_dir, "#{hieradata_file}.yaml")) + end + # Test a string. it { is_expected.to run.with_params('test_module_03::string_param').and_return('string value 1') } @@ -193,16 +198,20 @@ end context "on #{os} with compliance_engine::enforcement merging profiles in reverse order" do - let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } - let(:hieradata) { 'profile-merging' } + let(:facts) { os_facts.merge('custom_hiera' => hieradata_file) } + let(:hieradata) { hieradata_file } before(:each) do - File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + File.open(File.join(hieradata_dir, "#{hieradata_file}.yaml"), 'w') do |fh| test_hiera = { 'compliance_engine::enforcement' => ['profile_test2', 'profile_test1'] }.to_yaml fh.puts test_hiera end end + after(:each) do + FileUtils.rm_f(File.join(hieradata_dir, "#{hieradata_file}.yaml")) + end + # Test a string. it { is_expected.to run.with_params('test_module_03::string_param').and_return('string value 2') } diff --git a/spec/functions/lookup/04_enforcement_profile_merge_spec.rb b/spec/functions/lookup/04_enforcement_profile_merge_spec.rb index 26eeb92..c5ba2be 100755 --- a/spec/functions/lookup/04_enforcement_profile_merge_spec.rb +++ b/spec/functions/lookup/04_enforcement_profile_merge_spec.rb @@ -414,6 +414,7 @@ let(:test_module_path) { File.join(tmpdir, 'test_module_04') } let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } let(:hieradata_dir) { File.expand_path('../../data', __dir__) } + let(:hieradata_file) { "profile-merging-#{Process.pid}" } before(:each) do # Create the directory structure @@ -437,16 +438,20 @@ on_supported_os.each do |os, os_facts| context "on #{os} with compliance_engine::enforcement merging profiles" do - let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } - let(:hieradata) { 'profile-merging' } + let(:facts) { os_facts.merge('custom_hiera' => hieradata_file) } + let(:hieradata) { hieradata_file } before(:each) do - File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + File.open(File.join(hieradata_dir, "#{hieradata_file}.yaml"), 'w') do |fh| test_hiera = { 'compliance_engine::enforcement' => ['profile_test1', 'profile_test2'] }.to_yaml fh.puts test_hiera end end + after(:each) do + FileUtils.rm_f(File.join(hieradata_dir, "#{hieradata_file}.yaml")) + end + # Test a string. it { is_expected.to run.with_params('test_module_04::string_param').and_return('string value 1') } @@ -461,16 +466,20 @@ end context "on #{os} with compliance_engine::enforcement merging profiles in reverse order" do - let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } - let(:hieradata) { 'profile-merging' } + let(:facts) { os_facts.merge('custom_hiera' => hieradata_file) } + let(:hieradata) { hieradata_file } before(:each) do - File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + File.open(File.join(hieradata_dir, "#{hieradata_file}.yaml"), 'w') do |fh| test_hiera = { 'compliance_engine::enforcement' => ['profile_test2', 'profile_test1'] }.to_yaml fh.puts test_hiera end end + after(:each) do + FileUtils.rm_f(File.join(hieradata_dir, "#{hieradata_file}.yaml")) + end + # Test a string. it { is_expected.to run.with_params('test_module_04::string_param').and_return('string value 2') } diff --git a/spec/functions/lookup/05_enforcement_override_spec.rb b/spec/functions/lookup/05_enforcement_override_spec.rb index 38ff350..adcea52 100755 --- a/spec/functions/lookup/05_enforcement_override_spec.rb +++ b/spec/functions/lookup/05_enforcement_override_spec.rb @@ -69,6 +69,7 @@ let(:test_module_path) { File.join(tmpdir, 'test_module_05') } let(:compliance_dir) { File.join(test_module_path, 'SIMP', 'compliance_profiles') } let(:hieradata_dir) { File.expand_path('../../data', __dir__) } + let(:hieradata_file) { "profile-merging-#{Process.pid}" } before(:each) do # Create the directory structure @@ -92,26 +93,30 @@ on_supported_os.each do |os, os_facts| context "on #{os} with compliance data in modules" do - let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } - let(:hieradata) { 'profile-merging' } + let(:facts) { os_facts.merge('custom_hiera' => hieradata_file) } + let(:hieradata) { hieradata_file } before(:each) do - File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + File.open(File.join(hieradata_dir, "#{hieradata_file}.yaml"), 'w') do |fh| test_hiera = { 'compliance_engine::enforcement' => ['profile_test1'] }.to_yaml fh.puts test_hiera end end + after(:each) do + FileUtils.rm_f(File.join(hieradata_dir, "#{hieradata_file}.yaml")) + end + # Test a simple hash. it { is_expected.to run.with_params('test_module_05::hash_param').and_return({ 'hash key 1' => 'hash value 1', 'hash key 2' => 'hash value 2' }) } end context "on #{os} with compliance_engine::compliance_map override" do - let(:facts) { os_facts.merge('custom_hiera' => 'profile-merging') } - let(:hieradata) { 'profile-merging' } + let(:facts) { os_facts.merge('custom_hiera' => hieradata_file) } + let(:hieradata) { hieradata_file } before(:each) do - File.open(File.join(hieradata_dir, 'profile-merging.yaml'), 'w') do |fh| + File.open(File.join(hieradata_dir, "#{hieradata_file}.yaml"), 'w') do |fh| test_hiera = { 'compliance_engine::enforcement' => ['profile_test1'], 'compliance_engine::compliance_map' => { @@ -129,6 +134,10 @@ end end + after(:each) do + FileUtils.rm_f(File.join(hieradata_dir, "#{hieradata_file}.yaml")) + end + # Test a simple hash. it { is_expected.to run.with_params('test_module_05::hash_param').and_return({ 'hash key 1' => 'hash value 1' }) } end diff --git a/spec/functions/lookup/10_enforce_spec.rb b/spec/functions/lookup/10_enforce_spec.rb index 8a8c2fc..f1f84d7 100644 --- a/spec/functions/lookup/10_enforce_spec.rb +++ b/spec/functions/lookup/10_enforce_spec.rb @@ -2,83 +2,89 @@ require 'spec_helper' require 'spec_helper_puppet' -# require 'fileutils' +require 'fileutils' +require 'tmpdir' -def write_hieradata(policy_order) - data = { - 'compliance_engine::enforcement' => policy_order, - 'compliance_engine::compliance_map' => { - 'version' => '2.0.0', - 'profiles' => { - 'disa_stig' => { - 'controls' => { - 'disa_stig' => true, +RSpec.describe 'lookup' do + let(:tmpdir) { Dir.mktmpdir('compliance_engine_test') } + let(:hieradata_dir) { File.expand_path('../../data', __dir__) } + let(:hieradata_file) { "10_enforce_spec-#{Process.pid}" } + + def write_hieradata(hieradata_dir, hieradata_file, policy_order) + data = { + 'compliance_engine::enforcement' => policy_order, + 'compliance_engine::compliance_map' => { + 'version' => '2.0.0', + 'profiles' => { + 'disa_stig' => { + 'controls' => { + 'disa_stig' => true, + }, }, - }, - 'nist_800_53:rev4' => { - 'controls' => { - 'nist_800_53:rev4' => true, + 'nist_800_53:rev4' => { + 'controls' => { + 'nist_800_53:rev4' => true, + }, }, }, - }, - 'controls' => { - 'disa_stig' => {}, - 'nist_800_53:rev4' => {}, - }, - 'checks' => { - 'oval:com.puppet.test.disa.useradd_shells' => { - 'type' => 'puppet-class-parameter', - 'controls' => { - 'disa_stig' => true, - }, - 'identifiers' => { - 'FOO2' => ['FOO2'], - 'BAR2' => ['BAR2'] - }, - 'settings' => { - 'parameter' => 'useradd::shells', - 'value' => ['/bin/disa'] - } + 'controls' => { + 'disa_stig' => {}, + 'nist_800_53:rev4' => {}, }, - 'oval:com.puppet.test.nist.useradd_shells' => { - 'type' => 'puppet-class-parameter', - 'controls' => { - 'nist_800_53:rev4' => true + 'checks' => { + 'oval:com.puppet.test.disa.useradd_shells' => { + 'type' => 'puppet-class-parameter', + 'controls' => { + 'disa_stig' => true, + }, + 'identifiers' => { + 'FOO2' => ['FOO2'], + 'BAR2' => ['BAR2'] + }, + 'settings' => { + 'parameter' => 'useradd::shells', + 'value' => ['/bin/disa'] + } }, - 'identifiers' => { - 'FOO2' => ['FOO2'], - 'BAR2' => ['BAR2'] - }, - 'settings' => { - 'parameter' => 'useradd::shells', - 'value' => ['/bin/nist'] + 'oval:com.puppet.test.nist.useradd_shells' => { + 'type' => 'puppet-class-parameter', + 'controls' => { + 'nist_800_53:rev4' => true + }, + 'identifiers' => { + 'FOO2' => ['FOO2'], + 'BAR2' => ['BAR2'] + }, + 'settings' => { + 'parameter' => 'useradd::shells', + 'value' => ['/bin/nist'] + } } } } } - } - - # fixtures = File.expand_path('../../fixtures', __dir__) - # FileUtils.mkdir_p(File.join(fixtures, 'hieradata')) - # File.open(File.join(fixtures, 'hieradata', '10_enforce_spec.yaml'), 'w') do |fh| - File.open(File.join(File.expand_path('../..', __dir__), 'data', '10_enforce_spec.yaml'), 'w') do |fh| - fh.puts data.to_yaml + File.open(File.join(hieradata_dir, "#{hieradata_file}.yaml"), 'w') do |fh| + fh.puts data.to_yaml + end end -end -RSpec.describe 'lookup' do + after(:each) do + # Clean up temporary directory and hieradata file + FileUtils.rm_rf(tmpdir) + FileUtils.rm_f(File.join(hieradata_dir, "#{hieradata_file}.yaml")) + end on_supported_os.each do |os, os_facts| context "on #{os}" do - let(:facts) { os_facts.merge('custom_hiera' => '10_enforce_spec') } + let(:facts) { os_facts.merge('custom_hiera' => hieradata_file) } context 'with a single compliance map' do let(:lookup) { subject } - let(:hieradata) { '10_enforce_spec' } + let(:hieradata) { hieradata_file } let(:policy_order) { ['disa_stig'] } before(:each) do - write_hieradata(policy_order) + write_hieradata(hieradata_dir, hieradata_file, policy_order) end it 'returns /bin/disa' do @@ -101,11 +107,11 @@ def write_hieradata(policy_order) context 'when disa is higher priority' do let(:lookup) { subject } - let(:hieradata) { '10_enforce_spec' } + let(:hieradata) { hieradata_file } let(:policy_order) { ['disa_stig', 'nist_800_53:rev4'] } before(:each) do - write_hieradata(policy_order) + write_hieradata(hieradata_dir, hieradata_file, policy_order) end it 'returns /bin/disa and /bin/nist' do From 2ee81c482a534da299fbe90e3640d978e16d748d Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 18:14:49 +0000 Subject: [PATCH 16/22] Use parallel_spec for tests --- .github/workflows/pr_tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index e815bad..4090ed5 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -44,5 +44,5 @@ jobs: if [ "${{ matrix.ruby }}" = "4.0" ]; then bundle exec rspec spec/classes else - bundle exec rake spec + bundle exec rake parallel_spec fi From 3293880a770958dff01caacf06e281e483297eaa Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 18:17:32 +0000 Subject: [PATCH 17/22] Cleanup for rubocop --- spec/functions/lookup/10_enforce_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/functions/lookup/10_enforce_spec.rb b/spec/functions/lookup/10_enforce_spec.rb index f1f84d7..81d88ce 100644 --- a/spec/functions/lookup/10_enforce_spec.rb +++ b/spec/functions/lookup/10_enforce_spec.rb @@ -74,6 +74,7 @@ def write_hieradata(hieradata_dir, hieradata_file, policy_order) FileUtils.rm_rf(tmpdir) FileUtils.rm_f(File.join(hieradata_dir, "#{hieradata_file}.yaml")) end + on_supported_os.each do |os, os_facts| context "on #{os}" do let(:facts) { os_facts.merge('custom_hiera' => hieradata_file) } From 82228965a91a7cc8347583068b00d39b0c7df019 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 18:29:12 +0000 Subject: [PATCH 18/22] Add REFERENCE.md --- REFERENCE.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 REFERENCE.md diff --git a/REFERENCE.md b/REFERENCE.md new file mode 100644 index 0000000..27c3aaa --- /dev/null +++ b/REFERENCE.md @@ -0,0 +1,42 @@ +# Reference + + + +## Table of Contents + +### Functions + +* [`compliance_engine::enforcement`](#compliance_engine--enforcement): Hiera entry point for Compliance Engine + +## Functions + +### `compliance_engine::enforcement` + +Type: Ruby 4.x API + +Hiera entry point for Compliance Engine + +#### `compliance_engine::enforcement(String[1] $key, Hash[String[1], Any] $options, Puppet::LookupContext $context)` + +The compliance_engine::enforcement function. + +Returns: `String` The value of the key in the Hiera data + +##### `key` + +Data type: `String[1]` + +String The key to lookup in the Hiera data + +##### `options` + +Data type: `Hash[String[1], Any]` + + + +##### `context` + +Data type: `Puppet::LookupContext` + + + From 91563145422d20c125464d9e3c3e96844139b3a0 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 18:35:23 +0000 Subject: [PATCH 19/22] Update README.md --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index b1931f1..f79dcb7 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,34 @@ Options: See the [`ComplianceEngine::Data`](https://rubydoc.info/gems/compliance_engine/ComplianceEngine/Data) class for details. +## Using as a Puppet Module + +The Compliance Engine can be used as a Puppet module to provide a Hiera backend for compliance data. This allows you to enforce compliance profiles through Hiera lookups within your Puppet manifests. + +### Hiera Backend + +To use the Compliance Engine Hiera backend, configure it in your `hiera.yaml`: + +```yaml +--- +version: 5 +hierarchy: + - name: "Compliance Engine" + lookup_key: compliance_engine::enforcement +``` + +Specify the profile used by setting the `compliance_engine::enforcement` key in your Hiera data. + +```yaml +--- +compliance_engine::enforcement: + - your_profile +``` + +The `compliance_engine::enforcement` function serves as the Hiera entry point and allows you to look up compliance data based on configured profiles. + +For detailed information about available functions, parameters, and configuration options, see [REFERENCE.md](REFERENCE.md). + ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/simp/rubygem-simp-compliance_engine. From f8440ff62924404d178ae55b3efe3bea374481bb Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Tue, 13 Jan 2026 18:45:00 +0000 Subject: [PATCH 20/22] Re-add the option to enable bash-git-prompt --- .devcontainer/Dockerfile | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9308de8..dbfbc8e 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -40,14 +40,16 @@ RUN useradd -ms /bin/bash vscode \ USER vscode # Set up bash-git-prompt -# RUN git clone https://github.com/magicmonty/bash-git-prompt.git /home/vscode/.bash-git-prompt --depth 1 \ -# && cat >> /home/vscode/.bashrc <> /home/vscode/.bashrc < Date: Wed, 14 Jan 2026 16:06:01 +0000 Subject: [PATCH 21/22] Refactor enforcement_tolerance handling --- lib/compliance_engine/component.rb | 49 +++++++++++-------- .../compliance_engine/enforcement.rb | 2 + 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/lib/compliance_engine/component.rb b/lib/compliance_engine/component.rb index 333b02c..126fa67 100644 --- a/lib/compliance_engine/component.rb +++ b/lib/compliance_engine/component.rb @@ -182,6 +182,34 @@ def confine_away?(fragment) false end + # Check if a fragment is has remediation risk too high or if remediation is disabled + # + # @param fragment [Hash] The fragment to check + # @return [TrueClass, FalseClass] true if the fragment should be dropped + def risk_too_high?(fragment) + return false unless is_a?(ComplianceEngine::Check) + return false unless fragment.key?('remediation') + return false unless enforcement_tolerance.is_a?(Integer) && enforcement_tolerance.positive? + + if fragment['remediation'].key?('disabled') + message = "Remediation disabled for #{fragment}" + reason = fragment['remediation']['disabled']&.map { |value| value['reason'] }&.reject(&:nil?)&.join("\n") + message += "\n#{reason}" unless reason.nil? + ComplianceEngine.log.info message + return true + end + + if fragment['remediation'].key?('risk') + risk_level = fragment['remediation']['risk']&.map { |value| value['level'] }&.select { |value| value.is_a?(Integer) }&.max + if risk_level.is_a?(Integer) && risk_level >= enforcement_tolerance + ComplianceEngine.log.info "Remediation risk #{risk_level} exceeds enforcement enforcement_tolerance #{enforcement_tolerance} for #{fragment}" + return true + end + end + + false + end + # Returns the fragments of the component after confinement # # @return [Hash] the fragments of the component @@ -205,26 +233,7 @@ def fragments end next if confine_away?(fragment) - - # Confinement based on remediation risk - tolerance = enforcement_tolerance.is_a?(String) ? enforcement_tolerance.to_i : enforcement_tolerance - if tolerance.is_a?(Integer) && tolerance.positive? && is_a?(ComplianceEngine::Check) && fragment.key?('remediation') - if fragment['remediation'].key?('disabled') - message = "Remediation disabled for #{fragment}" - reason = fragment['remediation']['disabled']&.map { |value| value['reason'] }&.reject(&:nil?)&.join("\n") - message += "\n#{reason}" unless reason.nil? - ComplianceEngine.log.info message - next - end - - if fragment['remediation'].key?('risk') - risk_level = fragment['remediation']['risk']&.map { |value| value['level'] }&.select { |value| value.is_a?(Integer) }&.max - if risk_level.is_a?(Integer) && risk_level >= tolerance - ComplianceEngine.log.info "Remediation risk #{risk_level} exceeds enforcement tolerance #{tolerance} for #{fragment}" - next - end - end - end + next if risk_too_high?(fragment) @fragments[filename] = fragment end diff --git a/lib/puppet/functions/compliance_engine/enforcement.rb b/lib/puppet/functions/compliance_engine/enforcement.rb index d808dc5..5de903a 100644 --- a/lib/puppet/functions/compliance_engine/enforcement.rb +++ b/lib/puppet/functions/compliance_engine/enforcement.rb @@ -83,6 +83,8 @@ def enforcement_tolerance # For backwards compatibility with compliance_markup. tolerance = call_function('lookup', 'compliance_markup::enforcement_tolerance_level', { 'default_value' => nil }) if @compat && tolerance.nil? + tolerance = tolerance.to_i if tolerance.is_a?(String) + tolerance end end From bfe16459aee7b96297f8e6e5bf8df493ad6feb06 Mon Sep 17 00:00:00 2001 From: Steven Pritchard Date: Wed, 14 Jan 2026 16:28:44 +0000 Subject: [PATCH 22/22] Support compliance_engine::enforcement as a String This fixes backwards-compatibility with a compliance_markup edge case. Also fix running `lookup()` tests directly. --- .gitignore | 1 + Rakefile | 6 ++++++ lib/puppet/functions/compliance_engine/enforcement.rb | 4 ++-- spec/functions/lookup/10_enforce_spec.rb | 1 - 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 1a5b32a..f58214b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ /Gemfile.lock /vendor/ +/spec/fixtures/modules/ diff --git a/Rakefile b/Rakefile index 54216f5..a8c69e8 100644 --- a/Rakefile +++ b/Rakefile @@ -11,6 +11,12 @@ RSpec::Core::RakeTask.new('spec:standalone') do |t| t.pattern = 'spec/{classes,functions}/**/*_spec.rb' end +desc 'Prepare fixtures for testing' +task spec_prep: :'fixtures:prep' + +desc 'Clean up fixtures after testing' +task spec_clean: :'fixtures:clean' + desc 'Run spec tests and clean the fixtures directory if successful' task spec: :'fixtures:prep' do |_t, args| Rake::Task['spec:standalone'].invoke(*args.extras) diff --git a/lib/puppet/functions/compliance_engine/enforcement.rb b/lib/puppet/functions/compliance_engine/enforcement.rb index 5de903a..505cf72 100644 --- a/lib/puppet/functions/compliance_engine/enforcement.rb +++ b/lib/puppet/functions/compliance_engine/enforcement.rb @@ -60,10 +60,10 @@ def enforcement(key, options, context) end def profiles - profile_list = call_function('lookup', 'compliance_engine::enforcement', { 'default_value' => [] }) + profile_list = Array(call_function('lookup', 'compliance_engine::enforcement', { 'default_value' => [] })) # For backwards compatibility with compliance_markup. - profile_list += call_function('lookup', 'compliance_markup::enforcement', { 'default_value' => [] }) if @compat + profile_list += Array(call_function('lookup', 'compliance_markup::enforcement', { 'default_value' => [] })) if @compat profile_list.uniq end diff --git a/spec/functions/lookup/10_enforce_spec.rb b/spec/functions/lookup/10_enforce_spec.rb index 81d88ce..dab2124 100644 --- a/spec/functions/lookup/10_enforce_spec.rb +++ b/spec/functions/lookup/10_enforce_spec.rb @@ -98,7 +98,6 @@ def write_hieradata(hieradata_dir, hieradata_file, policy_order) let(:policy_order) { 'disa_stig' } it 'returns /bin/disa' do - skip('String value for compliance_engine::enforcement not supported, must be Array') result = lookup.execute('useradd::shells') expect(result).to be_instance_of(Array) expect(result).to include('/bin/disa')