add WebSSO support
* add support for relating with subordinate charms providing Service Provider functionality via apache2 authentication modules; * retrieve protocol, identity provider and user-facing name info from keystone service provider charm subordinates; * provide trusted dashboard information to keystone charm Change-Id: I15ca0dd1616ec12c7ad47dc05961b51bb45bb770
This commit is contained in:
parent
0707590487
commit
45be17c904
|
@ -44,6 +44,7 @@ from charmhelpers.core.host import pwgen
|
||||||
|
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
|
|
||||||
VALID_ENDPOINT_TYPES = {
|
VALID_ENDPOINT_TYPES = {
|
||||||
'PUBLICURL': 'publicURL',
|
'PUBLICURL': 'publicURL',
|
||||||
|
@ -282,3 +283,30 @@ class LocalSettingsContext(OSContextGenerator):
|
||||||
key=lambda r: r[1]['priority'])]
|
key=lambda r: r[1]['priority'])]
|
||||||
}
|
}
|
||||||
return ctxt
|
return ctxt
|
||||||
|
|
||||||
|
|
||||||
|
class WebSSOFIDServiceProviderContext(OSContextGenerator):
|
||||||
|
interfaces = ['websso-fid-service-provider']
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
websso_keys = ['protocol-name', 'idp-name', 'user-facing-name']
|
||||||
|
|
||||||
|
relations = []
|
||||||
|
for rid in relation_ids("websso-fid-service-provider"):
|
||||||
|
try:
|
||||||
|
# the first unit will do - the assumption is that all
|
||||||
|
# of them should advertise the same data. This needs
|
||||||
|
# refactoring if juju gets per-application relation data
|
||||||
|
# support
|
||||||
|
unit = related_units(rid)[0]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
rdata = relation_get(unit=unit, rid=rid)
|
||||||
|
if set(rdata).issuperset(set(websso_keys)):
|
||||||
|
relations.append({k: json.loads(rdata[k])
|
||||||
|
for k in websso_keys})
|
||||||
|
# populate the context with data from one or more
|
||||||
|
# service providers
|
||||||
|
ctxt = {'websso_data': relations} if relations else {}
|
||||||
|
return ctxt
|
||||||
|
|
|
@ -31,6 +31,7 @@ from charmhelpers.core.hookenv import (
|
||||||
is_leader,
|
is_leader,
|
||||||
local_unit,
|
local_unit,
|
||||||
WARNING,
|
WARNING,
|
||||||
|
network_get,
|
||||||
)
|
)
|
||||||
from charmhelpers.fetch import (
|
from charmhelpers.fetch import (
|
||||||
apt_update, apt_install,
|
apt_update, apt_install,
|
||||||
|
@ -152,6 +153,8 @@ def config_changed():
|
||||||
open_port(80)
|
open_port(80)
|
||||||
open_port(443)
|
open_port(443)
|
||||||
|
|
||||||
|
websso_trusted_dashboard_changed()
|
||||||
|
|
||||||
|
|
||||||
@hooks.hook('identity-service-relation-joined')
|
@hooks.hook('identity-service-relation-joined')
|
||||||
def keystone_joined(rel_id=None):
|
def keystone_joined(rel_id=None):
|
||||||
|
@ -342,6 +345,47 @@ def db_changed():
|
||||||
log('Not running neutron database migration, not leader')
|
log('Not running neutron database migration, not leader')
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.hook('websso-fid-service-provider-relation-joined',
|
||||||
|
'websso-fid-service-provider-relation-changed',
|
||||||
|
'websso-fid-service-provider-relation-departed')
|
||||||
|
@restart_on_change(restart_map(), stopstart=True, sleep=3)
|
||||||
|
def websso_sp_changed():
|
||||||
|
CONFIGS.write_all()
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.hook('websso-trusted-dashboard-relation-joined',
|
||||||
|
'websso-trusted-dashboard-relation-changed')
|
||||||
|
def websso_trusted_dashboard_changed():
|
||||||
|
"""
|
||||||
|
Provide L7 endpoint details for the dashboard and also
|
||||||
|
handle any config changes that may affect those.
|
||||||
|
"""
|
||||||
|
relations = relation_ids('websso-trusted-dashboard')
|
||||||
|
if not relations:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: check for vault relation in order to determine url scheme
|
||||||
|
tls_configured = config('ssl-key') or config('enforce-ssl')
|
||||||
|
scheme = 'https://' if tls_configured else 'http://'
|
||||||
|
|
||||||
|
if config('dns-ha') or config('os-public-hostname'):
|
||||||
|
hostname = config('os-public-hostname')
|
||||||
|
elif config('vip'):
|
||||||
|
hostname = config('vip')
|
||||||
|
else:
|
||||||
|
# use an ingress-address of a given unit as a fallback
|
||||||
|
netinfo = network_get('websso-trusted-dashboard')
|
||||||
|
hostname = netinfo['ingress-addresses'][0]
|
||||||
|
|
||||||
|
# provide trusted dashboard URL details
|
||||||
|
for rid in relations:
|
||||||
|
relation_set(relation_id=rid, relation_settings={
|
||||||
|
"scheme": scheme,
|
||||||
|
"hostname": hostname,
|
||||||
|
"path": "/auth/websso/"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
hooks.execute(sys.argv)
|
hooks.execute(sys.argv)
|
||||||
|
|
|
@ -93,7 +93,8 @@ CONFIG_FILES = OrderedDict([
|
||||||
horizon_contexts.IdentityServiceContext(),
|
horizon_contexts.IdentityServiceContext(),
|
||||||
context.SyslogContext(),
|
context.SyslogContext(),
|
||||||
horizon_contexts.LocalSettingsContext(),
|
horizon_contexts.LocalSettingsContext(),
|
||||||
horizon_contexts.ApacheSSLContext()],
|
horizon_contexts.ApacheSSLContext(),
|
||||||
|
horizon_contexts.WebSSOFIDServiceProviderContext()],
|
||||||
'services': ['apache2', 'memcached']
|
'services': ['apache2', 'memcached']
|
||||||
}),
|
}),
|
||||||
(APACHE_CONF, {
|
(APACHE_CONF, {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
horizon_hooks.py
|
|
@ -0,0 +1 @@
|
||||||
|
horizon_hooks.py
|
|
@ -0,0 +1 @@
|
||||||
|
horizon_hooks.py
|
|
@ -0,0 +1 @@
|
||||||
|
horizon_hooks.py
|
|
@ -0,0 +1 @@
|
||||||
|
horizon_hooks.py
|
|
@ -0,0 +1 @@
|
||||||
|
horizon_hooks.py
|
|
@ -0,0 +1 @@
|
||||||
|
horizon_hooks.py
|
|
@ -0,0 +1 @@
|
||||||
|
horizon_hooks.py
|
|
@ -23,6 +23,8 @@ provides:
|
||||||
dashboard-plugin:
|
dashboard-plugin:
|
||||||
interface: dashboard-plugin
|
interface: dashboard-plugin
|
||||||
scope: container
|
scope: container
|
||||||
|
websso-trusted-dashboard:
|
||||||
|
interface: websso-trusted-dashboard
|
||||||
requires:
|
requires:
|
||||||
identity-service:
|
identity-service:
|
||||||
interface: keystone
|
interface: keystone
|
||||||
|
@ -31,6 +33,8 @@ requires:
|
||||||
scope: container
|
scope: container
|
||||||
shared-db:
|
shared-db:
|
||||||
interface: mysql-shared
|
interface: mysql-shared
|
||||||
|
websso-fid-service-provider:
|
||||||
|
interface: websso-fid-service-provider
|
||||||
peers:
|
peers:
|
||||||
cluster:
|
cluster:
|
||||||
interface: openstack-dashboard-ha
|
interface: openstack-dashboard-ha
|
||||||
|
|
|
@ -991,3 +991,19 @@ ALLOWED_PRIVATE_SUBNET_CIDR = {'ipv4': [], 'ipv6': []}
|
||||||
# 'phone_num': _('Phone Number'),
|
# 'phone_num': _('Phone Number'),
|
||||||
#}
|
#}
|
||||||
{{ settings|join('\n\n') }}
|
{{ settings|join('\n\n') }}
|
||||||
|
|
||||||
|
{% if websso_data %}
|
||||||
|
WEBSSO_ENABLED = True
|
||||||
|
WEBSSO_CHOICES = (
|
||||||
|
{% for provider_data in websso_data -%}
|
||||||
|
("{{ '{}_{}'.format(provider_data['idp-name'], provider_data['protocol-name']) }}", "{{ provider_data['user-facing-name'] }}"),
|
||||||
|
{% endfor -%}
|
||||||
|
("credentials", _("Keystone Credentials"))
|
||||||
|
)
|
||||||
|
|
||||||
|
WEBSSO_IDP_MAPPING = {
|
||||||
|
{% for provider_data in websso_data -%}
|
||||||
|
"{{ '{}_{}'.format(provider_data['idp-name'], provider_data['protocol-name']) }}": ("{{ provider_data['idp-name'] }}", "{{ provider_data['protocol-name'] }}"),
|
||||||
|
{% endfor -%}
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -650,3 +650,76 @@ class TestHorizonContexts(CharmTestCase):
|
||||||
'BAR = False',
|
'BAR = False',
|
||||||
'# horizon-plugin/0\n'
|
'# horizon-plugin/0\n'
|
||||||
'FOO = True']})
|
'FOO = True']})
|
||||||
|
|
||||||
|
def test_WebSSOFIDServiceProviderContext(self):
|
||||||
|
def relation_ids_side_effect(rname):
|
||||||
|
return {
|
||||||
|
'websso-fid-service-provider': [
|
||||||
|
'websso-fid-service-provider:0',
|
||||||
|
'websso-fid-service-provider:1',
|
||||||
|
]
|
||||||
|
}[rname]
|
||||||
|
self.relation_ids.side_effect = relation_ids_side_effect
|
||||||
|
|
||||||
|
def related_units_side_effect(rid):
|
||||||
|
return {
|
||||||
|
'websso-fid-service-provider:0': [
|
||||||
|
'keystone-saml-mellon-red/0',
|
||||||
|
'keystone-saml-mellon-red/1',
|
||||||
|
],
|
||||||
|
'websso-fid-service-provider:1': [
|
||||||
|
'keystone-saml-mellon-green/0',
|
||||||
|
'keystone-saml-mellon-green/1',
|
||||||
|
],
|
||||||
|
}[rid]
|
||||||
|
self.related_units.side_effect = related_units_side_effect
|
||||||
|
|
||||||
|
def relation_get_side_effect(unit, rid):
|
||||||
|
return {
|
||||||
|
'websso-fid-service-provider:0': {
|
||||||
|
'keystone-saml-mellon-red/0': {
|
||||||
|
'ingress-address': '10.0.0.10',
|
||||||
|
'protocol-name': '"saml2"',
|
||||||
|
'idp-name': '"red"',
|
||||||
|
'user-facing-name': '"Red IDP"',
|
||||||
|
},
|
||||||
|
'keystone-saml-mellon-red/1': {
|
||||||
|
'ingress-address': '10.0.0.11',
|
||||||
|
'protocol-name': '"saml2"',
|
||||||
|
'idp-name': '"red"',
|
||||||
|
'user-facing-name': '"Red IDP"',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'websso-fid-service-provider:1': {
|
||||||
|
'keystone-saml-mellon-green/0': {
|
||||||
|
'ingress-address': '10.0.0.12',
|
||||||
|
'protocol-name': '"mapped"',
|
||||||
|
'idp-name': '"green"',
|
||||||
|
'user-facing-name': '"Green IDP"',
|
||||||
|
},
|
||||||
|
'keystone-saml-mellon-green/1': {
|
||||||
|
'ingress-address': '10.0.0.13',
|
||||||
|
'protocol-name': '"mapped"',
|
||||||
|
'idp-name': '"green"',
|
||||||
|
'user-facing-name': '"Green IDP"',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}[rid][unit]
|
||||||
|
self.relation_get.side_effect = relation_get_side_effect
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
horizon_contexts.WebSSOFIDServiceProviderContext()(),
|
||||||
|
{
|
||||||
|
'websso_data': [
|
||||||
|
{
|
||||||
|
'protocol-name': 'saml2',
|
||||||
|
'idp-name': 'red',
|
||||||
|
'user-facing-name': "Red IDP",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'protocol-name': 'mapped',
|
||||||
|
'idp-name': 'green',
|
||||||
|
'user-facing-name': "Green IDP",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
|
@ -260,7 +260,30 @@ class TestHorizonHooks(CharmTestCase):
|
||||||
|
|
||||||
@patch('horizon_hooks.keystone_joined')
|
@patch('horizon_hooks.keystone_joined')
|
||||||
def test_config_changed_no_upgrade(self, _joined):
|
def test_config_changed_no_upgrade(self, _joined):
|
||||||
self.relation_ids.return_value = ['identity/0']
|
def relation_ids_side_effect(rname):
|
||||||
|
return {
|
||||||
|
'websso-trusted-dashboard': [
|
||||||
|
'websso-trusted-dashboard:0',
|
||||||
|
'websso-trusted-dashboard:1',
|
||||||
|
],
|
||||||
|
'identity-service': [
|
||||||
|
'identity/0',
|
||||||
|
],
|
||||||
|
}[rname]
|
||||||
|
self.relation_ids.side_effect = relation_ids_side_effect
|
||||||
|
|
||||||
|
def config_side_effect(key):
|
||||||
|
return {
|
||||||
|
'ssl-key': 'somekey',
|
||||||
|
'enforce-ssl': True,
|
||||||
|
'dns-ha': True,
|
||||||
|
'os-public-hostname': 'dashboard.intranet.test',
|
||||||
|
'prefer-ipv6': False,
|
||||||
|
'action-managed-upgrade': False,
|
||||||
|
'webroot': '/horizon',
|
||||||
|
}[key]
|
||||||
|
self.config.side_effect = config_side_effect
|
||||||
|
|
||||||
self.openstack_upgrade_available.return_value = False
|
self.openstack_upgrade_available.return_value = False
|
||||||
self._call_hook('config-changed')
|
self._call_hook('config-changed')
|
||||||
_joined.assert_called_with('identity/0')
|
_joined.assert_called_with('identity/0')
|
||||||
|
@ -331,3 +354,41 @@ class TestHorizonHooks(CharmTestCase):
|
||||||
openstack_dir='/usr/share/openstack-dashboard',
|
openstack_dir='/usr/share/openstack-dashboard',
|
||||||
relation_id=None
|
relation_id=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_websso_fid_service_provider_changed(self):
|
||||||
|
self._call_hook('websso-fid-service-provider-relation-changed')
|
||||||
|
self.CONFIGS.write_all.assert_called_with()
|
||||||
|
|
||||||
|
def test_websso_trusted_dashboard_changed(self):
|
||||||
|
def relation_ids_side_effect(rname):
|
||||||
|
return {
|
||||||
|
'websso-trusted-dashboard': [
|
||||||
|
'websso-trusted-dashboard:0',
|
||||||
|
'websso-trusted-dashboard:1',
|
||||||
|
]
|
||||||
|
}[rname]
|
||||||
|
self.relation_ids.side_effect = relation_ids_side_effect
|
||||||
|
|
||||||
|
def config_side_effect(key):
|
||||||
|
return {
|
||||||
|
'ssl-key': 'somekey',
|
||||||
|
'enforce-ssl': True,
|
||||||
|
'dns-ha': True,
|
||||||
|
'os-public-hostname': 'dashboard.intranet.test',
|
||||||
|
}[key]
|
||||||
|
self.config.side_effect = config_side_effect
|
||||||
|
self._call_hook('websso-trusted-dashboard-relation-changed')
|
||||||
|
self.relation_set.assert_has_calls([
|
||||||
|
call(relation_id='websso-trusted-dashboard:0',
|
||||||
|
relation_settings={
|
||||||
|
"scheme": "https://",
|
||||||
|
"hostname": "dashboard.intranet.test",
|
||||||
|
"path": "/auth/websso/",
|
||||||
|
}),
|
||||||
|
call(relation_id='websso-trusted-dashboard:1',
|
||||||
|
relation_settings={
|
||||||
|
"scheme": "https://",
|
||||||
|
"hostname": "dashboard.intranet.test",
|
||||||
|
"path": "/auth/websso/",
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
Loading…
Reference in New Issue