diff options
author | Lamont Granquist <lamont@scriptkiddie.org> | 2021-10-27 13:08:34 -0700 |
---|---|---|
committer | Lamont Granquist <lamont@scriptkiddie.org> | 2021-11-16 11:30:30 -0800 |
commit | aa4fc49bbce33b9a5f17faae24b74d36a427b483 (patch) | |
tree | 13a7a786d4f344ca1ac781b282e7fc9ceff6637b | |
parent | a90cddca0af69895415cb34051b15966a9b28277 (diff) | |
download | chef-aa4fc49bbce33b9a5f17faae24b74d36a427b483.tar.gz |
Implement compliance phase interval runs
Copies the API from the audit cookbook including the same attributes
Signed-off-by: Lamont Granquist <lamont@scriptkiddie.org>
-rw-r--r-- | lib/chef/compliance/default_attributes.rb | 12 | ||||
-rw-r--r-- | lib/chef/compliance/runner.rb | 48 | ||||
-rw-r--r-- | spec/unit/compliance/runner_spec.rb | 60 |
3 files changed, 116 insertions, 4 deletions
diff --git a/lib/chef/compliance/default_attributes.rb b/lib/chef/compliance/default_attributes.rb index 3ecb1cd056..fc30716205 100644 --- a/lib/chef/compliance/default_attributes.rb +++ b/lib/chef/compliance/default_attributes.rb @@ -94,7 +94,17 @@ class Chef # Should the built-in compliance phase run. True and false force the behavior. Nil does magic based on if you have # profiles defined but do not have the audit cookbook enabled. - "compliance_phase" => false + "compliance_phase" => false, + + "interval" => { + # control how often inspec scans are run, if not on every node converge + # notes: false value will result in running inspec scan every converge + "enabled" => false, + + # controls how often inspec scans are run (in minutes) + # notes: only used if interval is enabled above + "time" => 1440, + } ) end end diff --git a/lib/chef/compliance/runner.rb b/lib/chef/compliance/runner.rb index ade35d4861..8aa4fe6140 100644 --- a/lib/chef/compliance/runner.rb +++ b/lib/chef/compliance/runner.rb @@ -71,7 +71,7 @@ class Chef logger.debug("#{self.class}##{__method__}: enabling Compliance Phase") - report + report_with_interval end def run_failed(_exception, _run_status) @@ -82,7 +82,7 @@ class Chef logger.debug("#{self.class}##{__method__}: enabling Compliance Phase") - report + report_with_interval end ### Below code adapted from audit cookbook's files/default/handler/audit_report.rb @@ -92,7 +92,6 @@ class Chef fail_if_not_present inspec_gem_source inspec_version - interval owner raise_if_unreachable }.freeze @@ -106,6 +105,18 @@ class Chef end end + def report_with_interval + interval = node["audit"]["interval"] + interval_enabled = node["audit"]["interval"]["enabled"] + interval_time = node["audit"]["interval"]["time"] + if check_interval_settings(interval, interval_enabled, interval_time) + create_timestamp_file if interval_enabled + report + else + logger.info "Skipping Chef Infra Compliance Phase due to interval settings" + end + end + def report(report = nil) logger.info "Starting Chef Infra Compliance Phase" report ||= generate_report @@ -362,6 +373,37 @@ class Chef def requested_reporters (Array(node["audit"]["reporter"]) + ["cli"]).uniq end + + def create_timestamp_file + timestamp = Time.now.utc + timestamp_file = File.new(report_timing_file, "w") + timestamp_file.puts(timestamp) + timestamp_file.close + end + + def report_timing_file + # Will create and return the complete folder path for the chef cache location and the passed in value + ::File.join(Chef::FileCache.create_cache_path("compliance"), "report_timing.json") + end + + def profile_overdue_to_run?(interval_seconds) + # Calculate when a report was last created so we delay the next report if necessary + return true unless ::File.exist?(report_timing_file) + + seconds_since_last_run = Time.now - ::File.mtime(report_timing_file) + seconds_since_last_run > interval_seconds + end + + def check_interval_settings(interval, interval_enabled, interval_time) + # handle intervals + interval_seconds = 0 # always run this by default, unless interval is defined + if !interval.nil? && interval_enabled + interval_seconds = interval_time * 60 # seconds in interval + Chef::Log.debug "Running Chef Infra Compliance Phase every #{interval_seconds} seconds" + end + # returns true if profile is overdue to run + profile_overdue_to_run?(interval_seconds) + end end end end diff --git a/spec/unit/compliance/runner_spec.rb b/spec/unit/compliance/runner_spec.rb index 602d675d4d..e8a08abfc1 100644 --- a/spec/unit/compliance/runner_spec.rb +++ b/spec/unit/compliance/runner_spec.rb @@ -1,4 +1,5 @@ require "spec_helper" +require "tmpdir" describe Chef::Compliance::Runner do let(:logger) { double(:logger).as_null_object } @@ -283,4 +284,63 @@ describe Chef::Compliance::Runner do expect(inputs["chef_node"]["chef_environment"]).to eq("_default") end end + + describe "interval running" do + let(:tempdir) { Dir.mktmpdir("chef-compliance-tests") } + + before do + allow(runner).to receive(:report_timing_file).and_return("#{tempdir}/report_timing.json") + end + + it "is disabled by default" do + expect(runner.node["audit"]["interval"]["enabled"]).to be false + end + + it "defaults to 24 hours / 1440 minutes" do + expect(runner.node["audit"]["interval"]["time"]).to be 1440 + end + + it "runs when the timing file does not exist" do + expect(runner).to receive(:report) + runner.report_with_interval + end + + it "runs when the timing file does not exist and intervals are enabled" do + node.normal["audit"]["interval"]["enabled"] = true + expect(runner).to receive(:report) + runner.report_with_interval + end + + it "runs when the timing file exists and has a recent timestamp" do + FileUtils.touch runner.report_timing_file + expect(runner).to receive(:report) + runner.report_with_interval + end + + it "does not runs when the timing file exists and has a recent timestamp and intervals are enabled" do + node.normal["audit"]["interval"]["enabled"] = true + FileUtils.touch runner.report_timing_file + expect(runner).not_to receive(:report) + runner.report_with_interval + end + + it "does not runs when the timing file exists and has a recent timestamp and intervals are enabled" do + node.normal["audit"]["interval"]["enabled"] = true + FileUtils.touch runner.report_timing_file + ten_minutes_ago = Time.now - 600 + File.utime ten_minutes_ago, ten_minutes_ago, runner.report_timing_file + expect(runner).not_to receive(:report) + runner.report_with_interval + end + + it "runs when the timing file exists and has a recent timestamp and intervals are enabled and the time is short" do + node.normal["audit"]["interval"]["enabled"] = true + node.normal["audit"]["interval"]["time"] = 9 + FileUtils.touch runner.report_timing_file + ten_minutes_ago = Time.now - 600 + File.utime ten_minutes_ago, ten_minutes_ago, runner.report_timing_file + expect(runner).to receive(:report) + runner.report_with_interval + end + end end |