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 + + +
+ Please wait... +
+ + +
+ + + 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 %} + + + + + + + + + + + + + + + + + + + + + +