summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2021-10-27 13:08:34 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2021-11-16 11:30:30 -0800
commitaa4fc49bbce33b9a5f17faae24b74d36a427b483 (patch)
tree13a7a786d4f344ca1ac781b282e7fc9ceff6637b
parenta90cddca0af69895415cb34051b15966a9b28277 (diff)
downloadchef-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.rb12
-rw-r--r--lib/chef/compliance/runner.rb48
-rw-r--r--spec/unit/compliance/runner_spec.rb60
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