foreman_openscap 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/app/assets/javascripts/foreman_openscap/openscap_proxy.js +7 -0
  4. data/app/assets/javascripts/foreman_openscap/policy_edit.js +15 -0
  5. data/app/controllers/api/v2/compliance/arf_reports_controller.rb +2 -2
  6. data/app/controllers/api/v2/compliance/policies_controller.rb +16 -4
  7. data/app/controllers/api/v2/compliance/scap_contents_controller.rb +2 -2
  8. data/app/controllers/api/v2/compliance/tailoring_files_controller.rb +92 -0
  9. data/app/controllers/concerns/foreman/controller/parameters/policy_api.rb +2 -2
  10. data/app/controllers/concerns/foreman/controller/parameters/tailoring_file.rb +15 -0
  11. data/app/controllers/openscap_proxies_controller.rb +31 -0
  12. data/app/controllers/policies_controller.rb +14 -15
  13. data/app/controllers/scap_contents_controller.rb +0 -10
  14. data/app/controllers/tailoring_files_controller.rb +75 -0
  15. data/app/helpers/compliance_dashboard_helper.rb +2 -2
  16. data/app/helpers/policies_helper.rb +29 -1
  17. data/app/helpers/tailoring_files_helper.rb +5 -0
  18. data/app/lib/proxy_api/openscap.rb +18 -2
  19. data/app/models/concerns/foreman_openscap/data_stream_content.rb +43 -0
  20. data/app/models/concerns/foreman_openscap/host_extensions.rb +1 -1
  21. data/app/models/concerns/foreman_openscap/hostgroup_extensions.rb +8 -0
  22. data/app/models/foreman_openscap/policy.rb +28 -3
  23. data/app/models/foreman_openscap/scap_content.rb +4 -72
  24. data/app/models/foreman_openscap/scap_content_profile.rb +2 -0
  25. data/app/models/foreman_openscap/tailoring_file.rb +19 -0
  26. data/app/services/foreman_openscap/openscap_proxy_version_check.rb +63 -0
  27. data/app/validators/foreman_openscap/data_stream_validator.rb +44 -0
  28. data/app/views/api/v2/compliance/policies/base.json.rabl +2 -1
  29. data/app/views/api/v2/compliance/tailoring_files/base.json.rabl +6 -0
  30. data/app/views/api/v2/compliance/tailoring_files/index.json.rabl +3 -0
  31. data/app/views/api/v2/compliance/tailoring_files/main.json.rabl +5 -0
  32. data/app/views/api/v2/compliance/tailoring_files/show.json.rabl +7 -0
  33. data/app/views/arf_reports/_list.html.erb +3 -2
  34. data/app/views/dashboard/_compliance_host_reports_widget.html.erb +3 -3
  35. data/app/views/policies/_form.html.erb +9 -0
  36. data/app/views/policies/_list.html.erb +16 -4
  37. data/app/views/policies/_tailoring_file_selected.html.erb +3 -0
  38. data/app/views/policies/steps/_scap_content_form.html.erb +8 -0
  39. data/app/views/policies/welcome.html.erb +12 -13
  40. data/app/views/scap_contents/_list.html.erb +1 -1
  41. data/app/views/scap_contents/welcome.html.erb +14 -13
  42. data/app/views/smart_proxies/_openscap_spool.html.erb +9 -0
  43. data/app/views/smart_proxies/plugins/_openscap.html.erb +12 -0
  44. data/app/views/tailoring_files/_form.html.erb +25 -0
  45. data/app/views/tailoring_files/_list.html.erb +29 -0
  46. data/app/views/tailoring_files/edit.html.erb +3 -0
  47. data/app/views/tailoring_files/index.html.erb +3 -0
  48. data/app/views/tailoring_files/new.html.erb +3 -0
  49. data/app/views/tailoring_files/welcome.html.erb +21 -0
  50. data/config/routes.rb +22 -0
  51. data/db/migrate/20161109155255_create_tailoring_files.rb +23 -0
  52. data/db/migrate/20161223153249_add_permissions_to_arf_report.rb +11 -0
  53. data/lib/foreman_openscap/engine.rb +30 -5
  54. data/lib/foreman_openscap/version.rb +1 -1
  55. data/test/factories/policy_factory.rb +2 -0
  56. data/test/factories/scap_content_related.rb +7 -0
  57. data/test/files/tailoring_files/ssg-firefox-ds-tailoring-2.xml +23 -0
  58. data/test/files/tailoring_files/ssg-firefox-ds-tailoring.xml +31 -0
  59. data/test/functional/api/v2/compliance/policies_controller_test.rb +35 -8
  60. data/test/functional/api/v2/compliance/scap_contents_controller_test.rb +1 -1
  61. data/test/functional/api/v2/compliance/tailoring_files_controller_test.rb +63 -0
  62. data/test/functional/openscap_proxies_controller_test.rb +14 -0
  63. data/test/functional/tailoring_files_controller_test.rb +38 -0
  64. data/test/test_plugin_helper.rb +18 -24
  65. data/test/unit/openscap_host_test.rb +11 -1
  66. data/test/unit/policy_test.rb +26 -0
  67. data/test/unit/services/tailoring_files_proxy_check_test.rb +27 -0
  68. data/test/unit/tailoring_file_test.rb +26 -0
  69. metadata +59 -20
