diff --git a/hooks/certificates-relation-changed b/hooks/certificates-relation-changed new file mode 120000 index 0000000000000000000000000000000000000000..3195386e535c48512196bb8461cbf221c81f7383 --- /dev/null +++ b/hooks/certificates-relation-changed @@ -0,0 +1 @@ +horizon_hooks.py \ No newline at end of file diff --git a/hooks/certificates-relation-departed b/hooks/certificates-relation-departed new file mode 120000 index 0000000000000000000000000000000000000000..3195386e535c48512196bb8461cbf221c81f7383 --- /dev/null +++ b/hooks/certificates-relation-departed @@ -0,0 +1 @@ +horizon_hooks.py \ No newline at end of file diff --git a/hooks/certificates-relation-joined b/hooks/certificates-relation-joined new file mode 120000 index 0000000000000000000000000000000000000000..3195386e535c48512196bb8461cbf221c81f7383 --- /dev/null +++ b/hooks/certificates-relation-joined @@ -0,0 +1 @@ +horizon_hooks.py \ No newline at end of file diff --git a/hooks/horizon_contexts.py b/hooks/horizon_contexts.py index 5fd0ff7b9021078440a854dab30cb64e666cdf3e..f4f19e2a257c7587817ec913fcf86a09adbeaf56 100644 --- a/hooks/horizon_contexts.py +++ b/hooks/horizon_contexts.py @@ -52,6 +52,9 @@ VALID_ENDPOINT_TYPES = { 'ADMINURL': 'adminURL', } +SSL_CERT_FILE = '/etc/apache2/ssl/horizon/cert_dashboard' +SSL_KEY_FILE = '/etc/apache2/ssl/horizon/key_dashboard' + class HorizonHAProxyContext(OSContextGenerator): def __call__(self): @@ -231,27 +234,37 @@ class ApacheContext(OSContextGenerator): class ApacheSSLContext(OSContextGenerator): def __call__(self): ''' Grab cert and key from configuration for SSL config ''' - ca_cert = get_ca_cert() - if ca_cert: + ctxt = {'ssl_configured': False} + use_local_ca = True + for rid in relation_ids('certificates'): + if related_units(rid): + use_local_ca = False + + if use_local_ca: + ca_cert = get_ca_cert() + if not ca_cert: + return ctxt install_ca_cert(b64decode(ca_cert)) - ssl_cert, ssl_key = get_cert() - if all([ssl_cert, ssl_key]): - with open('/etc/ssl/certs/dashboard.cert', 'w') as cert_out: - cert_out.write(b64decode(ssl_cert)) - with open('/etc/ssl/private/dashboard.key', 'w') as key_out: - key_out.write(b64decode(ssl_key)) - os.chmod('/etc/ssl/private/dashboard.key', 0600) - ctxt = { - 'ssl_configured': True, - 'ssl_cert': '/etc/ssl/certs/dashboard.cert', - 'ssl_key': '/etc/ssl/private/dashboard.key', - } + ssl_cert, ssl_key = get_cert() + if all([ssl_cert, ssl_key]): + with open('/etc/ssl/certs/dashboard.cert', 'w') as cert_out: + cert_out.write(b64decode(ssl_cert)) + with open('/etc/ssl/private/dashboard.key', 'w') as key_out: + key_out.write(b64decode(ssl_key)) + os.chmod('/etc/ssl/private/dashboard.key', 0600) + ctxt = { + 'ssl_configured': True, + 'ssl_cert': '/etc/ssl/certs/dashboard.cert', + 'ssl_key': '/etc/ssl/private/dashboard.key', + } else: - # Use snakeoil ones by default - ctxt = { - 'ssl_configured': False, - } + if os.path.exists(SSL_CERT_FILE) and os.path.exists(SSL_KEY_FILE): + ctxt = { + 'ssl_configured': True, + 'ssl_cert': SSL_CERT_FILE, + 'ssl_key': SSL_KEY_FILE, + } return ctxt diff --git a/hooks/horizon_hooks.py b/hooks/horizon_hooks.py index 254a38affe5f1d26c72e356a97ffc53743491e5a..40895cace9e04d4218e79f7d1b71978d642094de 100755 --- a/hooks/horizon_hooks.py +++ b/hooks/horizon_hooks.py @@ -39,6 +39,7 @@ from charmhelpers.fetch import ( ) from charmhelpers.core.host import ( lsb_release, + service_reload, ) from charmhelpers.contrib.openstack.utils import ( configure_installation_source, @@ -72,6 +73,10 @@ from charmhelpers.contrib.network.ip import ( is_ipv6, get_relation_ip, ) +from charmhelpers.contrib.openstack.cert_utils import ( + get_certificate_request, + process_certificates, +) from charmhelpers.contrib.hahelpers.apache import install_ca_cert from charmhelpers.contrib.hahelpers.cluster import get_hacluster_config from charmhelpers.payload.execd import execd_preinstall @@ -155,6 +160,9 @@ def config_changed(): check_custom_theme() open_port(80) open_port(443) + for relid in relation_ids('certificates'): + for unit in related_units(relid): + certs_changed(relation_id=relid, unit=unit) websso_trusted_dashboard_changed() @@ -397,5 +405,21 @@ def main(): assess_status(CONFIGS) +@hooks.hook('certificates-relation-joined') +def certs_joined(relation_id=None): + relation_set( + relation_id=relation_id, + relation_settings=get_certificate_request()) + + +@hooks.hook('certificates-relation-changed') +def certs_changed(relation_id=None, unit=None): + process_certificates('horizon', relation_id, unit, + custom_hostname_link='dashboard') + CONFIGS.write_all() + service_reload('apache2') + enable_ssl() + + if __name__ == '__main__': main() diff --git a/metadata.yaml b/metadata.yaml index 98c1a0a8cedb48ed0333289db7bcd0080827bf7b..e05fc5d187478cd7506999c1ae0d21d6b2094885 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -35,6 +35,8 @@ requires: interface: mysql-shared websso-fid-service-provider: interface: websso-fid-service-provider + certificates: + interface: tls-certificates peers: cluster: interface: openstack-dashboard-ha diff --git a/unit_tests/test_actions_openstack_upgrade.py b/unit_tests/test_actions_openstack_upgrade.py index d30d7e984112c40e5589534032d31d84a7628022..8c75233660303302533a9ee48d50b4c54bae7215 100644 --- a/unit_tests/test_actions_openstack_upgrade.py +++ b/unit_tests/test_actions_openstack_upgrade.py @@ -36,6 +36,8 @@ from test_utils import ( ) TO_PATCH = [ + 'CONFIGS', + 'do_action_openstack_upgrade', 'do_openstack_upgrade', 'config_changed', ] @@ -47,28 +49,25 @@ class TestHorizonUpgradeActions(CharmTestCase): super(TestHorizonUpgradeActions, self).setUp(openstack_upgrade, TO_PATCH) - @patch('charmhelpers.contrib.openstack.utils.config') - @patch('charmhelpers.contrib.openstack.utils.action_set') - @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available') - def test_openstack_upgrade_true(self, upgrade_avail, - action_set, config): - upgrade_avail.return_value = True - config.return_value = True + def test_openstack_upgrade_true(self): + self.do_action_openstack_upgrade.return_value = True openstack_upgrade.openstack_upgrade() - self.assertTrue(self.do_openstack_upgrade.called) - self.assertTrue(self.config_changed.called) + self.do_action_openstack_upgrade.assert_called_once_with( + 'openstack-dashboard', + self.do_openstack_upgrade, + self.CONFIGS) + self.config_changed.assert_called_once_with() - @patch('charmhelpers.contrib.openstack.utils.config') - @patch('charmhelpers.contrib.openstack.utils.action_set') - @patch('charmhelpers.contrib.openstack.utils.openstack_upgrade_available') - def test_openstack_upgrade_false(self, upgrade_avail, - action_set, config): - upgrade_avail.return_value = True - config.return_value = False + def test_openstack_upgrade_false(self): + self.do_action_openstack_upgrade.return_value = False openstack_upgrade.openstack_upgrade() + self.do_action_openstack_upgrade.assert_called_once_with( + 'openstack-dashboard', + self.do_openstack_upgrade, + self.CONFIGS) self.assertFalse(self.do_openstack_upgrade.called) self.assertFalse(self.config_changed.called) diff --git a/unit_tests/test_horizon_contexts.py b/unit_tests/test_horizon_contexts.py index 58b9dd4ce5a3749f13a6a223923875aedf923792..8a81a67efb1ba4d4aa174e2eb5e159206214869d 100644 --- a/unit_tests/test_horizon_contexts.py +++ b/unit_tests/test_horizon_contexts.py @@ -95,11 +95,13 @@ class TestHorizonContexts(CharmTestCase): 'hsts_max_age_seconds': 15768000, 'custom_theme': False}) - @patch.object(horizon_contexts, 'get_ca_cert', lambda: None) + @patch.object(horizon_contexts, 'get_ca_cert', lambda: 'ca_cert') + @patch.object(horizon_contexts, 'install_ca_cert') @patch('os.chmod') - def test_ApacheSSLContext_enabled(self, _chmod): + def test_ApacheSSLContext_enabled(self, _chmod, _install_ca_cert): + self.relation_ids.return_value = [] self.get_cert.return_value = ('cert', 'key') - self.b64decode.side_effect = ['cert', 'key'] + self.b64decode.side_effect = ['ca', 'cert', 'key'] with patch_open() as (_open, _file): self.assertEqual(horizon_contexts.ApacheSSLContext()(), {'ssl_configured': True, @@ -115,13 +117,27 @@ class TestHorizonContexts(CharmTestCase): ]) # Security check on key permissions _chmod.assert_called_with('/etc/ssl/private/dashboard.key', 0o600) + _install_ca_cert.assert_called_once() @patch.object(horizon_contexts, 'get_ca_cert', lambda: None) def test_ApacheSSLContext_disabled(self): + self.relation_ids.return_value = [] self.get_cert.return_value = (None, None) self.assertEqual(horizon_contexts.ApacheSSLContext()(), {'ssl_configured': False}) + @patch.object(horizon_contexts.os.path, 'exists') + def test_ApacheSSLContext_vault(self, _exists): + _exists.return_value = True + self.relation_ids.return_value = ['certificates:60'] + self.related_units.return_value = ['vault/0'] + self.assertEqual( + horizon_contexts.ApacheSSLContext()(), + { + 'ssl_configured': True, + 'ssl_cert': '/etc/apache2/ssl/horizon/cert_dashboard', + 'ssl_key': '/etc/apache2/ssl/horizon/key_dashboard'}) + def test_HorizonContext_defaults(self): self.assertEqual(horizon_contexts.HorizonContext()(), {'compress_offline': True, 'debug': False, diff --git a/unit_tests/test_horizon_hooks.py b/unit_tests/test_horizon_hooks.py index e7c20f2428e63589a9c08ee71665ce192a379d9e..53446b490fab07f6614d91dcd6a4dabe17be8047 100644 --- a/unit_tests/test_horizon_hooks.py +++ b/unit_tests/test_horizon_hooks.py @@ -273,6 +273,7 @@ class TestHorizonHooks(CharmTestCase): 'identity-service': [ 'identity/0', ], + 'certificates': [], }[rname] self.relation_ids.side_effect = relation_ids_side_effect @@ -287,7 +288,6 @@ class TestHorizonHooks(CharmTestCase): 'webroot': '/horizon', }[key] self.config.side_effect = config_side_effect - self.openstack_upgrade_available.return_value = False self._call_hook('config-changed') _joined.assert_called_with('identity/0')