foreman_openscap 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -6
  3. data/app/assets/javascript/foreman_openscap/period_selector.js +3 -0
  4. data/app/assets/javascript/foreman_openscap/policy_edit.js +37 -0
  5. data/app/assets/stylesheets/foreman_openscap/policy.css.scss +7 -0
  6. data/app/controllers/api/v2/{openscap → compliance}/arf_reports_controller.rb +6 -5
  7. data/app/controllers/scaptimony_arf_reports_controller.rb +9 -6
  8. data/app/controllers/scaptimony_policies_controller.rb +72 -11
  9. data/app/{models/scaptimony/asset.rb → controllers/scaptimony_policy_dashboard_controller.rb} +8 -9
  10. data/app/controllers/scaptimony_scap_contents_controller.rb +25 -16
  11. data/app/helpers/concerns/foreman_openscap/hosts_helper_extensions.rb +3 -4
  12. data/app/helpers/scaptimony_policies_helper.rb +22 -0
  13. data/app/helpers/scaptimony_policy_dashboard_helper.rb +43 -0
  14. data/app/helpers/scaptimony_report_dashboard_helper.rb +20 -0
  15. data/app/models/concerns/foreman_openscap/arf_report_extensions.rb +30 -1
  16. data/app/models/concerns/foreman_openscap/asset_extensions.rb +34 -0
  17. data/app/models/concerns/foreman_openscap/host_extensions.rb +38 -7
  18. data/app/models/concerns/foreman_openscap/policy_extensions.rb +214 -3
  19. data/app/models/concerns/foreman_openscap/scap_content_extensions.rb +21 -1
  20. data/app/overrides/hosts/index/host_arf_report.rb +5 -0
  21. data/app/services/scaptimony/policy_dashboard/data.rb +30 -0
  22. data/app/services/scaptimony/policy_dashboard/loader.rb +20 -0
  23. data/app/services/scaptimony/policy_dashboard/manager.rb +32 -0
  24. data/app/services/scaptimony/report_dashboard/data.rb +37 -0
  25. data/app/views/dashboard/_foreman_openscap_host_reports_widget.html.erb +24 -0
  26. data/app/views/dashboard/_foreman_openscap_reports_breakdown_widget.html.erb +3 -0
  27. data/app/views/scaptimony_arf_reports/_host_report.html.erb +8 -0
  28. data/app/views/scaptimony_arf_reports/_list.html.erb +2 -2
  29. data/app/views/scaptimony_arf_reports/show.html.erb +3 -0
  30. data/app/views/scaptimony_policies/_form.html.erb +23 -4
  31. data/app/views/scaptimony_policies/_list.html.erb +2 -4
  32. data/app/views/scaptimony_policies/create.html.erb +2 -0
  33. data/app/views/scaptimony_policies/edit.html.erb +1 -2
  34. data/app/views/scaptimony_policies/index.html.erb +1 -1
  35. data/app/views/scaptimony_policies/new.html.erb +1 -2
  36. data/app/views/scaptimony_policies/select_multiple_hosts.html.erb +8 -0
  37. data/app/views/scaptimony_policies/show.html.erb +3 -0
  38. data/app/views/scaptimony_policies/steps/_create_policy_form.html.erb +5 -0
  39. data/app/views/scaptimony_policies/steps/_hostgroups_form.html.erb +7 -0
  40. data/app/views/scaptimony_policies/steps/_locations_form.html.erb +13 -0
  41. data/app/views/scaptimony_policies/steps/_organizations_form.html.erb +13 -0
  42. data/app/views/scaptimony_policies/steps/_scap_content_form.html.erb +21 -0
  43. data/app/views/scaptimony_policies/steps/_schedule_form.html.erb +11 -0
  44. data/app/views/scaptimony_policies/steps/_step_form.html.erb +11 -0
  45. data/app/views/scaptimony_policy_dashboard/_policy_chart_widget.html.erb +4 -0
  46. data/app/views/scaptimony_policy_dashboard/_policy_reports.html.erb +24 -0
  47. data/app/views/scaptimony_policy_dashboard/_policy_status_widget.html.erb +12 -0
  48. data/app/views/scaptimony_policy_dashboard/index.html.erb +26 -0
  49. data/app/views/scaptimony_scap_contents/_form.html.erb +8 -0
  50. data/app/views/scaptimony_scap_contents/_list.html.erb +8 -6
  51. data/config/routes.rb +24 -6
  52. data/db/seeds.d/openscap_feature.rb +2 -0
  53. data/lib/foreman_openscap/engine.rb +18 -12
  54. data/lib/foreman_openscap/helper.rb +1 -7
  55. data/lib/foreman_openscap/version.rb +1 -1
  56. metadata +57 -31
  57. data/app/assets/javascript/policy_edit.js +0 -14
  58. data/app/overrides/dashboard/index/sample_override.html.erb.deface +0 -4
  59. data/db/migrate/20141017172055_create_scaptimony_auditable_hosts.rb +0 -9
