Merge "Support for Keystone as Identity Provider"

This commit is contained in:
Jenkins 2015-10-07 17:07:17 +00:00 committed by Gerrit Code Review
commit 5f15bbb1b6
3 changed files with 506 additions and 0 deletions

View File

@ -0,0 +1,144 @@
# == class: keystone::federation::identity_provider
#
# == Parameters
#
# [*certfile*]
# (Required) Path of the certfile for SAML signing. The path can not
# contain a comma. (string value).
# Defaults to $::keystone::ssl_ca_certs value.
#
# [*keyfile*]
# (Required) Path of the keyfile for SAML signing. The path can not
# contain a comma (string value).
# Defaults to $::keystone::ssl_ca_key value.
#
# [*idp_entity_id*]
# (Required) Entity ID value for unique Identity Provider identification
# (string value).
#
# [*idp_sso_endpoint*]
# (Required) Identity Provider Single-Sign-On service value (string value).
#
# [*idp_metadata_path*]
# (Required) Path to the Identity Provider Metadata file (string value).
#
# [*idp_organization_name*]
# (Optional) Organization name the installation belongs to (string value).
# Defaults to 'undef'.
#
# [*idp_organization_display_name*]
# (Optional) Organization name to be displayed (string value).
# Defaults to 'undef'.
#
# [*idp_organization_url*]
# (Optional) URL of the organization (string value).
# Defaults to 'undef'.
#
# [*idp_contact_company*]
# (Optional) Company of contact person (string value).
# Defaults to 'undef'.
#
# [*idp_contact_name*]
# (Optional) Given name of contact person (string value).
# Defaults to 'undef'.
#
# [*idp_contact_surname*]
# (Optional) Surname of contact person (string value).
# Defaults to 'undef'.
#
# [*idp_contact_email*]
# (Optional) Email address of contact person (string value).
# Defaults to 'undef'.
#
# [*idp_contact_telephone*]
# (Optional) Telephone number of contact person (string value).
# Defaults to 'undef'.
#
# [*idp_contact_type*]
# (Optional) Contact type. Allowed values are: technical, support,
# administrative billing, and other (string value).
# Defaults to 'undef'.
#
# [*user*]
# (Optional) User with access to keystone files. (string value)
# Defaults to 'keystone'.
#
# == Dependencies
# == Examples
# == Authors
#
# Iury Gregory iurygregory@gmail.com
#
# == Copyright
#
# Copyright 2013 eNovance <licensing@enovance.com>
#
class keystone::federation::identity_provider(
$idp_entity_id,
$idp_sso_endpoint,
$idp_metadata_path,
$certfile = $::keystone::ssl_ca_certs,
$keyfile = $::keystone::ssl_ca_key,
$user = 'keystone',
$idp_organization_name = undef,
$idp_organization_display_name = undef,
$idp_organization_url = undef,
$idp_contact_company = undef,
$idp_contact_name = undef,
$idp_contact_surname = undef,
$idp_contact_email = undef,
$idp_contact_telephone = undef,
$idp_contact_type = undef,
) {
include ::keystone::params
if $::keystone::service_name != 'httpd' {
fail ('Keystone need to be running under Apache for Federation work.')
}
ensure_packages(['xmlsec1','python-pysaml2'], {
ensure => present
})
keystone_config {
'saml/certfile': value => $certfile;
'saml/keyfile': value => $keyfile;
'saml/idp_entity_id': value => $idp_entity_id;
'saml/idp_sso_endpoint': value => $idp_sso_endpoint;
'saml/idp_metadata_path': value => $idp_metadata_path;
'saml/idp_organization_name': value => $idp_organization_name;
'saml/idp_organization_display_name': value => $idp_organization_display_name;
'saml/idp_organization_url': value => $idp_organization_url;
'saml/idp_contact_company': value => $idp_contact_company;
'saml/idp_contact_name': value => $idp_contact_name;
'saml/idp_contact_surname': value => $idp_contact_surname;
'saml/idp_contact_email': value => $idp_contact_email;
'saml/idp_contact_telephone': value => $idp_contact_telephone;
}
if $idp_contact_type and !($idp_contact_type in ['technical','support','administrative','billing','other']) {
fail('Allowed values for idp_contact_type are: technical, support, administrative, billing and other')
} else{
keystone_config {
'saml/idp_contact_type': value => $idp_contact_type;
}
}
exec {'saml_idp_metadata':
path => '/usr/bin',
user => "${user}",
command => "keystone-manage saml_idp_metadata > ${idp_metadata_path}",
creates => $idp_metadata_path,
notify => Service[$::keystone::params::service_name],
subscribe => Package['keystone'],
}
file { $idp_metadata_path:
ensure => present,
mode => '0600',
owner => "${user}",
}
Keystone_config<||> -> Exec<| title == 'saml_idp_metadata'|>
}

