From ebb2450ce50d40872868cb0379abff87aec96363 Mon Sep 17 00:00:00 2001 From: Iury Gregory Melo Ferreira Date: Tue, 25 Aug 2015 17:50:42 +0000 Subject: [PATCH] Support for Keystone as Service Provider This patch implements the class to configure Keystone as a Service Provider. It covers only Keystone as SP for K2K (Protocol is SAML and module is Shibboleth) On Debian based systems: 1- Configure keystone.conf 2- Install Shibboleth 3- Reconfigure the selected Keystone VirtualHost on Apache. On RedHat based systems: 1- Configure keystone.conf 2- Reconfigure the selected Keystone VirtualHost on Apache. Note: Step 2 will only execute if the user have add the extra repository or installed shibboleth. (About the extra repository, see: https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPLinuxRPMInstall) Implements: blueprint enabling-federation Change-Id: I32a1487c7674605124e6d0b182fe38ea4b58de87 --- examples/k2k_sp_shib.pp | 72 +++++++ manifests/federation/shibboleth.pp | 136 +++++++++++++ .../keystone_federation_shibboleth_spec.rb | 182 ++++++++++++++++++ .../classes/keystone_federation_shibboleth.rb | 114 +++++++++++ templates/shibboleth.conf.erb | 12 ++ 5 files changed, 516 insertions(+) create mode 100644 examples/k2k_sp_shib.pp create mode 100644 manifests/federation/shibboleth.pp create mode 100644 spec/acceptance/keystone_federation_shibboleth_spec.rb create mode 100644 spec/classes/keystone_federation_shibboleth.rb create mode 100644 templates/shibboleth.conf.erb diff --git a/examples/k2k_sp_shib.pp b/examples/k2k_sp_shib.pp new file mode 100644 index 000000000..b1a9f1642 --- /dev/null +++ b/examples/k2k_sp_shib.pp @@ -0,0 +1,72 @@ +# Example to configure Keystone as Service Provider for +# K2K Federation. +# +# To be sure everything is working, run: +# $ export OS_USERNAME=admin +# $ export OS_PASSWORD=ChangeMe +# $ export OS_TENANT_NAME=openstack +# $ export OS_AUTH_URL=http://keystone.local/keystone/main/v2.0 +# $ keystone catalog +# Service: identity +# +-------------+----------------------------------------------+ +# | Property | Value | +# +-------------+----------------------------------------------+ +# | adminURL | http://keystone.local:80/keystone/admin/v2.0 | +# | id | 4f0f55f6789d4c73a53c51f991559b72 | +# | internalURL | http://keystone.local:80/keystone/main/v2.0 | +# | publicURL | http://keystone.local:80/keystone/main/v2.0 | +# | region | RegionOne | +# +-------------+----------------------------------------------+ +# + +Exec { logoutput => 'on_failure' } + +# Note: The yumrepo part is only necessary if you are using RedHat. +# Yumrepo begin +yumrepo { 'shibboleth': + name => 'Shibboleth', + baseurl => 'http://download.opensuse.org/repositories/security:/shibboleth/CentOS_7/', + descr => 'Shibboleth repo for RedHat', + gpgcheck => 1, + gpgkey => 'http://download.opensuse.org/repositories/security:/shibboleth/CentOS_7/repodata/repomd.xml.key', + enabled => 1, + require => Anchor['openstack_extras_redhat'] +} + +Yumrepo['shibboleth'] -> Class['::keystone::federation::shibboleth'] +# Yumrepo end + +class { '::mysql::server': } +class { '::keystone::db::mysql': + password => 'keystone', +} + +class { '::keystone': + verbose => true, + debug => true, + database_connection => 'mysql://keystone:keystone@127.0.0.1/keystone', + catalog_type => 'sql', + admin_token => 'admin_token', + enabled => false, +} + +class { '::keystone::roles::admin': + email => 'test@puppetlabs.com', + password => 'ChangeMe', +} + +class { '::keystone::endpoint': + public_url => "https://${::fqdn}:5000/", + admin_url => "https://${::fqdn}:35357/", +} + +keystone_config { 'ssl/enable': value => true } + +include ::apache +class { '::keystone::wsgi::apache': + ssl => true +} + +class { '::keystone::federation::shibboleth': + methods => 'password, token, oauth1, saml2', +} diff --git a/manifests/federation/shibboleth.pp b/manifests/federation/shibboleth.pp new file mode 100644 index 000000000..cb02d66be --- /dev/null +++ b/manifests/federation/shibboleth.pp @@ -0,0 +1,136 @@ +# == class: keystone::federation::shibboleth +# +# == Parameters +# +# [*admin_port*] +# A boolean value to ensure that you want to configure K2K Federation +# using Keystone VirtualHost on port 35357. +# (Optional) Defaults to false. +# +# [*main_port*] +# A boolean value to ensure that you want to configure K2K Federation +# using Keystone VirtualHost on port 5000. +# (Optional) Defaults to true. +# +# [*methods*] +# A list of methods used for authentication separated by comma or an array. +# The allowed values are: 'external', 'password', 'token', 'oauth1', 'saml2' +# (Required) (string or array value). +# Note: The external value should be dropped to avoid problems. +# +# [*module_plugin*] +# The plugin for authentication acording to the choice made with protocol and +# module. +# (Optional) Defaults to 'keystone.auth.plugins.mapped.Mapped' (string value) +# +# [*suppress_warning*] +# A boolean value to disable the warning about not installing shibboleth on RedHat. +# (Optional) Defaults to false. +# +# [*template_order*] +# This number indicates the order for the concat::fragment that will apply +# the shibboleth configuration to Keystone VirtualHost. The value should +# The value should be greater than 330 an less then 999, according to: +# https://github.com/puppetlabs/puppetlabs-apache/blob/master/manifests/vhost.pp +# The value 330 corresponds to the order for concat::fragment "${name}-filters" +# and "${name}-limits". +# The value 999 corresponds to the order for concat::fragment "${name}-file_footer". +# (Optional) Defaults to 331. +# +# [*yum_repo_name*] +# This is the name of repo where one can find the shibboleth package on rhel +# platform. See the note below. For instance this snippet would enable the +# full configuration on RedHat platform: +# +# yumrepo { 'shibboleth': +# name => 'Shibboleth', +# baseurl => 'http://download.opensuse.org/repositories/security:/shibboleth/CentOS_7/', +# descr => 'Shibboleth repo for RedHat', +# gpgcheck => 1, +# gpgkey => 'http://download.opensuse.org/repositories/security:/shibboleth/CentOS_7/repodata/repomd.xml.key', +# enabled => 1, +# require => Anchor['openstack_extras_redhat'] +# } +# +# == Note about Redhat osfamily +# According to puppet-apache we need to enable a new repo, but in puppet-openstack +# we won't enable any exteral third party repo. +# http://wiki.aaf.edu.au/tech-info/sp-install-guide. We provide some helpers but +# as the packaging is lacking official support, we cannot guaranty it will work. +# +class keystone::federation::shibboleth( + $methods, + $admin_port = false, + $main_port = true, + $module_plugin = 'keystone.auth.plugins.mapped.Mapped', + $suppress_warning = false, + $template_order = 331, + $yum_repo_name = 'shibboleth' +) { + + include ::apache + + # Note: if puppet-apache modify these values, this needs to be updated + if $template_order <= 330 or $template_order >= 999 { + fail('The template order should be greater than 330 and less than 999.') + } + + if ('external' in $methods ) { + fail('The external method should be dropped to avoid any interference with some Apache + Shibboleth SP setups, where a REMOTE_USER env variable is always set, even as an empty value.') + } + + if !('saml2' in $methods ) { + fail('Methods should contain saml2 as one of the auth methods.') + }else{ + if ($module_plugin != 'keystone.auth.plugins.mapped.Mapped') { + fail('The plugin for saml and shibboleth should be keystone.auth.plugins.mapped.Mapped') + } + } + + validate_bool($admin_port) + validate_bool($main_port) + validate_bool($suppress_warning) + + if( !$admin_port and !$main_port){ + fail('No VirtualHost port to configure, please choose at least one.') + } + + keystone_config { + 'auth/methods': value => join(any2array($methods),','); + 'auth/saml2': value => $module_plugin; + } + + if $::osfamily == 'Debian' or ($::osfamily == 'RedHat' and (defined(Yumrepo[$yum_repo_name])) or defined(Package['shibboleth'])) { + if $::osfamily == 'RedHat' { + warning('The platform is not officially supported, use at your own risk. Check manifest documentation for more.') + apache::mod { 'shib2': + id => 'mod_shib', + path => '/usr/lib64/shibboleth/mod_shib_24.so' + } + } else { + class { '::apache::mod::shib': } + } + + if $admin_port { + concat::fragment { 'configure_shibboleth_on_port_35357': + target => "${keystone::wsgi::apache::priority}-keystone_wsgi_admin.conf", + content => template('keystone/shibboleth.conf.erb'), + order => $template_order, + } + } + + if $main_port { + concat::fragment { 'configure_shibboleth_on_port_5000': + target => "${keystone::wsgi::apache::priority}-keystone_wsgi_main.conf", + content => template('keystone/shibboleth.conf.erb'), + order => $template_order, + } + } + } elsif $::osfamily == 'Redhat' { + if !$suppress_warning { + warning( 'Can not configure Shibboleth in Apache on RedHat OS.Read the Note on this federation/shibboleth.pp' ) + } + } else { + fail('Unsupported platform') + } +} diff --git a/spec/acceptance/keystone_federation_shibboleth_spec.rb b/spec/acceptance/keystone_federation_shibboleth_spec.rb new file mode 100644 index 000000000..4ec3ac95b --- /dev/null +++ b/spec/acceptance/keystone_federation_shibboleth_spec.rb @@ -0,0 +1,182 @@ +require 'spec_helper_acceptance' + +describe 'keystone server running with Apache/WSGI as Service Provider with Shibboleth' do + + context 'default parameters' do + + it 'should work with no errors' do + pp= <<-EOS + include ::openstack_integration + include ::openstack_integration::repos + include ::openstack_integration::mysql + include ::openstack_integration::keystone + + ::keystone::resource::service_identity { 'beaker-ci': + service_type => 'beaker', + service_description => 'beaker service', + service_name => 'beaker', + password => 'secret', + public_url => 'http://127.0.0.1:1234', + admin_url => 'http://127.0.0.1:1234', + internal_url => 'http://127.0.0.1:1234', + } + # v3 admin + # we don't use ::keystone::roles::admin but still create resources manually: + keystone_domain { 'admin_domain': + ensure => present, + enabled => true, + description => 'Domain for admin v3 users', + } + keystone_domain { 'service_domain': + ensure => present, + enabled => true, + description => 'Domain for admin v3 users', + } + keystone_tenant { 'servicesv3::service_domain': + ensure => present, + enabled => true, + description => 'Tenant for the openstack services', + } + keystone_tenant { 'openstackv3::admin_domain': + ensure => present, + enabled => true, + description => 'admin tenant', + } + keystone_user { 'adminv3::admin_domain': + ensure => present, + enabled => true, + email => 'test@example.tld', + password => 'a_big_secret', + } + keystone_user_role { 'adminv3::admin_domain@openstackv3::admin_domain': + ensure => present, + roles => ['admin'], + } + # service user exists only in the service_domain - must + # use v3 api + ::keystone::resource::service_identity { 'beaker-civ3::service_domain': + service_type => 'beakerv3', + service_description => 'beakerv3 service', + service_name => 'beakerv3', + password => 'secret', + tenant => 'servicesv3::service_domain', + public_url => 'http://127.0.0.1:1234/v3', + admin_url => 'http://127.0.0.1:1234/v3', + internal_url => 'http://127.0.0.1:1234/v3', + user_domain => 'service_domain', + project_domain => 'service_domain', + } + class { '::keystone::federation::shibboleth': + methods => 'password, token, oauth1, saml2', + } + EOS + + + # Run it twice and test for idempotency + apply_manifest(pp, :catch_failures => true) + apply_manifest(pp, :catch_changes => true) + end + + describe port(5000) do + it { is_expected.to be_listening } + end + + describe port(35357) do + it { is_expected.to be_listening } + end + + describe cron do + it { is_expected.to have_entry('1 0 * * * keystone-manage token_flush >>/var/log/keystone/keystone-tokenflush.log 2>&1').with_user('keystone') } + end + + shared_examples_for 'keystone user/tenant/service/role/endpoint resources using v2 API' do |auth_creds| + it 'should find users in the default domain' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 user list") do |r| + expect(r.stdout).to match(/admin/) + expect(r.stderr).to be_empty + end + end + it 'should find tenants in the default domain' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 project list") do |r| + expect(r.stdout).to match(/openstack/) + expect(r.stderr).to be_empty + end + end + it 'should find beaker service' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 service list") do |r| + expect(r.stdout).to match(/beaker/) + expect(r.stderr).to be_empty + end + end + it 'should find admin role' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 role list") do |r| + expect(r.stdout).to match(/admin/) + expect(r.stderr).to be_empty + end + end + it 'should find beaker endpoints' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 endpoint list --long") do |r| + expect(r.stdout).to match(/1234/) + expect(r.stderr).to be_empty + end + end + end + shared_examples_for 'keystone user/tenant/service/role/endpoint resources using v3 API' do |auth_creds| + it 'should find beaker user' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 user list") do |r| + expect(r.stdout).to match(/beaker/) + expect(r.stderr).to be_empty + end + end + it 'should find services tenant' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 project list") do |r| + expect(r.stdout).to match(/services/) + expect(r.stderr).to be_empty + end + end + it 'should find beaker service' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 service list") do |r| + expect(r.stdout).to match(/beaker/) + expect(r.stderr).to be_empty + end + end + it 'should find admin role' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 role list") do |r| + expect(r.stdout).to match(/admin/) + expect(r.stderr).to be_empty + end + end + it 'should find beaker endpoints' do + shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 endpoint list") do |r| + expect(r.stdout).to match(/1234/) + expect(r.stderr).to be_empty + end + end + end + describe 'with v2 admin with v2 credentials' do + include_examples 'keystone user/tenant/service/role/endpoint resources using v2 API', + '--os-username admin --os-password a_big_secret --os-project-name openstack' + end + describe 'with v2 service with v2 credentials' do + include_examples 'keystone user/tenant/service/role/endpoint resources using v2 API', + '--os-username beaker-ci --os-password secret --os-project-name services' + end + describe 'with v2 admin with v3 credentials' do + include_examples 'keystone user/tenant/service/role/endpoint resources using v3 API', + '--os-username admin --os-password a_big_secret --os-project-name openstack --os-user-domain-name Default --os-project-domain-name Default' + end + describe "with v2 service with v3 credentials" do + include_examples 'keystone user/tenant/service/role/endpoint resources using v3 API', + '--os-username beaker-ci --os-password secret --os-project-name services --os-user-domain-name Default --os-project-domain-name Default' + end + describe 'with v3 admin with v3 credentials' do + include_examples 'keystone user/tenant/service/role/endpoint resources using v3 API', + '--os-username adminv3 --os-password a_big_secret --os-project-name openstackv3 --os-user-domain-name admin_domain --os-project-domain-name admin_domain' + end + describe "with v3 service with v3 credentials" do + include_examples 'keystone user/tenant/service/role/endpoint resources using v3 API', + '--os-username beaker-civ3 --os-password secret --os-project-name servicesv3 --os-user-domain-name service_domain --os-project-domain-name service_domain' + end + + end +end diff --git a/spec/classes/keystone_federation_shibboleth.rb b/spec/classes/keystone_federation_shibboleth.rb new file mode 100644 index 000000000..687dd1797 --- /dev/null +++ b/spec/classes/keystone_federation_shibboleth.rb @@ -0,0 +1,114 @@ +require 'spec_helper' + +describe 'keystone::federation::service_provider' do + + describe 'with invalid params' do + before do + params.merge!(:methods => 'external, password, token, oauth1') + end + + it_raises 'a Puppet::Error', /The external method should be dropped to avoid any interference with some Apache + Shibboleth SP setups, where a REMOTE_USER env variable is always set, even as an empty value./ + + before do + params.merge!(:methods => 'password, token, oauth1') + end + + it_raises 'a Puppet::Error', /Methods should contain saml2 as one of the auth methods./ + + before do + params.merge!(:methods => 'password, token, oauth1, saml2', + :module_plugin => 'keystone.auth.plugins') + end + + it_raises 'a Puppet:Error', /The plugin for saml and shibboleth should be keystone.auth.plugins.mapped.Mapped/ + + before do + params.merge!(:admin_port => false, + :main_port => false) + end + + it_raises 'a Puppet:Error', /No VirtualHost port to configure, please choose at least one./ + + befode do + params.merge!(:template_port => 330) + end + + it_raises 'a Puppet:Error', /The template order should be greater than 330 and less than 999./ + + befode do + params.merge!(:template_port => 999) + end + + it_raises 'a Puppet:Error', /The template order should be greater than 330 and less than 999./ + end + + context 'on a RedHat osfamily' do + let :facts do + { :osfamily => 'RedHat', + :operatingsystemrelease => '7.0', + :concat_basedir => '/var/lib/puppet/concat' } + end + + context 'with only required parameters' do + let :params do + { :methods => 'password, token, saml2' } + end + + it 'should have basic params for shibboleth in Keystone configuration' do + is_expected.to contain_keystone_config('auth/methods').with_value('password, token, saml2') + is_expected.to contain_keystone_config('auth/saml2').with_value('keystone.auth.plugins.mapped.Mapped') + end + end + + end + + context 'on a Debian osfamily' do + let :facts do + { :osfamily => 'Debian', + :operatingsystemrelease => '7.8', + :concat_basedir => '/var/lib/puppet/concat' } + end + + context 'with only required parameters' do + let :params do + { :methods => 'password, token, saml2' } + end + + it 'should have basic params for shibboleth in Keystone configuration' do + is_expected.to contain_keystone_config('auth/methods').with_value('password, token, saml2') + is_expected.to contain_keystone_config('auth/saml2').with_value('keystone.auth.plugins.mapped.Mapped') + end + + it { is_expected.to contain_concat__fragment('configure_shibboleth_on_port_5000').with({ + :target => "${keystone::wsgi::apache::priority}-keystone_wsgi_main.conf", + :order => params[:template_order], + })} + end + + context 'with override default parameters' do + let :params do + { :methods => 'password, token, saml2', + :admin_port => true } + end + + it 'should have basic params for shibboleth in Keystone configuration' do + is_expected.to contain_keystone_config('auth/methods').with_value('password, token, saml2') + is_expected.to contain_keystone_config('auth/saml2').with_value('keystone.auth.plugins.mapped.Mapped') + end + + it { is_expected.to contain_class('apache::mod::shib') } + + it { is_expected.to contain_concat__fragment('configure_shibboleth_on_port_5000').with({ + :target => "${keystone::wsgi::apache::priority}-keystone_wsgi_main.conf", + :order => params[:template_order], + })} + + it { is_expected.to contain_concat__fragment('configure_shibboleth_on_port_35357').with({ + :target => "${keystone::wsgi::apache::priority}-keystone_wsgi_admin.conf", + :order => params[:template_order], + })} + end + + end + +end diff --git a/templates/shibboleth.conf.erb b/templates/shibboleth.conf.erb new file mode 100644 index 000000000..23bc0d326 --- /dev/null +++ b/templates/shibboleth.conf.erb @@ -0,0 +1,12 @@ + WSGIScriptAliasMatch ^(/v3/OS-FEDERATION/identity_providers/.*?/protocols/.*?/auth)$ <%= scope['keystone::params::keystone_wsgi_script_path'] -%>/$1 + + + SetHandler shib + + + + ShibRequestSetting requireSession 1 + AuthType shibboleth + ShibExportAssertion Off + Require valid-user +