diff --git a/playbooks/library/keystone b/playbooks/library/keystone
index 98c18d845b..60c8889009 100644
--- a/playbooks/library/keystone
+++ b/playbooks/library/keystone
@@ -195,7 +195,8 @@ options:
'ensure_user', 'ensure_user_role', 'ensure_tenant',
'ensure_project', 'ensure_service_provider',
'ensure_group', 'ensure_identity_provider',
- 'ensure_protocol', ensure_mapping']
+ 'ensure_protocol', ensure_mapping',
+ 'ensure_group_role']
required: true
insecure:
description:
@@ -251,6 +252,13 @@ EXAMPLES = """
project_name: "service"
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
- keystone:
command: "ensure_service"
@@ -353,6 +361,13 @@ COMMAND_MAP = {
'role_name'
]
},
+ 'ensure_group_role': {
+ 'variables': [
+ 'group_name',
+ 'project_name',
+ 'role_name'
+ ]
+ },
'ensure_project': {
'variables': [
'project_name',
@@ -716,7 +731,7 @@ class ManageKeystone(object):
return self._facts(facts={'id': user.id})
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.
@@ -728,8 +743,25 @@ class ManageKeystone(object):
else:
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):
- """Return a tenant by name.
+ """Return a role by name.
This will return `None` if the ``name`` is not found.
@@ -749,14 +781,17 @@ class ManageKeystone(object):
return self._facts(facts={'id': role_data.id})
- def _get_role_data(self, user_name, project_name, role_name):
- user = self._get_user(name=user_name)
- if user is None:
- self.failure(
- error='user [ %s ] was not found.' % user_name,
- rc=2,
- msg='User was not found, does it exist?'
- )
+ 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)
+ if user is None:
+ self.failure(
+ error='user [ %s ] was not found.' % user_name,
+ rc=2,
+ msg='User was not found, does it exist?'
+ )
+ else:
+ user = None
project = self._get_project(name=project_name)
if project is None:
@@ -774,7 +809,18 @@ class ManageKeystone(object):
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):
"""Create a new role within Keystone if it does not exist.
@@ -802,6 +848,13 @@ class ManageKeystone(object):
else:
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):
self._authenticate()
required_vars = ['user_name', 'role_name']
@@ -814,8 +867,9 @@ class ManageKeystone(object):
or variables_dict.pop('tenant_name'))
role_name = variables_dict.pop('role_name')
- user, project, role = self._get_role_data(
- user_name=user_name, project_name=project_name, role_name=role_name
+ user, project, role, group = self._get_role_data(
+ user_name=user_name, project_name=project_name,
+ role_name=role_name, group_name=None
)
user_role = self._get_user_roles(
@@ -832,6 +886,64 @@ class ManageKeystone(object):
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):
for entry in self.keystone.services.list():
if srv_type is not None:
@@ -975,23 +1087,6 @@ class ManageKeystone(object):
# return no facts in this case.
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):
self._authenticate()
return self._ensure_generic(
diff --git a/playbooks/roles/os_horizon/templates/horizon_local_settings.py.j2 b/playbooks/roles/os_horizon/templates/horizon_local_settings.py.j2
index 814c4a386e..17ec3c1dd8 100644
--- a/playbooks/roles/os_horizon/templates/horizon_local_settings.py.j2
+++ b/playbooks/roles/os_horizon/templates/horizon_local_settings.py.j2
@@ -170,21 +170,27 @@ OPENSTACK_KEYSTONE_URL = "{{ horizon_keystone_endpoint }}"
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_"
-# Enables keystone web single-sign-on if set to True.
-#WEBSSO_ENABLED = False
+{% if keystone_sp is defined %}
+# Enables keystone web single-sign-on
+WEBSSO_ENABLED = True
# Determines which authentication choice to show as default.
-#WEBSSO_INITIAL_CHOICE = "credentials"
+WEBSSO_INITIAL_CHOICE = "credentials"
# The list of authentication mechanisms
# which include keystone federation protocols.
# Current supported protocol IDs are 'saml2' and 'oidc'
# which represent SAML 2.0, OpenID Connect respectively.
# Do not remove the mandatory credentials mechanism.
-#WEBSSO_CHOICES = (
-# ("credentials", _("Keystone Credentials")),
-# ("oidc", _("OpenID Connect")),
-# ("saml2", _("Security Assertion Markup Language")))
+WEBSSO_CHOICES = (
+ ("credentials", _("Keystone Credentials")),
+{% for idp in keystone_sp.trusted_idp_list %}
+{% 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):
OPENSTACK_SSL_NO_VERIFY = {{ horizon_ssl_no_verify | bool }}
diff --git a/playbooks/roles/os_keystone/defaults/main.yml b/playbooks/roles/os_keystone/defaults/main.yml
index 0bb5a62ee6..7461503922 100644
--- a/playbooks/roles/os_keystone/defaults/main.yml
+++ b/playbooks/roles/os_keystone/defaults/main.yml
@@ -120,7 +120,7 @@ keystone_service_internalurl: "{{ keystone_service_internalurl_v3 }}"
keystone_service_adminurl: "{{ keystone_service_adminurl_v3 }}"
## Set this value to override the "public_endpoint" keystone.conf variable
-#keystone_public_endpoint:
+#keystone_public_endpoint: "{{ keystone_service_publicuri }}"
## Apache setup
keystone_apache_log_level: info
@@ -190,6 +190,82 @@ keystone_recreate_keys: False
# contact_telephone: 555-55-5555
# 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
keystone_apt_packages:
- apache2
diff --git a/playbooks/roles/os_keystone/files/sso_callback_template.html b/playbooks/roles/os_keystone/files/sso_callback_template.html
new file mode 100644
index 0000000000..3364d69e55
--- /dev/null
+++ b/playbooks/roles/os_keystone/files/sso_callback_template.html
@@ -0,0 +1,22 @@
+
+
+
+ Keystone WebSSO redirect
+
+
+
+
+
+
diff --git a/playbooks/roles/os_keystone/handlers/main.yml b/playbooks/roles/os_keystone/handlers/main.yml
index 989637d10b..259c53eaa1 100644
--- a/playbooks/roles/os_keystone/handlers/main.yml
+++ b/playbooks/roles/os_keystone/handlers/main.yml
@@ -22,3 +22,13 @@
until: apache_restart|success
retries: 5
delay: 2
+
+- name: Restart Shibd
+ service:
+ name: "shibd"
+ state: "restarted"
+ pattern: "shibd"
+ register: shibd_restart
+ until: shibd_restart|success
+ retries: 5
+ delay: 2
diff --git a/playbooks/roles/os_keystone/library/keystone_sp b/playbooks/roles/os_keystone/library/keystone_sp
new file mode 100644
index 0000000000..da6e73dfef
--- /dev/null
+++ b/playbooks/roles/os_keystone/library/keystone_sp
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+# (c) 2015, Kevin Carter
+#
+# 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()
diff --git a/playbooks/roles/os_keystone/tasks/keystone_apache.yml b/playbooks/roles/os_keystone/tasks/keystone_apache.yml
index 6fb729695e..e97e8e7dd6 100644
--- a/playbooks/roles/os_keystone/tasks/keystone_apache.yml
+++ b/playbooks/roles/os_keystone/tasks/keystone_apache.yml
@@ -61,5 +61,17 @@
apache2_module:
name: ssl
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:
- keystone-httpd
diff --git a/playbooks/roles/os_keystone/tasks/keystone_federation_sp_idp_setup.yml b/playbooks/roles/os_keystone/tasks/keystone_federation_sp_idp_setup.yml
new file mode 100644
index 0000000000..9d0bb7eedb
--- /dev/null
+++ b/playbooks/roles/os_keystone/tasks/keystone_federation_sp_idp_setup.yml
@@ -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
diff --git a/playbooks/roles/os_keystone/tasks/keystone_federation_sp_setup.yml b/playbooks/roles/os_keystone/tasks/keystone_federation_sp_setup.yml
new file mode 100644
index 0000000000..ba21f0d6d6
--- /dev/null
+++ b/playbooks/roles/os_keystone/tasks/keystone_federation_sp_setup.yml
@@ -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
diff --git a/playbooks/roles/os_keystone/tasks/keystone_install.yml b/playbooks/roles/os_keystone/tasks/keystone_install.yml
index 216f6cea84..21ff7a0e22 100644
--- a/playbooks/roles/os_keystone/tasks/keystone_install.yml
+++ b/playbooks/roles/os_keystone/tasks/keystone_install.yml
@@ -49,6 +49,19 @@
tags:
- 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
pip:
name: "{{ item }}"
diff --git a/playbooks/roles/os_keystone/tasks/keystone_post_install.yml b/playbooks/roles/os_keystone/tasks/keystone_post_install.yml
index 2911954701..6e92e9aecd 100644
--- a/playbooks/roles/os_keystone/tasks/keystone_post_install.yml
+++ b/playbooks/roles/os_keystone/tasks/keystone_post_install.yml
@@ -36,6 +36,7 @@
mode: "{{ item.mode|default('0644') }}"
with_items:
- { 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/main", mode: "0755" }
notify:
diff --git a/playbooks/roles/os_keystone/tasks/main.yml b/playbooks/roles/os_keystone/tasks/main.yml
index fc8c834635..b2b9a66c2f 100644
--- a/playbooks/roles/os_keystone/tasks/main.yml
+++ b/playbooks/roles/os_keystone/tasks/main.yml
@@ -29,6 +29,10 @@
- include: keystone_post_install.yml
+- include: keystone_federation_sp_setup.yml
+ when: >
+ keystone_sp is defined
+
- include: keystone_db_setup.yml
when: >
inventory_hostname == groups['keystone_all'][0]
@@ -40,6 +44,11 @@
when: >
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
meta: flush_handlers
diff --git a/playbooks/roles/os_keystone/templates/keystone-httpd.conf.j2 b/playbooks/roles/os_keystone/templates/keystone-httpd.conf.j2
index 3c86f32528..e3f850962b 100644
--- a/playbooks/roles/os_keystone/templates/keystone-httpd.conf.j2
+++ b/playbooks/roles/os_keystone/templates/keystone-httpd.conf.j2
@@ -25,6 +25,32 @@ WSGIDaemonProcess keystone user={{ keystone_system_user_name }} group=nogroup pr
SSLOptions +StdEnvVars +ExportCertData
{% endif %}
+ {% if keystone_sp is defined -%}
+ ShibURLScheme {{ keystone_service_publicuri_proto }}
+
+
+ SetHandler shib
+
+
+
+ AuthType shibboleth
+ ShibRequestSetting requireSession 1
+ ShibRequestSetting exportAssertion 1
+ ShibRequireSession On
+ ShibExportAssertion On
+ Require valid-user
+
+
+
+ ShibRequestSetting requireSession 1
+ AuthType shibboleth
+ ShibExportAssertion Off
+ Require valid-user
+
+
+ WSGIScriptAliasMatch ^(/v3/OS-FEDERATION/identity_providers/.*?/protocols/.*?/auth)$ /var/www/cgi-bin/keystone/main/$1
+ {%- endif %}
+
WSGIScriptAlias / /var/www/cgi-bin/keystone/main
WSGIProcessGroup keystone
diff --git a/playbooks/roles/os_keystone/templates/keystone.conf.j2 b/playbooks/roles/os_keystone/templates/keystone.conf.j2
index b2c61a4a66..4eb82f4422 100644
--- a/playbooks/roles/os_keystone/templates/keystone.conf.j2
+++ b/playbooks/roles/os_keystone/templates/keystone.conf.j2
@@ -43,8 +43,12 @@ cache_time = {{ keystone_revocation_cache_time }}
[auth]
+{% if keystone_sp is defined %}
+methods = {{ keystone_auth_methods }},saml2
+saml2 = keystone.auth.plugins.mapped.Mapped
+{% else %}
methods = {{ keystone_auth_methods }}
-
+{% endif %}
[database]
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_userid = {{ rabbitmq_userid }}
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 %}
diff --git a/playbooks/roles/os_keystone/templates/shibboleth-attribute-map.xml.j2 b/playbooks/roles/os_keystone/templates/shibboleth-attribute-map.xml.j2
new file mode 100644
index 0000000000..7e5271d0b1
--- /dev/null
+++ b/playbooks/roles/os_keystone/templates/shibboleth-attribute-map.xml.j2
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% 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 %}
+
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ {% endfor %}
+
+
diff --git a/playbooks/roles/os_keystone/templates/shibboleth2.xml.j2 b/playbooks/roles/os_keystone/templates/shibboleth2.xml.j2
new file mode 100644
index 0000000000..1aff057a4e
--- /dev/null
+++ b/playbooks/roles/os_keystone/templates/shibboleth2.xml.j2
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ SAML2 SAML1
+
+
+
+
+ SAML2 Local
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% if keystone_sp.trusted_idp_list is defined -%}
+ {% for item in keystone_sp.trusted_idp_list %}
+
+ {% endfor %}
+{% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+