@@ -1,8 +1,8 @@
1
1
  module ComplianceDashboardHelper
2
2
 
3
3
  def latest_compliance_headers
4
- string = "<th>#{_("Host")}</th>"
5
- string += "<th>#{_("Policy")}</th>"
4
+ string = "<th class='col-md-7'>#{_("Host")}</th>"
5
+ string += "<th class='col-md-3'>#{_("Policy")}</th>"
6
6
  # TRANSLATORS: initial character of Passed
7
7
  string += translated_header(s_('Passed|P'), _('Passed'))
8
8
  # TRANSLATORS: initial character of Failed
@@ -5,8 +5,16 @@ module PoliciesHelper
5
5
  return []
6
6
  end
7
7
 
8
+ def policy_profile_from_scap_content(policy)
9
+ policy.scap_content_profile.nil? ? "Default" : policy.scap_content_profile.title
10
+ end
11
+
12
+ def effective_policy_profile(policy)
13
+ policy.tailoring_file ? policy.tailoring_file_profile.title : policy_profile_from_scap_content(policy)
14
+ end
15
+
8
16
  def scap_content_selector(form)
9
- scap_contents = ::ForemanOpenscap::ScapContent.all
17
+ scap_contents = ::ForemanOpenscap::ScapContent.authorized(:view_scap_contents).all
10
18
  if scap_contents.length > 1
11
19
  select_f form, :scap_content_id, scap_contents, :id, :title,
12
20
  {:include_blank => _("Choose existing SCAP Content")},
@@ -38,6 +46,26 @@ module PoliciesHelper
38
46
  end
39
47
  end
40
48
 
49
+ def tailoring_file_selector(form)
50
+ select_f form, :tailoring_file_id, ForemanOpenscap::TailoringFile.all.authorized(:view_tailoring_files), :id, :name,
51
+ { :include_blank => _('Choose Tailoring File') },
52
+ { :label => _('Tailoring File'),
53
+ :onchange => 'tailoring_file_selected(this)',
54
+ :'data-url' => method_path('tailoring_file_selected') }
55
+ end
56
+
57
+ def tailoring_file_profile_selector(form, tailoring_file)
58
+ if tailoring_file
59
+ select_f form, :tailoring_file_profile_id, tailoring_file.scap_content_profiles, :id, :title,
60
+ { :selected => tailoring_file.scap_content_profiles.first.id },
61
+ { :label => _("XCCDF Profile in Tailoring File"),
62
+ :help_inline => _("This profile will be used to override the one from scap content") }
63
+ else
64
+ # to make sure tailoring profile id is nil when tailoring file is deselected
65
+ form.hidden_field(:tailoring_file_profile_id, :value => nil)
66
+ end
67
+ end
68
+
41
69
  def submit_or_cancel_policy(form, overwrite = nil, args = { })
