foreman_openscap 0.4.3 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/foreman_openscap/load_report.js +1 -1
- data/app/assets/javascripts/foreman_openscap/policy_edit.js +0 -2
- data/app/controllers/api/v2/compliance/arf_reports_controller.rb +7 -22
- data/app/controllers/api/v2/compliance/policies_controller.rb +5 -5
- data/app/controllers/api/v2/compliance/scap_contents_controller.rb +3 -3
- data/app/controllers/arf_reports_controller.rb +62 -0
- data/app/controllers/{scaptimony_dashboard_controller.rb → compliance_dashboard_controller.rb} +1 -1
- data/app/controllers/compliance_hosts_controller.rb +5 -0
- data/app/controllers/{scaptimony_policies_controller.rb → policies_controller.rb} +13 -13
- data/app/controllers/{scaptimony_policy_dashboard_controller.rb → policy_dashboard_controller.rb} +3 -3
- data/app/controllers/{scaptimony_scap_contents_controller.rb → scap_contents_controller.rb} +11 -11
- data/app/helpers/{scaptimony_report_dashboard_helper.rb → arf_report_dashboard_helper.rb} +3 -5
- data/app/helpers/arf_reports_helper.rb +21 -0
- data/app/helpers/compliance_hosts_helper.rb +25 -0
- data/app/helpers/concerns/foreman_openscap/hosts_helper_extensions.rb +2 -32
- data/app/helpers/{scaptimony_policies_helper.rb → policies_helper.rb} +6 -2
- data/app/helpers/{scaptimony_policy_dashboard_helper.rb → policy_dashboard_helper.rb} +8 -8
- data/app/lib/proxy_api/available_proxy.rb +26 -0
- data/app/lib/proxy_api/openscap.rb +40 -0
- data/app/mailers/foreman_openscap/policy_mailer.rb +42 -0
- data/app/models/concerns/foreman_openscap/compliance_status_scoped_search.rb +91 -0
- data/app/models/concerns/foreman_openscap/host_extensions.rb +73 -17
- data/app/models/concerns/foreman_openscap/hostgroup_extensions.rb +3 -5
- data/app/models/foreman_openscap/arf_report.rb +165 -0
- data/app/models/foreman_openscap/asset.rb +27 -0
- data/app/models/foreman_openscap/asset_policy.rb +6 -0
- data/app/models/foreman_openscap/compliance_status.rb +50 -0
- data/app/models/{concerns/foreman_openscap/policy_extensions.rb → foreman_openscap/policy.rb} +72 -45
- data/app/models/foreman_openscap/policy_arf_report.rb +8 -0
- data/app/models/foreman_openscap/policy_revision.rb +6 -0
- data/app/models/foreman_openscap/scap_content.rb +112 -0
- data/app/models/foreman_openscap/scap_content_profile.rb +6 -0
- data/app/overrides/hosts/overview/host_compliance_status.rb +4 -4
- data/app/services/foreman_openscap/arf_report_status_calculator.rb +45 -0
- data/app/services/{scaptimony → foreman_openscap}/host_report_dashboard/data.rb +12 -6
- data/app/services/{scaptimony → foreman_openscap}/policy_dashboard/data.rb +5 -5
- data/app/services/{scaptimony → foreman_openscap}/report_dashboard/data.rb +4 -4
- data/app/views/api/v2/compliance/policies/create.json.rabl +3 -0
- data/app/views/{scaptimony_arf_reports → arf_reports}/_list.html.erb +4 -4
- data/app/views/arf_reports/_metrics.html.erb +37 -0
- data/app/views/arf_reports/_output.html.erb +23 -0
- data/app/views/{scaptimony_arf_reports → arf_reports}/index.html.erb +0 -0
- data/app/views/arf_reports/show.html.erb +14 -0
- data/app/views/{scaptimony_arf_reports/show.html.erb → arf_reports/show_html.html.erb} +2 -3
- data/app/views/compliance_hosts/_compliance_status.erb +6 -0
- data/app/views/{scaptimony_hosts → compliance_hosts}/show.html.erb +9 -2
- data/app/views/dashboard/{_foreman_openscap_host_reports_widget.html.erb → _compliance_host_reports_widget.html.erb} +3 -3
- data/app/views/dashboard/{_foreman_openscap_reports_breakdown_widget.html.erb → _compliance_reports_breakdown_widget.html.erb} +1 -1
- data/app/views/foreman_openscap/policy_mailer/_dashboard.erb +21 -0
- data/app/views/foreman_openscap/policy_mailer/_hosts.erb +44 -0
- data/app/views/foreman_openscap/policy_mailer/_list.erb +10 -0
- data/app/views/foreman_openscap/policy_mailer/_policy.erb +7 -0
- data/app/views/foreman_openscap/policy_mailer/policy_summary.erb +19 -0
- data/app/views/{scaptimony_policies → policies}/_form.html.erb +2 -8
- data/app/views/{scaptimony_policies → policies}/_list.html.erb +5 -5
- data/app/views/policies/_scap_content_results.html.erb +3 -0
- data/app/views/policies/create.html.erb +2 -0
- data/app/views/{scaptimony_policies → policies}/disassociate_multiple_hosts.html.erb +2 -2
- data/app/views/{scaptimony_policies → policies}/edit.html.erb +0 -0
- data/app/views/{scaptimony_policies → policies}/index.html.erb +1 -1
- data/app/views/policies/new.html.erb +2 -0
- data/app/views/{scaptimony_policies → policies}/select_multiple_hosts.html.erb +2 -2
- data/app/views/{scaptimony_policies → policies}/show.html.erb +1 -1
- data/app/views/{scaptimony_policies → policies}/steps/_create_policy_form.html.erb +0 -0
- data/app/views/{scaptimony_policies → policies}/steps/_hostgroups_form.html.erb +0 -0
- data/app/views/{scaptimony_policies → policies}/steps/_locations_form.html.erb +0 -0
- data/app/views/{scaptimony_policies → policies}/steps/_organizations_form.html.erb +0 -0
- data/app/views/policies/steps/_scap_content_form.html.erb +9 -0
- data/app/views/{scaptimony_policies → policies}/steps/_schedule_form.html.erb +1 -1
- data/app/views/{scaptimony_policies → policies}/steps/_step_form.html.erb +3 -3
- data/app/views/{scaptimony_policies → policies}/welcome.html.erb +2 -2
- data/app/views/{scaptimony_policy_dashboard → policy_dashboard}/_policy_chart_widget.html.erb +0 -0
- data/app/views/{scaptimony_policy_dashboard → policy_dashboard}/_policy_reports.html.erb +2 -2
- data/app/views/{scaptimony_policy_dashboard → policy_dashboard}/_policy_status_widget.html.erb +3 -3
- data/app/views/{scaptimony_policy_dashboard → policy_dashboard}/index.html.erb +0 -0
- data/app/views/{scaptimony_scap_contents → scap_contents}/_form.html.erb +5 -6
- data/app/views/{scaptimony_scap_contents → scap_contents}/_list.html.erb +3 -3
- data/app/views/{scaptimony_scap_contents → scap_contents}/edit.html.erb +0 -0
- data/app/views/{scaptimony_scap_contents → scap_contents}/index.html.erb +1 -1
- data/app/views/{scaptimony_scap_contents → scap_contents}/new.html.erb +0 -0
- data/app/views/{scaptimony_scap_contents → scap_contents}/welcome.html.erb +2 -2
- data/config/routes.rb +15 -11
- data/db/migrate/20141013172051_create_scaptimony_policies.rb +9 -0
- data/db/migrate/20141014105333_create_scaptimony_assets.rb +10 -0
- data/db/migrate/20141015092642_create_scaptimony_arf_reports.rb +13 -0
- data/db/migrate/20141015115511_add_arf_report_unique_constraint.rb +6 -0
- data/db/migrate/20141104164201_create_scaptimony_scap_contents.rb +7 -0
- data/db/migrate/20141104171545_create_scaptimony_policy_revisions.rb +14 -0
- data/db/migrate/20141105174625_add_description_to_scaptimony_policy_revisions.rb +5 -0
- data/db/migrate/20141105174834_add_columns_to_scaptimony_policies.rb +12 -0
- data/db/migrate/20141107091756_add_columns_to_scaptimony_scap_contents.rb +8 -0
- data/db/migrate/20141111104519_add_constraint_to_scaptimony_scap_contents.rb +5 -0
- data/db/migrate/20141113221054_create_scaptimony_scap_content_profiles.rb +12 -0
- data/db/migrate/20141116170632_remove_xccdf_profile_from_scaptimony_policies.rb +5 -0
- data/db/migrate/20141116171305_add_profile_to_scaptimony_policies.rb +6 -0
- data/db/migrate/20141118142954_add_constraint_to_scaptimony_policies.rb +5 -0
- data/db/migrate/20141119164918_create_scaptimony_xccdf_results.rb +8 -0
- data/db/migrate/20141119175434_create_scaptimony_xccdf_rules.rb +8 -0
- data/db/migrate/20141119182606_create_scaptimony_xccdf_rule_results.rb +9 -0
- data/db/migrate/20141121120326_create_scaptimony_arf_report_breakdowns.rb +24 -0
- data/db/migrate/20141121164042_replace_arf_report_breakdown_view.rb +25 -0
- data/db/migrate/20141206211151_create_scaptimony_assets_policies.rb +9 -0
- data/db/migrate/20141214112917_add_scap_file_to_scap_content.rb +5 -0
- data/db/migrate/20141216154502_rename_scaptimony_asset_policies.rb +5 -0
- data/db/migrate/20150111085317_polymorph_asset.rb +8 -0
- data/db/migrate/20150112152944_create_scaptimony_arf_report_raws.rb +10 -0
- data/db/migrate/20150114210634_rename_scaptimony_arf_report_raw_raw.rb +5 -0
- data/db/migrate/20150115155947_add_scaptimony_scap_content_digest.rb +21 -0
- data/db/migrate/20150116083129_add_day_of_month_and_cron_line_to_scaptimony_policy.rb +6 -0
- data/db/migrate/20150821100137_migrate_from_scaptimony.rb +59 -0
- data/db/migrate/20150827123826_remove_scaptimony_permissions.rb +21 -0
- data/db/migrate/20150925124959_create_policy_arf_reports.rb +13 -0
- data/db/migrate/20150929124853_add_result_to_logs.rb +9 -0
- data/db/migrate/20150929152345_move_arf_reports_to_reports_table.rb +179 -0
- data/db/migrate/20151023131950_link_arf_report_directly_to_host.rb +17 -0
- data/db/seeds.d/openscap_policy_notification.rb +9 -0
- data/lib/foreman_openscap/bulk_upload.rb +3 -1
- data/lib/foreman_openscap/engine.rb +53 -42
- data/lib/foreman_openscap/helper.rb +8 -0
- data/lib/foreman_openscap/version.rb +1 -1
- data/lib/tasks/foreman_openscap_tasks.rake +14 -0
- data/test/factories/arf_report_factory.rb +9 -6
- data/test/factories/asset_factory.rb +1 -1
- data/test/factories/compliance_host_factory.rb +9 -0
- data/test/factories/compliance_log_factory.rb +11 -0
- data/test/factories/policy_arf_report_factory.rb +6 -0
- data/test/factories/policy_factory.rb +3 -2
- data/test/factories/scap_content_related.rb +2 -2
- data/test/functional/api/v2/compliance/arf_reports_controller_test.rb +4 -3
- data/test/functional/api/v2/compliance/policies_controller_test.rb +2 -2
- data/test/functional/api/v2/compliance/scap_contents_controller_test.rb +3 -1
- data/test/lib/foreman_openscap/bulk_upload_test.rb +1 -1
- data/test/test_plugin_helper.rb +30 -0
- data/test/unit/arf_report_status_calculator_test.rb +11 -0
- data/test/unit/arf_report_test.rb +148 -0
- data/test/unit/compliance_status_test.rb +71 -0
- data/test/unit/openscap_host_test.rb +38 -7
- data/test/unit/policy_mailer_test.rb +38 -0
- data/test/unit/scap_content_test.rb +32 -0
- metadata +130 -74
- data/app/controllers/scaptimony_arf_reports_controller.rb +0 -34
- data/app/controllers/scaptimony_hosts_controller.rb +0 -5
- data/app/models/concerns/foreman_openscap/arf_report_extensions.rb +0 -50
- data/app/models/concerns/foreman_openscap/asset_extensions.rb +0 -34
- data/app/models/concerns/foreman_openscap/scap_content_extensions.rb +0 -40
- data/app/overrides/hosts/index/host_arf_report.rb +0 -5
- data/app/views/scaptimony_arf_reports/_host_report.html.erb +0 -8
- data/app/views/scaptimony_hosts/_host_status.html.erb +0 -17
- data/app/views/scaptimony_policies/_scap_content_results.html.erb +0 -7
- data/app/views/scaptimony_policies/create.html.erb +0 -2
- data/app/views/scaptimony_policies/new.html.erb +0 -2
- data/app/views/scaptimony_policies/steps/_scap_content_form.html.erb +0 -17
@@ -1,13 +1,11 @@
|
|
1
|
-
require 'scaptimony/asset'
|
2
|
-
|
3
1
|
module ForemanOpenscap
|
4
2
|
module HostgroupExtensions
|
5
3
|
extend ActiveSupport::Concern
|
6
4
|
|
7
5
|
included do
|
8
|
-
has_one :asset, :as => :assetable, :class_name => "::
|
9
|
-
has_many :asset_policies, :through => :asset, :class_name => "::
|
10
|
-
has_many :policies, :through => :asset_policies, :class_name => "::
|
6
|
+
has_one :asset, :as => :assetable, :class_name => "::ForemanOpenscap::Asset"
|
7
|
+
has_many :asset_policies, :through => :asset, :class_name => "::ForemanOpenscap::AssetPolicy"
|
8
|
+
has_many :policies, :through => :asset_policies, :class_name => "::ForemanOpenscap::Policy"
|
11
9
|
end
|
12
10
|
end
|
13
11
|
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'foreman_openscap/helper'
|
2
|
+
|
3
|
+
module ForemanOpenscap
|
4
|
+
class ArfReport < ::Report
|
5
|
+
include Taxonomix
|
6
|
+
|
7
|
+
RESULT = %w(pass fail error unknown notapplicable notchecked notselected informational fixed)
|
8
|
+
METRIC = %w(passed othered failed)
|
9
|
+
BIT_NUM = 10
|
10
|
+
MAX = (1 << BIT_NUM) - 1
|
11
|
+
|
12
|
+
has_one :policy_arf_report, :dependent => :destroy
|
13
|
+
has_one :policy, :through => :policy_arf_report
|
14
|
+
has_one :asset, :through => :host, :class_name => 'ForemanOpenscap::Asset'
|
15
|
+
after_save :assign_locations_organizations
|
16
|
+
validate :result, :inclusion => { :in => RESULT }
|
17
|
+
|
18
|
+
default_scope {
|
19
|
+
with_taxonomy_scope do
|
20
|
+
order("#{self.table_name}.created_at DESC")
|
21
|
+
end
|
22
|
+
}
|
23
|
+
|
24
|
+
scope :hosts, lambda { includes(:policy) }
|
25
|
+
scope :of_policy, lambda { |policy_id| joins(:policy_arf_report).merge(PolicyArfReport.of_policy(policy_id)) }
|
26
|
+
|
27
|
+
scope :latest,
|
28
|
+
joins('INNER JOIN (SELECT host_id, policy_id, max(reports.id) AS id
|
29
|
+
FROM reports INNER JOIN foreman_openscap_policy_arf_reports
|
30
|
+
ON reports.id = foreman_openscap_policy_arf_reports.arf_report_id
|
31
|
+
GROUP BY host_id, policy_id) latest
|
32
|
+
ON reports.id = latest.id')
|
33
|
+
|
34
|
+
scope :latest_of_policy, lambda { |policy|
|
35
|
+
joins("INNER JOIN (SELECT host_id, policy_id, max(reports.id) AS id
|
36
|
+
FROM reports INNER JOIN foreman_openscap_policy_arf_reports
|
37
|
+
ON reports.id = foreman_openscap_policy_arf_reports.arf_report_id
|
38
|
+
WHERE policy_id = #{policy.id}
|
39
|
+
GROUP BY host_id, policy_id) latest
|
40
|
+
ON reports.id = latest.id")
|
41
|
+
}
|
42
|
+
|
43
|
+
scope :failed, lambda { where("(#{report_status_column} >> #{ bit_mask 'failed' }) > 0") }
|
44
|
+
scope :not_failed, lambda { where("(#{report_status_column} >> #{ bit_mask 'failed' }) = 0") }
|
45
|
+
|
46
|
+
scope :othered, lambda { where("(#{report_status_column} >> #{ bit_mask 'othered' }) > 0").merge(not_failed) }
|
47
|
+
scope :not_othered, lambda { where("(#{report_status_column} >> #{ bit_mask 'othered' }) = 0") }
|
48
|
+
|
49
|
+
scope :passed, lambda { where("(#{report_status_column} >> #{ bit_mask 'passed' }) > 0").merge(not_failed).merge(not_othered) }
|
50
|
+
|
51
|
+
def self.bit_mask(status)
|
52
|
+
ComplianceStatus.bit_mask(status)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.report_status_column
|
56
|
+
"status"
|
57
|
+
end
|
58
|
+
|
59
|
+
def status=(st)
|
60
|
+
s = case st
|
61
|
+
when Integer, Fixnum
|
62
|
+
st
|
63
|
+
when Hash
|
64
|
+
ArfReportStatusCalculator.new(:counters => st).calculate
|
65
|
+
else
|
66
|
+
fail Foreman::Exception(N_('Unsupported report status format'))
|
67
|
+
end
|
68
|
+
write_attribute(:status, s)
|
69
|
+
end
|
70
|
+
|
71
|
+
delegate :status, :status_of, :to => :calculator
|
72
|
+
delegate(*METRIC, :to => :calculator)
|
73
|
+
|
74
|
+
def calculator
|
75
|
+
ArfReportStatusCalculator.new(:bit_field => read_attribute(self.class.report_status_column))
|
76
|
+
end
|
77
|
+
|
78
|
+
def passed
|
79
|
+
status_of "passed"
|
80
|
+
end
|
81
|
+
|
82
|
+
def failed
|
83
|
+
status_of "failed"
|
84
|
+
end
|
85
|
+
|
86
|
+
def othered
|
87
|
+
status_of "othered"
|
88
|
+
end
|
89
|
+
|
90
|
+
def rules_count
|
91
|
+
status.values.sum
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.create_arf(asset, params)
|
95
|
+
# fail if policy does not exist.
|
96
|
+
arf_report = nil
|
97
|
+
policy = Policy.find(params[:policy_id])
|
98
|
+
ArfReport.transaction do
|
99
|
+
# TODO:RAILS-4.0: This should become arf_report = ArfReport.find_or_create_by! ...
|
100
|
+
arf_report = ArfReport.create!(:host_id => asset.host.id,
|
101
|
+
:reported_at => Time.at(params[:date].to_i),
|
102
|
+
:status => params[:metrics],
|
103
|
+
:metrics => params[:metrics])
|
104
|
+
PolicyArfReport.where(:arf_report_id => arf_report.id, :policy_id => policy.id, :digest => params[:digest]).first_or_create!
|
105
|
+
if params[:logs]
|
106
|
+
params[:logs].each do |log|
|
107
|
+
src = Source.find_or_create(log[:source])
|
108
|
+
msg = Message.find_or_create(N_(log[:title]))
|
109
|
+
#TODO: log level
|
110
|
+
Log.create!(:source_id => src.id,
|
111
|
+
:message_id => msg.id,
|
112
|
+
:level_id => 1,
|
113
|
+
:result => log[:result],
|
114
|
+
:report_id => arf_report.id)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
arf_report
|
119
|
+
end
|
120
|
+
|
121
|
+
def assign_locations_organizations
|
122
|
+
if host
|
123
|
+
self.location_ids = [host.location_id] if SETTINGS[:locations_enabled]
|
124
|
+
self.organization_ids = [host.organization_id] if SETTINGS[:organizations_enabled]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def failed?
|
129
|
+
failed > 0
|
130
|
+
end
|
131
|
+
|
132
|
+
def passed?
|
133
|
+
passed > 0 && failed == 0 && othered == 0
|
134
|
+
end
|
135
|
+
|
136
|
+
def othered?
|
137
|
+
!passed? && !failed?
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_html
|
141
|
+
proxy.arf_report_html(self, ForemanOpenscap::Helper::find_name_or_uuid_by_host(host))
|
142
|
+
end
|
143
|
+
|
144
|
+
def to_bzip
|
145
|
+
proxy.arf_report_bzip(self, ForemanOpenscap::Helper::find_name_or_uuid_by_host(host))
|
146
|
+
end
|
147
|
+
|
148
|
+
def equal?(other)
|
149
|
+
results = [logs, other.logs].flatten.group_by(&:source_id).values
|
150
|
+
# for each rule, there should be one result from both reports
|
151
|
+
return false unless results.map(&:length).all? { |item| item == 2 }
|
152
|
+
results.all? { |result| result.first.source_id == result.last.source_id } &&
|
153
|
+
host_id == other.host_id &&
|
154
|
+
policy.id == other.policy.id
|
155
|
+
end
|
156
|
+
|
157
|
+
def proxy
|
158
|
+
return @proxy if @proxy
|
159
|
+
scap_class = host.info['classes']['foreman_scap_client']
|
160
|
+
port = scap_class['port']
|
161
|
+
server = scap_class['server']
|
162
|
+
@proxy = ::ProxyAPI::Openscap.new(:url => "https://#{server}:#{port}")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ForemanOpenscap
|
2
|
+
class Asset < ActiveRecord::Base
|
3
|
+
has_many :asset_policies
|
4
|
+
has_many :policies, :through => :asset_policies
|
5
|
+
belongs_to :assetable, :polymorphic => true
|
6
|
+
|
7
|
+
scope :hosts, where(:assetable_type => 'Host::Base')
|
8
|
+
|
9
|
+
def host
|
10
|
+
fetch_asset('Host::Base')
|
11
|
+
end
|
12
|
+
|
13
|
+
def hostgroup
|
14
|
+
fetch_asset('Hostgroup')
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
assetable.name
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def fetch_asset(type)
|
24
|
+
assetable if assetable_type == type
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ForemanOpenscap
|
2
|
+
class ComplianceStatus < ::HostStatus::Status
|
3
|
+
COMPLIANT = 0
|
4
|
+
INCONCLUSIVE = 1
|
5
|
+
INCOMPLIANT = 2
|
6
|
+
|
7
|
+
def self.status_name
|
8
|
+
N_('Compliance')
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.bit_mask(status)
|
12
|
+
"#{ArfReport::BIT_NUM * ArfReport::METRIC.index(status)} & #{ArfReport::MAX}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_label(options = {})
|
16
|
+
case to_status
|
17
|
+
when COMPLIANT
|
18
|
+
N_('Compliant')
|
19
|
+
when INCONCLUSIVE
|
20
|
+
N_('Inconclusive')
|
21
|
+
when INCOMPLIANT
|
22
|
+
N_('Incompliant')
|
23
|
+
else
|
24
|
+
N_('Unknown Compliance status')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_global(options = {})
|
29
|
+
case to_status
|
30
|
+
when COMPLIANT
|
31
|
+
::HostStatus::Global::OK
|
32
|
+
when INCONCLUSIVE
|
33
|
+
::HostStatus::Global::WARN
|
34
|
+
else
|
35
|
+
::HostStatus::Global::ERROR
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def relevant?
|
40
|
+
host.policies.present?
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_status(options = {})
|
44
|
+
latest_reports = host.policies.map { |p| host.last_report_for_policy p }.flatten
|
45
|
+
return INCOMPLIANT if latest_reports.any?(&:failed?)
|
46
|
+
return INCONCLUSIVE if latest_reports.any?(&:othered?)
|
47
|
+
COMPLIANT
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/app/models/{concerns/foreman_openscap/policy_extensions.rb → foreman_openscap/policy.rb}
RENAMED
@@ -1,49 +1,64 @@
|
|
1
|
-
#
|
2
|
-
# Copyright (c) 2014 Red Hat Inc.
|
3
|
-
#
|
4
|
-
# This software is licensed to you under the GNU General Public License,
|
5
|
-
# version 3 (GPLv3). There is NO WARRANTY for this software, express or
|
6
|
-
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
|
7
|
-
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv3
|
8
|
-
# along with this software; if not, see http://www.gnu.org/licenses/gpl.txt
|
9
|
-
#
|
10
|
-
|
11
1
|
module ForemanOpenscap
|
12
|
-
|
13
|
-
extend ActiveSupport::Concern
|
14
|
-
|
2
|
+
class Policy < ActiveRecord::Base
|
15
3
|
include Authorizable
|
16
4
|
include Taxonomix
|
5
|
+
attr_accessible :description, :name, :period, :scap_content_id, :scap_content_profile_id,
|
6
|
+
:weekday, :day_of_month, :cron_line, :location_ids, :organization_ids,
|
7
|
+
:current_step, :hostgroup_ids
|
8
|
+
attr_writer :current_step
|
9
|
+
|
10
|
+
belongs_to :scap_content
|
11
|
+
belongs_to :scap_content_profile
|
12
|
+
has_many :policy_arf_reports
|
13
|
+
has_many :arf_reports, :through => :policy_arf_reports, :dependent => :destroy
|
14
|
+
has_many :asset_policies
|
15
|
+
has_many :assets, :through => :asset_policies
|
16
|
+
|
17
|
+
scoped_search :on => :name, :complete_value => true
|
18
|
+
|
19
|
+
SCAP_PUPPET_CLASS = 'foreman_scap_client'
|
20
|
+
POLICIES_CLASS_PARAMETER = 'policies'
|
21
|
+
SERVER_CLASS_PARAMETER = 'server'
|
22
|
+
PORT_CLASS_PARAMETER = 'port'
|
23
|
+
|
24
|
+
validates :name, :presence => true, :uniqueness => true, :format => {without: /\s/}
|
25
|
+
validate :ensure_needed_puppetclasses
|
26
|
+
validates :period, :inclusion => {:in => %w[weekly monthly custom]},
|
27
|
+
:if => Proc.new { |policy| policy.new_record? ? policy.step_index > 3 : !policy.id.blank? }
|
28
|
+
validates :weekday, :inclusion => {:in => Date::DAYNAMES.map(&:downcase)},
|
29
|
+
:if => Proc.new { |policy| policy.period == 'weekly' && (policy.new_record? ? policy.step_index > 3 : !policy.id.blank?) }
|
30
|
+
validates :day_of_month, :numericality => {:greater_than => 0, :less_than => 32},
|
31
|
+
:if => Proc.new { |policy| policy.period == 'monthly'&& (policy.new_record? ? policy.step_index > 3 : !policy.id.blank?) }
|
32
|
+
validate :valid_cron_line
|
33
|
+
validate :ensure_period_specification_present
|
34
|
+
|
35
|
+
|
36
|
+
after_save :assign_policy_to_hostgroups
|
37
|
+
# before_destroy - ensure that the policy has no hostgroups, or classes
|
38
|
+
|
39
|
+
default_scope {
|
40
|
+
with_taxonomy_scope do
|
41
|
+
order("foreman_openscap_policies.name")
|
42
|
+
end
|
43
|
+
}
|
17
44
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
validate :valid_cron_line
|
36
|
-
validate :ensure_period_specification_present
|
37
|
-
|
38
|
-
|
39
|
-
after_save :assign_policy_to_hostgroups
|
40
|
-
# before_destroy - ensure that the policy has no hostgroups, or classes
|
41
|
-
|
42
|
-
default_scope {
|
43
|
-
with_taxonomy_scope do
|
44
|
-
order("scaptimony_policies.name")
|
45
|
-
end
|
46
|
-
}
|
45
|
+
def assign_assets(a)
|
46
|
+
self.asset_ids = (self.asset_ids + a.collect(&:id)).uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_html
|
50
|
+
if scap_content.nil? || scap_content_profile.nil?
|
51
|
+
return (_('<h2>Cannot generate HTML guide for %{scap_content}/%{profile}</h2>') %
|
52
|
+
{ :scap_content => self.scap_content, :profile => self.scap_content_profile }).html_safe
|
53
|
+
end
|
54
|
+
|
55
|
+
if (proxy = scap_content.proxy_url)
|
56
|
+
api = ProxyAPI::Openscap.new(:url => proxy)
|
57
|
+
else
|
58
|
+
return _('<h2>No valid OpenScap proxy server found.</h2>').html_safe
|
59
|
+
end
|
60
|
+
|
61
|
+
api.policy_html_guide(scap_content.scap_file, scap_content_profile.profile_id)
|
47
62
|
end
|
48
63
|
|
49
64
|
def hostgroup_ids
|
@@ -66,6 +81,18 @@ module ForemanOpenscap
|
|
66
81
|
hostgroup_ids = hostgroups.map(&:id).map(&:to_s)
|
67
82
|
end
|
68
83
|
|
84
|
+
def host_ids
|
85
|
+
assets.where(:assetable_type => 'Host::Base').pluck(:assetable_id)
|
86
|
+
end
|
87
|
+
|
88
|
+
def hosts
|
89
|
+
Host.where(:id => host_ids)
|
90
|
+
end
|
91
|
+
|
92
|
+
def hosts=(hosts)
|
93
|
+
host_ids = hosts.map(&:id).map(&:to_s)
|
94
|
+
end
|
95
|
+
|
69
96
|
def steps
|
70
97
|
base_steps = ['Create policy', 'SCAP Content', 'Schedule']
|
71
98
|
base_steps << 'Locations' if SETTINGS[:locations_enabled]
|
@@ -111,13 +138,13 @@ module ForemanOpenscap
|
|
111
138
|
|
112
139
|
def used_location_ids
|
113
140
|
Location.joins(:taxable_taxonomies).where(
|
114
|
-
'taxable_taxonomies.taxable_type' => '
|
141
|
+
'taxable_taxonomies.taxable_type' => 'ForemanOpenscap::Policy',
|
115
142
|
'taxable_taxonomies.taxable_id' => id).pluck("#{Location.arel_table.name}.id")
|
116
143
|
end
|
117
144
|
|
118
145
|
def used_organization_ids
|
119
146
|
Organization.joins(:taxable_taxonomies).where(
|
120
|
-
'taxable_taxonomies.taxable_type' => '
|
147
|
+
'taxable_taxonomies.taxable_type' => 'ForemanOpenscap::Policy',
|
121
148
|
'taxable_taxonomies.taxable_id' => id).pluck("#{Location.arel_table.name}.id")
|
122
149
|
end
|
123
150
|
|
@@ -130,7 +157,7 @@ module ForemanOpenscap
|
|
130
157
|
end
|
131
158
|
|
132
159
|
def unassign_hosts(hosts)
|
133
|
-
host_asset_ids =
|
160
|
+
host_asset_ids = ForemanOpenscap::Asset.where(:assetable_type => 'Host::Base', :assetable_id => hosts.map(&:id)).pluck(:id)
|
134
161
|
self.asset_ids = self.asset_ids - host_asset_ids
|
135
162
|
end
|
136
163
|
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
|
3
|
+
module ForemanOpenscap
|
4
|
+
class DataStreamValidator < ActiveModel::Validator
|
5
|
+
def validate(scap_content)
|
6
|
+
return unless scap_content.scap_file_changed?
|
7
|
+
|
8
|
+
unless SmartProxy.with_features('Openscap').any?
|
9
|
+
scap_content.errors.add(:base, _('No Proxy with OpenScap features'))
|
10
|
+
return false
|
11
|
+
end
|
12
|
+
|
13
|
+
if scap_content.proxy_url.nil?
|
14
|
+
scap_content.errors.add(:base, _('No Available Proxy to validate SCAP content'))
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
api = ProxyAPI::Openscap.new(:url => scap_content.proxy_url)
|
20
|
+
errors = api.validate_scap_content(scap_content.scap_file)
|
21
|
+
if errors && errors['errors'].any?
|
22
|
+
errors['errors'].each {|error| scap_content.errors.add(:scap_file, _(error))}
|
23
|
+
return false
|
24
|
+
end
|
25
|
+
rescue *ProxyAPI::AvailableProxy::HTTP_ERRORS => e
|
26
|
+
scap_content.errors.add(:base, _('No available proxy to validate. Returned with error: %s') % e)
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
unless (scap_content.scap_content_profiles.map(&:profile_id) - scap_content.fetch_profiles.keys).empty?
|
32
|
+
scap_content.errors.add(:scap_file, _('Changed file does not include existing SCAP Content profiles'))
|
33
|
+
return false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ScapContent < ActiveRecord::Base
|
39
|
+
include Authorizable
|
40
|
+
include Taxonomix
|
41
|
+
|
42
|
+
attr_accessible :original_filename, :scap_file, :title, :location_ids, :organization_ids
|
43
|
+
|
44
|
+
has_many :scap_content_profiles, :dependent => :destroy
|
45
|
+
has_many :policies
|
46
|
+
|
47
|
+
before_destroy EnsureNotUsedBy.new(:policies)
|
48
|
+
|
49
|
+
validates_with DataStreamValidator
|
50
|
+
validates :title, :presence => true
|
51
|
+
validates :digest, :presence => true
|
52
|
+
validates :scap_file, :presence => true
|
53
|
+
|
54
|
+
after_save :create_profiles
|
55
|
+
before_validation :redigest, :if => lambda { |scap_content| scap_content.persisted? && scap_content.scap_file_changed? }
|
56
|
+
|
57
|
+
scoped_search :on => :title, :complete_value => true
|
58
|
+
scoped_search :on => :original_filename, :complete_value => true, :rename => :filename
|
59
|
+
|
60
|
+
default_scope {
|
61
|
+
with_taxonomy_scope do
|
62
|
+
order("foreman_openscap_scap_contents.title")
|
63
|
+
end
|
64
|
+
}
|
65
|
+
|
66
|
+
def used_location_ids
|
67
|
+
Location.joins(:taxable_taxonomies).where(
|
68
|
+
'taxable_taxonomies.taxable_type' => 'ForemanOpenscap::ScapContent',
|
69
|
+
'taxable_taxonomies.taxable_id' => id).pluck("#{Location.arel_table.name}.id")
|
70
|
+
end
|
71
|
+
|
72
|
+
def used_organization_ids
|
73
|
+
Organization.joins(:taxable_taxonomies).where(
|
74
|
+
'taxable_taxonomies.taxable_type' => 'ForemanOpenscap::ScapContent',
|
75
|
+
'taxable_taxonomies.taxable_id' => id).pluck("#{Location.arel_table.name}.id")
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_label
|
79
|
+
title
|
80
|
+
end
|
81
|
+
|
82
|
+
def digest
|
83
|
+
self[:digest] ||= Digest::SHA256.hexdigest "#{scap_file}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def fetch_profiles
|
87
|
+
api = ProxyAPI::Openscap.new(:url => proxy_url)
|
88
|
+
profiles = api.fetch_policies_for_scap_content(scap_file)
|
89
|
+
profiles
|
90
|
+
end
|
91
|
+
|
92
|
+
def proxy_url
|
93
|
+
@proxy_url ||= SmartProxy.with_features('Openscap').each do |proxy|
|
94
|
+
available = ProxyAPI::AvailableProxy.new(:url => proxy.url)
|
95
|
+
break proxy.url if available.available?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def create_profiles
|
102
|
+
profiles = fetch_profiles
|
103
|
+
profiles.each {|key, title|
|
104
|
+
scap_content_profiles.find_or_create_by_profile_id_and_title(key, title)
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def redigest
|
109
|
+
self[:digest] = Digest::SHA256.hexdigest "#{scap_file}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|