class Chef::Compliance::Runner
Constants
- DEPRECATED_CONFIG_VALUES
-
Below code adapted from audit cookbook’s files/default/handler/audit_report.rb
- SUPPORTED_FETCHERS
- SUPPORTED_REPORTERS
Attributes
Public Instance Methods
Source
# File lib/chef/compliance/runner.rb, line 312 def chef_server_automate_url url = if node["audit"]["server"] URI(node["audit"]["server"]) else URI(Chef::Config[:chef_server_url]).tap do |u| u.path = "" end end org = Chef::Config[:chef_server_url].split("/").last url.path = File.join(url.path, "organizations/#{org}/data-collector") url end
Source
# File lib/chef/compliance/runner.rb, line 60 def converge_start(run_context) # With all attributes - including cookbook - loaded, we now have enough data to validate # configuration. Because the converge is best coupled with the associated compliance run, these validations # will raise (and abort the converge) if the compliance phase configuration is incorrect/will # prevent compliance phase from completing and submitting its report to all configured reporters. # can abort the converge if the compliance phase configuration (node attributes and client config) load_and_validate! end
Source
# File lib/chef/compliance/runner.rb, line 52 def cookbook_compilation_start(run_context) @run_context = run_context end
This hook gives us the run_context
immediately after it is created so that we can wire up this object to it.
(see EventDispatch::Base
#)
Source
# File lib/chef/compliance/runner.rb, line 379 def create_timestamp_file FileUtils.touch report_timing_file end
Source
# File lib/chef/compliance/runner.rb, line 19 def enabled? return false if @node.nil? # Did we parse the libraries file from the audit cookbook? This class dates back to when Chef Automate was # renamed from Chef Visibility in 2017, so should capture all modern versions of the audit cookbook. audit_cookbook_present = defined?(::Reporter::ChefAutomate) logger.debug("#{self.class}##{__method__}: #{Inspec::Dist::PRODUCT_NAME} profiles? #{inspec_profiles.any?}") logger.debug("#{self.class}##{__method__}: audit cookbook? #{audit_cookbook_present}") logger.debug("#{self.class}##{__method__}: compliance phase attr? #{node["audit"]["compliance_phase"]}") if safe_profile_collection&.using_profiles? true elsif node["audit"]["compliance_phase"].nil? inspec_profiles.any? && !audit_cookbook_present else node["audit"]["compliance_phase"] end end
Source
# File lib/chef/compliance/runner.rb, line 231 def failed_report(err) logger.error "#{Inspec::Dist::PRODUCT_NAME} has raised a runtime exception. Generating a minimal failed report." logger.error err { "platform": { "name": "unknown", "release": "unknown", }, "profiles": [], "statistics": { "duration": 0.0000001, }, "version": Inspec::VERSION, "status": "failed", "status_message": err, } end
In case InSpec raises a runtime exception without providing a valid report, we make one up and add two new fields to it: ‘status` and `status_message`
Source
# File lib/chef/compliance/runner.rb, line 205 def generate_report(opts: inspec_opts, profiles: inspec_profiles) load_fetchers! logger.debug "Options are set to: #{opts}" runner = ::Inspec::Runner.new(opts) if profiles.empty? failed_report("No #{Inspec::Dist::PRODUCT_NAME} profiles are defined.") return end profiles.each { |target| runner.add_target(target) } logger.info "Running profiles from: #{profiles.inspect}" runner.run runner.report.tap do |r| logger.debug "Compliance Phase report #{r}" end rescue Inspec::FetcherFailure => e failed_report("Cannot fetch all profiles: #{profiles}. Please make sure you're authenticated and the server is reachable. #{e.message}") rescue => e failed_report(e.message) end
Source
# File lib/chef/compliance/runner.rb, line 136 def inputs_from_attributes if !node["audit"]["inputs"].empty? node["audit"]["inputs"].to_h else node["audit"]["attributes"].to_h end end
Source
# File lib/chef/compliance/runner.rb, line 144 def inputs_from_collection safe_input_collection&.inspec_data || {} end
Source
# File lib/chef/compliance/runner.rb, line 152 def inspec_opts inputs = inputs_from_attributes.merge(inputs_from_collection).merge(waivers_from_collection) if node["audit"]["chef_node_attribute_enabled"] inputs["chef_node"] = node.to_h inputs["chef_node"]["chef_environment"] = node.chef_environment end { backend_cache: node["audit"]["inspec_backend_cache"], inputs: inputs, logger: logger, # output: STDOUT, output: node["audit"]["quiet"] ? ::File::NULL : STDOUT, report: true, reporter: ["json-automate"], # reporter: ["cli"], reporter_backtrace_inclusion: node["audit"]["result_include_backtrace"], reporter_message_truncation: node["audit"]["result_message_limit"], waiver_file: waiver_files, } end
Source
# File lib/chef/compliance/runner.rb, line 179 def inspec_profiles profiles = node["audit"]["profiles"] unless profiles.respond_to?(:map) && profiles.all? { |_, p| p.respond_to?(:transform_keys) && p.respond_to?(:update) } raise "CMPL010: #{Inspec::Dist::PRODUCT_NAME} profiles specified in an unrecognized format, expected a hash of hashes." end from_attributes = profiles.map do |name, profile| profile.transform_keys(&:to_sym).update(name: name) end || [] from_cookbooks = safe_profile_collection&.inspec_data || [] from_attributes + from_cookbooks end
Source
# File lib/chef/compliance/runner.rb, line 391 def interval_enabled @interval_enabled ||= node.read("audit", "interval", "enabled") end
Source
# File lib/chef/compliance/runner.rb, line 395 def interval_seconds @interval_seconds ||= if interval_enabled logger.debug "Running Chef Infra Compliance Phase every #{interval_time} minutes" interval_time * 60 else logger.debug "Running Chef Infra Compliance Phase on every run" 0 end end
Source
# File lib/chef/compliance/runner.rb, line 406 def interval_seconds_left return 0 unless ::File.exist?(report_timing_file) seconds_since_last_run = Time.now - ::File.mtime(report_timing_file) interval_seconds - seconds_since_last_run end
Source
# File lib/chef/compliance/runner.rb, line 387 def interval_time @interval_time ||= node.read("audit", "interval", "time") end
Source
# File lib/chef/compliance/runner.rb, line 330 def load_and_validate! return unless enabled? @reporters = {} # Note that the docs don't say you can use an array, but our implementation # supports it. requested_reporters.each do |type| unless SUPPORTED_REPORTERS.include? type raise "CMPL003: '#{type}' found in node['audit']['reporter'] is not a supported reporter for Compliance Phase. Supported reporters are: #{SUPPORTED_REPORTERS.join(", ")}. For more information, see the documentation at https://docs.chef.io/chef_compliance_phase#reporters" end @reporters[type] = reporter(type) @reporters[type].validate_config! end unless (fetcher = node["audit"]["fetcher"]).nil? unless SUPPORTED_FETCHERS.include? fetcher raise "CMPL002: Unrecognized Compliance Phase fetcher (node['audit']['fetcher'] = #{fetcher}). Supported fetchers are: #{SUPPORTED_FETCHERS.join(", ")}, or nil. For more information, see the documentation at https://docs.chef.io/chef_compliance_phase#fetch-profiles" end end if !node["audit"]["attributes"].empty? && !node["audit"]["inputs"].empty? raise "CMPL011: both node['audit']['inputs'] and node['audit']['attributes'] are set. The node['audit']['attributes'] setting is deprecated and should not be used." end @validation_passed = true end
Load the resources required for this runner, and validate configuration is correct to proceed. Requires node state to be loaded. Will raise exception if fetcher is not valid, if a reporter is not valid, or the configuration required by a reporter is not provided.
Source
# File lib/chef/compliance/runner.rb, line 194 def load_fetchers! case node["audit"]["fetcher"] when "chef-automate" require_relative "fetcher/automate" when "chef-server" require_relative "fetcher/chef_server" when nil # intentionally blank end end
Source
# File lib/chef/compliance/runner.rb, line 39 def node=(node) @node = node node.default["audit"] = Chef::Compliance::DEFAULT_ATTRIBUTES.merge(node.default["audit"]) end
Source
# File lib/chef/compliance/runner.rb, line 250 def node_info chef_server_uri = URI(Chef::Config[:chef_server_url]) runlist_roles = node.run_list.select { |item| item.type == :role }.map(&:name) runlist_recipes = node.run_list.select { |item| item.type == :recipe }.map(&:name) { node: node.name, os: { release: node["platform_version"], family: node["platform"], }, environment: node.environment, roles: runlist_roles, recipes: runlist_recipes, policy_name: node.policy_name || "", policy_group: node.policy_group || "", chef_tags: node.tags, organization_name: chef_server_uri.path.split("/").last || "", source_fqdn: chef_server_uri.host || "", ipaddress: node["ipaddress"], fqdn: node["fqdn"], } end
extracts relevant node data
Source
# File lib/chef/compliance/runner.rb, line 44 def node_load_completed(node, _expanded_run_list, _config) self.node = node end
Source
# File lib/chef/compliance/runner.rb, line 117 def report(report = nil) logger.info "Starting Chef Infra Compliance Phase" report ||= generate_report # This is invoked at report-time instead of with the normal validations at node loaded, # because we want to ensure that it is visible in the output - and not lost in back-scroll. warn_for_deprecated_config_values! if report.empty? logger.error "Compliance report was not generated properly, skipped reporting" return end requested_reporters.each do |reporter_type| logger.info "Reporting to #{reporter_type}" @reporters[reporter_type].send_report(report) end logger.info "Chef Infra Compliance Phase Complete" end
Source
# File lib/chef/compliance/runner.rb, line 383 def report_timing_file ::File.join(Chef::FileCache.create_cache_path("compliance"), "report_timing.json") end
Source
# File lib/chef/compliance/runner.rb, line 108 def report_with_interval if interval_seconds_left <= 0 create_timestamp_file if interval_enabled report else logger.info "Skipping Chef Infra Compliance Phase due to interval settings (next run in #{interval_seconds_left / 60.0} mins)" end end
Source
# File lib/chef/compliance/runner.rb, line 274 def reporter(reporter_type) case reporter_type when "chef-automate" require_relative "reporter/automate" opts = { control_results_limit: node["audit"]["control_results_limit"], entity_uuid: node["chef_guid"], insecure: node["audit"]["insecure"], node_info: node_info, run_id: run_id, run_time_limit: node["audit"]["run_time_limit"], } Chef::Compliance::Reporter::Automate.new(opts) when "chef-server-automate" require_relative "reporter/chef_server_automate" opts = { control_results_limit: node["audit"]["control_results_limit"], entity_uuid: node["chef_guid"], insecure: node["audit"]["insecure"], node_info: node_info, run_id: run_id, run_time_limit: node["audit"]["run_time_limit"], url: chef_server_automate_url, } Chef::Compliance::Reporter::ChefServerAutomate.new(opts) when "json-file" require_relative "reporter/json_file" path = node.dig("audit", "json_file", "location") Chef::Compliance::Reporter::JsonFile.new(file: path) when "audit-enforcer", "compliance-enforcer" require_relative "reporter/compliance_enforcer" Chef::Compliance::Reporter::ComplianceEnforcer.new when "cli" require_relative "reporter/cli" Chef::Compliance::Reporter::Cli.new end end
Source
# File lib/chef/compliance/runner.rb, line 370 def requested_reporters if node["audit"]["quiet"] logger.info "node[\"audit\"][\"quiet\"] is set to true, skipping cli reporter" Array(node["audit"]["reporter"]).uniq - ["cli"] else (Array(node["audit"]["reporter"]) + ["cli"]).uniq end end
Source
# File lib/chef/compliance/runner.rb, line 69 def run_completed(_node, _run_status) return unless enabled? logger.debug("#{self.class}##{__method__}: enabling Compliance Phase") report_with_interval end
Source
# File lib/chef/compliance/runner.rb, line 77 def run_failed(_exception, _run_status) # If the run has failed because our own validation of compliance # phase configuration has failed, we don't want to submit a report # because we're still not configured correctly. return unless enabled? && @validation_passed logger.debug("#{self.class}##{__method__}: enabling Compliance Phase") report_with_interval end
Source
# File lib/chef/compliance/runner.rb, line 56 def run_started(run_status) self.run_id = run_status.run_id end
Source
# File lib/chef/compliance/runner.rb, line 366 def safe_input_collection run_context&.input_collection end
Source
# File lib/chef/compliance/runner.rb, line 358 def safe_profile_collection run_context&.profile_collection end
Source
# File lib/chef/compliance/runner.rb, line 362 def safe_waiver_collection run_context&.waiver_collection end
Source
# File lib/chef/compliance/runner.rb, line 175 def waiver_files Array(node["audit"]["waiver_file"]) end
Source
# File lib/chef/compliance/runner.rb, line 148 def waivers_from_collection safe_waiver_collection&.inspec_data || {} end
Source
# File lib/chef/compliance/runner.rb, line 99 def warn_for_deprecated_config_values! deprecated_config_values = (node["audit"].keys & DEPRECATED_CONFIG_VALUES) if deprecated_config_values.any? values = deprecated_config_values.sort.map { |v| "'#{v}'" }.join(", ") logger.warn "audit cookbook config values #{values} are not supported in #{ChefUtils::Dist::Infra::PRODUCT}'s Compliance Phase." end end