42
70
  args[:cancel_path] ||= send("#{controller_name}_path")
43
71
  content_tag(:div, :class => "clearfix") do
@@ -0,0 +1,5 @@
1
+ module TailoringFilesHelper
2
+ def run_tailoring_proxy_check
3
+ ForemanOpenscap::OpenscapProxyVersionCheck.new.run
4
+ end
5
+ end
@@ -4,14 +4,24 @@ module ::ProxyAPI
4
4
  @url = args[:url] + '/compliance/'
5
5
  super args
6
6
  @connect_params[:headers].merge!(:content_type => :xml)
7
+ @connect_params[:timeout] = timeout
7
8
  end
8
9
 
9
10
  def fetch_policies_for_scap_content(scap_file)
10
11
  parse(post(scap_file, "scap_content/policies"))
11
12
  end
12
13
 
13
- def validate_scap_content(scap_file)
14
- parse(post(scap_file, "scap_content/validator"))
14
+ def fetch_profiles_for_tailoring_file(scap_file)
15
+ parse(post(scap_file, "tailoring_file/profiles"))
16
+ end
17
+
18
+ def validate_scap_file(scap_file, type)
19
+ parse(post(scap_file, "scap_file/validator/#{type}"))
20
+ rescue RestClient::RequestTimeout => e
21
+ raise ::ProxyAPI::ProxyException.new(url, e, N_("Request timed out. Please try increasing Settings -> proxy_request_timeout"))
22
+ rescue RestClient::ResourceNotFound => e
23
+ raise ::ProxyAPI::ProxyException.new(url, e,
24
+ N_("Could not validate %s. Please make sure you have appropriate proxy version to use this functionality") % scap_file.class)
15
25
  end
16
26
 
17
27
  def policy_html_guide(scap_file, policy)
@@ -46,5 +56,11 @@ module ::ProxyAPI
46
56
  false
47
57
  end
48
58
  end
59
+
60
+ private
61
+
62
+ def timeout
63
+ Setting[:proxy_request_timeout] && Setting[:proxy_request_timeout] > 120 ? Setting[:proxy_request_timeout] : 120
64
+ end
49
65
  end
50
66
  end
@@ -0,0 +1,43 @@
1
+ module ForemanOpenscap
2
+ module DataStreamContent
3
+ require 'digest/sha2'
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ validates :digest, :presence => true
9
+ validates :scap_file, :presence => true
10
+
11
+ validates_with ForemanOpenscap::DataStreamValidator
12
+
13
+ after_save :create_profiles
14
+
15
+ before_validation :redigest, :if => lambda { |ds_content| ds_content.persisted? && ds_content.scap_file_changed? }
16
+ before_destroy ActiveRecord::Base::EnsureNotUsedBy.new(:policies)
17
+ end
18
+
19
+ def proxy_url
20
+ @proxy_url ||= SmartProxy.with_features('Openscap').find do |proxy|
21
+ available = ProxyAPI::AvailableProxy.new(:url => proxy.url)
22
+ available.available?
23
+ end.try(:url)
24
+ @proxy_url
25
+ end
26
+
27
+ def digest
28
+ self[:digest] ||= Digest::SHA256.hexdigest(scap_file.to_s)
29
+ end
30
+
31
+ private
32
+
33
+ def redigest
34
+ self[:digest] = Digest::SHA256.hexdigest(scap_file.to_s)
35
+ end
36
+
37
+ def create_profiles
38
+ fetch_profiles.each do |key, title|
39
+ ScapContentProfile.where(:profile_id => key, :title => title, "#{self.class.to_s.demodulize.underscore}_id".to_sym => id).first_or_create
40
+ end
41
+ end
42
+ end
43
+ end
@@ -67,7 +67,7 @@ module ForemanOpenscap
67
67
  end