@@ -0,0 +1,43 @@
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
+ module ScaptimonyPolicyDashboardHelper
12
+ Colors = {
13
+ :compliant_hosts => '#89A54E',
14
+ :incompliant_hosts => '#AA4643',
15
+ :inconclusive_hosts => '#DB843D',
16
+ :report_missing => '#92A8CD',
17
+ }
18
+
19
+ def policy_widget_list
20
+ Scaptimony::PolicyDashboard::Manager.widgets
21
+ end
22
+
23
+ def host_breakdown_chart(report, options = {})
24
+ data = []
25
+ [[:compliant_hosts, _('Compliant hosts')],
26
+ [:incompliant_hosts, _('Incompliant hosts')],
27
+ [:inconclusive_hosts, _('Inconclusive')],
28
+ [:report_missing, _('Not audited')],
29
+ ].each { |i|
30
+ data << {:label => i[1], :data => report[i[0]], :color => Colors[i[0]]}
31
+ }
32
+ flot_pie_chart 'overview', _('Compliance Status'), data, options
33
+ end
34
+
35
+ def status_link(name, label, path)
36
+ content_tag :li do
37
+ content_tag(:i, raw('&nbsp;'), :class=>'label', :style => 'background-color:' + Colors[label]) +
38
+ raw('&nbsp;') +
39
+ link_to(name, path, :class=>'dashboard-links') +
40
+ content_tag(:h4, @report[label])
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ module ScaptimonyReportDashboardHelper
2
+ Colors = {
3
+ :passed => '#89A54E',
4
+ :failed => '#AA4643',
5
+ :othered => '#DB843D',
6
+ }
7
+
8
+
9
+ def reports_breakdown_chart(report, options = {})
10
+ data = []
11
+ [[:failed, _('Failed')],
12
+ [:passed, _('Passed')],
13
+ [:othered, _('Othered')],
14
+ ].each { |i|
15
+ data << {:label => i[1], :data => report[i[0]], :color => Colors[i[0]]}
16
+ }
17
+ flot_pie_chart 'overview', _('Compliance reports breakdown'), data, options
18
+ end
19
+
20
+ end
@@ -13,8 +13,37 @@ require 'scaptimony/arf_report'
13
13
  module ForemanOpenscap
14
14
  module ArfReportExtensions
15
15
  extend ActiveSupport::Concern
16
+ include Taxonomix
16
17
  included do
17
- scoped_search :in => :asset, :on => :name, :complete_value => :true, :rename => "host"
18
+ has_one :host, :through => :asset, :as => :assetable, :source => :assetable, :source_type => 'Host::Base'
19
+
20
+ after_save :assign_locations_organizations
21
+
22
+ scope :hosts, lambda { includes(:policy, :arf_report_breakdown) }
23
+ scope :latest, lambda { includes(:host, :policy, :arf_report_breakdown).limit(5).order("scaptimony_arf_reports.created_at DESC") }
24
+
25
+ scoped_search :in => :host, :on => :name, :complete_value => :true, :rename => "host"
26
+
27
+ default_scope {
28
+ with_taxonomy_scope do
29
+ order("scaptimony_arf_reports.created_at DESC")
30
+ end
31
+ }
32
+ end
33
+
34
+ def assign_locations_organizations
35
+ if host
36
+ self.locations = [host.location]
37
+ self.organizations = [host.organization]
38
+ end
39
+ end
40
+
41
+ def failed?
42
+ failed > 0
43
+ end
44
+
45
+ def passed?
46
+ passed > 0 && !failed?
18
47
  end
19
48
  end
20
49
  end
@@ -0,0 +1,34 @@
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
+ require 'scaptimony/asset'
12
+
13
+ module ForemanOpenscap
14
+ module AssetExtensions
15
+ extend ActiveSupport::Concern
16
+ included do
17
+ belongs_to :assetable, :polymorphic => true
18
+ scope :hosts, where(:assetable_type => 'Host::Base')
19
+ end
20
+
21
+ def host
22
+ fetch_asset('Host::Base')
23
+ end
24
+
25
+ def name
26
+ assetable.name
27
+ end
28
+
29
+ private
30
+ def fetch_asset(type)
31
+ assetable if assetable_type == type
32
+ end
33
+ end
34
+ end
@@ -3,21 +3,52 @@ require 'scaptimony/asset'
3
3
  module ForemanOpenscap
