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:
parent
d82bbb4336
commit
23da164fe4
@ -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(
|
||||
|
@ -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 }}
|
||||
|
@ -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
|
||||
|
22
playbooks/roles/os_keystone/files/sso_callback_template.html
Normal file
22
playbooks/roles/os_keystone/files/sso_callback_template.html
Normal 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>
|
@ -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
|
||||
|
117
playbooks/roles/os_keystone/library/keystone_sp
Normal file
117
playbooks/roles/os_keystone/library/keystone_sp
Normal 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()
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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 }}"
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 }}
|
||||
|
||||
<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
|
||||
WSGIProcessGroup keystone
|
||||
</VirtualHost>
|
||||
|
@ -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 %}
|
||||
|
@ -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>
|
104
playbooks/roles/os_keystone/templates/shibboleth2.xml.j2
Normal file
104
playbooks/roles/os_keystone/templates/shibboleth2.xml.j2
Normal 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>
|
Loading…
Reference in New Issue
Block a user