View File

@ -0,0 +1,251 @@
require 'spec_helper_acceptance'
describe 'keystone server running with Apache/WSGI as Identity Provider' do
context 'default parameters' do
it 'should work with no errors' do
pp= <<-EOS
Exec { logoutput => 'on_failure' }
# Common resources
case $::osfamily {
'Debian': {
include ::apt
apt::ppa { 'ppa:ubuntu-cloud-archive/liberty-staging':
# it's false by default in 2.x series but true in 1.8.x
package_manage => false,
}
Exec['apt_update'] -> Package<||>
}
'RedHat': {
class { '::openstack_extras::repo::redhat::redhat':
manage_rdo => false,
repo_hash => {
# we need kilo repo to be installed for dependencies
'rdo-kilo' => {
'baseurl' => 'https://repos.fedorapeople.org/repos/openstack/openstack-kilo/el7/',
'descr' => 'RDO kilo',
'gpgcheck' => 'no',
},
'rdo-liberty' => {
'baseurl' => 'http://trunk.rdoproject.org/centos7/current/',
'descr' => 'RDO trunk',
'gpgcheck' => 'no',
},
},
}
package { 'openstack-selinux': ensure => 'latest' }
}
default: {
fail("Unsupported osfamily (${::osfamily})")
}
}
class { '::mysql::server': }
# Keystone resources
class { '::keystone::client': }
class { '::keystone::cron::token_flush': }
class { '::keystone::db::mysql':
password => 'keystone',
}
class { '::keystone':
verbose => true,
debug => true,
database_connection => 'mysql://keystone:keystone@127.0.0.1/keystone',
admin_token => 'admin_token',
enabled => true,
service_name => 'httpd',
default_domain => 'default_domain',
}
include ::apache
class { '::keystone::wsgi::apache':
ssl => false,
}
# "v2" admin and service
class { '::keystone::roles::admin':
email => 'test@example.tld',
password => 'a_big_secret',
}
class { '::keystone::endpoint':
public_url => "http://127.0.0.1:5000/",
admin_url => "http://127.0.0.1:35357/",
default_domain => 'admin',
}
::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':
ensure => present,
enabled => true,
description => 'Tenant for the openstack services',
domain => 'service_domain',
}
keystone_tenant { 'openstackv3':
ensure => present,
enabled => true,
description => 'admin tenant',
domain => 'admin_domain',
}
keystone_user { 'adminv3':
ensure => present,
enabled => true,
tenant => 'openstackv3', # note: don't have to use 'openstackv3::admin_domain' here since the tenant name 'openstackv3' is unique among all domains
email => 'test@example.tld',
password => 'a_big_secret',
domain => 'admin_domain',
}
keystone_user_role { 'adminv3@openstackv3':
ensure => present,
roles => ['admin'],
}
# service user exists only in the service_domain - must
# use v3 api
::keystone::resource::service_identity { 'beaker-civ3':
service_type => 'beakerv3',
service_description => 'beakerv3 service',
service_name => 'beakerv3',
password => 'secret',
tenant => 'servicesv3',
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::identity_provider':
idp_entity_id => 'http://127.0.0.1:5000/v3/OS-FEDERATION/saml2/idp',
idp_sso_endpoint => 'http://127.0.0.1:5000/v3/OS-FEDERATION/saml2/sso',
idp_metadata_path => '/etc/keystone/saml2_idp_metadata.xml',
}
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_domain --os-project-domain-name default_domain'
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_domain --os-project-domain-name default_domain'
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

View File

