diff --git a/defaults/main.yml b/defaults/main.yml
index 0bb5a62e..74615039 100644
--- a/defaults/main.yml
+++ b/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/files/sso_callback_template.html b/files/sso_callback_template.html
new file mode 100644
index 00000000..3364d69e
--- /dev/null
+++ b/files/sso_callback_template.html
@@ -0,0 +1,22 @@
+
+
+
+ Keystone WebSSO redirect
+
+
+
+
+
+
diff --git a/handlers/main.yml b/handlers/main.yml
index 989637d1..259c53ea 100644
--- a/handlers/main.yml
+++ b/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/library/keystone_sp b/library/keystone_sp
new file mode 100644
index 00000000..da6e73df
--- /dev/null
+++ b/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/tasks/keystone_apache.yml b/tasks/keystone_apache.yml
index 6fb72969..e97e8e7d 100644
--- a/tasks/keystone_apache.yml
+++ b/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/tasks/keystone_federation_sp_idp_setup.yml b/tasks/keystone_federation_sp_idp_setup.yml
new file mode 100644
index 00000000..9d0bb7ee
--- /dev/null
+++ b/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/tasks/keystone_federation_sp_setup.yml b/tasks/keystone_federation_sp_setup.yml
new file mode 100644
index 00000000..ba21f0d6
--- /dev/null
+++ b/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/tasks/keystone_install.yml b/tasks/keystone_install.yml
index 216f6cea..21ff7a0e 100644
--- a/tasks/keystone_install.yml
+++ b/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/tasks/keystone_post_install.yml b/tasks/keystone_post_install.yml
index 29119547..6e92e9ae 100644
--- a/tasks/keystone_post_install.yml
+++ b/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/tasks/main.yml b/tasks/main.yml
index fc8c8346..b2b9a66c 100644
--- a/tasks/main.yml
+++ b/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/templates/keystone-httpd.conf.j2 b/templates/keystone-httpd.conf.j2
index 3c86f325..e3f85096 100644
--- a/templates/keystone-httpd.conf.j2
+++ b/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/templates/keystone.conf.j2 b/templates/keystone.conf.j2
index b2c61a4a..4eb82f44 100644
--- a/templates/keystone.conf.j2
+++ b/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/templates/shibboleth-attribute-map.xml.j2 b/templates/shibboleth-attribute-map.xml.j2
new file mode 100644
index 00000000..7e5271d0
--- /dev/null
+++ b/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/templates/shibboleth2.xml.j2 b/templates/shibboleth2.xml.j2
new file mode 100644
index 00000000..1aff057a
--- /dev/null
+++ b/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 %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+