68
68
 
69
69
  def combined_policies
70
- combined = self.hostgroup ? self.policies + self.hostgroup.policies : self.policies
70
+ combined = self.hostgroup ? self.policies + self.hostgroup.policies + self.hostgroup.inherited_policies : self.policies
71
71
  combined.uniq
72
72
  end
73
73
 
@@ -8,6 +8,14 @@ module ForemanOpenscap
8
8
  has_many :policies, :through => :asset_policies, :class_name => "::ForemanOpenscap::Policy"
9
9
  end
10
10
 
11
+ def inherited_policies
12
+ return [] unless parent
13
+
14
+ ancestors.inject([]) do |policies, hostgroup|
15
+ policies += hostgroup.policies
16
+ end.uniq
17
+ end
18
+
11
19
  unless defined?(Katello::System)
12
20
  private
13
21
 
@@ -6,6 +6,8 @@ module ForemanOpenscap
6
6
 
7
7
  belongs_to :scap_content
8
8
  belongs_to :scap_content_profile
9
+ belongs_to :tailoring_file
10
+ belongs_to :tailoring_file_profile, :class_name => ForemanOpenscap::ScapContentProfile
9
11
  has_many :policy_arf_reports
10
12
  has_many :arf_reports, :through => :policy_arf_reports, :dependent => :destroy
11
13
  has_many :asset_policies
@@ -28,7 +30,7 @@ module ForemanOpenscap
28
30
  validates :scap_content_id, presence: true, if: Proc.new { |policy| policy.should_validate?('SCAP Content') }
29
31
  validates :scap_content_profile_id, presence: true, if: Proc.new { |policy| policy.should_validate?('SCAP Content') }
30
32
 
31
- validate :valid_cron_line, :valid_weekday, :valid_day_of_month
33
+ validate :valid_cron_line, :valid_weekday, :valid_day_of_month, :valid_tailoring, :valid_tailoring_profile
32
34
 
33
35
  after_save :assign_policy_to_hostgroups
34
36
  # before_destroy - ensure that the policy has no hostgroups, or classes
@@ -166,9 +168,11 @@ module ForemanOpenscap
166
168
  def to_enc
167
169
  {
168
170
  'id' => self.id,
169
- 'profile_id' => self.scap_content_profile.try(:profile_id) || '',
171
+ 'profile_id' => profile_for_scan,
170
172
  'content_path' => "/var/lib/openscap/content/#{self.scap_content.digest}.xml",
171
- 'download_path' => "/compliance/policies/#{self.id}/content" # default to proxy path
173
+ 'tailoring_path' => tailoring_file ? "/var/lib/openscap/tailoring/#{self.tailoring_file.digest}.xml" : '',
174
+ 'download_path' => "/compliance/policies/#{self.id}/content", # default to proxy path
175
+ 'tailoring_download_path' => "/compliance/policies/#{self.id}/tailoring"
172
176
  }.merge(period_enc)
173
177
  end
174
178
 
@@ -273,6 +277,17 @@ module ForemanOpenscap
273
277
  end
274
278
  end
275
279
 
280
+ def valid_tailoring
281
+ errors.add(:tailoring_file_id, _("must be present when tailoring file profile present")) if tailoring_file_profile_id && !tailoring_file_id
282
+ errors.add(:tailoring_file_profile_id, _("must be present when tailoring file present")) if !tailoring_file_profile_id && tailoring_file_id
283
+ end
284
+
285
+ def valid_tailoring_profile
286
+ if tailoring_file && tailoring_file_profile && !ScapContentProfile.where(:tailoring_file_id => tailoring_file_id).include?(tailoring_file_profile)
287
+ errors.add(:tailoring_file_profile, _("does not come from selected tailoring file"))
288
+ end
289
+ end
290
+
276
291
  def assign_policy_to_hostgroups