@ -0,0 +1,111 @@
require 'spec_helper'
describe 'keystone::federation::identity_provider' do
let :pre_condition do
"class { 'keystone':
admin_tokend => 'dummy',
service_name => 'httpd',
enable_ssl=> true }"
end
let :params do
{ :user => 'keystone',
:certfile => '/etc/keystone/ssl/certs/signing_cert.pem',
:keyfile => '/etc/keystone/ssl/private/signing_key.pem',
:idp_entity_id => 'https://keystone.example.com/v3/OS-FEDERATION/saml2/idp',
:idp_sso_endpoint => 'https://keystone.example.com/v3/OS-FEDERATION/saml2/sso',
:idp_metadata_path => '/etc/keystone/saml2_idp_metadata.xml' }
end
let :optional_params do
{ :idp_organization_name => 'ExampleCompany',
:idp_organization_display_name => 'Example',
:idp_organization_url => 'www.example.com',
:idp_contact_company => 'someone',
:idp_contact_name => 'name',
:idp_contact_surname => 'surname',
:idp_contact_email => 'name@example.com',
:idp_contact_telephone => '+55000000000',
:idp_contact_type => 'other' }
end
shared_examples_for 'keystone federation identity provider' do
it { is_expected.to contain_class('keystone::params') }
context 'keystone not running under apache' do
let :pre_condition do
"class { 'keystone':
admin_tokend => 'dummy',
service_name => 'keystone',
enable_ssl=> true }"
end
it_raises 'a Puppet::Error', /Keystone need to be running under Apache for Federation work./
end
it 'should have' do
is_expected.to contain_package('xmlsec1').with(
:ensure => 'present',
)
is_expected.to contain_package('python-pysaml2').with(
:ensure => 'present',
)
end
it 'should configure keystone.conf' do
is_expected.to contain_keystone_config('saml/certfile').with_value(params[:certfile])
is_expected.to contain_keystone_config('saml/keyfile').with_value(params[:keyfile])
is_expected.to contain_keystone_config('saml/idp_entity_id').with_value(params[:idp_entity_id])
is_expected.to contain_keystone_config('saml/idp_sso_endpoint').with_value(params[:idp_sso_endpoint])
is_expected.to contain_keystone_config('saml/idp_metadata_path').with_value(params[:idp_metadata_path])
end
it { is_expected.to contain_exec('saml_idp_metadata').with(
:command => "keystone-manage saml_idp_metadata > #{params[:idp_metadata_path]}",
:creates => "#{params[:idp_metadata_path]}",
) }
it 'creates saml idp metadata file' do
is_expected.to contain_file("#{params[:idp_metadata_path]}").with(
:ensure => 'present',
:mode => '0600',
:owner => 'keystone',
)
end
context 'configure Keystone with optional params' do
before :each do
params.merge!(optional_params)
end
it 'should configure keystone.conf' do
is_expected.to contain_keystone_config('saml/certfile').with_value(params[:certfile])
is_expected.to contain_keystone_config('saml/keyfile').with_value(params[:keyfile])
is_expected.to contain_keystone_config('saml/idp_entity_id').with_value(params[:idp_entity_id])
is_expected.to contain_keystone_config('saml/idp_sso_endpoint').with_value(params[:idp_sso_endpoint])
is_expected.to contain_keystone_config('saml/idp_metadata_path').with_value(params[:idp_metadata_path])
is_expected.to contain_keystone_config('saml/idp_organization_name').with_value(params[:idp_organization_name])
is_expected.to contain_keystone_config('saml/idp_organization_display_name').with_value(params[:idp_organization_display_name])
is_expected.to contain_keystone_config('saml/idp_organization_url').with_value(params[:idp_organization_url])
is_expected.to contain_keystone_config('saml/idp_contact_company').with_value(params[:idp_contact_company])
is_expected.to contain_keystone_config('saml/idp_contact_name').with_value(params[:idp_contact_name])
is_expected.to contain_keystone_config('saml/idp_contact_surname').with_value(params[:idp_contact_surname])
is_expected.to contain_keystone_config('saml/idp_contact_email').with_value(params[:idp_contact_email])
is_expected.to contain_keystone_config('saml/idp_contact_telephone').with_value(params[:idp_contact_telephone])
is_expected.to contain_keystone_config('saml/idp_contact_type').with_value(params[:idp_contact_type])
end
end
context 'with invalid values for idp_contact_type' do
before do
params.merge!(:idp_contact_type => 'foobar')
end
it_raises 'a Puppet::Error', /Allowed values for idp_contact_type are: technical, support, administrative, billing and other/
end
end
end