diff --git a/.rubocop.yml b/.rubocop.yml index 6a23fda61..14668aeb0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,3 +21,15 @@ Metrics/LineLength: Style/StringLiterals: Enabled: false + +Style/CollectionMethods: + Enabled: false + +Style/FrozenStringLiteralComment: + Enabled: false + +Rails/HttpPositionalArguments: + Enabled: false + +Style/EmptyMethod: + EnforcedStyle: expanded diff --git a/app/assets/javascripts/foreman_openscap/policy_edit.js b/app/assets/javascripts/foreman_openscap/policy_edit.js index e54831ca3..c051eb53a 100644 --- a/app/assets/javascripts/foreman_openscap/policy_edit.js +++ b/app/assets/javascripts/foreman_openscap/policy_edit.js @@ -13,6 +13,21 @@ function scap_content_selected(element){ }) } +function tailoring_file_selected(element) { + var attrs = attribute_hash(['tailoring_file_id']); + var url = $(element).attr('data-url'); + tfm.tools.showSpinner(); + $.ajax({ + data: attrs, + type: 'post', + url: url, + complete: function() { reloadOnAjaxComplete($(element));}, + success: function(request) { + $('#tailoring_file_profile_select').html(request); + } + }) +} + function previous_step(previous) { $('#policy_current_step').val(previous); $('#new_policy').submit(); diff --git a/app/controllers/api/v2/compliance/policies_controller.rb b/app/controllers/api/v2/compliance/policies_controller.rb index d8f992b25..3192f178b 100644 --- a/app/controllers/api/v2/compliance/policies_controller.rb +++ b/app/controllers/api/v2/compliance/policies_controller.rb @@ -4,7 +4,7 @@ class PoliciesController < ::Api::V2::BaseController include Foreman::Controller::SmartProxyAuth include Foreman::Controller::Parameters::PolicyApi - add_smart_proxy_filters :content, :features => 'Openscap' + add_smart_proxy_filters [:content, :tailoring], :features => 'Openscap' before_filter :find_resource, :except => %w(index create) @@ -46,6 +46,8 @@ def show param :day_of_month, Integer, :desc => N_('Policy schedule day of month (only if period == "monthly")') param :cron_line, String, :desc => N_('Policy schedule cron line (only if period == "custom")') param :hostgroup_ids, Array, :desc => N_('Apply policy to host groups') + param :tailoring_file_id, Integer, :desc => N_('Tailoring file ID') + param :tailoring_file_profile_id, Integer, :desc => N_('Tailoring file profile ID') param_group :taxonomies, ::Api::V2::BaseController end end @@ -83,6 +85,16 @@ def content :filename => @scap_content.original_filename end + api :GET, '/compliance/policies/:id/tailoring', N_("Show a policy's Tailoring file") + param :id, :identifier, :required => true + + def tailoring + @tailoring_file = @policy.tailoring_file + send_data @tailoring_file.scap_file, + :type => 'application/xml', + :filename => @tailoring_file.original_filename + end + private def find_resource not_found and return if params[:id].blank? @@ -91,7 +103,7 @@ def find_resource def action_permission case params[:action] - when 'content' + when 'content', 'tailoring' :view else super diff --git a/app/controllers/api/v2/compliance/tailoring_files_controller.rb b/app/controllers/api/v2/compliance/tailoring_files_controller.rb new file mode 100644 index 000000000..7df857c71 --- /dev/null +++ b/app/controllers/api/v2/compliance/tailoring_files_controller.rb @@ -0,0 +1,84 @@ +module Api::V2 + module Compliance + class TailoringFilesController < ::Api::V2::BaseController + include Foreman::Controller::Parameters::TailoringFile + before_filter :find_resource, :except => %w(index create) + + def resource_name + '::ForemanOpenscap::TailoringFile' + end + + def get_resource + instance_variable_get :"@tailoring_file" or fail 'no resource loaded' + end + + api :GET, '/compliance/tailoring_files', N_('List Tailoring files') + param_group :search_and_pagination, ::Api::V2::BaseController + + def index + @tailoring_files = resource_scope_for_index(:permission => :view_tailoring_files) + end + + api :GET, '/compliance/tailoring_files/:id/xml', N_('Show a Tailoring file as XML') + param :id, :identifier, :required => true + + def xml + send_data @tailoring_file.scap_file, + :type => 'application/xml', + :filename => @tailoring_file.original_filename || "#{@tailoring_file.name}.xml" + end + + api :GET, '/compliance/tailoring_files/:id', N_('Show a Tailoring file') + param :id, :identifier, :required => true + def show + end + + def_param_group :tailoring_file do + param :tailoring_file, Hash, :required => true, :action_aware => true do + param :name, String, :required => true, :desc => N_('Tailoring file name') + param :scap_file, String, :required => true, :desc => N_('XML containing tailoring file') + param :original_filename, String, :desc => N_('Original file name of the XML file') + param_group :taxonomies, ::Api::V2::BaseController + end + end + + api :POST, '/compliance/tailoring_files', N_('Create a Tailoring file') + param_group :tailoring_file, :as => :create + + def create + @tailoring_file = ForemanOpenscap::TailoringFile.new(tailoring_file_params) + process_response @tailoring_file.save + end + + api :PUT, '/compliance/tailoring_files/:id', N_('Update a Tailoring file') + param :id, :identifier, :required => true + param_group :tailoring_file + + def update + process_response @tailoring_file.update_attributes(tailoring_file_params) + end + + api :DELETE, '/compliance/tailoring_files/:id', N_('Deletes a Tailoring file') + param :id, :identifier, :required => true + + def destroy + process_response @tailoring_file.destroy + end + + private + def find_resource + not_found and return if params[:id].blank? + instance_variable_set("@tailoring_file", resource_scope.find(params[:id])) + end + + def action_permission + case params[:action] + when 'xml' + :view + else + super + end + end + end + end +end diff --git a/app/controllers/concerns/foreman/controller/parameters/policy_api.rb b/app/controllers/concerns/foreman/controller/parameters/policy_api.rb index 201355c6d..dbb544be8 100644 --- a/app/controllers/concerns/foreman/controller/parameters/policy_api.rb +++ b/app/controllers/concerns/foreman/controller/parameters/policy_api.rb @@ -4,8 +4,8 @@ module Foreman::Controller::Parameters::PolicyApi class_methods do def filter_params_list [:description, :name, :period, :scap_content_id, :scap_content_profile_id, - :weekday, :day_of_month, :cron_line, :location_ids => [], :organization_ids => [], - :hostgroup_ids => []] + :weekday, :day_of_month, :cron_line, :tailoring_file_id, :tailoring_file_profile_id, + :location_ids => [], :organization_ids => [], :hostgroup_ids => []] end def policy_params_filter diff --git a/app/controllers/concerns/foreman/controller/parameters/tailoring_file.rb b/app/controllers/concerns/foreman/controller/parameters/tailoring_file.rb new file mode 100644 index 000000000..1a4192227 --- /dev/null +++ b/app/controllers/concerns/foreman/controller/parameters/tailoring_file.rb @@ -0,0 +1,15 @@ +module Foreman::Controller::Parameters::TailoringFile + extend ActiveSupport::Concern + + class_methods do + def tailoring_file_params_filter + Foreman::ParameterFilter.new(::ForemanOpenscap::TailoringFile).tap do |filter| + filter.permit :name, :scap_file, :original_filename, :location_ids => [], :organization_ids => [] + end + end + end + + def tailoring_file_params + self.class.tailoring_file_params_filter.filter_params(params, parameter_filter_context) + end +end diff --git a/app/controllers/policies_controller.rb b/app/controllers/policies_controller.rb index 86a6c14c8..0fad9bce5 100644 --- a/app/controllers/policies_controller.rb +++ b/app/controllers/policies_controller.rb @@ -4,16 +4,16 @@ class PoliciesController < ApplicationController before_filter :find_by_id, :only => [:show, :edit, :update, :parse, :destroy] before_filter :find_multiple, :only => [:select_multiple_hosts, :update_multiple_hosts, :disassociate_multiple_hosts, :remove_policy_from_multiple_hosts] + before_filter :find_tailoring_file, :only => [:tailoring_file_selected] def model_of_controller ::ForemanOpenscap::Policy end def index - @policies = resource_base - .search_for(params[:search], :order => params[:order]) - .paginate(:page => params[:page], :per_page => params[:per_page]) - .includes(:scap_content, :scap_content_profile) + @policies = resource_base.search_for(params[:search], :order => params[:order]). + paginate(:page => params[:page], :per_page => params[:per_page]). + includes(:scap_content, :scap_content_profile, :tailoring_file) if @policies.empty? && ForemanOpenscap::ScapContent.unconfigured? redirect_to scap_contents_path end @@ -70,6 +70,11 @@ def scap_content_selected end end + def tailoring_file_selected + @policy ||= ::ForemanOpenscap::Policy.new + render :partial => 'tailoring_file_selected', :locals => { :policy => @policy, :tailoring_file => @tailoring_file } + end + def select_multiple_hosts; end def update_multiple_hosts @@ -104,6 +109,10 @@ def find_by_id @policy = resource_base.find(params[:id]) end + def find_tailoring_file + @tailoring_file = ForemanOpenscap::TailoringFile.find(params[:tailoring_file_id]) if params[:tailoring_file_id].present? + end + def find_multiple # Lets search by name or id and make sure one of them exists first if params[:host_ids].present? @@ -126,7 +135,7 @@ def find_multiple def action_permission case params[:action] - when 'parse' + when 'parse', 'tailoring_file_selected' :view else super diff --git a/app/controllers/tailoring_files_controller.rb b/app/controllers/tailoring_files_controller.rb new file mode 100644 index 000000000..3c14a52e6 --- /dev/null +++ b/app/controllers/tailoring_files_controller.rb @@ -0,0 +1,75 @@ +class TailoringFilesController < ApplicationController + include Foreman::Controller::AutoCompleteSearch + include Foreman::Controller::Parameters::TailoringFile + + before_filter :find_tailoring_file, :only => [:destroy, :update, :edit, :xml] + before_filter :handle_file_upload, :only => [:create, :update] + + def model_of_controller + ::ForemanOpenscap::TailoringFile + end + + def index + @tailoring_files = resource_base.search_for(params[:search], :order => params[:order]) + .paginate(:page => params[:page], :per_page => params[:per_page]) + end + + def new + @tailoring_file = ::ForemanOpenscap::TailoringFile.new + end + + def create + @tailoring_file = ForemanOpenscap::TailoringFile.new(tailoring_file_params) + if @tailoring_file.save + process_success + else + process_error + end + end + + def edit + end + + def update + if @tailoring_file.update_attributes(tailoring_file_params) + process_success + else + process_error + end + end + + def destroy + if @tailoring_file.destroy + process_success + else + process_error :object => @tailoring_file + end + end + + def xml + send_data @tailoring_file.scap_file, + :type => 'application/xml', + :filename => @tailoring_file.original_filename || "#{@tailoring_file.name}.xml" + end + + private + + def find_tailoring_file + @tailoring_file = resource_base.find(params[:id]) + end + + def handle_file_upload + return unless params[:tailoring_file] && raw_file = params[:tailoring_file][:scap_file] + params[:tailoring_file][:original_filename] = raw_file.original_filename + params[:tailoring_file][:scap_file] = raw_file.tempfile.read if raw_file.respond_to?(:tempfile) && raw_file.tempfile.respond_to?(:read) + end + + def action_permission + case params[:action] + when 'xml' + :view + else + super + end + end +end diff --git a/app/helpers/policies_helper.rb b/app/helpers/policies_helper.rb index 787251d42..eef888a1f 100644 --- a/app/helpers/policies_helper.rb +++ b/app/helpers/policies_helper.rb @@ -5,6 +5,14 @@ def profiles_selection return [] end + def policy_profile_from_scap_content(policy) + policy.scap_content_profile.nil? ? "Default" : policy.scap_content_profile.title + end + + def effective_policy_profile(policy) + policy.tailoring_file ? policy.tailoring_file_profile.title : policy_profile_from_scap_content(policy) + end + def scap_content_selector(form) scap_contents = ::ForemanOpenscap::ScapContent.authorized(:view_scap_contents).all if scap_contents.length > 1 @@ -38,6 +46,23 @@ def scap_content_profile_selector(form) end end + def tailoring_file_selector(form) + select_f form, :tailoring_file_id, ForemanOpenscap::TailoringFile.all.authorized(:view_tailoring_files), :id, :name, + { :include_blank => _('Choose Tailoring File') }, + { :label => _('Tailoring File'), + :onchange => 'tailoring_file_selected(this)', + :'data-url' => method_path('tailoring_file_selected') } + end + + def tailoring_file_profile_selector(form, tailoring_file) + if tailoring_file + select_f form, :tailoring_file_profile_id, tailoring_file.scap_content_profiles, :id, :title, + { :selected => tailoring_file.scap_content_profiles.first.id }, + { :label => _("XCCDF Profile in Tailoring File"), + :help_inline => _("This profile will be used to override the one from scap content") } + end + end + def submit_or_cancel_policy(form, overwrite = nil, args = { }) args[:cancel_path] ||= send("#{controller_name}_path") content_tag(:div, :class => "clearfix") do diff --git a/app/lib/proxy_api/openscap.rb b/app/lib/proxy_api/openscap.rb index 718a12b24..b6bde57e4 100644 --- a/app/lib/proxy_api/openscap.rb +++ b/app/lib/proxy_api/openscap.rb @@ -11,10 +11,17 @@ def fetch_policies_for_scap_content(scap_file) parse(post(scap_file, "scap_content/policies")) end - def validate_scap_content(scap_file) - parse(post(scap_file, "scap_content/validator")) + def fetch_profiles_for_tailoring_file(scap_file) + parse(post(scap_file, "tailoring_file/profiles")) + end + + def validate_scap_file(scap_file, type) + parse(post(scap_file, "scap_file/validator/#{type}")) rescue RestClient::RequestTimeout => e raise ::ProxyAPI::ProxyException.new(url, e, N_("Request timed out. Please try increasing Settings -> proxy_request_timeout")) + rescue RestClient::ResourceNotFound => e + raise ::ProxyAPI::ProxyException.new(url, e, + N_("Could not validate %s. Please make sure you have appropriate proxy version to use this functionality") % scap_file.class) end def policy_html_guide(scap_file, policy) diff --git a/app/models/concerns/foreman_openscap/data_stream_content.rb b/app/models/concerns/foreman_openscap/data_stream_content.rb new file mode 100644 index 000000000..e5dd0b7a8 --- /dev/null +++ b/app/models/concerns/foreman_openscap/data_stream_content.rb @@ -0,0 +1,43 @@ +module ForemanOpenscap + module DataStreamContent + require 'digest/sha2' + + extend ActiveSupport::Concern + + included do + validates :digest, :presence => true + validates :scap_file, :presence => true + + validates_with ForemanOpenscap::DataStreamValidator + + after_save :create_profiles + + before_validation :redigest, :if => lambda { |ds_content| ds_content.persisted? && ds_content.scap_file_changed? } + before_destroy ActiveRecord::Base::EnsureNotUsedBy.new(:policies) + end + + def proxy_url + @proxy_url ||= SmartProxy.with_features('Openscap').find do |proxy| + available = ProxyAPI::AvailableProxy.new(:url => proxy.url) + available.available? + end.try(:url) + @proxy_url + end + + def digest + self[:digest] ||= Digest::SHA256.hexdigest(scap_file.to_s) + end + + private + + def redigest + self[:digest] = Digest::SHA256.hexdigest(scap_file.to_s) + end + + def create_profiles + fetch_profiles.each do |key, title| + ScapContentProfile.where(:profile_id => key, :title => title, "#{self.class.to_s.demodulize.underscore}_id".to_sym => id).first_or_create + end + end + end +end diff --git a/app/models/foreman_openscap/policy.rb b/app/models/foreman_openscap/policy.rb index 09830adc5..01beaf700 100644 --- a/app/models/foreman_openscap/policy.rb +++ b/app/models/foreman_openscap/policy.rb @@ -6,6 +6,8 @@ class Policy < ActiveRecord::Base belongs_to :scap_content belongs_to :scap_content_profile + belongs_to :tailoring_file + belongs_to :tailoring_file_profile, :class_name => ForemanOpenscap::ScapContentProfile has_many :policy_arf_reports has_many :arf_reports, :through => :policy_arf_reports, :dependent => :destroy has_many :asset_policies @@ -28,7 +30,7 @@ class Policy < ActiveRecord::Base validates :scap_content_id, presence: true, if: Proc.new { |policy| policy.should_validate?('SCAP Content') } validates :scap_content_profile_id, presence: true, if: Proc.new { |policy| policy.should_validate?('SCAP Content') } - validate :valid_cron_line, :valid_weekday, :valid_day_of_month + validate :valid_cron_line, :valid_weekday, :valid_day_of_month, :valid_tailoring, :valid_tailoring_profile after_save :assign_policy_to_hostgroups # before_destroy - ensure that the policy has no hostgroups, or classes @@ -166,9 +168,11 @@ def unassign_hosts(hosts) def to_enc { 'id' => self.id, - 'profile_id' => self.scap_content_profile.try(:profile_id) || '', + 'profile_id' => profile_for_scan, 'content_path' => "/var/lib/openscap/content/#{self.scap_content.digest}.xml", - 'download_path' => "/compliance/policies/#{self.id}/content" # default to proxy path + 'tailoring_path' => tailoring_file ? "/var/lib/openscap/tailoring/#{self.tailoring_file.digest}.xml" : '', + 'download_path' => "/compliance/policies/#{self.id}/content", # default to proxy path + 'tailoring_download_path' => "/compliance/policies/#{self.id}/tailoring" }.merge(period_enc) end @@ -273,6 +277,17 @@ def valid_day_of_month end end + def valid_tailoring + errors.add(:tailoring_file_id, _("must be present when tailoring file profile present")) if tailoring_file_profile_id && !tailoring_file_id + errors.add(:tailoring_file_profile_id, _("must be present when tailoring file present")) if !tailoring_file_profile_id && tailoring_file_id + end + + def valid_tailoring_profile + if tailoring_file && tailoring_file_profile && !ScapContentProfile.where(:tailoring_file_id => tailoring_file_id).include?(tailoring_file_profile) + errors.add(:tailoring_file_profile, _("does not come from selected tailoring file")) + end + end + def assign_policy_to_hostgroups if hostgroups.any? puppetclass = find_scap_puppetclass @@ -283,6 +298,16 @@ def assign_policy_to_hostgroups end end + def profile_for_scan + if tailoring_file_profile + tailoring_file_profile.profile_id + elsif scap_content_profile + scap_content_profile.profile_id + else + '' + end + end + def find_scap_puppetclass Puppetclass.find_by_name(SCAP_PUPPET_CLASS) end diff --git a/app/models/foreman_openscap/scap_content.rb b/app/models/foreman_openscap/scap_content.rb index ca3c6d89b..e2586c3af 100644 --- a/app/models/foreman_openscap/scap_content.rb +++ b/app/models/foreman_openscap/scap_content.rb @@ -1,56 +1,13 @@ -require 'digest/sha2' - module ForemanOpenscap - class DataStreamValidator < ActiveModel::Validator - def validate(scap_content) - return unless scap_content.scap_file_changed? - - unless SmartProxy.with_features('Openscap').any? - scap_content.errors.add(:base, _('No proxy with OpenSCAP features')) - return false - end - - if scap_content.proxy_url.nil? - scap_content.errors.add(:base, _('No available proxy to validate SCAP content')) - return false - end - - begin - api = ProxyAPI::Openscap.new(:url => scap_content.proxy_url) - errors = api.validate_scap_content(scap_content.scap_file) - if errors && errors['errors'].any? - errors['errors'].each { |error| scap_content.errors.add(:scap_file, _(error)) } - return false - end - rescue *ProxyAPI::AvailableProxy::HTTP_ERRORS => e - scap_content.errors.add(:base, _('No available proxy to validate. Returned with error: %s') % e) - return false - end - - - unless (scap_content.scap_content_profiles.map(&:profile_id) - scap_content.fetch_profiles.keys).empty? - scap_content.errors.add(:scap_file, _('Changed file does not include existing SCAP content profiles')) - return false - end - end - end - class ScapContent < ActiveRecord::Base include Authorizable include Taxonomix + include DataStreamContent has_many :scap_content_profiles, :dependent => :destroy has_many :policies - before_destroy EnsureNotUsedBy.new(:policies) - - validates_with DataStreamValidator validates :title, :presence => true, :length => { :maximum => 255 } - validates :digest, :presence => true - validates :scap_file, :presence => true - - after_save :create_profiles - before_validation :redigest, :if => lambda { |scap_content| scap_content.persisted? && scap_content.scap_file_changed? } scoped_search :on => :title, :complete_value => true scoped_search :on => :original_filename, :complete_value => true, :rename => :filename @@ -77,8 +34,9 @@ def to_label title end - def digest - self[:digest] ||= Digest::SHA256.hexdigest "#{scap_file}" + def as_json(*args) + hash = super + hash["scap_file"] = scap_file.to_s.encode('utf-8', :invalid => :replace, :undef => :replace, :replace => '_') end def fetch_profiles @@ -86,31 +44,5 @@ def fetch_profiles profiles = api.fetch_policies_for_scap_content(scap_file) profiles end - - def proxy_url - @proxy_url ||= SmartProxy.with_features('Openscap').find do |proxy| - available = ProxyAPI::AvailableProxy.new(:url => proxy.url) - available.available? - end.try(:url) - @proxy_url - end - - def as_json(*args) - hash = super - hash["scap_file"] = scap_file.to_s.encode('utf-8', :invalid => :replace, :undef => :replace, :replace => '_') - end - - private - - def create_profiles - profiles = fetch_profiles - profiles.each {|key, title| - scap_content_profiles.where(:profile_id => key, :title => title).first_or_create - } - end - - def redigest - self[:digest] = Digest::SHA256.hexdigest "#{scap_file}" - end end end diff --git a/app/models/foreman_openscap/scap_content_profile.rb b/app/models/foreman_openscap/scap_content_profile.rb index 16551ceb2..8492caea9 100644 --- a/app/models/foreman_openscap/scap_content_profile.rb +++ b/app/models/foreman_openscap/scap_content_profile.rb @@ -2,5 +2,7 @@ module ForemanOpenscap class ScapContentProfile < ActiveRecord::Base belongs_to :scap_content has_many :policies + belongs_to :tailoring_file + has_many :tailoring_file_policies, :class_name => ForemanOpenscap::Policy end end diff --git a/app/models/foreman_openscap/tailoring_file.rb b/app/models/foreman_openscap/tailoring_file.rb new file mode 100644 index 000000000..b1aa382e5 --- /dev/null +++ b/app/models/foreman_openscap/tailoring_file.rb @@ -0,0 +1,19 @@ +module ForemanOpenscap + class TailoringFile < ActiveRecord::Base + include Authorizable + include Taxonomix + include DataStreamContent + + has_many :policies + has_many :scap_content_profiles, :dependent => :destroy + validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 255 } + + scoped_search :on => :name, :complete_value => true + scoped_search :on => :original_filename, :complete_value => true, :rename => :filename + + def fetch_profiles + api = ProxyAPI::Openscap.new(:url => proxy_url) + api.fetch_profiles_for_tailoring_file(scap_file) + end + end +end diff --git a/app/validators/foreman_openscap/data_stream_validator.rb b/app/validators/foreman_openscap/data_stream_validator.rb new file mode 100644 index 000000000..0ab8a117c --- /dev/null +++ b/app/validators/foreman_openscap/data_stream_validator.rb @@ -0,0 +1,44 @@ +module ForemanOpenscap + class DataStreamValidator < ActiveModel::Validator + def validate(data_stream_content) + return unless data_stream_content.scap_file_changed? + + content_type = data_type(data_stream_content) + + unless SmartProxy.with_features('Openscap').any? + data_stream_content.errors.add(:base, _('No proxy with OpenSCAP features')) + return false + end + + if data_stream_content.proxy_url.nil? + data_stream_content.errors.add(:base, _('No available proxy to validate SCAP data stream file')) + return false + end + + begin + api = ProxyAPI::Openscap.new(:url => data_stream_content.proxy_url) + errors = api.validate_scap_file(data_stream_content.scap_file, content_type) + if errors && errors['errors'].any? + errors['errors'].each { |error| data_stream_content.errors.add(:scap_file, _(error)) } + return false + end + rescue *ProxyAPI::AvailableProxy::HTTP_ERRORS => e + data_stream_content.errors.add(:base, _('No available proxy to validate. Returned with error: %s') % e) + return false + end + + is_scap_content = content_type == 'scap_content' + + if is_scap_content && !(data_stream_content.scap_content_profiles.map(&:profile_id) - data_stream_content.fetch_profiles.keys).empty? + data_stream_content.errors.add(:scap_file, _('Changed file does not include existing SCAP content profiles')) + return false + end + end + + private + + def data_type(data_stream_content) + data_stream_content.class.to_s.demodulize.underscore + end + end +end diff --git a/app/views/api/v2/compliance/policies/base.json.rabl b/app/views/api/v2/compliance/policies/base.json.rabl index 8b2573b1e..29ae470a5 100644 --- a/app/views/api/v2/compliance/policies/base.json.rabl +++ b/app/views/api/v2/compliance/policies/base.json.rabl @@ -3,4 +3,5 @@ object @policy extends "api/v2/compliance/common/org" extends "api/v2/compliance/common/loc" -attributes :id, :name, :period, :weekday, :description, :scap_content_id, :scap_content_profile_id, :day_of_month, :cron_line +attributes :id, :name, :period, :weekday, :description, :scap_content_id, :scap_content_profile_id, :day_of_month, :cron_line, + :tailoring_file_id, :tailoring_file_profile_id diff --git a/app/views/api/v2/compliance/tailoring_files/base.json.rabl b/app/views/api/v2/compliance/tailoring_files/base.json.rabl new file mode 100644 index 000000000..8bdbd99d3 --- /dev/null +++ b/app/views/api/v2/compliance/tailoring_files/base.json.rabl @@ -0,0 +1,6 @@ +object @tailoring_file + +extends "api/v2/compliance/common/org" +extends "api/v2/compliance/common/loc" + +attributes :id, :name, :original_filename, :digest diff --git a/app/views/api/v2/compliance/tailoring_files/index.json.rabl b/app/views/api/v2/compliance/tailoring_files/index.json.rabl new file mode 100644 index 000000000..bb6ec4b0c --- /dev/null +++ b/app/views/api/v2/compliance/tailoring_files/index.json.rabl @@ -0,0 +1,3 @@ +collection @tailoring_files + +extends "api/v2/compliance/tailoring_files/main" diff --git a/app/views/api/v2/compliance/tailoring_files/main.json.rabl b/app/views/api/v2/compliance/tailoring_files/main.json.rabl new file mode 100644 index 000000000..cd0e76cfc --- /dev/null +++ b/app/views/api/v2/compliance/tailoring_files/main.json.rabl @@ -0,0 +1,5 @@ +object @tailoring_file + +extends "api/v2/compliance/tailoring_files/base" + +attributes :created_at, :updated_at diff --git a/app/views/api/v2/compliance/tailoring_files/show.json.rabl b/app/views/api/v2/compliance/tailoring_files/show.json.rabl new file mode 100644 index 000000000..52dbfaa39 --- /dev/null +++ b/app/views/api/v2/compliance/tailoring_files/show.json.rabl @@ -0,0 +1,7 @@ +object @tailoring_file + +extends "api/v2/compliance/tailoring_files/main" + +child :scap_content_profiles => :tailoring_file_profiles do |profile| + attributes :id, :profile_id, :title +end diff --git a/app/views/policies/_form.html.erb b/app/views/policies/_form.html.erb index 24db89079..25a0ef605 100644 --- a/app/views/policies/_form.html.erb +++ b/app/views/policies/_form.html.erb @@ -26,6 +26,15 @@ <%= scap_content_profile_selector(f) %> + + <%= tailoring_file_selector(f) %> + + + <% if @policy.tailoring_file %> + <%= render 'tailoring_file_selected', :f => f, :policy => @policy, :tailoring_file => @policy.tailoring_file %> + <% end %> + +
Name | -Content | -Profile | +<%= _('Name') %> | +<%= _('Content') %> | +<%= _('Profile') %> | +<%= _('Tailoring File') %> | +<%= _('Effective Profile') %> | - <%= policy.scap_content_profile.nil? ? "Default" : policy.scap_content_profile.title %> + <%= policy_profile_from_scap_content policy %> + | ++ <% if policy.tailoring_file %> + <%= link_to_if_authorized policy.tailoring_file.name, hash_for_edit_tailoring_file_path(:id => policy.tailoring_file_id) %> + <% else %> + <%= _('None') %> + <% end%> + | ++ <%= effective_policy_profile policy %> |
<%= action_buttons(
diff --git a/app/views/policies/_tailoring_file_selected.html.erb b/app/views/policies/_tailoring_file_selected.html.erb
new file mode 100644
index 000000000..c0a6b2e21
--- /dev/null
+++ b/app/views/policies/_tailoring_file_selected.html.erb
@@ -0,0 +1,3 @@
+<%= fields_for policy do |f| %>
+ <%= tailoring_file_profile_selector(f, tailoring_file) %>
+<% end %>
diff --git a/app/views/policies/steps/_scap_content_form.html.erb b/app/views/policies/steps/_scap_content_form.html.erb
index d6c0ddb7a..df7c3aa87 100644
--- a/app/views/policies/steps/_scap_content_form.html.erb
+++ b/app/views/policies/steps/_scap_content_form.html.erb
@@ -5,5 +5,13 @@
<%= scap_content_profile_selector(f) %>
+
+ <%= tailoring_file_selector(f) %>
+
+
+ <% if @policy.tailoring_file %>
+ <%= render 'tailoring_file_selected', :f => f, :policy => @policy, :tailoring_file => @policy.tailoring_file %>
+ <% end %>
+
diff --git a/app/views/tailoring_files/_form.html.erb b/app/views/tailoring_files/_form.html.erb
new file mode 100644
index 000000000..a81057056
--- /dev/null
+++ b/app/views/tailoring_files/_form.html.erb
@@ -0,0 +1,25 @@
+<%= form_for @tailoring_file,
+ :url => (@tailoring_file.id? ?
+ tailoring_file_path(:id => @tailoring_file.id) : tailoring_files_path),
+ :html => { :multipart => true } do |f| %>
+
+ <%= base_errors_for @tailoring_file %>
+
+
+
+
+<% end %>
diff --git a/app/views/tailoring_files/_list.html.erb b/app/views/tailoring_files/_list.html.erb
new file mode 100644
index 000000000..eb9052e68
--- /dev/null
+++ b/app/views/tailoring_files/_list.html.erb
@@ -0,0 +1,25 @@
+
+ <%= text_f(f, :name) %>
+ <%= file_field_f f, :scap_file, :help_block => _("Upload DataStream Tailoring file") %>
+
+ <%= render 'taxonomies/loc_org_tabs', :f => f, :obj => @tailoring_file %>
+ <%= submit_or_cancel f %>
+
+
diff --git a/config/routes.rb b/config/routes.rb
index fc90a4d2a..d3d7f9de5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -25,6 +25,7 @@
collection do
get 'auto_complete_search'
post 'scap_content_selected'
+ post 'tailoring_file_selected'
get 'select_multiple_hosts'
post 'update_multiple_hosts'
get 'disassociate_multiple_hosts'
@@ -38,6 +39,15 @@
end
end
+ resources :tailoring_files, :except => [:show] do
+ member do
+ get 'xml'
+ end
+ collection do
+ get 'auto_complete_search'
+ end
+ end
+
resources :hosts, :only => [:show], :as => :compliance_hosts, :controller => :compliance_hosts
end
@@ -50,9 +60,15 @@
get 'xml'
end
end
+ resources :tailoring_files, :except => [:new, :edit] do
+ member do
+ get 'xml'
+ end
+ end
resources :policies, :except => [:new, :edit] do
member do
get 'content'
+ get 'tailoring'
end
end
resources :arf_reports, :only => [:index, :show, :destroy] do
diff --git a/db/migrate/20161109155255_create_tailoring_files.rb b/db/migrate/20161109155255_create_tailoring_files.rb
new file mode 100644
index 000000000..dcfa87d61
--- /dev/null
+++ b/db/migrate/20161109155255_create_tailoring_files.rb
@@ -0,0 +1,23 @@
+class CreateTailoringFiles < ActiveRecord::Migration
+ def up
+ create_table :foreman_openscap_tailoring_files do |t|
+ t.string :name, :unique => true, :null => false
+ t.text :scap_file
+ t.string :original_filename
+ t.datetime :created_at
+ t.datetime :updated_at
+ t.string :digest, :null => false
+ end
+
+ add_column :foreman_openscap_policies, :tailoring_file_id, :integer, :references => :tailoring_file
+ add_column :foreman_openscap_policies, :tailoring_file_profile_id, :integer, :references => :scap_content_profile
+ add_column :foreman_openscap_scap_content_profiles, :tailoring_file_id, :integer, :references => :tailoring_file
+ end
+
+ def down
+ remove_column :foreman_openscap_policies, :tailoring_file_id
+ remove_column :foreman_openscap_policies, :tailoring_file_profile_id
+ remove_column :foreman_openscap_scap_content_profiles, :tailoring_file_id
+ drop_table :foreman_openscap_tailoring_files
+ end
+end
diff --git a/lib/foreman_openscap/engine.rb b/lib/foreman_openscap/engine.rb
index b9184c105..936e013ad 100644
--- a/lib/foreman_openscap/engine.rb
+++ b/lib/foreman_openscap/engine.rb
@@ -90,12 +90,27 @@ class Engine < ::Rails::Engine
:resource_type => 'ForemanOpenscap::ScapContent'
permission :edit_hosts, { :hosts => [:openscap_proxy_changed] }, :resource_type => "Host"
permission :edit_hostgroups, { :hostgroups => [:openscap_proxy_changed] }, :resource_type => "Hostgroup"
+ permission :create_tailoring_files, { :tailoring_files => [:create, :new],
+ 'api/v2/compliance/tailoring_files' => [:create]},
+ :resource_type => 'ForemanOpenscap::TailoringFile'
+ permission :view_tailoring_files, { :tailoring_files => [:index, :auto_complete_search, :xml],
+ :policies => [:tailoring_file_selected],
+ 'api/v2/compliance/tailoring_files' => [:show, :xml, :index],
+ 'api/v2/compliance/policies' => [:tailoring] },
+ :resource_type => 'ForemanOpenscap::TailoringFile'
+ permission :edit_tailoring_files, { :tailoring_files => [:edit, :update],
+ 'api/v2/compliance/tailoring_files' => [:update] },
+ :resource_type => 'ForemanOpenscap::TailoringFile'
+ permission :destroy_tailoring_files, { :tailoring_files => [:destroy],
+ 'api/v2/compliance/tailoring_files' => [:destroy] },
+ :resource_type => 'ForemanOpenscap::TailoringFile'
end
- role "Compliance viewer", [:view_arf_reports, :view_policies, :view_scap_contents]
+ role "Compliance viewer", [:view_arf_reports, :view_policies, :view_scap_contents, :view_tailoring_files]
role "Compliance manager", [:view_arf_reports, :view_policies, :view_scap_contents,
:destroy_arf_reports, :edit_policies, :edit_scap_contents, :assign_policies,
- :create_policies, :create_scap_contents, :destroy_policies, :destroy_scap_contents]
+ :create_policies, :create_scap_contents, :destroy_policies, :destroy_scap_contents,
+ :create_tailoring_files, :view_tailoring_files, :edit_tailoring_files, :destroy_tailoring_files]
role "Create ARF report", [:create_arf_reports] # special as only Proxy can create
#add menu entries
@@ -109,6 +124,10 @@ class Engine < ::Rails::Engine
menu :top_menu, :compliance_reports, :caption => N_('Reports'),
:url_hash => {:controller => :arf_reports, :action => :index},
:parent => :hosts_menu
+ menu :top_menu, :compliance_files, :caption => N_('Tailoring Files'),
+ :url_hash => {:controller => :tailoring_files, :action => :index},
+ :parent => :hosts_menu
+
# add dashboard widget
widget 'compliance_host_reports_widget',
diff --git a/test/factories/policy_factory.rb b/test/factories/policy_factory.rb
index 2fe46a480..17cf6c635 100644
--- a/test/factories/policy_factory.rb
+++ b/test/factories/policy_factory.rb
@@ -5,6 +5,8 @@
weekday 'monday'
scap_content
scap_content_profile
+ tailoring_file nil
+ tailoring_file_profile nil
day_of_month nil
cron_line nil
hosts []
diff --git a/test/factories/scap_content_related.rb b/test/factories/scap_content_related.rb
index e388ee279..b400eb3cd 100644
--- a/test/factories/scap_content_related.rb
+++ b/test/factories/scap_content_related.rb
@@ -12,4 +12,11 @@
f.profile_id 'xccdf_org.test.common_test_profile'
f.title 'test Profile for testing'
end
+
+ factory :tailoring_file, :class => ForemanOpenscap::TailoringFile do |f|
+ f.sequence(:name) { |n| "tailoring_file_#{n}" }
+ f.original_filename 'original tailoring filename'
+ f.scap_file { File.new("#{ForemanOpenscap::Engine.root}/test/files/tailoring_files/ssg-firefox-ds-tailoring.xml", 'rb').read }
+ f.scap_content_profiles []
+ end
end
diff --git a/test/files/tailoring_files/ssg-firefox-ds-tailoring-2.xml b/test/files/tailoring_files/ssg-firefox-ds-tailoring-2.xml
new file mode 100644
index 000000000..c1b26944e
--- /dev/null
+++ b/test/files/tailoring_files/ssg-firefox-ds-tailoring-2.xml
@@ -0,0 +1,23 @@
+
+
+ <%= _('Tailoring Files') %>+<%= _('It may sometimes be required to adjust the security policy to your specific needs. ') %> + <%= (_('In Foreman, tailoring_files represent the custom modifications to default XCCDF profiles and they can be applied to hosts + via %s') % link_to('compliance policies', policies_path)).html_safe %> + + +
+ <%= new_link(_('New Tailoring File'), {}, { :class => "btn-lg" }) %>
+
+ |
---|