4
4
  module HostExtensions
5
5
  extend ActiveSupport::Concern
6
+ ::Host::Managed::Jail.allow :policies_enc
6
7
 
7
8
  included do
8
- has_one :auditable_host, :class_name => "::Scaptimony::AuditableHost",
9
- :foreign_key => :host_id, :inverse_of => :host
9
+ has_one :asset, :as => :assetable, :class_name => "::Scaptimony::Asset"
10
+ has_many :asset_policies, :through => :asset, :class_name => "::Scaptimony::AssetPolicy"
11
+ has_many :policies, :through => :asset_policies, :class_name => "::Scaptimony::Policy"
12
+ has_many :arf_reports, :through => :asset, :class_name => '::Scaptimony::ArfReport'
13
+
14
+ scoped_search :in => :policies, :on => :name, :complete_value => true, :rename => :'compliance_policy',
15
+ :only_explicit => true, :operators => ['= ', '!= '], :ext_method => :search_by_policy_name
16
+ scoped_search :in => :policies, :on => :name, :complete_value => true, :rename => :'compliance_report_missing_for',
17
+ :only_explicit => true, :operators => ['= ', '!= '], :ext_method => :search_by_missing_arf
18
+ end
19
+
20
+ def get_asset
21
+ Scaptimony::Asset.where(:assetable_type => 'Host::Base', :assetable_id => id).first_or_create!
10
22
  end
11
23
 
12
- # create or overwrite instance methods...
13
- def instance_method_name
24
+ def policies_enc
25
+ self.policies.map(&:to_enc).to_json
14
26
  end
15
27
 
16
28
  module ClassMethods
17
- # create or overwrite class methods...
18
- def class_method_name
29
+ def search_by_policy_name(key, operator, policy_name)
30
+ cond = sanitize_sql_for_conditions(["scaptimony_policies.name #{operator} ?", value_to_sql(operator, policy_name)])
31
+ { :conditions => Host::Managed.arel_table[:id].in(
32
+ Host::Managed.select(Host::Managed.arel_table[:id]).joins(:policies).where(cond).ast
33
+ ).to_sql }
19
34
  end
20
- end
21
35
 
36
+ def search_by_missing_arf(key, operator, policy_name)
37
+ cond = sanitize_sql_for_conditions(["scaptimony_policies.name #{operator} ?", value_to_sql(operator, policy_name)])
38
+ { :conditions => Host::Managed.arel_table[:id].in(
39
+ Host::Managed.select(Host::Managed.arel_table[:id])
40
+ .joins(:policies)
41
+ .where(cond)
42
+ .where('scaptimony_assets.id not in (
43
+ SELECT distinct scaptimony_arf_reports.asset_id
44
+ FROM scaptimony_arf_reports
45
+ WHERE scaptimony_arf_reports.asset_id = scaptimony_assets.id
46
+ AND scaptimony_arf_reports.policy_id = scaptimony_policies.id)
47
+ ')
48
+ .ast
49
+ ).to_sql
50
+ }
51
+ end
52
+ end
22
53
  end
23
54
  end
@@ -8,13 +8,224 @@
8
8
  # along with this software; if not, see http://www.gnu.org/licenses/gpl.txt
9
9
  #
10
10
 
11
- require 'scaptimony/policy'
12
-
13
11
  module ForemanOpenscap
14
12
  module PolicyExtensions
15
13
  extend ActiveSupport::Concern
14
+
15
+ include Authorizable
16
+ include Taxonomix
17
+
16
18
  included do