277
292
  if hostgroups.any?
278
293
  puppetclass = find_scap_puppetclass
@@ -283,6 +298,16 @@ module ForemanOpenscap
283
298
  end
284
299
  end
285
300
 
301
+ def profile_for_scan
302
+ if tailoring_file_profile
303
+ tailoring_file_profile.profile_id
304
+ elsif scap_content_profile
305
+ scap_content_profile.profile_id
306
+ else
307
+ ''
308
+ end
309
+ end
310
+
286
311
  def find_scap_puppetclass
287
312
  Puppetclass.find_by_name(SCAP_PUPPET_CLASS)
288
313
  end
@@ -1,56 +1,13 @@
1
- require 'digest/sha2'
2
-
3
1
  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
2
  class ScapContent < ActiveRecord::Base
39
3
  include Authorizable
40
4
  include Taxonomix
5
+ include DataStreamContent
41
6
 
42
7
  has_many :scap_content_profiles, :dependent => :destroy
43
8
  has_many :policies
44
9
 
45
- before_destroy EnsureNotUsedBy.new(:policies)
46
-
47
- validates_with DataStreamValidator
48
10
  validates :title, :presence => true, :length => { :maximum => 255 }
49
- validates :digest, :presence => true
50
- validates :scap_file, :presence => true
51
-
52
- after_save :create_profiles
53
- before_validation :redigest, :if => lambda { |scap_content| scap_content.persisted? && scap_content.scap_file_changed? }
54
11
 
55
12
  scoped_search :on => :title, :complete_value => true
56
13
  scoped_search :on => :original_filename, :complete_value => true, :rename => :filename
@@ -77,8 +34,9 @@ module ForemanOpenscap
77
34
  title
78
35
  end
79
36
 
80
- def digest
81
- self[:digest] ||= Digest::SHA256.hexdigest "#{scap_file}"
37
+ def as_json(*args)
38
+ hash = super
39
+ hash["scap_file"] = scap_file.to_s.encode('utf-8', :invalid => :replace, :undef => :replace, :replace => '_')
82
40
  end
83
41
 
84
42
  def fetch_profiles
@@ -86,31 +44,5 @@ module ForemanOpenscap
86
44
  profiles = api.fetch_policies_for_scap_content(scap_file)
87
45
  profiles
88
46
  end
89
-
90
- def proxy_url
91
- @proxy_url ||= SmartProxy.with_features('Openscap').find do |proxy|
92
- available = ProxyAPI::AvailableProxy.new(:url => proxy.url)
93
- available.available?
94
- end.try(:url)
95
- @proxy_url
96
- end
97
-
98
- def as_json(*args)
99
- hash = super
100
- hash["scap_file"] = scap_file.to_s.encode('utf-8', :invalid => :replace, :undef => :replace, :replace => '_')
101
- end
102
-
103
- private
104
-
105
- def create_profiles
106
- profiles = fetch_profiles
107
- profiles.each {|key, title|
108
- scap_content_profiles.where(:profile_id => key, :title => title).first_or_create
109
- }
110
- end
111
-
112
- def redigest
113
- self[:digest] = Digest::SHA256.hexdigest "#{scap_file}"
114
- end
115
47
  end
116
48
  end
@@ -2,5 +2,7 @@ module ForemanOpenscap
2
2
  class ScapContentProfile < ActiveRecord::Base
3
3
  belongs_to :scap_content
4
4
  has_many :policies
5
+ belongs_to :tailoring_file
6
+ has_many :tailoring_file_policies, :class_name => ForemanOpenscap::Policy
5
7
  end
6
8
  end
