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
node[R]
run_context[R]
run_id[RW]
Public Instance Methods
chef_server_automate_url()
click to toggle source
# File lib/chef/compliance/runner.rb, line 322 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
converge_start(run_context)
click to toggle 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
cookbook_compilation_start(run_context)
click to toggle source
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
#)
# File lib/chef/compliance/runner.rb, line 52 def cookbook_compilation_start(run_context) @run_context = run_context end
create_timestamp_file()
click to toggle source
# File lib/chef/compliance/runner.rb, line 384 def create_timestamp_file FileUtils.touch report_timing_file end
enabled?()
click to toggle 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
failed_report(err)
click to toggle source
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`
# File lib/chef/compliance/runner.rb, line 241 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
generate_report(opts: inspec_opts, profiles: inspec_profiles)
click to toggle 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) # Switch from local to remote backend for Target Mode if ChefConfig::Config.target_mode? logger.info "Configure InSpec backend to use established connection" connection = Chef.run_context.transport_connection backend = Inspec::Backend.new(connection) runner.set_backend(backend) end 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
inputs_from_attributes()
click to toggle 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
inputs_from_collection()
click to toggle source
# File lib/chef/compliance/runner.rb, line 144 def inputs_from_collection safe_input_collection&.inspec_data || {} end
inspec_opts()
click to toggle 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
inspec_profiles()
click to toggle 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
interval_enabled()
click to toggle source
# File lib/chef/compliance/runner.rb, line 396 def interval_enabled @interval_enabled ||= node.read("audit", "interval", "enabled") end
interval_seconds()
click to toggle source
# File lib/chef/compliance/runner.rb, line 400 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
interval_seconds_left()
click to toggle source
# File lib/chef/compliance/runner.rb, line 411 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
interval_time()
click to toggle source
# File lib/chef/compliance/runner.rb, line 392 def interval_time @interval_time ||= node.read("audit", "interval", "time") end
load_and_validate!()
click to toggle source
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.
# File lib/chef/compliance/runner.rb, line 340 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_fetchers!()
click to toggle 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
node=(node)
click to toggle 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
node_info()
click to toggle source
extracts relevant node data
# File lib/chef/compliance/runner.rb, line 260 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
node_load_completed(node, _expanded_run_list, _config)
click to toggle source
# File lib/chef/compliance/runner.rb, line 44 def node_load_completed(node, _expanded_run_list, _config) self.node = node end
report(report = nil)
click to toggle 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
report_timing_file()
click to toggle source
# File lib/chef/compliance/runner.rb, line 388 def report_timing_file ::File.join(Chef::FileCache.create_cache_path("compliance"), "report_timing.json") end
report_with_interval()
click to toggle 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
reporter(reporter_type)
click to toggle source
# File lib/chef/compliance/runner.rb, line 284 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
requested_reporters()
click to toggle source
# File lib/chef/compliance/runner.rb, line 380 def requested_reporters (Array(node["audit"]["reporter"]) + ["cli"]).uniq end
run_completed(_node, _run_status)
click to toggle 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
run_failed(_exception, _run_status)
click to toggle 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
run_started(run_status)
click to toggle source
# File lib/chef/compliance/runner.rb, line 56 def run_started(run_status) self.run_id = run_status.run_id end
safe_input_collection()
click to toggle source
# File lib/chef/compliance/runner.rb, line 376 def safe_input_collection run_context&.input_collection end
safe_profile_collection()
click to toggle source
# File lib/chef/compliance/runner.rb, line 368 def safe_profile_collection run_context&.profile_collection end
safe_waiver_collection()
click to toggle source
# File lib/chef/compliance/runner.rb, line 372 def safe_waiver_collection run_context&.waiver_collection end
waiver_files()
click to toggle source
# File lib/chef/compliance/runner.rb, line 175 def waiver_files Array(node["audit"]["waiver_file"]) end
waivers_from_collection()
click to toggle source
# File lib/chef/compliance/runner.rb, line 148 def waivers_from_collection safe_waiver_collection&.inspec_data || {} end
warn_for_deprecated_config_values!()
click to toggle 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