diff --git a/config.yaml b/config.yaml index 1a75ed0d..860ad4b5 100644 --- a/config.yaml +++ b/config.yaml @@ -135,10 +135,6 @@ options: type: int default: 500 description: Number of keystone tokens to hold in local cache. - revocation-check-interval: - type: int - default: 600 - description: Interval between revocation checks to keystone. # HA config use-syslog: type: boolean diff --git a/hooks/ceph_radosgw_context.py b/hooks/ceph_radosgw_context.py index aec22968..7a2b23d7 100644 --- a/hooks/ceph_radosgw_context.py +++ b/hooks/ceph_radosgw_context.py @@ -98,7 +98,6 @@ class IdentityServiceContext(context.IdentityServiceContext): ctxt['auth_type'] = 'keystone' ctxt['user_roles'] = config('operator-roles') ctxt['cache_size'] = config('cache-size') - ctxt['revocation_check_interval'] = config('revocation-check-interval') if self.context_complete(ctxt): return ctxt return {} @@ -204,12 +203,6 @@ class MonContext(context.CephContext): 'unit_public_ip': unit_public_ip(), } - certs_path = '/var/lib/ceph/nss' - paths = [os.path.join(certs_path, 'ca.pem'), - os.path.join(certs_path, 'signing_certificate.pem')] - if all([os.path.isfile(p) for p in paths]): - ctxt['cms'] = True - # NOTE(dosaboy): these sections must correspond to what is supported in # the config template. sections = ['global', 'client.radosgw.gateway'] diff --git a/hooks/hooks.py b/hooks/hooks.py index 77f42263..4fb19c7b 100755 --- a/hooks/hooks.py +++ b/hooks/hooks.py @@ -75,12 +75,10 @@ from charmhelpers.contrib.openstack.ha.utils import ( generate_ha_relation_data, ) from utils import ( - enable_pocket, register_configs, setup_ipv6, services, assess_status, - setup_keystone_certs, disable_unused_apache_sites, pause_unit_helper, resume_unit_helper, @@ -99,17 +97,10 @@ from charmhelpers.contrib.openstack.cert_utils import ( hooks = Hooks() CONFIGS = register_configs() -NSS_DIR = '/var/lib/ceph/nss' - PACKAGES = [ 'haproxy', - 'libnss3-tools', 'ntp', - 'python-keystoneclient', - 'python-six', # Ensures correct version is installed for precise - # since python-keystoneclient does not pull in icehouse - # version 'radosgw', 'apache2' ] @@ -166,10 +157,7 @@ def install_packages(): def install(): status_set('maintenance', 'Executing pre-install') execd_preinstall() - enable_pocket('multiverse') install_packages() - if not os.path.exists(NSS_DIR): - os.makedirs(NSS_DIR) if not os.path.exists('/etc/ceph'): os.makedirs('/etc/ceph') @@ -385,8 +373,6 @@ def configure_https(): if not is_unit_paused_set(): service_reload('apache2', restart_on_failure=True) - setup_keystone_certs(CONFIGS) - @hooks.hook('update-status') @harden() diff --git a/hooks/utils.py b/hooks/utils.py index b6622adc..14dbe3bb 100644 --- a/hooks/utils.py +++ b/hooks/utils.py @@ -13,10 +13,8 @@ # limitations under the License. import os -import re import socket import subprocess -import sys from collections import OrderedDict from copy import deepcopy @@ -24,19 +22,11 @@ from copy import deepcopy import ceph_radosgw_context from charmhelpers.core.hookenv import ( - log, - DEBUG, - ERROR, - INFO, relation_get, relation_ids, related_units, application_version_set, ) -from charmhelpers.contrib.network.ip import ( - format_ipv6_addr, - is_ipv6, -) from charmhelpers.contrib.openstack import ( context, templating, @@ -46,9 +36,6 @@ from charmhelpers.contrib.openstack.utils import ( pause_unit, resume_unit, ) -from charmhelpers.contrib.openstack.keystone import ( - format_endpoint, -) from charmhelpers.contrib.hahelpers.cluster import ( get_hacluster_config, https, @@ -56,7 +43,6 @@ from charmhelpers.contrib.hahelpers.cluster import ( from charmhelpers.core.host import ( cmp_pkgrevno, lsb_release, - mkdir, CompareHostReleases, init_is_systemd, ) @@ -69,36 +55,6 @@ from charmhelpers.fetch import ( get_upstream_version, ) -# NOTE: some packages are installed by the charm so may not be available -# yet. Calls that depend on them should be aware of this (and use the -# defer_if_unavailable() decorator). -try: - import keystoneclient - from keystoneclient.v2_0 import client - from keystoneclient.v3 import client as client_v3 - try: - # Kilo and newer - from keystoneclient.exceptions import ( - ConnectionRefused, - Forbidden, - InternalServerError, - ) - except ImportError: - # Juno and older - from keystoneclient.exceptions import ( - ConnectionError as ConnectionRefused, - Forbidden, - InternalServerError, - ) -except ImportError: - keystoneclient = None - -# This is installed as a dep of python-keystoneclient -try: - import requests -except ImportError: - requests = None - # The interface is said to be satisfied if anyone of the interfaces in the # list has a complete context. REQUIRED_INTERFACES = { @@ -139,14 +95,6 @@ BASE_RESOURCE_MAP = OrderedDict([ ]) -class KSCertSetupException(BaseException): - """Keystone SSL Certificate Setup Exception. - - This exception should be raised if any part of cert setup fails. - """ - pass - - def resource_map(): """Dynamically generate a map of resources. @@ -198,18 +146,6 @@ def services(): return list(set(_services)) -def enable_pocket(pocket): - apt_sources = "/etc/apt/sources.list" - with open(apt_sources, "r") as sources: - lines = sources.readlines() - with open(apt_sources, "w") as sources: - for line in lines: - if pocket in line: - sources.write(re.sub('^# deb', 'deb', line)) - else: - sources.write(line) - - def get_optional_interfaces(): """Return the optional interfaces that should be checked if the relavent relations have appeared. @@ -347,213 +283,6 @@ def get_pkg_version(name): return version -def defer_if_unavailable(modules): - """If a function depends on a package/module that is installed by the charm - but may not yet have been installed, it can be deferred using this - decorator. - - :param modules: list of modules that must be importable. - """ - def _inner1_defer_if_unavailable(f): - def _inner2_defer_if_unavailable(*args, **kwargs): - for m in modules: - if m not in sys.modules: - log("Module '{}' does not appear to be available " - "yet - deferring call to '{}' until it " - "is.".format(m, f.__name__), level=INFO) - return - - return f(*args, **kwargs) - - return _inner2_defer_if_unavailable - - return _inner1_defer_if_unavailable - - -@defer_if_unavailable(['keystoneclient']) -def get_ks_cert(ksclient, auth_endpoint, cert_type): - """Get certificate from keystone. - - :param ksclient: Keystone client - :param auth_endpoint: Keystone auth endpoint url - :param certs_path: Path to local certs store - :returns: certificate - """ - if ksclient.version == 'v3': - if cert_type == 'signing': - cert_type = 'certificates' - request = ("{}OS-SIMPLE-CERT/{}" - "".format(auth_endpoint, cert_type)) - else: - request = "{}/certificates/{}".format(auth_endpoint, cert_type) - - try: - try: - # Kilo and newer - if cert_type == 'ca': - cert = ksclient.certificates.get_ca_certificate() - elif cert_type in ['signing', 'certificates']: - cert = ksclient.certificates.get_signing_certificate() - else: - raise KSCertSetupException("Invalid cert type " - "'{}'".format(cert_type)) - except AttributeError: - # Keystone v3 or Juno and older - response = requests.request('GET', request) - if response.status_code == requests.codes.ok: - cert = response.text - else: - raise KSCertSetupException("Unable to retrieve certificate") - except (ConnectionRefused, requests.exceptions.ConnectionError, - Forbidden, InternalServerError): - raise KSCertSetupException("Error connecting to keystone") - - return cert - - -@defer_if_unavailable(['keystoneclient']) -def get_ks_ca_cert(ksclient, auth_endpoint, certs_path): - """"Get and store keystone CA certificate. - - :param ksclient: Keystone client - :param auth_endpoint: Keystone auth endpoint url - :param certs_path: Path to local certs store - :returns: None - """ - - ca_cert = get_ks_cert(ksclient, auth_endpoint, 'ca') - if ca_cert: - try: - # Cert should not contain unicode chars. - str(ca_cert) - except UnicodeEncodeError: - raise KSCertSetupException("Did not get a valid ca cert from " - "keystone - cert setup incomplete") - - log("Updating ca cert from keystone", level=DEBUG) - ca = os.path.join(certs_path, 'ca.pem') - with open(ca, 'w') as fd: - fd.write(ca_cert) - - out = subprocess.check_output(['openssl', 'x509', '-in', ca, - '-pubkey']) - p = subprocess.Popen(['certutil', '-d', certs_path, '-A', '-n', 'ca', - '-t', 'TCu,Cu,Tuw'], stdin=subprocess.PIPE) - p.communicate(out) - else: - raise KSCertSetupException("No ca cert available from keystone") - - -@defer_if_unavailable(['keystoneclient']) -def get_ks_signing_cert(ksclient, auth_endpoint, certs_path): - """"Get and store keystone signing certificate. - - :param ksclient: Keystone client - :param auth_endpoint: Keystone auth endpoint url - :param certs_path: Path to local certs store - :returns: None - """ - signing_cert = get_ks_cert(ksclient, auth_endpoint, 'signing') - if signing_cert: - try: - # Cert should not contain unicode chars. - str(signing_cert) - except UnicodeEncodeError: - raise KSCertSetupException("Invalid signing cert from keystone") - - log("Updating signing cert from keystone", level=DEBUG) - signing_cert_path = os.path.join(certs_path, 'signing_certificate.pem') - with open(signing_cert_path, 'w') as fd: - fd.write(signing_cert) - - out = subprocess.check_output(['openssl', 'x509', '-in', - signing_cert_path, '-pubkey']) - p = subprocess.Popen(['certutil', '-A', '-d', certs_path, '-n', - 'signing_cert', '-t', 'P,P,P'], - stdin=subprocess.PIPE) - p.communicate(out) - else: - raise KSCertSetupException("No signing cert available from keystone") - - -@defer_if_unavailable(['keystoneclient']) -def setup_keystone_certs(CONFIGS): - """ - Get CA and signing certs from Keystone used to decrypt revoked token list. - - :param unit: context unit id - :param rid: context relation id - :returns: None - """ - certs_path = '/var/lib/ceph/nss' - if not os.path.exists(certs_path): - mkdir(certs_path) - - # Do not continue until identity-relation is complete - if 'identity-service' not in CONFIGS.complete_contexts(): - log("Missing relation settings - deferring cert setup", - level=DEBUG) - return - - ksclient = get_keystone_client_from_relation() - if not ksclient: - log("Failed to get keystoneclient", level=ERROR) - return - - auth_endpoint = ksclient.auth_endpoint - - try: - get_ks_ca_cert(ksclient, auth_endpoint, certs_path) - get_ks_signing_cert(ksclient, auth_endpoint, certs_path) - except KSCertSetupException as e: - log("Keystone certs setup incomplete - {}".format(e), level=INFO) - - -# TODO: Move to charmhelpers -# TODO: Make it session aware -def get_keystone_client_from_relation(relation_type='identity-service'): - """ Get keystone client from relation data - - :param relation_type: Relation to keystone - :returns: Keystone client - """ - required = ['admin_token', 'auth_host', 'auth_port', 'api_version'] - settings = {} - - rdata = {} - for relid in relation_ids(relation_type): - for unit in related_units(relid): - rdata = relation_get(unit=unit, rid=relid) or {} - if set(required).issubset(set(rdata.keys())): - settings = {key: rdata.get(key) for key in required} - break - - if not settings: - log("Required settings not yet provided by any identity-service " - "relation units", INFO) - return None - - auth_protocol = rdata.get('auth_protocol', 'http') - if is_ipv6(settings.get('auth_host')): - settings['auth_host'] = format_ipv6_addr(settings.get('auth_host')) - - api_version = rdata.get('api_version') - auth_endpoint = format_endpoint(auth_protocol, - settings['auth_host'], - settings['auth_port'], - settings['api_version']) - - if api_version and '3' in api_version: - ksclient = client_v3.Client(token=settings['admin_token'], - endpoint=auth_endpoint) - else: - ksclient = client.Client(token=settings['admin_token'], - endpoint=auth_endpoint) - # Add simple way to retrieve keystone auth endpoint - ksclient.auth_endpoint = auth_endpoint - return ksclient - - def disable_unused_apache_sites(): """Ensure that unused apache configurations are disabled to prevent them from conflicting with the charm-provided version. diff --git a/templates/ceph.conf b/templates/ceph.conf index 7b403a82..3b832c4b 100644 --- a/templates/ceph.conf +++ b/templates/ceph.conf @@ -48,11 +48,7 @@ rgw keystone admin token = {{ admin_token }} {% endif -%} rgw keystone accepted roles = {{ user_roles }} rgw keystone token cache size = {{ cache_size }} -rgw keystone revocation interval = {{ revocation_check_interval }} rgw s3 auth use keystone = true -{% if cms -%} -nss db path = /var/lib/ceph/nss -{% endif %} {% else -%} rgw swift url = http://{{ unit_public_ip }} {% endif -%} diff --git a/unit_tests/test_ceph_radosgw_context.py b/unit_tests/test_ceph_radosgw_context.py index 7bf051b2..c1a75daf 100644 --- a/unit_tests/test_ceph_radosgw_context.py +++ b/unit_tests/test_ceph_radosgw_context.py @@ -85,7 +85,6 @@ class IdentityServiceContextTest(CharmTestCase): _format_ipv6_addr, jewel_installed=False): self.test_config.set('operator-roles', 'Babel') self.test_config.set('cache-size', '42') - self.test_config.set('revocation-check-interval', '7500000') self.test_relation.set({'admin_token': 'ubuntutesting'}) self.relation_ids.return_value = ['identity-service:5'] self.related_units.return_value = ['keystone/0'] @@ -122,7 +121,6 @@ class IdentityServiceContextTest(CharmTestCase): 'auth_protocol': 'http', 'auth_type': 'keystone', 'cache_size': '42', - 'revocation_check_interval': '7500000', 'service_host': '127.0.0.4', 'service_port': 9876, 'service_protocol': 'http', @@ -143,7 +141,6 @@ class IdentityServiceContextTest(CharmTestCase): jewel_installed=False): self.test_config.set('operator-roles', 'Babel') self.test_config.set('cache-size', '42') - self.test_config.set('revocation-check-interval', '7500000') self.test_relation.set({'admin_token': 'ubuntutesting'}) self.relation_ids.return_value = ['identity-service:5'] self.related_units.return_value = ['keystone/0'] @@ -178,7 +175,6 @@ class IdentityServiceContextTest(CharmTestCase): 'auth_protocol': 'http', 'auth_type': 'keystone', 'cache_size': '42', - 'revocation_check_interval': '7500000', 'service_host': '127.0.0.4', 'service_port': 9876, 'service_protocol': 'http', @@ -199,7 +195,6 @@ class IdentityServiceContextTest(CharmTestCase): jewel_installed=False): self.test_config.set('operator-roles', 'Babel') self.test_config.set('cache-size', '42') - self.test_config.set('revocation-check-interval', '7500000') self.test_relation.set({'admin_token': 'ubuntutesting'}) self.relation_ids.return_value = ['identity-service:5'] self.related_units.return_value = ['keystone/0'] @@ -239,7 +234,6 @@ class IdentityServiceContextTest(CharmTestCase): 'auth_protocol': 'http', 'auth_type': 'keystone', 'cache_size': '42', - 'revocation_check_interval': '7500000', 'service_host': '127.0.0.4', 'service_port': 9876, 'service_protocol': 'http', @@ -262,7 +256,6 @@ class IdentityServiceContextTest(CharmTestCase): _ctxt_comp, _format_ipv6_addr): self.test_config.set('operator-roles', 'Babel') self.test_config.set('cache-size', '42') - self.test_config.set('revocation-check-interval', '7500000') self.test_relation.set({}) self.relation_ids.return_value = ['identity-service:5'] self.related_units.return_value = ['keystone/0'] diff --git a/unit_tests/test_ceph_radosgw_utils.py b/unit_tests/test_ceph_radosgw_utils.py index fcb1dd98..6d1e6b14 100644 --- a/unit_tests/test_ceph_radosgw_utils.py +++ b/unit_tests/test_ceph_radosgw_utils.py @@ -13,9 +13,7 @@ # limitations under the License. from mock import ( - call, patch, - mock_open, MagicMock, ) @@ -26,7 +24,6 @@ from test_utils import CharmTestCase TO_PATCH = [ 'application_version_set', 'get_upstream_version', - 'format_endpoint', 'https', 'relation_ids', 'relation_get', @@ -96,137 +93,6 @@ class CephRadosGWUtilTests(CharmTestCase): # ports=None whilst port checks are disabled. f.assert_called_once_with('assessor', services='s1', ports=None) - @patch.object(utils, 'get_keystone_client_from_relation') - @patch.object(utils, 'is_ipv6', lambda addr: False) - @patch.object(utils, 'get_ks_signing_cert') - @patch.object(utils, 'get_ks_ca_cert') - @patch.object(utils, 'mkdir') - def test_setup_keystone_certs(self, mock_mkdir, - mock_get_ks_ca_cert, - mock_get_ks_signing_cert, - mock_get_keystone_client): - auth_host = 'foo/bar' - auth_port = 80 - auth_url = 'http://%s:%s/v2.0' % (auth_host, auth_port) - mock_ksclient = MagicMock() - mock_ksclient.auth_endpoint = auth_url - mock_get_keystone_client.return_value = mock_ksclient - - configs = MagicMock() - configs.complete_contexts.return_value = ['identity-service'] - - utils.setup_keystone_certs(configs) - mock_get_ks_signing_cert.assert_has_calls([call(mock_ksclient, - auth_url, - '/var/lib/ceph/nss')]) - mock_get_ks_ca_cert.assert_has_calls([call(mock_ksclient, auth_url, - '/var/lib/ceph/nss')]) - - @patch.object(utils, 'client_v3') - @patch.object(utils, 'client') - @patch.object(utils, 'related_units') - @patch.object(utils, 'relation_ids') - @patch.object(utils, 'is_ipv6', lambda addr: False) - @patch.object(utils, 'relation_get') - def test_get_ks_client_from_relation(self, mock_relation_get, - mock_relation_ids, - mock_related_units, - mock_client, - mock_client_v3): - auth_host = 'foo/bar' - auth_port = 80 - admin_token = '666' - auth_url = 'http://%s:%s/v2.0' % (auth_host, auth_port) - self.format_endpoint.return_value = auth_url - mock_relation_ids.return_value = ['identity-service:5'] - mock_related_units.return_value = ['keystone/1'] - rel_data = {'auth_host': auth_host, - 'auth_port': auth_port, - 'admin_token': admin_token, - 'api_version': '2'} - - mock_relation_get.return_value = rel_data - utils.get_keystone_client_from_relation() - mock_client.Client.assert_called_with(endpoint=auth_url, - token=admin_token) - - auth_url = 'http://%s:%s/v3' % (auth_host, auth_port) - self.format_endpoint.return_value = auth_url - rel_data['api_version'] = '3' - mock_relation_get.return_value = rel_data - utils.get_keystone_client_from_relation() - mock_client_v3.Client.assert_called_with(endpoint=auth_url, - token=admin_token) - - @patch.object(utils, 'client_v3') - @patch.object(utils, 'client') - @patch.object(utils, 'related_units') - @patch.object(utils, 'relation_ids') - @patch.object(utils, 'is_ipv6', lambda addr: False) - @patch.object(utils, 'relation_get') - def test_get_ks_client_from_relation_not_available(self, mock_relation_get, - mock_relation_ids, - mock_related_units, - mock_client, - mock_client_v3): - mock_relation_ids.return_value = ['identity-service:5'] - mock_related_units.return_value = ['keystone/1'] - rel_data = {'auth_port': '5000', - 'admin_token': 'foo', - 'api_version': '2'} - - mock_relation_get.return_value = rel_data - ksclient = utils.get_keystone_client_from_relation() - self.assertIsNone(ksclient) - - @patch.object(utils, 'get_ks_cert') - @patch.object(utils.subprocess, 'Popen') - @patch.object(utils.subprocess, 'check_output') - def test_get_ks_signing_cert(self, mock_check_output, mock_Popen, - mock_get_ks_cert): - auth_host = 'foo/bar' - auth_port = 80 - admin_token = '666' - auth_url = 'http://%s:%s/v2.0' % (auth_host, auth_port) - - m = mock_open() - with patch.object(utils, 'open', m, create=True): - - mock_get_ks_cert.return_value = 'signing_cert_data' - utils.get_ks_signing_cert(admin_token, auth_url, '/foo/bar') - - mock_get_ks_cert.return_value = None - with self.assertRaises(utils.KSCertSetupException): - utils.get_ks_signing_cert(admin_token, auth_url, '/foo/bar') - - c = ['openssl', 'x509', '-in', - '/foo/bar/signing_certificate.pem', - '-pubkey'] - mock_check_output.assert_called_with(c) - - @patch.object(utils, 'get_ks_cert') - @patch.object(utils.subprocess, 'Popen') - @patch.object(utils.subprocess, 'check_output') - def test_get_ks_ca_cert(self, mock_check_output, mock_Popen, - mock_get_ks_cert): - auth_host = 'foo/bar' - auth_port = 80 - admin_token = '666' - auth_url = 'http://%s:%s/v2.0' % (auth_host, auth_port) - - m = mock_open() - with patch.object(utils, 'open', m, create=True): - mock_get_ks_cert.return_value = 'ca_cert_data' - utils.get_ks_ca_cert(admin_token, auth_url, '/foo/bar') - - mock_get_ks_cert.return_value = None - with self.assertRaises(utils.KSCertSetupException): - utils.get_ks_ca_cert(admin_token, auth_url, '/foo/bar') - - c = ['openssl', 'x509', '-in', '/foo/bar/ca.pem', - '-pubkey'] - mock_check_output.assert_called_with(c) - def _setup_relation_data(self, data): self.relation_ids.return_value = data.keys() self.related_units.side_effect = ( diff --git a/unit_tests/test_hooks.py b/unit_tests/test_hooks.py index a6ddb431..070ce8e0 100644 --- a/unit_tests/test_hooks.py +++ b/unit_tests/test_hooks.py @@ -37,7 +37,6 @@ TO_PATCH = [ 'config', 'cmp_pkgrevno', 'execd_preinstall', - 'enable_pocket', 'log', 'open_port', 'os', @@ -55,7 +54,6 @@ TO_PATCH = [ 'service_stop', 'service_restart', 'service', - 'setup_keystone_certs', 'service_name', 'socket', 'restart_map', @@ -150,8 +148,6 @@ class CephRadosGWTests(CharmTestCase): ceph_hooks.install() self.assertTrue(self.execd_preinstall.called) self.assertTrue(_install_packages.called) - self.enable_pocket.assert_called_with('multiverse') - self.os.makedirs.called_with('/var/lib/ceph/nss') @patch.object(ceph_hooks, 'certs_joined') @patch.object(ceph_hooks, 'update_nrpe_config')