From 9fe38def3ae16681e99f76a4f6a274da0abe1d5f Mon Sep 17 00:00:00 2001 From: Ondrej Prazak Date: Wed, 27 Jan 2021 11:39:19 +0100 Subject: [PATCH] Fixes #31685 - Add treshold for ds policies --- .../controller/parameters/policy_api.rb | 2 +- .../compliance_status_scoped_search.rb | 14 ++-------- app/models/foreman_openscap/arf_report.rb | 25 +++++------------ .../foreman_openscap/compliance_status.rb | 1 - app/models/foreman_openscap/policy.rb | 27 ++++++++++++++++++- app/views/policies/_form.html.erb | 1 + .../steps/_policy_attributes_form.html.erb | 1 + .../20210126144230_add_treshold_to_policy.rb | 6 +++++ 8 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 db/migrate/20210126144230_add_treshold_to_policy.rb diff --git a/app/controllers/concerns/foreman/controller/parameters/policy_api.rb b/app/controllers/concerns/foreman/controller/parameters/policy_api.rb index 10fdd4f3a..398f0bca9 100644 --- a/app/controllers/concerns/foreman/controller/parameters/policy_api.rb +++ b/app/controllers/concerns/foreman/controller/parameters/policy_api.rb @@ -3,7 +3,7 @@ module Foreman::Controller::Parameters::PolicyApi class_methods do def filter_params_list - [:description, :name, :period, :scap_content_id, :scap_content_profile_id, :deploy_by, + [:description, :name, :treshold, :period, :scap_content_id, :scap_content_profile_id, :deploy_by, :weekday, :day_of_month, :cron_line, :tailoring_file_id, :tailoring_file_profile_id, :location_ids => [], :organization_ids => [], :hostgroup_ids => [], :host_ids => []] end diff --git a/app/models/concerns/foreman_openscap/compliance_status_scoped_search.rb b/app/models/concerns/foreman_openscap/compliance_status_scoped_search.rb index be5bc8ff2..2080b3f71 100644 --- a/app/models/concerns/foreman_openscap/compliance_status_scoped_search.rb +++ b/app/models/concerns/foreman_openscap/compliance_status_scoped_search.rb @@ -16,10 +16,6 @@ def search_by_not_comply_with(_key, _operator, policy_name) search_by_policy_results policy_name, &:failed end - def search_by_inconclusive_with(_key, _operator, policy_name) - search_by_policy_results policy_name, &:othered - end - def search_by_policy_results(policy_name, &selection) scope = ArfReport.of_policy(Policy.find_by(:name => policy_name).id) .instance_eval(&selection) @@ -67,8 +63,6 @@ def search_by_compliance_status(key, operator, value) ArfReport.passed when 'incompliant' ArfReport.failed - when 'inconclusive' - ArfReport.othered end query_conditions_from_scope scope end @@ -131,9 +125,6 @@ def apply_condition(scope, negate, conditions) scoped_search :relation => :policy, :on => :name, :complete_value => true, :rename => :not_comply_with, :only_explicit => true, :operators => ['= '], :ext_method => :search_by_not_comply_with - scoped_search :relation => :policy, :on => :name, :complete_value => true, :rename => :inconclusive_with, - :only_explicit => true, :operators => ['= '], :ext_method => :search_by_inconclusive_with - scoped_search :relation => :openscap_proxy, :on => :name, :complete_value => true, :only_explicit => true, :rename => :openscap_proxy scoped_search :relation => :sources, :on => :value, :rename => :xccdf_rule_name, @@ -151,9 +142,8 @@ def apply_condition(scope, negate, conditions) scoped_search :on => :status, :rename => :compliance_status, :operators => ['= '], :ext_method => :search_by_compliance_status, :complete_value => { :compliant => ::ForemanOpenscap::ComplianceStatus::COMPLIANT, - :incompliant => ::ForemanOpenscap::ComplianceStatus::INCOMPLIANT, - :inconclusive => ::ForemanOpenscap::ComplianceStatus::INCONCLUSIVE }, - :validator => ->(value) { ['compliant', 'incompliant', 'inconclusive'].reduce(false) { |memo, item| memo || (item == value) } } + :incompliant => ::ForemanOpenscap::ComplianceStatus::INCOMPLIANT }, + :validator => ->(value) { ['compliant', 'incompliant'].reduce(false) { |memo, item| memo || (item == value) } } end end end diff --git a/app/models/foreman_openscap/arf_report.rb b/app/models/foreman_openscap/arf_report.rb index 48c8f7591..ffe2655f6 100644 --- a/app/models/foreman_openscap/arf_report.rb +++ b/app/models/foreman_openscap/arf_report.rb @@ -51,13 +51,9 @@ class ArfReport < ::Report ON reports.id = latest.id") } - scope :failed, lambda { where("(#{report_status_column} >> #{bit_mask 'failed'}) > 0") } - scope :not_failed, lambda { where("(#{report_status_column} >> #{bit_mask 'failed'}) = 0") } - - scope :othered, lambda { where("(#{report_status_column} >> #{bit_mask 'othered'}) > 0").merge(not_failed) } - scope :not_othered, lambda { where("(#{report_status_column} >> #{bit_mask 'othered'}) = 0") } - - scope :passed, lambda { where("(#{report_status_column} >> #{bit_mask 'passed'}) > 0").merge(not_failed).merge(not_othered) } + scope :with_score, lambda { where.not(:score => nil) } + scope :passed, lambda { with_score.includes(:policy).where('foreman_openscap_policies.treshold <= reports.score') } + scope :failed, lambda { where.not(:id => ArfReport.passed.pluck(:id)) } scope :by_rule_result, lambda { |rule_name, rule_result| joins(:sources).where(:sources => { :value => rule_name }, :logs => { :result => rule_result }) } @@ -93,11 +89,7 @@ def passed end def failed - status_of "failed" - end - - def othered - status_of "othered" + status_of("failed") + status_of("othered") end def rules_count @@ -114,6 +106,7 @@ def self.create_arf(asset, proxy, params) :reported_at => Time.at(params[:date].to_i), :status => params[:metrics], :metrics => params[:metrics], + :score => params[:score], :openscap_proxy => proxy) return arf_report unless arf_report.persisted? PolicyArfReport.where(:arf_report_id => arf_report.id, :policy_id => policy.id, :digest => params[:digest]).first_or_create! @@ -162,15 +155,11 @@ def assign_locations_organizations end def failed? - failed > 0 + !passed? end def passed? - passed > 0 && failed == 0 && othered == 0 - end - - def othered? - !passed? && !failed? + score && score >= policy.treshold end def to_html diff --git a/app/models/foreman_openscap/compliance_status.rb b/app/models/foreman_openscap/compliance_status.rb index 22705c6ca..303b8c626 100644 --- a/app/models/foreman_openscap/compliance_status.rb +++ b/app/models/foreman_openscap/compliance_status.rb @@ -49,7 +49,6 @@ def relevant?(options = {}) def to_status(options = {}) latest_reports = host.policies.map { |p| host.last_report_for_policy p }.flatten return INCOMPLIANT if latest_reports.any?(&:failed?) - return INCONCLUSIVE if latest_reports.any?(&:othered?) COMPLIANT end end diff --git a/app/models/foreman_openscap/policy.rb b/app/models/foreman_openscap/policy.rb index 96caa019e..2e5a6092c 100644 --- a/app/models/foreman_openscap/policy.rb +++ b/app/models/foreman_openscap/policy.rb @@ -31,6 +31,8 @@ def self.deploy_by_variants validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 255 }, :if => Proc.new { |policy| policy.should_validate?('Policy Attributes') } + validates :treshold, :presence => true, :if => Proc.new { |policy| policy.should_validate?('Policy Attributes') } + validates :period, :inclusion => { :in => %w[weekly monthly custom], :message => _('is not a valid value') }, :if => Proc.new { |policy| policy.should_validate?('Schedule') } validates :deploy_by, :inclusion => { :in => Policy.deploy_by_variants }, @@ -39,9 +41,10 @@ def self.deploy_by_variants validates :scap_content_id, presence: true, if: Proc.new { |policy| policy.should_validate?('SCAP Content') } validate :matching_content_profile, if: Proc.new { |policy| policy.should_validate?('SCAP Content') } - validate :valid_tailoring, :valid_tailoring_profile, :no_mixed_deployments + validate :valid_tailoring, :valid_tailoring_profile, :no_mixed_deployments, :treshold_value validate :valid_cron_line, :valid_weekday, :valid_day_of_month, :if => Proc.new { |policy| policy.should_validate?('Schedule') } after_save :assign_policy_to_hostgroups + after_save :refresh_compliance_status # before_destroy - ensure that the policy has no hostgroups, or classes default_scope do @@ -107,6 +110,17 @@ def hosts=(hosts) host_ids = hosts.map(&:id).map(&:to_s) end + def all_hosts + hg_ids = hostgroups.flat_map(&:subtree_ids).uniq + (Host.where(:hostgroup_id => hg_ids) + hosts).uniq + end + + def refresh_host_statuses + all_hosts.map do |host| + host.refresh_statuses([HostStatus.find_status_by_humanized_name("compliance")]) + end + end + def step_to_i(step_name) steps.index(step_name) + 1 end @@ -272,5 +286,16 @@ def no_mixed_deployments end end end + + def treshold_value + if (100 < treshold || treshold < 0) && should_validate?('Policy Attributes') + errors.add(:treshold, _("must be between 0 and 100")) + end + end + + def refresh_compliance_status + return if id_previously_changed? + refresh_host_statuses + end end end diff --git a/app/views/policies/_form.html.erb b/app/views/policies/_form.html.erb index f364569aa..a6cc534c4 100644 --- a/app/views/policies/_form.html.erb +++ b/app/views/policies/_form.html.erb @@ -24,6 +24,7 @@
<%= text_f(f, :name) %> <%= textarea_f(f, :description, :rows => :auto ) %> + <%= counter_f(f, :treshold) %>
<%= scap_content_selector(f) %> diff --git a/app/views/policies/steps/_policy_attributes_form.html.erb b/app/views/policies/steps/_policy_attributes_form.html.erb index 679145e23..5ef5f285a 100644 --- a/app/views/policies/steps/_policy_attributes_form.html.erb +++ b/app/views/policies/steps/_policy_attributes_form.html.erb @@ -2,4 +2,5 @@ <%= wizard_header @policy.step_index, *translate_steps(@policy) %> <%= text_f(f, :name) %> <%= text_f(f, :description, :size => "col-md-8" ) %> + <%= counter_f(f, :treshold) %>
diff --git a/db/migrate/20210126144230_add_treshold_to_policy.rb b/db/migrate/20210126144230_add_treshold_to_policy.rb new file mode 100644 index 000000000..42fbc7b56 --- /dev/null +++ b/db/migrate/20210126144230_add_treshold_to_policy.rb @@ -0,0 +1,6 @@ +class AddTresholdToPolicy < ActiveRecord::Migration[6.0] + def change + add_column :foreman_openscap_policies, :treshold, :numeric, :null => false, :default => 100 + add_column :reports, :score, :numeric + end +end