17
- scoped_search :on => :name, :complete_value => true
19
+ attr_accessible :location_ids, :organization_ids, :current_step, :hostgroup_ids
20
+ attr_writer :current_step
21
+
22
+ SCAP_PUPPET_CLASS = 'foreman_scap_client'
23
+ POLICIES_CLASS_PARAMETER = 'policies'
24
+ SERVER_CLASS_PARAMETER = 'server'
25
+
26
+ validates :name, :presence => true, :uniqueness => true, :format => { without: /\s/ }
27
+ validate :ensure_needed_puppetclasses
28
+ validates :period, :inclusion => {:in => %w[weekly monthly custom]}, :if => Proc.new { | policy | policy.step_index > 3 }
29
+ validates :weekday, :inclusion => {:in => Date::DAYNAMES.map(&:downcase)},
30
+ :if => Proc.new { | policy | policy.step_index > 3 && policy.period == 'weekly' }
31
+ validates :day_of_month, :numericality => {:greater_than => 0, :less_than => 32},
32
+ :if => Proc.new { | policy | policy.step_index > 3 && policy.period == 'monthly' }
33
+ validate :valid_cron_line
34
+ validate :ensure_period_specification_present
35
+
36
+
37
+ after_save :assign_policy_to_hostgroups
38
+ # before_destroy - ensure that the policy has no hostgroups, or classes
39
+
40
+ default_scope {
41
+ with_taxonomy_scope do
42
+ order("scaptimony_policies.name")
43
+ end
44
+ }
45
+ end
46
+
47
+ def hostgroup_ids
48
+ assets.where(:assetable_type => 'Hostgroup').pluck(:assetable_id)
49
+ end
50
+
51
+ def hostgroup_ids=(ids)
52
+ ids.reject(&:empty?).map do |id|
53
+ self.assets << assets.where(:assetable_type => 'Hostgroup', :assetable_id => id).first_or_create!
54
+ end
55
+ end
56
+
57
+ def hostgroups
58
+ Hostgroup.find(hostgroup_ids)
59
+ end
60
+
61
+ def hostgroups=(hostgroups)
62
+ hostgroup_ids = hostgroups.map(&:id).map(&:to_s)
63
+ end
64
+
65
+ def steps
66
+ base_steps = ['Create policy', 'SCAP Content', 'Schedule']
67
+ base_steps << 'Locations' if SETTINGS[:locations_enabled]
68
+ base_steps << 'Organizations' if SETTINGS[:organizations_enabled]
69
+ base_steps << 'Hostgroups' #always be last.
70
+ end
71
+
72
+ def current_step
73
+ @current_step || steps.first
74
+ end
75
+
76
+ def previous_step
77
+ steps[steps.index(current_step) - 1]
78
+ end
79
+
80
+ def next_step
81
+ steps[steps.index(current_step) + 1 ]
82
+ end
83
+
84
+ def rewind_step
85
+ @current_step = previous_step
86
+ end
87
+
88
+ def first_step?
89
+ current_step == steps.first
90
+ end
91
+
92
+ def last_step?
93
+ current_step == steps.last
94
+ end
95
+
96
+ def wizard_completed?
97
+ current_step.blank?
98
+ end
99
+
100
+ def step_index
101
+ wizard_completed? ? steps.index(steps.last) : steps.index(current_step) + 1
102
+ end
103
+
104
+ def scan_name
105
+ name
106
+ end
107
+
108
+ def used_location_ids
109
+ Location.joins(:taxable_taxonomies).where(
110
+ 'taxable_taxonomies.taxable_type' => 'Scaptimony::Policy',
111
+ 'taxable_taxonomies.taxable_id' => id).pluck("#{Location.arel_table.name}.id")
112
+ end
113
+
114
+ def used_organization_ids
115
+ Organization.joins(:taxable_taxonomies).where(
116
+ 'taxable_taxonomies.taxable_type' => 'Scaptimony::Policy',
117
+ 'taxable_taxonomies.taxable_id' => id).pluck("#{Location.arel_table.name}.id")
118
+ end
119
+
120
+ def used_hostgroup_ids
121
+ []
122
+ end
123
+
124
+ def assign_hosts(hosts)
125
+ assign_assets hosts.map &:get_asset
126
+ end
127
+
128
+ def to_enc
129
+ {
130
+ 'id' => self.id,
131
+ 'profile_id' => self.scap_content_profile.try(:profile_id) || '',
132
+ 'content_path' => "/var/lib/openscap/content/#{self.scap_content.digest}.xml",
133
+ }.merge(period_enc)
134
+ end
135
+
136
+ private
137
+
138
+ def period_enc
139
+ # get crontab expression as an array (minute hour day_of_month month day_of_week)
140
+ cron_parts = case period
141
+ when 'weekly'
142
+ [ '0', '1', '*', '*', weekday_number.to_s ]
143
+ when 'monthly'
144
+ [ '0', '1', day_of_month.to_s, '*', '*']
145
+ when 'custom'
146
+ cron_line_split
147
+ else
148
+ raise 'invalid period specification'
149
+ end
150
+
151
+ {
152
+ 'minute' => cron_parts[0],
153
+ 'hour' => cron_parts[1],
154
+ 'monthday' => cron_parts[2],
155
+ 'month' => cron_parts[3],
156
+ 'weekday' => cron_parts[4],
157
+ }
158
+ end
159
+
160
+ def weekday_number
161
+ # 0 is sunday, 1 is monday in cron, while DAYS_INTO_WEEK has 0 as monday, 6 as sunday
162
+ (Date::DAYS_INTO_WEEK.with_indifferent_access[weekday] + 1) % 7
163
+ end
164
+
165
+ def ensure_needed_puppetclasses
166
+ unless puppetclass = Puppetclass.find_by_name(SCAP_PUPPET_CLASS)
167
+ errors[:base] << _("Required Puppet class %{class} is not found, please ensure it imported first.") % {:class => SCAP_PUPPET_CLASS}
168
+ return false
169
+ end
170
+
171
+ unless policies_param = puppetclass.class_params.where(:key => POLICIES_CLASS_PARAMETER).first
172
+ errors[:base] << _("Puppet class %{class} does not have %{parameter} class parameter.") % {:class => SCAP_PUPPET_CLASS, :parameter => POLICIES_CLASS_PARAMETER}
173
+ return false
174
+ end
175
+
176
+ policies_param.override = true
177
+ policies_param.key_type = 'array'
178
+ policies_param.default_value = '<%= @host.policies_enc %>'
179
+
180
+ if policies_param.changed? && !policies_param.save
181
+ errors[:base] << _("%{parameter} class parameter for class %{class} could not be configured.") % {:class => SCAP_PUPPET_CLASS, :parameter => POLICIES_CLASS_PARAMETER}
182
+ return false
183
+ end
184
+ end
185
+
186
+ def cron_line_split
187
+ cron_line.split(' ')
188
+ end
189
+
190
+ def valid_cron_line
191
+ return true if period != 'custom' || step_index != 4
192
+
193
+ unless cron_line_split.size == 5
194
+ errors[:base] << _("Cron line does not consist of 5 parts separated by space")
195
+ return false
196
+ end
197
+ end
198
+
199
+ def ensure_period_specification_present
200
+ return true if period.blank? || step_index != 4
201
+
202
+ error = nil
203
+ error = _("You must fill weekday") if weekday.blank? && period == 'weekday'
204
+ error = _("You must fill day of month") if day_of_month.blank? && period == 'monthly'
205
+ error = _("You must fill cron line") if cron_line.blank? && period == 'custom'
206
+ if error
207
+ errors[:base] << error
208
+ return false
209
+ end
210
+ end
211
+
212
+ def assign_policy_to_hostgroups
213
+ if hostgroups.any?
214
+ puppetclass = Puppetclass.find_by_name(SCAP_PUPPET_CLASS)
215
+ hostgroups.each do |hostgroup|
216
+ hostgroup.puppetclasses << puppetclass unless hostgroup.puppetclasses.include? puppetclass
217
+ populate_overrides(puppetclass, hostgroup)
218
+ end
219
+ end
220
+ end
221
+
222
+ def populate_overrides(puppetclass, hostgroup)
223
+ puppetclass.class_params.where(:override => true, :key => SERVER_CLASS_PARAMETER).each do |override|
224
+ if hostgroup.puppet_proxy && (url = hostgroup.puppet_proxy.url).present?
225
+ lookup_value = LookupValue.where(:match => "hostgroup=#{hostgroup.to_label}", :lookup_key_id => override.id).first_or_initialize
226
+ lookup_value.update_attribute(:value, url)
227
+ end
228
+ end
18
229
  end
19
230
  end
20
231
  end
@@ -13,8 +13,28 @@ require 'scaptimony/scap_content'
13
13
  module ForemanOpenscap
14
14
  module ScapContentExtensions
15
15
  extend ActiveSupport::Concern
16
+ include Authorizable
17
+ include Taxonomix
16
18
  included do
17
- scoped_search :on => :digest
19
+ attr_accessible :location_ids, :organization_ids
20
+
21
+ default_scope {
22
+ with_taxonomy_scope do
23
+ order("scaptimony_scap_contents.title")
24
+ end
25
+ }
26
+ end
27
+
28
+ def used_location_ids
29
+ Location.joins(:taxable_taxonomies).where(
30
+ 'taxable_taxonomies.taxable_type' => 'Scaptimony::ScapContent',
31
+ 'taxable_taxonomies.taxable_id' => id).pluck("#{Location.arel_table.name}.id")
32
+ end
33
+
34
+ def used_organization_ids
35
+ Organization.joins(:taxable_taxonomies).where(
36
+ 'taxable_taxonomies.taxable_type' => 'Scaptimony::ScapContent',
37
+ 'taxable_taxonomies.taxable_id' => id).pluck("#{Location.arel_table.name}.id")
18
38
  end
19
39
  end
20
40
  end