@@ -0,0 +1,19 @@
1
+ module ForemanOpenscap
2
+ class TailoringFile < ActiveRecord::Base
3
+ include Authorizable
4
+ include Taxonomix
5
+ include DataStreamContent
6
+
7
+ has_many :policies
8
+ has_many :scap_content_profiles, :dependent => :destroy
9
+ validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 255 }
10
+
11
+ scoped_search :on => :name, :complete_value => true
12
+ scoped_search :on => :original_filename, :complete_value => true, :rename => :filename
13
+
14
+ def fetch_profiles
15
+ api = ProxyAPI::Openscap.new(:url => proxy_url)
16
+ api.fetch_profiles_for_tailoring_file(scap_file)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,63 @@
1
+ module ForemanOpenscap
2
+ class OpenscapProxyVersionCheck
3
+
4
+ def initialize
5
+ @versions = {}
6
+ @message = ''
7
+ @down = []
8
+ end
9
+
10
+ def run
11
+ @versions = openscap_proxy_versions.select do |key, value|
12
+ Gem::Version.new(value) <= Gem::Version.new("0.6.1")
13
+ end
14
+ self
15
+ end
16
+
17
+ def pass?
18
+ !any_outdated? && !any_unreachable?
19
+ end
20
+
21
+ def any_outdated?
22
+ !@versions.empty?
23
+ end
24
+
25
+ def any_unreachable?
26
+ !@down.empty?
27
+ end
28
+
29
+ def message
30
+ if pass?
31
+ @message
32
+ else
33
+ build_message
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def build_message
40
+ @message = _('This feature is temporarily disabled. ')
41
+ @message << _('The following Smart Proxies need to be updated to unlock the feature: %s. ') % @versions.keys.to_sentence if any_outdated?
42
+ @message << _('The following proxies could not be reached: %s. Please make sure they are available so Foreman can check their versions.') % @down.to_sentence if any_unreachable?
43
+ @message
44
+ end
45
+
46
+ def get_openscap_proxies
47
+ SmartProxy.with_features "Openscap"
48
+ end
49
+
50
+ def openscap_proxy_versions
51
+ get_openscap_proxies.inject({}) do |memo, proxy|
52
+ begin
53
+ status = ProxyStatus::Version.new(proxy).version
54
+ openscap_version = status["modules"]["openscap"]
55
+ memo[proxy.name] = openscap_version
56
+ rescue Foreman::WrappedException
57
+ @down << proxy.name
58
+ end
59
+ memo
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,44 @@
1
+ module ForemanOpenscap
2
+ class DataStreamValidator < ActiveModel::Validator
3
+ def validate(data_stream_content)
4
+ return unless data_stream_content.scap_file_changed?
5
+
6
+ content_type = data_type(data_stream_content)
7
+
8
+ unless SmartProxy.with_features('Openscap').any?
9
+ data_stream_content.errors.add(:base, _('No proxy with OpenSCAP features'))
10
+ return false
11
+ end
12
+
13
+ if data_stream_content.proxy_url.nil?
14
+ data_stream_content.errors.add(:base, _('No available proxy to validate SCAP data stream file'))
15
+ return false
16
+ end
17
+
18
+ begin
19
+ api = ProxyAPI::Openscap.new(:url => data_stream_content.proxy_url)
20
+ errors = api.validate_scap_file(data_stream_content.scap_file, content_type)
21
+ if errors && errors['errors'].any?
22
+ errors['errors'].each { |error| data_stream_content.errors.add(:scap_file, _(error)) }
23
+ return false
24
+ end
25
+ rescue *ProxyAPI::AvailableProxy::HTTP_ERRORS => e
26
+ data_stream_content.errors.add(:base, _('No available proxy to validate. Returned with error: %s') % e)
27
+ return false
28
+ end
29
+
30
+ is_scap_content = content_type == 'scap_content'
31
+
32
+ if is_scap_content && !(data_stream_content.scap_content_profiles.map(&:profile_id) - data_stream_content.fetch_profiles.keys).empty?
33
+ data_stream_content.errors.add(:scap_file, _('Changed file does not include existing SCAP content profiles'))
34
+ return false
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def data_type(data_stream_content)
41
+ data_stream_content.class.to_s.demodulize.underscore
42
+ end
43
+ end
44
+ end