Keystone Federation Service Provider Configuration

This patch adds the ability to configure Keystone as a Service
Provider (SP) for a Federated Identity Provider (IdP).

* New variables to configure Keystone as a service provider are now
  supported under a root `keystone_sp` variable. Example configurations
  can be seen in Keystone's defaults file. This configuration includes
  the list of identity providers and trusted dashboards. (At this time
  only one identity provider is supported).

* Identity provider configuration includes the remote-to-local user
  mapping and the list of remote attributes the SP can obtain from the
  IdP.

* Shibboleth is installed and configured in the Keystone containers when
  SP configuration is present.

* Horizon is configured for SSO login

DocImpact
UpgradeImpact
Implements: blueprint keystone-federation
Change-Id: I78b3d740434ea4b3ca0bd9f144e4a07026be23c6
Co-Authored-By: Jesse Pretorius <jesse.pretorius@rackspace.co.uk>
This commit is contained in:
Miguel Grinberg 2015-06-18 19:06:56 -07:00 committed by Jesse Pretorius
parent d82bbb4336
commit 23da164fe4
16 changed files with 861 additions and 40 deletions

View File

@ -195,7 +195,8 @@ options:
'ensure_user', 'ensure_user_role', 'ensure_tenant', 'ensure_user', 'ensure_user_role', 'ensure_tenant',
'ensure_project', 'ensure_service_provider', 'ensure_project', 'ensure_service_provider',
'ensure_group', 'ensure_identity_provider', 'ensure_group', 'ensure_identity_provider',
'ensure_protocol', ensure_mapping'] 'ensure_protocol', ensure_mapping',
'ensure_group_role']
required: true required: true
insecure: insecure:
description: description:
@ -251,6 +252,13 @@ EXAMPLES = """
project_name: "service" project_name: "service"
role_name: "admin" role_name: "admin"
# Add a project role to a group
- keystone:
command: "ensure_group_role"
group_name: "fedgroup"
project_name: "fedproject"
role_name: "_member_"
# Create a service # Create a service
- keystone: - keystone:
command: "ensure_service" command: "ensure_service"
@ -353,6 +361,13 @@ COMMAND_MAP = {
'role_name' 'role_name'
] ]
}, },
'ensure_group_role': {
'variables': [
'group_name',
'project_name',
'role_name'
]
},
'ensure_project': { 'ensure_project': {
'variables': [ 'variables': [
'project_name', 'project_name',
@ -716,7 +731,7 @@ class ManageKeystone(object):
return self._facts(facts={'id': user.id}) return self._facts(facts={'id': user.id})
def _get_role(self, name): def _get_role(self, name):
"""Return a tenant by name. """Return a role by name.
This will return `None` if the ``name`` is not found. This will return `None` if the ``name`` is not found.
@ -728,8 +743,25 @@ class ManageKeystone(object):
else: else:
return None return None
def _get_group(self, name, domain=None):
"""Return a group by name.
This will return `None` if the ``name`` is not found.
:param name: ``str`` Name of the role.
"""
for entry in self.keystone.groups.list():
if domain is None:
if entry.name == name:
return entry
else:
if entry.name == name and entry.domain_id == domain.id:
return entry
else:
return None
def get_role(self, variables): def get_role(self, variables):
"""Return a tenant by name. """Return a role by name.
This will return `None` if the ``name`` is not found. This will return `None` if the ``name`` is not found.
@ -749,7 +781,8 @@ class ManageKeystone(object):
return self._facts(facts={'id': role_data.id}) return self._facts(facts={'id': role_data.id})
def _get_role_data(self, user_name, project_name, role_name): def _get_role_data(self, user_name, project_name, role_name, group_name):
if user_name is not None:
user = self._get_user(name=user_name) user = self._get_user(name=user_name)
if user is None: if user is None:
self.failure( self.failure(
@ -757,6 +790,8 @@ class ManageKeystone(object):
rc=2, rc=2,
msg='User was not found, does it exist?' msg='User was not found, does it exist?'
) )
else:
user = None
project = self._get_project(name=project_name) project = self._get_project(name=project_name)
if project is None: if project is None:
@ -774,7 +809,18 @@ class ManageKeystone(object):
msg='role was not found, does it exist?' msg='role was not found, does it exist?'
) )
return user, project, role if group_name is not None:
group = self._get_group(name=group_name)
if group is None:
self.failure(
error='group [ %s ] was not found.' % group_name,
rc=2,
msg='group was not found, does it exist?'
)
else:
group = None
return user, project, role, group
def ensure_role(self, variables): def ensure_role(self, variables):
"""Create a new role within Keystone if it does not exist. """Create a new role within Keystone if it does not exist.
@ -802,6 +848,13 @@ class ManageKeystone(object):
else: else:
return None return None
def _get_group_roles(self, name, group, project):
for entry in self.keystone.roles.list(group=group, project=project):
if entry.name == name:
return entry
else:
return None
def ensure_user_role(self, variables): def ensure_user_role(self, variables):
self._authenticate() self._authenticate()
required_vars = ['user_name', 'role_name'] required_vars = ['user_name', 'role_name']
@ -814,8 +867,9 @@ class ManageKeystone(object):
or variables_dict.pop('tenant_name')) or variables_dict.pop('tenant_name'))
role_name = variables_dict.pop('role_name') role_name = variables_dict.pop('role_name')
user, project, role = self._get_role_data( user, project, role, group = self._get_role_data(
user_name=user_name, project_name=project_name, role_name=role_name user_name=user_name, project_name=project_name,
role_name=role_name, group_name=None
) )
user_role = self._get_user_roles( user_role = self._get_user_roles(
@ -832,6 +886,64 @@ class ManageKeystone(object):
return self._facts(facts={'id': user_role.id}) return self._facts(facts={'id': user_role.id})
def ensure_group_role(self, variables):
self._authenticate()
required_vars = ['group_name', 'project_name', 'role_name']
variables_dict = self._get_vars(variables, required=required_vars)
group_name = variables_dict.pop('group_name')
project_name = variables_dict.pop('project_name')
role_name = variables_dict.pop('role_name')
user, project, role, group = self._get_role_data(
group_name=group_name, project_name=project_name,
role_name=role_name, user_name=None
)
group_role = self._get_group_roles(
name=role_name, group=group, project=project
)
if group_role is None:
self.keystone.roles.grant(
group=group, role=role, project=project
)
group_role = self._get_group_roles(
name=role_name, group=group, project=project
)
return self._facts(facts={'id': group_role.id})
def ensure_group(self, variables):
"""Create a new group within Keystone if it does not exist.
Returns the group ID on a successful run.
:param variables: ``list`` List of all variables that are available to
use within the Keystone Command.
"""
self._authenticate()
required_vars = ['group_name', 'domain_name']
variables_dict = self._get_vars(variables, required=required_vars)
group_name = variables_dict.pop('group_name')
domain_name = variables_dict.pop('domain_name')
domain = self._get_domain(
name=domain_name
)
group = self._get_group(
name=group_name, domain=domain
)
if group is None:
self.state_change = True
group = self.keystone.groups.create(
name=group_name, domain=domain
)
return self._facts(facts={'id': group.id})
def _get_service(self, name, srv_type=None): def _get_service(self, name, srv_type=None):
for entry in self.keystone.services.list(): for entry in self.keystone.services.list():
if srv_type is not None: if srv_type is not None:
@ -975,23 +1087,6 @@ class ManageKeystone(object):
# return no facts in this case. # return no facts in this case.
return self._facts(facts={}) return self._facts(facts={})
def ensure_group(self, variables):
"""Create a new group within Keystone if it does not exist.
Returns the group ID on a successful run.
:param variables: ``list`` List of all variables that are available to
use within the Keystone Command.
"""
self._authenticate()
return self._ensure_generic(
manager=self.keystone.groups,
required_vars={'group_name': 'name',
'domain_name': 'domain'},
variables=variables
)
def ensure_identity_provider(self, variables): def ensure_identity_provider(self, variables):
self._authenticate() self._authenticate()
return self._ensure_generic( return self._ensure_generic(

View File

@ -170,21 +170,27 @@ OPENSTACK_KEYSTONE_URL = "{{ horizon_keystone_endpoint }}"
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_" OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_"
# Enables keystone web single-sign-on if set to True. {% if keystone_sp is defined %}
#WEBSSO_ENABLED = False # Enables keystone web single-sign-on
WEBSSO_ENABLED = True
# Determines which authentication choice to show as default. # Determines which authentication choice to show as default.
#WEBSSO_INITIAL_CHOICE = "credentials" WEBSSO_INITIAL_CHOICE = "credentials"
# The list of authentication mechanisms # The list of authentication mechanisms
# which include keystone federation protocols. # which include keystone federation protocols.
# Current supported protocol IDs are 'saml2' and 'oidc' # Current supported protocol IDs are 'saml2' and 'oidc'
# which represent SAML 2.0, OpenID Connect respectively. # which represent SAML 2.0, OpenID Connect respectively.
# Do not remove the mandatory credentials mechanism. # Do not remove the mandatory credentials mechanism.
#WEBSSO_CHOICES = ( WEBSSO_CHOICES = (
# ("credentials", _("Keystone Credentials")), ("credentials", _("Keystone Credentials")),
# ("oidc", _("OpenID Connect")), {% for idp in keystone_sp.trusted_idp_list %}
# ("saml2", _("Security Assertion Markup Language"))) {% for protocol in idp.protocols %}
("{{ protocol.name }}", _("{{ idp.name }}")){% if not loop.last %},{% endif %}
{% endfor %}
{% endfor %}
)
{% endif %}
# Disable SSL certificate checks (useful for self-signed certificates): # Disable SSL certificate checks (useful for self-signed certificates):
OPENSTACK_SSL_NO_VERIFY = {{ horizon_ssl_no_verify | bool }} OPENSTACK_SSL_NO_VERIFY = {{ horizon_ssl_no_verify | bool }}

View File

@ -120,7 +120,7 @@ keystone_service_internalurl: "{{ keystone_service_internalurl_v3 }}"
keystone_service_adminurl: "{{ keystone_service_adminurl_v3 }}" keystone_service_adminurl: "{{ keystone_service_adminurl_v3 }}"
## Set this value to override the "public_endpoint" keystone.conf variable ## Set this value to override the "public_endpoint" keystone.conf variable
#keystone_public_endpoint: #keystone_public_endpoint: "{{ keystone_service_publicuri }}"
## Apache setup ## Apache setup
keystone_apache_log_level: info keystone_apache_log_level: info
@ -190,6 +190,82 @@ keystone_recreate_keys: False
# contact_telephone: 555-55-5555 # contact_telephone: 555-55-5555
# contact_type: technical # contact_type: technical
# Enable the following section in order to install and configure
# Keystone as a Resource Service Provider (SP) and to configure
# trusts with specific Identity Providers (IdP).
#keystone_sp:
# cert_duration_years: 5
# trusted_dashboard_list:
# - "https://{{ external_lb_vip_address }}/auth/websso/"
# trusted_idp_list:
# note that only one of these is supported at any one time for now
# - name: "keystone-idp"
# entity_ids:
# - 'https://keystone-idp:5000/v3/OS-FEDERATION/saml2/idp'
# metadata_uri: 'https://keystone-idp:5000/v3/OS-FEDERATION/saml2/metadata'
# metadata_file: 'metadata-keystone-idp.xml'
# metadata_reload: 1800
# federated_identities:
# - domain: Default
# project: fedproject
# group: fedgroup
# role: _member_
# protocols:
# - name: saml2
# mapping:
# name: keystone-idp-mapping
# rules:
# - remote:
# - type: openstack_user
# local:
# - group:
# name: fedgroup
# domain:
# name: Default
# user:
# name: '{0}'
# attributes:
# - name: openstack_user
# id: openstack_user
# - name: openstack_roles
# id: openstack_roles
# - name: openstack_project
# id: openstack_project
# - name: openstack_user_domain
# id: openstack_user_domain
# - name: openstack_project_domain
# id: openstack_project_domain
#
# - name: 'testshib-idp'
# entity_ids:
# - 'https://idp.testshib.org/idp/shibboleth'
# metadata_uri: 'http://www.testshib.org/metadata/testshib-providers.xml'
# metadata_file: 'metadata-testshib-idp.xml'
# metadata_reload: 1800
# federated_identities:
# - domain: Default
# project: fedproject
# group: fedgroup
# role: _member_
# protocols:
# - name: saml2
# mapping:
# name: testshib-idp-mapping
# rules:
# - remote:
# - type: eppn
# local:
# - group:
# name: fedgroup
# domain:
# name: Default
# - user:
# name: '{0}'
# Keystone Federation SP Packages
keystone_sp_apt_packages:
- libapache2-mod-shib2
# Common apt packages # Common apt packages
keystone_apt_packages: keystone_apt_packages:
- apache2 - apache2

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Keystone WebSSO redirect</title>
</head>
<body>
<form id="sso" name="sso" action="$host" method="post">
Please wait...
<br/>
<input type="hidden" name="token" id="token" value="$token"/>
<noscript>
<input type="submit" name="submit_no_javascript" id="submit_no_javascript"
value="If your JavaScript is disabled, please click to continue"/>
</noscript>
</form>
<script type="text/javascript">
window.onload = function() {
document.forms['sso'].submit();
}
</script>
</body>
</html>

View File

@ -22,3 +22,13 @@
until: apache_restart|success until: apache_restart|success
retries: 5 retries: 5
delay: 2 delay: 2
- name: Restart Shibd
service:
name: "shibd"
state: "restarted"
pattern: "shibd"
register: shibd_restart
until: shibd_restart|success
retries: 5
delay: 2

View File

@ -0,0 +1,117 @@
#!/usr/bin/python
# (c) 2015, Kevin Carter <kevin.carter@rackspace.com>
#
# Copyright 2015, Rackspace US, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
DOCUMENTATION = """
---
module: keystone_sp
version_added: "1.9.2"
short_description:
- Creates a fact for keystone_federated_identities and keystone_protocols
description:
- Sets facts called `keystone_federated_identities` and
`keystone_federated_protocols`, which are lists of hashes built from
keystone_sp using the information in the `federated_identities` and
`protocols` keys.
options:
sp_data:
description:
- Hash to build the service provider lists from
required: true
author: Kevin Carter
"""
EXAMPLES = """
# Set the keystone_federated_identities and keystone_federated_protocols facts
- keystone_sp:
sp_data: "{{ keystone_sp }}"
when: keystone_sp is defined
"""
# Keystone service provider data structure example.
"""
keystone_sp:
trusted_idp_list:
- name: "keystone-idp"
federated_identities:
- domain: Default
project: fedproject
group: fedgroup
role: _member_
protocols:
- name: saml2
mapping:
...
- name: 'testshib-idp'
federated_identities:
- domain: Default
project: fedproject2
group: fedgroup2
role: _member_
protocols:
- name: saml2
mapping:
...
"""
class KeystoneSp(object):
def __init__(self, module):
"""Generate an integer from a name."""
self.module = module
self.identities_return_list = list()
self.protocols_return_list = list()
self.sp_data = self.module.params['sp_data']
def populate_sp_data(self):
trusted_idp_list = self.sp_data['trusted_idp_list']
for trusted_idp in trusted_idp_list:
federated_identities = trusted_idp.get('federated_identities')
if federated_identities:
self.identities_return_list.extend(federated_identities)
protocols = trusted_idp.get('protocols')
if protocols:
for protocol in protocols:
self.protocols_return_list.append(
{'idp': trusted_idp, 'protocol': protocol})
def main():
module = AnsibleModule(
argument_spec=dict(
sp_data=dict(
required=True
)
),
supports_check_mode=False
)
try:
ksp = KeystoneSp(module=module)
ksp.populate_sp_data()
module.exit_json(
changed=True,
ansible_facts={
'keystone_federated_identities': ksp.identities_return_list,
'keystone_federated_protocols': ksp.protocols_return_list}
)
except Exception as exp:
resp = {'stderr': exp}
module.fail_json(msg='Failed Process', **resp)
# import module snippets
from ansible.module_utils.basic import *
if __name__ == '__main__':
main()

View File

@ -61,5 +61,17 @@
apache2_module: apache2_module:
name: ssl name: ssl
state: "{{ (keystone_ssl_enabled | bool) | ternary('present', 'absent') }}" state: "{{ (keystone_ssl_enabled | bool) | ternary('present', 'absent') }}"
notify:
- Restart Apache
tags:
- keystone-httpd
- name: Enable/disable mod_shib2 for apache2
apache2_module:
name: shib2
state: "{{ ( keystone_sp is defined ) | ternary('present', 'absent') }}"
ignore_errors: yes
notify:
- Restart Apache
tags: tags:
- keystone-httpd - keystone-httpd

View File

@ -0,0 +1,152 @@
---
# Copyright 2014, Rackspace US, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# note that these tasks will run when the id/name parameter is present.
# Providing the id/name without the other required params is a user error.
# TODO: Revisit this method when Ansible 2 releases
# User with_subelements instead, but in v1.x it's broken
- name: Set keystone_federated_identities fact
keystone_sp:
sp_data: "{{ keystone_sp }}"
tags:
- keystone-federation-sp
- name: Ensure domain which remote IDP users are mapped onto exists
keystone:
command: ensure_domain
domain_name: "{{ item.domain }}"
token: "{{ keystone_auth_admin_token }}"
endpoint: "{{ keystone_service_adminurl }}"
insecure: "{{ keystone_service_adminuri_insecure }}"
when: item.domain is defined
with_items: keystone_federated_identities
tags:
- keystone-federation-sp
- name: Ensure project which remote IDP users are mapped onto exists
keystone:
command: ensure_project
project_name: "{{ item.project }}"
domain_name: "{{ item.domain | default('Default') }}"
token: "{{ keystone_auth_admin_token }}"
endpoint: "{{ keystone_service_adminurl }}"
insecure: "{{ keystone_service_adminuri_insecure }}"
when: item.project is defined
with_items: keystone_federated_identities
tags:
- keystone-federation-sp
- name: Ensure user which remote IDP users are mapped onto exists
keystone:
command: ensure_user
user_name: "{{ item.user }}"
password: "{{ item.password }}"
project_name: "{{ item.project }}"
domain_name: "{{ item.domain | default('Default') }}"
token: "{{ keystone_auth_admin_token }}"
endpoint: "{{ keystone_service_adminurl }}"
insecure: "{{ keystone_service_adminuri_insecure }}"
when: >
item.user is defined and
item.password is defined and
item.project is defined
with_items: keystone_federated_identities
tags:
- keystone-federation-sp
- name: Ensure Group for external IDP users exists
keystone:
command: ensure_group
group_name: "{{ item.group }}"
domain_name: "{{ item.domain | default('Default') }}"
token: "{{ keystone_auth_admin_token }}"
endpoint: "{{ keystone_service_adminurl }}"
insecure: "{{ keystone_service_adminuri_insecure }}"
when: item.group is defined
with_items: keystone_federated_identities
tags:
- keystone-federation-sp
- name: Ensure Role for external IDP users exists
keystone:
command: "ensure_role"
role_name: "{{ item.role | default('_member_') }}"
token: "{{ keystone_auth_admin_token }}"
endpoint: "{{ keystone_service_adminurl }}"
insecure: "{{ keystone_service_adminuri_insecure }}"
when: >
item.group is defined and
item.project is defined
with_items: keystone_federated_identities
tags:
- keystone-federation-sp
- name: Ensure Group/Project/Role mapping exists
keystone:
command: ensure_group_role
group_name: "{{ item.group }}"
project_name: "{{ item.project }}"
role_name: "{{ item.role | default('_member_') }}"
token: "{{ keystone_auth_admin_token }}"
endpoint: "{{ keystone_service_adminurl }}"
insecure: "{{ keystone_service_adminuri_insecure }}"
when: >
item.group is defined and
item.project is defined
with_items: keystone_federated_identities
tags:
- keystone-federation-sp
- name: Ensure mapping for external IDP attributes exists
keystone:
command: ensure_mapping
mapping_name: "{{ item.protocol.mapping.name }}"
mapping_rules: "{{ item.protocol.mapping.rules }}"
token: "{{ keystone_auth_admin_token }}"
endpoint: "{{ keystone_service_adminurl }}"
insecure: "{{ keystone_service_adminuri_insecure }}"
when: item.protocol.mapping.name is defined
with_items: keystone_federated_protocols
tags:
- keystone-federation-sp
- name: Ensure external IDP
keystone:
command: ensure_identity_provider
idp_name: "{{ item.name }}"
idp_remote_ids: "{{ item.entity_ids }}"
idp_enabled: true
token: "{{ keystone_auth_admin_token }}"
endpoint: "{{ keystone_service_adminurl }}"
insecure: "{{ keystone_service_adminuri_insecure }}"
when: item.name is defined
with_items: keystone_sp.trusted_idp_list
tags:
- keystone-federation-sp
- name: Ensure federation protocol exists
keystone:
command: ensure_protocol
protocol_name: "{{ item.protocol.name }}"
idp_name: "{{ item.idp.name }}"
mapping_name: "{{ item.protocol.mapping.name }}"
token: "{{ keystone_auth_admin_token }}"
endpoint: "{{ keystone_service_adminurl }}"
insecure: "{{ keystone_service_adminuri_insecure }}"
when: item.protocol.name is defined
with_items: keystone_federated_protocols
tags:
- keystone-federation-sp

View File

@ -0,0 +1,101 @@
---
# Copyright 2015, Rackspace US, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
- name: Drop Shibboleth Config
template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
owner: "{{ keystone_system_user_name }}"
group: "{{ keystone_system_group_name }}"
mode: "{{ item.mode|default('0644') }}"
with_items:
- { src: "shibboleth-attribute-map.xml.j2", dest: "/etc/shibboleth/attribute-map.xml" }
- { src: "shibboleth2.xml.j2", dest: "/etc/shibboleth/shibboleth2.xml" }
notify:
- Restart Shibd
tags:
- keystone-config
- keystone-federation-sp
- name: Generate the Shibboleth SP key-pair
shell: "shib-keygen -h {{ external_lb_vip_address }} -y {{ keystone_sp.cert_duration_years }}"
args:
creates: "/etc/shibboleth/sp-cert.pem"
when: inventory_hostname == groups['keystone_all'][0]
notify:
- Restart Apache
- Restart Shibd
tags:
- keystone-config
- keystone-federation-sp
- name: Store Shibboleth SP key-pair
memcached:
name: "{{ item.name }}"
file_path: "{{ item.src }}"
state: "present"
server: "{{ memcached_servers }}"
encrypt_string: "{{ memcached_encryption_key }}"
with_items:
- { src: "/etc/shibboleth/sp-cert.pem", name: "keystone_sp_cert" }
- { src: "/etc/shibboleth/sp-key.pem", name: "keystone_sp_key" }
register: memcache_keys
until: memcache_keys|success
retries: 5
delay: 2
when: inventory_hostname == groups['keystone_all'][0]
tags:
- keystone-config
- keystone-federation-sp
- name: Distribute the Shibboleth SP key-pair
memcached:
name: "{{ item.name }}"
file_path: "{{ item.src }}"
state: "retrieve"
file_mode: "{{ item.file_mode }}"
dir_mode: "{{ item.dir_mode }}"
server: "{{ memcached_servers }}"
encrypt_string: "{{ memcached_encryption_key }}"
with_items:
- { src: "/etc/shibboleth/sp-cert.pem", name: "keystone_sp_cert", file_mode: "0640", dir_mode: "0750" }
- { src: "/etc/shibboleth/sp-key.pem", name: "keystone_sp_key", file_mode: "0600", dir_mode: "0750" }
register: memcache_keys
until: memcache_keys|success
retries: 5
delay: 2
when: inventory_hostname != groups['keystone_all'][0]
notify:
- Restart Apache
- Restart Shibd
tags:
- keystone-config
- keystone-federation-sp
- name: Set appropriate file ownership on the Shibboleth SP key-pair
file:
path: "{{ item }}"
owner: "_shibd"
group: "_shibd"
with_items:
- "/etc/shibboleth/sp-cert.pem"
- "/etc/shibboleth/sp-key.pem"
when: inventory_hostname != groups['keystone_all'][0]
notify:
- Restart Apache
- Restart Shibd
tags:
- keystone-config
- keystone-federation-sp

View File

@ -49,6 +49,19 @@
tags: tags:
- keystone-apt-packages - keystone-apt-packages
- name: Install SP apt packages
apt:
pkg: "{{ item }}"
state: latest
register: install_packages
until: install_packages|success
retries: 5
delay: 2
with_items: keystone_sp_apt_packages
when: keystone_sp is defined
tags:
- keystone-apt-packages
- name: Install pip packages - name: Install pip packages
pip: pip:
name: "{{ item }}" name: "{{ item }}"

View File

@ -36,6 +36,7 @@
mode: "{{ item.mode|default('0644') }}" mode: "{{ item.mode|default('0644') }}"
with_items: with_items:
- { src: "keystone-paste.ini", dest: "/etc/keystone/keystone-paste.ini" } - { src: "keystone-paste.ini", dest: "/etc/keystone/keystone-paste.ini" }
- { src: "sso_callback_template.html", dest: "/etc/keystone/sso_callback_template.html" }
- { src: "keystone-wsgi.py", dest: "/var/www/cgi-bin/keystone/admin", mode: "0755" } - { src: "keystone-wsgi.py", dest: "/var/www/cgi-bin/keystone/admin", mode: "0755" }
- { src: "keystone-wsgi.py", dest: "/var/www/cgi-bin/keystone/main", mode: "0755" } - { src: "keystone-wsgi.py", dest: "/var/www/cgi-bin/keystone/main", mode: "0755" }
notify: notify:

View File

@ -29,6 +29,10 @@
- include: keystone_post_install.yml - include: keystone_post_install.yml
- include: keystone_federation_sp_setup.yml
when: >
keystone_sp is defined
- include: keystone_db_setup.yml - include: keystone_db_setup.yml
when: > when: >
inventory_hostname == groups['keystone_all'][0] inventory_hostname == groups['keystone_all'][0]
@ -40,6 +44,11 @@
when: > when: >
inventory_hostname == groups['keystone_all'][0] inventory_hostname == groups['keystone_all'][0]
- include: keystone_federation_sp_idp_setup.yml
when: >
keystone_sp is defined and
inventory_hostname == groups['keystone_all'][0]
- name: Flush handlers - name: Flush handlers
meta: flush_handlers meta: flush_handlers

View File

@ -25,6 +25,32 @@ WSGIDaemonProcess keystone user={{ keystone_system_user_name }} group=nogroup pr
SSLOptions +StdEnvVars +ExportCertData SSLOptions +StdEnvVars +ExportCertData
{% endif %} {% endif %}
{% if keystone_sp is defined -%}
ShibURLScheme {{ keystone_service_publicuri_proto }}
<Location /Shibboleth.sso>
SetHandler shib
</Location>
<Location /v3/auth/OS-FEDERATION/websso/saml2>
AuthType shibboleth
ShibRequestSetting requireSession 1
ShibRequestSetting exportAssertion 1
ShibRequireSession On
ShibExportAssertion On
Require valid-user
</Location>
<LocationMatch /v3/OS-FEDERATION/identity_providers/.*?/protocols/saml2/auth>
ShibRequestSetting requireSession 1
AuthType shibboleth
ShibExportAssertion Off
Require valid-user
</LocationMatch>
WSGIScriptAliasMatch ^(/v3/OS-FEDERATION/identity_providers/.*?/protocols/.*?/auth)$ /var/www/cgi-bin/keystone/main/$1
{%- endif %}
WSGIScriptAlias / /var/www/cgi-bin/keystone/main WSGIScriptAlias / /var/www/cgi-bin/keystone/main
WSGIProcessGroup keystone WSGIProcessGroup keystone
</VirtualHost> </VirtualHost>

View File

@ -43,8 +43,12 @@ cache_time = {{ keystone_revocation_cache_time }}
[auth] [auth]
{% if keystone_sp is defined %}
methods = {{ keystone_auth_methods }},saml2
saml2 = keystone.auth.plugins.mapped.Mapped
{% else %}
methods = {{ keystone_auth_methods }} methods = {{ keystone_auth_methods }}
{% endif %}
[database] [database]
connection = mysql://{{ keystone_galera_user }}:{{ keystone_container_mysql_password }}@{{ keystone_galera_address }}/{{ keystone_galera_database }}?charset=utf8 connection = mysql://{{ keystone_galera_user }}:{{ keystone_container_mysql_password }}@{{ keystone_galera_address }}/{{ keystone_galera_database }}?charset=utf8
@ -132,3 +136,13 @@ public_port = {{ keystone_service_port }}
rabbit_hosts = {{ rabbitmq_servers }} rabbit_hosts = {{ rabbitmq_servers }}
rabbit_userid = {{ rabbitmq_userid }} rabbit_userid = {{ rabbitmq_userid }}
rabbit_password = {{ rabbitmq_password }} rabbit_password = {{ rabbitmq_password }}
{% if keystone_sp is defined %}
[federation]
remote_id_attribute = Shib-Identity-Provider
{% if keystone_sp.trusted_dashboard_list is defined %}
{% for item in keystone_sp.trusted_dashboard_list %}
trusted_dashboard = {{ item }}
{% endfor %}
{% endif %}
{% endif %}

View File

@ -0,0 +1,63 @@
<Attributes xmlns="urn:mace:shibboleth:2.0:attribute-map"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<!--
The below default attributes are standard for a Shibboleth
Identity Provider and will likely work with many other
standard SAML2 Identity Providers.
-->
<Attribute name="urn:mace:dir:attribute-def:eduPersonPrincipalName" id="eppn">
<AttributeDecoder xsi:type="ScopedAttributeDecoder"/>
</Attribute>
<Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" id="eppn">
<AttributeDecoder xsi:type="ScopedAttributeDecoder"/>
</Attribute>
<Attribute name="urn:mace:dir:attribute-def:eduPersonScopedAffiliation" id="affiliation">
<AttributeDecoder xsi:type="ScopedAttributeDecoder" caseSensitive="false"/>
</Attribute>
<Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" id="affiliation">
<AttributeDecoder xsi:type="ScopedAttributeDecoder" caseSensitive="false"/>
</Attribute>
<Attribute name="urn:mace:dir:attribute-def:eduPersonAffiliation" id="unscoped-affiliation">
<AttributeDecoder xsi:type="StringAttributeDecoder" caseSensitive="false"/>
</Attribute>
<Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.1" id="unscoped-affiliation">
<AttributeDecoder xsi:type="StringAttributeDecoder" caseSensitive="false"/>
</Attribute>
<Attribute name="urn:mace:dir:attribute-def:eduPersonEntitlement" id="entitlement"/>
<Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.7" id="entitlement"/>
<!-- A persistent id attribute that supports personalized anonymous access. -->
<Attribute name="urn:mace:dir:attribute-def:eduPersonTargetedID" id="targeted-id">
<AttributeDecoder xsi:type="ScopedAttributeDecoder"/>
</Attribute>
<Attribute name="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" id="persistent-id">
<AttributeDecoder xsi:type="NameIDAttributeDecoder" formatter="$NameQualifier!$SPNameQualifier!$Name" defaultQualifiers="true"/>
</Attribute>
<Attribute name="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent" id="persistent-id">
<AttributeDecoder xsi:type="NameIDAttributeDecoder" formatter="$NameQualifier!$SPNameQualifier!$Name" defaultQualifiers="true"/>
</Attribute>
<!--
The following attributes have been configured through Ansible.
-->
{% for idp in keystone_sp.trusted_idp_list %}
{% if idp.protocols is defined %}
{% for protocol in idp.protocols %}
{% if protocol.name == "saml2" and protocol.attributes is defined %}
{% for attr in protocol.attributes %}
<Attribute{% for k in attr %} {{ k }}="{{ attr[k] }}"{% endfor %}/>
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</Attributes>

View File

@ -0,0 +1,104 @@
<SPConfig xmlns="urn:mace:shibboleth:2.0:native:sp:config"
xmlns:conf="urn:mace:shibboleth:2.0:native:sp:config"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
clockSkew="180">
<!-- The entityID is the name by which your IdP will know your SP. -->
<ApplicationDefaults entityID="{{ keystone_service_publicuri }}/shibboleth">
<!-- You should use secure cookies if at all possible. See cookieProps in this Wiki article. -->
<!-- https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPSessions -->
<Sessions lifetime="28800"
timeout="3600"
relayState="ss:mem"
checkAddress="false"
handlerSSL="{% if keystone_ssl_enabled | bool %}true{% else %}false{% endif %}"
{% if keystone_service_publicuri_proto == "https" %}cookieProps="; path=/; secure"{% endif %}>
<!-- Triggers a login request directly to the IdP. -->
<!-- https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPServiceSSO -->
<SSO ECP="true" entityID="{{ keystone_sp.trusted_idp_list.0.entity_ids.0 }}">
SAML2 SAML1
</SSO>
<!-- SAML and local-only logout. -->
<!-- https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPServiceLogout -->
<Logout>SAML2 Local</Logout>
<!--
Handlers allow you to interact with the SP and gather more information.
Attribute values received by the SP through SAML will be visible at:
{{ keystone_service_publicuri }}/Shibboleth.sso/Session
-->
<!-- Extension service that generates "approximate" metadata based on SP configuration. -->
<Handler type="MetadataGenerator"
Location="/Metadata"
signing="false"/>
<!-- Status reporting service. -->
<Handler type="Status" Location="/Status" acl="127.0.0.1 ::1"/>
<!-- Session diagnostic service. -->
<Handler type="Session" Location="/Session" showAttributeValues="true"/>
<!-- JSON feed of discovery information. -->
<Handler type="DiscoveryFeed" Location="/DiscoFeed"/>
</Sessions>
<!--
Allows overriding of error template information/filenames. You can
also add attributes with values that can be plugged into the templates.
-->
<Errors supportContact="root@localhost"
helpLocation="/about.html"
styleSheet="/shibboleth-sp/main.css"/>
<!--
Loads and trusts a list of metadata files which describes
the trusted IdP's and how to communicate with them.
-->
{% if keystone_sp.trusted_idp_list is defined -%}
{% for item in keystone_sp.trusted_idp_list %}
<MetadataProvider type="XML"
uri="{{ item.metadata_uri }}"
backingFilePath="{{ item.metadata_file }}"
reloadInterval="{{ item.metadata_reload }}" />
{% endfor %}
{% endif %}
<!-- Map to extract attributes from SAML assertions. -->
<AttributeExtractor type="XML"
validate="true"
reloadChanges="false"
path="attribute-map.xml"/>
<!-- Use a SAML query if no attributes are supplied during SSO. -->
<AttributeResolver type="Query" subjectMatch="true"/>
<!-- Default filtering policy for recognized attributes, lets other data pass. -->
<AttributeFilter type="XML"
validate="true"
path="attribute-policy.xml"/>
<!-- Your SP generated these credentials. They're used to talk to IdP's. -->
<CredentialResolver type="File"
key="sp-key.pem"
certificate="sp-cert.pem"/>
</ApplicationDefaults>
<!-- Policies that determine how to process and authenticate runtime messages. -->
<SecurityPolicyProvider type="XML"
validate="true"
path="security-policy.xml"/>
<!-- Low-level configuration about protocols and bindings available for use. -->
<ProtocolProvider type="XML"
validate="true"
reloadChanges="false"
path="protocols.xml"/>
</SPConfig>