From 7b96a0bf1c4b13daeab33edfdab25213660fb5cd Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Fri, 7 Jan 2022 00:56:28 +0900 Subject: [PATCH] Allow customizing policy files Horizon requires policy files placed in its own config directory to enable/disable some features according to policy rules. This change introduces capability to manage contents of each policy files so that users can inject customized rules. Change-Id: Id82f6fd416c0f563b181af66f541c850128a5778 --- manifests/dashboards/heat.pp | 20 +++ manifests/dashboards/manila.pp | 22 +++- manifests/dashboards/octavia.pp | 20 +++ manifests/deps.pp | 5 + manifests/init.pp | 7 + manifests/params.pp | 2 + manifests/policy.pp | 83 ++++++++++++ manifests/policy/base.pp | 59 +++++++++ .../notes/policy-05bec63b1ab26902.yaml | 19 +++ spec/classes/horizon_dashboards_heat_spec.rb | 46 +++++++ .../classes/horizon_dashboards_manila_spec.rb | 46 +++++++ .../horizon_dashboards_octavia_spec.rb | 46 +++++++ spec/classes/horizon_policy_spec.rb | 120 ++++++++++++++++++ spec/defines/horizon_policy_base_spec.rb | 88 +++++++++++++ templates/local_settings.py.erb | 6 +- 15 files changed, 584 insertions(+), 5 deletions(-) create mode 100644 manifests/policy.pp create mode 100644 manifests/policy/base.pp create mode 100644 releasenotes/notes/policy-05bec63b1ab26902.yaml create mode 100644 spec/classes/horizon_policy_spec.rb create mode 100644 spec/defines/horizon_policy_base_spec.rb diff --git a/manifests/dashboards/heat.pp b/manifests/dashboards/heat.pp index bfd5456a..a8595b03 100644 --- a/manifests/dashboards/heat.pp +++ b/manifests/dashboards/heat.pp @@ -33,11 +33,16 @@ # (optional) Concurrency to retrieve response from template generator. # Defualts to 2 # +# [*policies*] +# (optional) Set of policies to configure. +# Defaults to undef +# class horizon::dashboards::heat( $enable_user_pass = true, $policy_file = 'heat_policy.yaml', $template_generator_api_timeout = 60, $template_generator_api_parallel = 2, + $policies = undef, ) { include horizon::deps @@ -81,4 +86,19 @@ class horizon::dashboards::heat( content => template('horizon/_1699_orchestration_settings.py.erb'), order => '50', } + + if $policies != undef { + # The horizon::policy class should be included so that some common + # parameters about policy management can be picked here + if !defined(Class[horizon::policy]){ + fail('The horizon::policy class should be include in advance to customize policies') + } + + horizon::policy::base { $policy_file_real: + policies => $policies, + file_mode => $::horizon::policy::file_mode, + file_format => $::horizon::policy::file_format, + purge_config => $::horizon::policy::purge_config, + } + } } diff --git a/manifests/dashboards/manila.pp b/manifests/dashboards/manila.pp index e21687a9..89c5dc2d 100644 --- a/manifests/dashboards/manila.pp +++ b/manifests/dashboards/manila.pp @@ -32,9 +32,14 @@ # 'enable_public_shares': Boolean # 'enabled_share_protocols': Array # +# [*policies*] +# (optional) Set of policies to configure. +# Defaults to undef +# class horizon::dashboards::manila( $policy_file = 'manila_policy.yaml', - $manila_options = {} + $manila_options = {}, + $policies = undef, ) { include horizon::deps @@ -90,4 +95,19 @@ class horizon::dashboards::manila( content => template('horizon/_90_manila_shares.py.erb'), order => '50', } + + if $policies != undef { + # The horizon::policy class should be included so that some common + # parameters about policy management can be picked here + if !defined(Class[horizon::policy]){ + fail('The horizon::policy class should be include in advance to customize policies') + } + + horizon::policy::base { $policy_file_real: + policies => $policies, + file_mode => $::horizon::policy::file_mode, + file_format => $::horizon::policy::file_format, + purge_config => $::horizon::policy::purge_config, + } + } } diff --git a/manifests/dashboards/octavia.pp b/manifests/dashboards/octavia.pp index 8635a0e4..4b5cc2ec 100644 --- a/manifests/dashboards/octavia.pp +++ b/manifests/dashboards/octavia.pp @@ -21,8 +21,13 @@ # (optional) Local copy of service policy files. # Defaults to 'octavia_policy.yaml' # +# [*policies*] +# (optional) Set of policies to configure. +# Defaults to undef +# class horizon::dashboards::octavia( $policy_file = 'octavia_policy.yaml', + $policies = undef, ) { include horizon::deps @@ -65,4 +70,19 @@ class horizon::dashboards::octavia( content => template('horizon/_1499_load_balancer_settings.py.erb'), order => '50', } + + if $policies != undef { + # The horizon::policy class should be included so that some common + # parameters about policy management can be picked here + if !defined(Class[horizon::policy]){ + fail('The horizon::policy class should be include in advance to customize policies') + } + + horizon::policy::base { $policy_file_real: + policies => $policies, + file_mode => $::horizon::policy::file_mode, + file_format => $::horizon::policy::file_format, + purge_config => $::horizon::policy::purge_config, + } + } } diff --git a/manifests/deps.pp b/manifests/deps.pp index f24f43d8..3e8f39a5 100644 --- a/manifests/deps.pp +++ b/manifests/deps.pp @@ -36,6 +36,11 @@ class horizon::deps { -> Service<| title == 'httpd' |> ~> anchor { 'horizon::service::end': } + # policy config should occur in the config block + Anchor['horizon::config::begin'] + -> Openstacklib::Policy<||> + ~> Anchor['horizon::config::end'] + # Installation or config changes will always restart services. Anchor['horizon::install::end'] ~> Anchor['horizon::service::begin'] Anchor['horizon::config::end'] ~> Anchor['horizon::service::begin'] diff --git a/manifests/init.pp b/manifests/init.pp index be4918f3..75183389 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -706,6 +706,13 @@ and usage of a quoted value is deprecated.') $neutron_options_real = merge($neutron_defaults,$neutron_options) $instance_options_real = merge($instance_defaults,$instance_options) + if $policy_files_path != undef { + validate_legacy(String, 'validate_string', $policy_files_path) + $policy_files_path_real = $policy_files_path + } else { + $policy_files_path_real = $::horizon::params::policy_dir + } + validate_legacy(Hash, 'validate_hash', $api_versions) validate_legacy(Enum['on', 'off'], 'validate_re', $password_autocomplete, [['^on$', '^off$']]) validate_legacy(Enum['legacy', 'angular'], 'validate_re', $images_panel, [['^legacy$', '^angular$']]) diff --git a/manifests/params.pp b/manifests/params.pp index a484a2c2..559eb61b 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -14,6 +14,7 @@ class horizon::params { $config_dir = '/etc/openstack-dashboard' $conf_d_dir = '/etc/openstack-dashboard/local_settings.d' $config_file = '/etc/openstack-dashboard/local_settings' + $policy_dir = '/etc/openstack-dashboard' $httpd_config_file = '/etc/httpd/conf.d/openstack-dashboard.conf' $httpd_listen_config_file = '/etc/httpd/conf/httpd.conf' $root_url = '/dashboard' @@ -29,6 +30,7 @@ class horizon::params { $config_dir = '/etc/openstack-dashboard' $conf_d_dir = '/etc/openstack-dashboard/local_settings.d' $config_file = '/etc/openstack-dashboard/local_settings.py' + $policy_dir = undef $httpd_listen_config_file = '/etc/apache2/ports.conf' $root_url = '/horizon' $static_path = '/var/lib' diff --git a/manifests/policy.pp b/manifests/policy.pp new file mode 100644 index 00000000..9b72e616 --- /dev/null +++ b/manifests/policy.pp @@ -0,0 +1,83 @@ +# == Class horizon::policy +# +# Manage policy files for Horizon +# +# == Parameters +# +# [*file_mode*] +# (Optional) Permission mode for the policy file. +# Defaults to '0640' +# +# [*file_format*] +# (Optional) Format for file contents. +# Defaults to 'yaml'. +# +# [*purge_config*] +# (Optional) Whether to set only the specified policy rules in the policy +# file. +# Defaults to false. +# +# [*cinder_policies*] +# (Optional) Set of cinder policies to configure. +# Defaults to {} +# +# [*glance_policies*] +# (Optional) Set of glance policies to configure. +# Defaults to {} +# +# [*keystone_policies*] +# (Optional) Set of keystone policies to configure. +# Defaults to {} +# +# [*neutron_policies*] +# (Optional) Set of neutron policies to configure. +# Defaults to {} +# +# [*nova_policies*] +# (Optional) Set of nova policies to configure. +# Defaults to {} +# +class horizon::policy( + # common parameters + $file_mode = '0640', + $file_format = 'yaml', + $purge_config = false, + # service specific parameters + $cinder_policies = {}, + $glance_policies = {}, + $keystone_policies = {}, + $neutron_policies = {}, + $nova_policies = {}, +) { + include horizon::deps + + if !defined(Class[horizon]){ + fail('The horizon class should be included in advance') + } + + $policy_files = pick($::horizon::policy_files, {}) + $policy_files_default = { + 'identity' => 'keystone_policy.yaml', + 'compute' => 'nova_policy.yaml', + 'volume' => 'cinder_policy.yaml', + 'image' => 'glance_policy.yaml', + 'network' => 'neutron_policy.yaml', + } + $policy_files_real = merge($policy_files_default, $policy_files) + + $policy_resources = { + $policy_files_real['volume'] => { 'policies' => $cinder_policies }, + $policy_files_real['image'] => { 'policies' => $glance_policies }, + $policy_files_real['identity'] => { 'policies' => $keystone_policies }, + $policy_files_real['network'] => { 'policies' => $neutron_policies }, + $policy_files_real['compute'] => { 'policies' => $nova_policies }, + } + + $policy_defaults = { + 'file_mode' => $file_mode, + 'file_format' => $file_format, + 'purge_config' => $purge_config + } + + create_resources('horizon::policy::base', $policy_resources, $policy_defaults) +} diff --git a/manifests/policy/base.pp b/manifests/policy/base.pp new file mode 100644 index 00000000..c18c429f --- /dev/null +++ b/manifests/policy/base.pp @@ -0,0 +1,59 @@ +# == Define horizon::policy::base +# +# Manage a policy file for Horizon +# +# == Parameters +# +# [*policy_file*] +# (Optional) Name to the policy file. +# Defaults to $name +# +# [*policies*] +# (Optional) Set of policies to configure +# +# [*file_mode*] +# (Optional) Permission mode for the policy file +# Defaults to '0640' +# +# [*file_format*] +# (Optional) Format for file contents. Valid values +# Defaults to 'yaml'. +# +# [*purge_config*] +# (Optional) Whether to set only the specified policy rules in the policy +# file. +# Defaults to false. +# +define horizon::policy::base( + $policy_file = $name, + $policies = {}, + $file_mode = '0640', + $file_format = 'yaml', + $purge_config = false, +) { + include horizon::deps + include horizon::params + + validate_legacy(String, 'validate_string', $policy_file) + + if !defined(Class[horizon]){ + fail('The horizon class should be included in advance') + } + + $policy_files_path = $::horizon::policy_files_path_real + if ! $policy_files_path { + # In Ubuntu/Debian, the default policies files are located in source + # directories, and the path should be updated to more appropriate path + # like /etc. + fail('Please set the horizon::policy_files_path parameter to customize policies') + } + + openstacklib::policy { "${policy_files_path}/${policy_file}" : + policies => $policies, + file_user => $::horizon::params::wsgi_user, + file_group => $::horizon::params::wsgi_group, + file_mode => $file_mode, + file_format => $file_format, + purge_config => $purge_config + } +} diff --git a/releasenotes/notes/policy-05bec63b1ab26902.yaml b/releasenotes/notes/policy-05bec63b1ab26902.yaml new file mode 100644 index 00000000..cd162e04 --- /dev/null +++ b/releasenotes/notes/policy-05bec63b1ab26902.yaml @@ -0,0 +1,19 @@ +--- +features: + - | + The new ``horizon::policy`` class has been added. This class can be used + to manage policy rules for the following services. + + - ``cinder`` + - ``glance`` + - ``keystone`` + - ``neutron`` + - ``nova`` + + - | + The new ``policies`` parameter has been added to the following classes, + to allow customizing policiy rules for additional services. + + - ``horizon::dashboards::heat`` + - ``horizon::dashboards::octavia`` + - ``horizon::dashboards::manila`` diff --git a/spec/classes/horizon_dashboards_heat_spec.rb b/spec/classes/horizon_dashboards_heat_spec.rb index 558c4607..621049b9 100644 --- a/spec/classes/horizon_dashboards_heat_spec.rb +++ b/spec/classes/horizon_dashboards_heat_spec.rb @@ -91,6 +91,52 @@ eos context 'without the horizon class defined' do it { should raise_error(Puppet::Error) } end + + context 'with policy customization' do + let(:pre_condition) do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/etc/openstack-dashboard', + } + class { 'horizon::policy': } +eos + end + + before do + params.merge!({ + :policies => {} + }) + end + + it 'configures policy' do + is_expected.to contain_horizon__policy__base('heat_policy.yaml').with( + :policies => {}, + :file_mode => '0640', + :file_format => 'yaml', + :purge_config => false, + ) + end + end + + context 'with policy customization but without the horizon::policy class' do + let(:pre_condition) do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/etc/openstack-dashboard', + } +eos + end + + before do + params.merge!({ + :policies => {} + }) + end + + it { should raise_error(Puppet::Error) } + end end on_supported_os({ diff --git a/spec/classes/horizon_dashboards_manila_spec.rb b/spec/classes/horizon_dashboards_manila_spec.rb index cf2d9405..746f9022 100644 --- a/spec/classes/horizon_dashboards_manila_spec.rb +++ b/spec/classes/horizon_dashboards_manila_spec.rb @@ -46,6 +46,52 @@ eos context 'without the horizon class defined' do it { should raise_error(Puppet::Error) } end + + context 'with policy customization' do + let(:pre_condition) do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/etc/openstack-dashboard', + } + class { 'horizon::policy': } +eos + end + + before do + params.merge!({ + :policies => {} + }) + end + + it 'configures policy' do + is_expected.to contain_horizon__policy__base('manila_policy.yaml').with( + :policies => {}, + :file_mode => '0640', + :file_format => 'yaml', + :purge_config => false, + ) + end + end + + context 'with policy customization but without the horizon::policy class' do + let(:pre_condition) do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/etc/openstack-dashboard', + } +eos + end + + before do + params.merge!({ + :policies => {} + }) + end + + it { should raise_error(Puppet::Error) } + end end on_supported_os({ diff --git a/spec/classes/horizon_dashboards_octavia_spec.rb b/spec/classes/horizon_dashboards_octavia_spec.rb index e7f1c9b5..098e43f0 100644 --- a/spec/classes/horizon_dashboards_octavia_spec.rb +++ b/spec/classes/horizon_dashboards_octavia_spec.rb @@ -37,6 +37,52 @@ eos context 'without the horizon class defined' do it { should raise_error(Puppet::Error) } end + + context 'with policy customization' do + let(:pre_condition) do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/etc/openstack-dashboard', + } + class { 'horizon::policy': } +eos + end + + before do + params.merge!({ + :policies => {} + }) + end + + it 'configures policy' do + is_expected.to contain_horizon__policy__base('octavia_policy.yaml').with( + :policies => {}, + :file_mode => '0640', + :file_format => 'yaml', + :purge_config => false, + ) + end + end + + context 'with policy customization but without the horizon::policy class' do + let(:pre_condition) do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/etc/openstack-dashboard', + } +eos + end + + before do + params.merge!({ + :policies => {} + }) + end + + it { should raise_error(Puppet::Error) } + end end on_supported_os({ diff --git a/spec/classes/horizon_policy_spec.rb b/spec/classes/horizon_policy_spec.rb new file mode 100644 index 00000000..04c6a05e --- /dev/null +++ b/spec/classes/horizon_policy_spec.rb @@ -0,0 +1,120 @@ +require 'spec_helper' + +describe 'horizon::policy' do + + let :params do + {} + end + + shared_examples_for 'horizon::policy' do + + context 'with default parameters' do + let(:pre_condition) do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/etc/openstack-dashboard' + } +eos + end + + it 'configures defaults' do + is_expected.to contain_horizon__policy__base('cinder_policy.yaml').with( + :file_mode => '0640', + :file_format => 'yaml', + :purge_config => false, + ) + is_expected.to contain_horizon__policy__base('glance_policy.yaml').with( + :file_mode => '0640', + :file_format => 'yaml', + :purge_config => false, + ) + is_expected.to contain_horizon__policy__base('keystone_policy.yaml').with( + :file_mode => '0640', + :file_format => 'yaml', + :purge_config => false, + ) + is_expected.to contain_horizon__policy__base('neutron_policy.yaml').with( + :file_mode => '0640', + :file_format => 'yaml', + :purge_config => false, + ) + is_expected.to contain_horizon__policy__base('nova_policy.yaml').with( + :file_mode => '0640', + :file_format => 'yaml', + :purge_config => false, + ) + end + end + + context 'with parameters' do + let(:pre_condition) do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/opt/openstack-dashboard', + policy_files => { + 'identity' => 'keystone.yaml', + 'compute' => 'nova.yaml', + 'volume' => 'cinder.yaml', + 'image' => 'glance.yaml', + 'network' => 'neutron.yaml' + } + } +eos + end + + let :params do + { + :file_mode => '0644', + :purge_config => true, + } + end + + it 'configures defaults' do + is_expected.to contain_horizon__policy__base('cinder.yaml').with( + :file_mode => '0644', + :file_format => 'yaml', + :purge_config => true, + ) + is_expected.to contain_horizon__policy__base('glance.yaml').with( + :file_mode => '0644', + :file_format => 'yaml', + :purge_config => true, + ) + is_expected.to contain_horizon__policy__base('keystone.yaml').with( + :file_mode => '0644', + :file_format => 'yaml', + :purge_config => true, + ) + is_expected.to contain_horizon__policy__base('neutron.yaml').with( + :file_mode => '0644', + :file_format => 'yaml', + :purge_config => true, + ) + is_expected.to contain_horizon__policy__base('nova.yaml').with( + :file_mode => '0644', + :file_format => 'yaml', + :purge_config => true, + ) + end + end + + context 'without the horizon class defined' do + it { should raise_error(Puppet::Error) } + end + end + + on_supported_os({ + :supported_os => OSDefaults.get_supported_os + }).each do |os,facts| + context "on #{os}" do + let (:facts) do + facts.merge!(OSDefaults.get_facts()) + end + + it_behaves_like 'horizon::policy' + end + end + +end diff --git a/spec/defines/horizon_policy_base_spec.rb b/spec/defines/horizon_policy_base_spec.rb new file mode 100644 index 00000000..ccffb540 --- /dev/null +++ b/spec/defines/horizon_policy_base_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe 'horizon::policy::base' do + let (:title) { 'keystone_policy.yaml' } + + shared_examples 'horizon::policy::base' do + context 'with default' do + let :pre_condition do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/etc/openstack-dashboard' + } +eos + end + + let :params do + {} + end + + it 'should configure defaults' do + is_expected.to contain_openstacklib__policy('/etc/openstack-dashboard/keystone_policy.yaml').with( + :policies => {}, + :file_user => platform_params[:wsgi_user], + :file_group => platform_params[:wsgi_group], + :file_mode => '0640', + :purge_config => false, + ) + end + end + + context 'with parameters' do + let :pre_condition do + <<-eos + class { 'horizon': + secret_key => 'elj1IWiLoWHgcyYxFVLj7cM5rGOOxWl0', + policy_files_path => '/opt/openstack-dashboard' + } +eos + end + + let :params do + { + :file_mode => '0644', + :purge_config => true, + } + end + + it 'should configure defaults' do + is_expected.to contain_openstacklib__policy('/opt/openstack-dashboard/keystone_policy.yaml').with( + :policies => {}, + :file_user => platform_params[:wsgi_user], + :file_group => platform_params[:wsgi_group], + :file_mode => '0644', + :purge_config => true, + ) + end + end + + context 'without the horizon class defined' do + it { should raise_error(Puppet::Error) } + end + end + + on_supported_os({ + :supported_os => OSDefaults.get_supported_os + }).each do |os,facts| + context "on #{os}" do + let (:facts) do + facts.merge!(OSDefaults.get_facts()) + end + + let (:platform_params) do + case facts[:osfamily] + when 'Debian' + { :wsgi_user => 'horizon', + :wsgi_group => 'horizon' } + when 'RedHat' + { :wsgi_user => 'apache', + :wsgi_group => 'apache' } + end + end + + it_behaves_like 'horizon::policy::base' + end + end + +end diff --git a/templates/local_settings.py.erb b/templates/local_settings.py.erb index ebd0f865..89351907 100644 --- a/templates/local_settings.py.erb +++ b/templates/local_settings.py.erb @@ -672,10 +672,8 @@ DEFAULT_THEME = '<%= @default_theme %>' # Path to directory containing policy.json files #POLICY_FILES_PATH = os.path.join(ROOT_PATH, "conf") -<% if !(@policy_files_path.nil?) %> -POLICY_FILES_PATH = '<%= @policy_files_path %>' -<% elsif @osfamily == 'RedHat' %> -POLICY_FILES_PATH = '/etc/openstack-dashboard' +<% if !(@policy_files_path_real.nil?) -%> +POLICY_FILES_PATH = '<%= @policy_files_path_real %>' <% end -%> # Map of local copy of service policy files.