From 0a02c30fe5f4650235519897b71588ae22fa0971 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Mon, 9 Mar 2020 15:06:09 +0100 Subject: [PATCH] Replace use of admin_token with Keystone bootstrap Stop the use of the admin_token and use the bootstrap process to initialize Keystone instead. Fortunately the implementation of the bootstrap process is both idempotent when it needs to be and it can be safely called on an existing deployment. Subsequently we can migrate by just removing the admin_token from the configuration and create new credentials for use by the charm with a call to ``keystone-manage bootstrap``. Remove configuration templates for versions prior to Mitaka, by doing this we need to move any configuration initially defined prior to Miataka forward to the ``templates/mitaka`` folder. A side effect of this migration is that newly bootstrapped deployments will get their ``default`` domain created with a literal ID of ``default``. Prior to this change third party software making assumptions about that being the case may have had issues. Closes-Bug: #1859844 Closes-Bug: #1837113 Related-Bug: #1774733 Closes-Bug: #1648719 Closes-Bug: #1578678 Func-Test-Pr: https://github.com/openstack-charmers/zaza-openstack-tests/pull/191 Change-Id: I23940720c24527ee34149f035c3bdf9ff54812c9 --- config.yaml | 6 - hooks/keystone_context.py | 6 +- hooks/keystone_hooks.py | 9 + hooks/keystone_types.py | 40 ++ hooks/keystone_utils.py | 169 ++++++-- hooks/manager.py | 126 ++++-- templates/icehouse/keystone.conf | 106 ----- templates/icehouse/logging.conf | 49 --- templates/kilo/keystone.conf | 121 ------ templates/liberty/policy.json | 382 ------------------- templates/mitaka/keystone.conf | 3 +- templates/{kilo => mitaka}/logging.conf | 0 templates/mitaka/policy.json | 6 +- templates/{liberty => mitaka}/policy.json.v2 | 0 templates/newton/policy.json | 6 +- templates/ocata/keystone.conf | 1 - templates/ocata/policy.json | 6 +- templates/queens/keystone.conf | 1 - templates/rocky/keystone.conf | 1 - unit_tests/test_keystone_hooks.py | 24 +- unit_tests/test_keystone_utils.py | 88 ++++- 21 files changed, 391 insertions(+), 759 deletions(-) create mode 100644 hooks/keystone_types.py delete mode 100644 templates/icehouse/keystone.conf delete mode 100644 templates/icehouse/logging.conf delete mode 100644 templates/kilo/keystone.conf delete mode 100644 templates/liberty/policy.json rename templates/{kilo => mitaka}/logging.conf (100%) rename templates/{liberty => mitaka}/policy.json.v2 (100%) diff --git a/config.yaml b/config.yaml index 6c19c508..9e9465d2 100644 --- a/config.yaml +++ b/config.yaml @@ -82,12 +82,6 @@ options: Admin password. To be used *for testing only*. Randomly generated by default. To retreive generated password, juju run --unit keystone/0 leader-get admin_passwd - admin-token: - type: string - default: None - description: | - Admin token. If set, this token will be used for all services instead of - being generated per service. admin-role: type: string default: 'Admin' diff --git a/hooks/keystone_context.py b/hooks/keystone_context.py index 3a65d94c..5ec53043 100644 --- a/hooks/keystone_context.py +++ b/hooks/keystone_context.py @@ -175,12 +175,11 @@ class KeystoneContext(context.OSContextGenerator): def __call__(self): from keystone_utils import ( - api_port, set_admin_token, endpoint_url, resolve_address, + api_port, endpoint_url, resolve_address, PUBLIC, ADMIN, ADMIN_DOMAIN, snap_install_requested, get_api_version, ) ctxt = {} - ctxt['token'] = set_admin_token(config('admin-token')) ctxt['api_version'] = get_api_version() ctxt['admin_role'] = config('admin-role') if ctxt['api_version'] > 2: @@ -191,6 +190,9 @@ class KeystoneContext(context.OSContextGenerator): leader_get(attribute='admin_domain_id') ctxt['default_domain_id'] = \ leader_get(attribute='default_domain_id') + # This is required prior to system-scope being implemented (Queens) + ctxt['transitional_charm_user_id'] = leader_get( + attribute='transitional_charm_user_id') ctxt['admin_port'] = determine_api_port(api_port('keystone-admin'), singlenode_mode=True) ctxt['public_port'] = determine_api_port(api_port('keystone-public'), diff --git a/hooks/keystone_hooks.py b/hooks/keystone_hooks.py index 186bebc7..e4fa8018 100755 --- a/hooks/keystone_hooks.py +++ b/hooks/keystone_hooks.py @@ -85,6 +85,7 @@ from keystone_context import fernet_enabled from keystone_utils import ( add_service_to_keystone, + bootstrap_keystone, ensure_all_service_accounts_protected_for_pci_dss_options, add_credentials_to_keystone, determine_packages, @@ -370,6 +371,7 @@ def update_all_fid_backends(): update_keystone_fid_service_provider(relation_id=rid) +@restart_on_change(restart_map(), restart_functions=restart_function_map()) def leader_init_db_if_ready(use_current_context=False): """ Initialise the keystone db if it is ready and mark it as initialised. @@ -393,6 +395,7 @@ def leader_init_db_if_ready(use_current_context=False): return migrate_database() + bootstrap_keystone(configs=CONFIGS) ensure_initial_admin(config) if CompareOpenStackReleases( os_release('keystone')) >= 'liberty': @@ -705,6 +708,12 @@ def upgrade_charm(): status_set('maintenance', 'Regenerating configuration files') CONFIGS.write_all() + # We no longer use the admin_token and need to ensure the charm has + # credentials. This call is idempotent and safe to run on existing + # deployments. + if is_leader(): + bootstrap_keystone(configs=CONFIGS) + # See LP bug 1519035 leader_init_db_if_ready() diff --git a/hooks/keystone_types.py b/hooks/keystone_types.py new file mode 100644 index 00000000..ea77b781 --- /dev/null +++ b/hooks/keystone_types.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python2 + +# Copyright 2020 Canonical Ltd +# +# 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(fnordahl): This file needs to remain Python2 as it is used both by the +# charm code which is Python3 and the special ``manager.py`` script that is in +# place to support both Python2 and Python3 systems with the same codebase. +# +# The need for jumping through these hoops come from classic charms being +# deployed without any form of Python environment with direct dependencies. +# Subsequently we live at the grace of whatever dependencies our payload has +# in the running system. This may change underneath us as we upgrade between +# UCA pockets and across series. + +import collections + + +CharmCredentials = collections.namedtuple( + 'CharmCredentials', + ( + 'username', + 'password', + 'system_scope', + 'project_name', # For V2 and pre system scope compatibility + 'project_domain_name', # For Mitaka -> Pike (pre system scope) + 'user_domain_name', # For Mitaka -> Pike (pre system scope) + ), +) diff --git a/hooks/keystone_utils.py b/hooks/keystone_utils.py index 7b0d1488..df03e805 100644 --- a/hooks/keystone_utils.py +++ b/hooks/keystone_utils.py @@ -129,6 +129,8 @@ import keystone_context import uds_comms as uds +import keystone_types + TEMPLATES = 'templates/' # removed from original: charm-helper-sh @@ -178,7 +180,6 @@ if snap_install_requested(): KEYSTONE_LOGGER_CONF = "{}/logging.conf".format(SNAP_COMMON_KEYSTONE_DIR) SNAP_LIB_DIR = '{}/lib'.format(SNAP_COMMON_DIR) STORED_PASSWD = "{}/keystone.passwd".format(SNAP_LIB_DIR) - STORED_TOKEN = "{}/keystone.token".format(SNAP_LIB_DIR) STORED_ADMIN_DOMAIN_ID = ("{}/keystone.admin_domain_id" "".format(SNAP_LIB_DIR)) STORED_DEFAULT_DOMAIN_ID = ("{}/keystone.default_domain_id" @@ -196,7 +197,6 @@ else: KEYSTONE_LOGGER_CONF = "/etc/keystone/logging.conf" KEYSTONE_CONF_DIR = os.path.dirname(KEYSTONE_CONF) STORED_PASSWD = "/var/lib/keystone/keystone.passwd" - STORED_TOKEN = "/var/lib/keystone/keystone.token" STORED_ADMIN_DOMAIN_ID = "/var/lib/keystone/keystone.admin_domain_id" STORED_DEFAULT_DOMAIN_ID = "/var/lib/keystone/keystone.default_domain_id" SERVICE_PASSWD_PATH = '/var/lib/keystone/services.passwd' @@ -212,6 +212,7 @@ APACHE_CONF = '/etc/apache2/sites-available/openstack_https_frontend' APACHE_24_CONF = '/etc/apache2/sites-available/openstack_https_frontend.conf' MEMCACHED_CONF = '/etc/memcached.conf' +CHARM_USER = '_charm-keystone-admin' CLUSTER_RES = 'grp_ks_vips' ADMIN_DOMAIN = 'admin_domain' ADMIN_PROJECT = 'admin' @@ -772,6 +773,10 @@ def do_openstack_upgrade(configs): if is_elected_leader(CLUSTER_RES): if is_db_ready(): migrate_database() + # After an OpenStack upgrade we re-run bootstrap to make sure role + # assignments are up to date. One example is the system role + # assignment support that first appeared at Queens. + bootstrap_keystone(configs=configs) else: log("Database not ready - deferring to shared-db relation", level=INFO) @@ -821,6 +826,94 @@ def migrate_database(): leader_set({'db-initialised': True}) stop_manager_instance() + +def is_bootstrapped(): + """Determines whether Keystone has been bootstrapped. + + :returns: True when Keystone bootstrap has been run, False otherwise. + :rtype: bool + """ + return ( + leader_get('keystone-bootstrapped') is True and + leader_get('{}_passwd'.format(CHARM_USER)) is not None + ) + + +def bootstrap_keystone(configs=None): + """Runs ``keystone-manage bootstrap`` to bootstrap keystone. + + The bootstrap command is designed to be idempotent when it needs to be, + i.e. if nothing has changed it will do nothing. It is also safe to run + the bootstrap command on a already deployed Keystone. + + The bootstrap command creates resources in the ``default`` domain. It + assigns a system-scoped role to the created user and as such the charm can + use it to manage any domains resources. + + For successful operation of the ``keystoneclient`` used by the charm, we + must create initial endpoints at bootstrap time. For HA deployments these + will be replaced as soon as HA configuration is complete. + + :param configs: Registered configs + :type configs: Optional[Dict] + """ + log('Bootstrapping keystone.', level=INFO) + status_set('maintenance', 'Bootstrapping keystone') + # NOTE: The bootstrap process is necessary for the charm to be able to + # talk to Keystone. We will still rely on ``ensure_initial_admin`` to + # maintain Keystone's endpoints and the rest of the CRUD. + api_suffix = get_api_suffix() + charm_password = leader_get('{}_passwd'.format(CHARM_USER)) or pwgen(64) + subprocess.check_call(( + 'keystone-manage', 'bootstrap', + '--bootstrap-username', CHARM_USER, + '--bootstrap-password', charm_password, + '--bootstrap-project-name', ADMIN_PROJECT, + '--bootstrap-role-name', config('admin-role'), + '--bootstrap-service-name', 'keystone', + '--bootstrap-admin-url', endpoint_url( + resolve_address(ADMIN), + config('admin-port'), + api_suffix), + '--bootstrap-public-url', endpoint_url( + resolve_address(PUBLIC), + config('service-port'), + api_suffix), + '--bootstrap-internal-url', endpoint_url( + resolve_address(INTERNAL), + config('service-port'), + api_suffix), + '--bootstrap-region-id', config('region').split()[0]), + ) + # TODO: we should consider to add --immutable-roles for supported releases + # and/or make it configurable. Saving for a future change as this one is + # big enough as-is. + leader_set({ + 'keystone-bootstrapped': True, + '{}_passwd'.format(CHARM_USER): charm_password, + }) + + cmp_release = CompareOpenStackReleases(os_release('keystone')) + if configs and cmp_release < 'queens': + # For Mitaka through Pike we need to work around the lack of support + # for system scope by having a special bootstrap version of the + # policy.json that ensures the charm has access to retrieve the user ID + # created for the charm in the bootstrap process. + # + # As soon as the user ID is retrieved it will be stored in leader + # storage which will be picked up by a context and subsequently written + # to the runtime policy.json. + # + # NOTE: Remove this and the associated policy change as soon as + # support for Mitaka -> Pike is removed. + manager = get_manager() + transitional_charm_user_id = manager.resolve_user_id( + CHARM_USER, user_domain='default') + leader_set({ + 'transitional_charm_user_id': transitional_charm_user_id, + }) + configs.write_all() + # OLD @@ -850,41 +943,30 @@ def get_local_endpoint(api_suffix=None): return local_endpoint -def set_admin_token(admin_token='None'): - """Set admin token according to deployment config or use a randomly - generated token if none is specified (default). - """ - if admin_token != 'None': - log('Configuring Keystone to use a pre-configured admin token.') - token = admin_token - else: - log('Configuring Keystone to use a random admin token.') - if os.path.isfile(STORED_TOKEN): - msg = 'Loading a previously generated' \ - ' admin token from %s' % STORED_TOKEN - log(msg) - with open(STORED_TOKEN, 'r') as f: - token = f.read().strip() - else: - token = pwgen(length=64) - with open(STORED_TOKEN, 'w') as out: - out.write('%s\n' % token) - return(token) +def get_charm_credentials(): + """Retrieve credentials for use by charm when managing identity CRUD. + The bootstrap process creates a user for the charm in the default domain + and assigns a system level role. Subsequently the charm authenticates with + a system-scoped token so it can manage all domain's resources. -def get_admin_token(): - """Temporary utility to grab the admin token as configured in - keystone.conf + :returns: CharmCredentials with username, password and defaults for scoping + :rtype: collections.namedtuple[str,str,str,str,str,str] + :raises: RuntimeError """ - with open(KEYSTONE_CONF, 'r') as f: - for l in f.readlines(): - if l.split(' ')[0] == 'admin_token': - try: - return l.split('=')[1].strip() - except Exception: - error_out('Could not parse admin_token line from %s' % - KEYSTONE_CONF) - error_out('Could not find admin_token line in %s' % KEYSTONE_CONF) + charm_password = leader_get('{}_passwd'.format(CHARM_USER)) + if charm_password is None: + raise RuntimeError('Leader unit has not provided credentials required ' + 'for speaking with Keystone yet.') + + return keystone_types.CharmCredentials( + CHARM_USER, + charm_password, + 'all', + ADMIN_PROJECT, # For V2 and pre system scope compatibility + 'default', # For Mitaka -> Pike (pre system scope) + 'default', # For Mitaka -> Pike (pre system scope) + ) def is_service_present(service_name, service_type): @@ -950,7 +1032,14 @@ def create_endpoint_template_v2(manager, region, service, publicurl, adminurl, else: # delete endpoint and recreate if endpoint urls need updating. log("Updating endpoint template with new endpoint urls.") - manager.delete_endpoint_by_id(ep['id']) + # NOTE: When using the 2.0 API and not using the admin_token + # the call to delete_endpoint_by_id returns 404. + # Deleting service works and will cascade delete endpoint. + svc = manager.get_service_by_id(service_id) + manager.delete_service_by_id(service_id) + # NOTE: We do not get the service description in API v2.0 + create_service_entry(svc['name'], svc['type'], '') + service_id = manager.resolve_service_id(service) manager.create_endpoints(region=region, service_id=service_id, @@ -1133,7 +1222,7 @@ def _proxy_manager_call(path, api_version, args, kwargs): package = dict(path=path, api_version=api_version, api_local_endpoint=get_local_endpoint(), - admin_token=get_admin_token(), + charm_credentials=get_charm_credentials(), args=args, kwargs=kwargs) serialized = json.dumps(package, **JSON_ENCODE_OPTIONS) @@ -1696,7 +1785,11 @@ def ensure_all_service_accounts_protected_for_pci_dss_options(): if get_api_version() < 3: return log("Ensuring all service users are protected from PCI-DSS options") - users = list_users_for_domain(domain=SERVICE_DOMAIN) + # We want to make sure our own charm credentials are protected too, they + # only exist in DEFAULT_DOMAIN, but the called function gracefully deals + # with that. + users = [{'name': CHARM_USER}] + users += list_users_for_domain(domain=SERVICE_DOMAIN) for user in users: protect_user_account_from_pci_dss_force_change_password(user['name']) @@ -1799,7 +1892,6 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): if not service_username: return - token = get_admin_token() roles = get_requested_roles(settings) service_password = create_service_credentials(service_username, new_roles=roles) @@ -1829,7 +1921,6 @@ def add_service_to_keystone(relation_id=None, remote_unit=None): relation_data = { "auth_host": resolve_address(ADMIN), "service_host": resolve_address(PUBLIC), - "admin_token": token, "service_port": config("service-port"), "auth_port": config("admin-port"), "service_username": service_username, diff --git a/hooks/manager.py b/hooks/manager.py index 6d1dc14f..db01d9dc 100755 --- a/hooks/manager.py +++ b/hooks/manager.py @@ -23,13 +23,17 @@ import stat import sys import time -from keystoneclient.v2_0 import client +from keystoneauth1 import session as ks_session +from keystoneauth1.identity import v2 as ks_identity_v2 +from keystoneauth1.identity import v3 as ks_identity_v3 +from keystoneclient.v2_0 import client as keystoneclient_v2 from keystoneclient.v3 import client as keystoneclient_v3 -from keystoneclient.auth import token_endpoint -from keystoneclient import session, exceptions +from keystoneclient import exceptions import uds_comms as uds +import keystone_types + _usage = """This file is called from the keystone_utils.py file to implement various keystone calls and functions. It is called with one parameter which is @@ -42,7 +46,7 @@ following keys: 'path': The api path on the keystone manager object. 'api_version': the keystone API version to use. 'api_local_endpoint': the local endpoint to connect to. - 'admin_token': the admin token to use with keystone. + 'charm_credentials': the credentials to use when speaking with Keystone. 'args': the non-keyword argument to supply to the keystone manager call. 'kwargs': any keyword args to supply to the keystone manager call. } @@ -75,17 +79,17 @@ else: econnrefused = exceptions.ConnectionError -def _get_keystone_manager_class(endpoint, token, api_version): +def _get_keystone_manager_class(endpoint, charm_credentials, api_version): """Return KeystoneManager class for the given API version @param endpoint: the keystone endpoint to point client at - @param token: the keystone admin_token + @param charm_credentials: the keystone credentials @param api_version: version of the keystone api the client should use @returns keystonemanager class used for interrogating keystone """ if api_version == 2: - return KeystoneManager2(endpoint, token) + return KeystoneManager2(endpoint, charm_credentials) if api_version == 3: - return KeystoneManager3(endpoint, token) + return KeystoneManager3(endpoint, charm_credentials) raise ValueError('No manager found for api version {}'.format(api_version)) @@ -118,7 +122,7 @@ def retry_on_exception(num_retries, base_delay=0, exc_type=Exception): @retry_on_exception(5, base_delay=3, exc_type=econnrefused) -def get_keystone_manager(endpoint, token, api_version=None): +def get_keystone_manager(endpoint, charm_credentials, api_version=None): """Return a keystonemanager for the correct API version If api_version has not been set then create a manager based on the endpoint @@ -131,17 +135,20 @@ def get_keystone_manager(endpoint, token, api_version=None): simplified @param endpoint: the keystone endpoint to point client at - @param token: the keystone admin_token + @param charm_credentials: the keystone credentials @param api_version: version of the keystone api the client should use @returns keystonemanager class used for interrogating keystone """ if api_version: - return _get_keystone_manager_class(endpoint, token, api_version) + return _get_keystone_manager_class( + endpoint, charm_credentials, api_version) else: if 'v2.0' in endpoint.split('/'): - manager = _get_keystone_manager_class(endpoint, token, 2) + manager = _get_keystone_manager_class( + endpoint, charm_credentials, 2) else: - manager = _get_keystone_manager_class(endpoint, token, 3) + manager = _get_keystone_manager_class( + endpoint, charm_credentials, 3) if endpoint.endswith('/'): base_ep = endpoint.rsplit('/', 2)[0] else: @@ -158,10 +165,12 @@ def get_keystone_manager(endpoint, token, api_version=None): break if version and version == 'v2.0': new_ep = base_ep + "/" + 'v2.0' - return _get_keystone_manager_class(new_ep, token, 2) + return _get_keystone_manager_class( + new_ep, charm_credentials, 2) elif version and version == 'v3': new_ep = base_ep + "/" + 'v3' - return _get_keystone_manager_class(new_ep, token, 3) + return _get_keystone_manager_class( + new_ep, charm_credentials, 3) else: return manager @@ -207,6 +216,11 @@ class KeystoneManager(object): if type == s['type']: return s['id'] + def get_service_by_id(self, service_id): + """Get a service by the service id""" + service = self.api.services.get(service_id) + return service.to_dict() + def delete_service_by_id(self, service_id): """Delete a service by the service id""" self.api.services.delete(service_id) @@ -232,9 +246,21 @@ class KeystoneManager(object): class KeystoneManager2(KeystoneManager): - def __init__(self, endpoint, token): + def __init__(self, endpoint, charm_credentials): self.api_version = 2 - self.api = client.Client(endpoint=endpoint, token=token) + auth = ks_identity_v2.Password( + auth_url=endpoint, + username=charm_credentials.username, + password=charm_credentials.password, + tenant_name=charm_credentials.project_name) + session = ks_session.Session(auth=auth) + + # NOTE: We need to also provide the local endpoint URL as an + # endpoint_override, otherwise the client will attempt to discover the + # endpoint to use in the catalog. Since we are managing said catalog + # we need to avoid situations where there is no endpoint to be found. + self.api = keystoneclient_v2.Client( + session=session, endpoint_override=endpoint) def resolve_user_id(self, name, user_domain=None): """Find the user_id of a given user""" @@ -300,11 +326,36 @@ class KeystoneManager2(KeystoneManager): class KeystoneManager3(KeystoneManager): - def __init__(self, endpoint, token): + def __init__(self, endpoint, charm_credentials): self.api_version = 3 - keystone_auth_v3 = token_endpoint.Token(endpoint=endpoint, token=token) - keystone_session_v3 = session.Session(auth=keystone_auth_v3) - self.api = keystoneclient_v3.Client(session=keystone_session_v3) + # The bootstrap process creates a user for the charm in the ``default`` + # domain and assigns a system level role. We need to specify domain + # name even when we request a system scoped token. + try: + auth = ks_identity_v3.Password( + auth_url=endpoint, + username=charm_credentials.username, + password=charm_credentials.password, + system_scope=charm_credentials.system_scope, + project_domain_name=charm_credentials.project_domain_name, + user_domain_name=charm_credentials.user_domain_name) + except TypeError: + # Support for OpenStack versions prior to Queens + auth = ks_identity_v3.Password( + auth_url=endpoint, + username=charm_credentials.username, + password=charm_credentials.password, + project_name=charm_credentials.project_name, + project_domain_name=charm_credentials.project_domain_name, + user_domain_name=charm_credentials.user_domain_name) + keystone_session_v3 = ks_session.Session(auth=auth) + + # NOTE: We need to also provide the local endpoint URL as an + # endpoint_override, otherwise the client will attempt to discover the + # endpoint to use in the catalog. Since we are managing said catalog + # we need to avoid situations where there is no endpoint to be found. + self.api = keystoneclient_v3.Client( + session=keystone_session_v3, endpoint_override=endpoint) def resolve_tenant_id(self, name, domain=None): """Find the tenant_id of a given tenant""" @@ -532,19 +583,22 @@ class KeystoneManager3(KeystoneManager): _keystone_manager = dict( api_version=None, api_local_endpoint=None, - admin_token=None, + charm_credentials=None, manager=None) -def get_manager(api_version=None, api_local_endpoint=None, admin_token=None): +def get_manager(api_version=None, api_local_endpoint=None, + charm_credentials=None): """Return a keystonemanager for the correct API version This function actually returns a singleton of the right kind of KeystoneManager (v2 or v3). If the api_version, api_local_endpoint and - admin_token haven't changed then the current _keystone_manager object is - returned, otherwise a new one is created (and thus the old one goes out of - scope and is closed). This is to that repeated calls to get_manager(...) - only results in a single authorisation request if the details don't change. + charm_credentials haven't changed then the current _keystone_manager object + is returned, otherwise a new one is created (and thus the old one goes out + of scope and is closed). This is to that repeated calls to + get_manager(...) only results in a single authorisation request if the + details don't change. + This is to speed up calls from the keystone charm into keystone and make the charm more performant. It's hoped that the complexity/performance trade-off is a good choice. @@ -552,25 +606,26 @@ def get_manager(api_version=None, api_local_endpoint=None, admin_token=None): :param api_verion: The version of the api to use or None. if None then the version is determined from the api_local_enpoint variable. :param api_local_endpoint: where to find the keystone API - :param admin_token: the token used for authentication. - :raises: RuntimeError if api_local_endpoint or admin_token is not set. + :param charm_credentials: the credentials used for authentication. + :raises: RuntimeError if api_local_endpoint or charm_credentials is not + set. :returns: a KeystoneManager derived class (possibly the singleton). """ if api_local_endpoint is None: raise RuntimeError("get_manager(): api_local_endpoint is not set") - if admin_token is None: - raise RuntimeError("get_manager(): admin_token is not set") + if charm_credentials is None: + raise RuntimeError("get_manager(): charm_credentials is not set") global _keystone_manager if (api_version == _keystone_manager['api_version'] and api_local_endpoint == _keystone_manager['api_local_endpoint'] and - admin_token == _keystone_manager['admin_token']): + charm_credentials == _keystone_manager['charm_credentials']): return _keystone_manager['manager'] # only retain the params IF getting the manager actually works _keystone_manager['manager'] = get_keystone_manager( - api_local_endpoint, admin_token, api_version) + api_local_endpoint, charm_credentials, api_version) _keystone_manager['api_version'] = api_version _keystone_manager['api_local_endpoint'] = api_local_endpoint - _keystone_manager['admin_token'] = admin_token + _keystone_manager['charm_credentials'] = charm_credentials return _keystone_manager['manager'] @@ -638,7 +693,8 @@ if __name__ == '__main__': manager = get_manager( api_version=spec['api_version'], api_local_endpoint=spec['api_local_endpoint'], - admin_token=spec['admin_token']) + charm_credentials=keystone_types.CharmCredentials._make( + spec['charm_credentials'])) _callable = manager for attr in spec['path']: _callable = getattr(_callable, attr) diff --git a/templates/icehouse/keystone.conf b/templates/icehouse/keystone.conf deleted file mode 100644 index 9f071c4f..00000000 --- a/templates/icehouse/keystone.conf +++ /dev/null @@ -1,106 +0,0 @@ -# icehouse -############################################################################### -# [ WARNING ] -# Configuration file maintained by Juju. Local changes may be overwritten. -############################################################################### -[DEFAULT] -admin_token = {{ token }} -admin_port = {{ admin_port }} -public_port = {{ public_port }} -use_syslog = {{ use_syslog }} -log_config = /etc/keystone/logging.conf -debug = {{ debug }} -verbose = {{ verbose }} -public_endpoint = {{ public_endpoint }} -admin_endpoint = {{ admin_endpoint }} -bind_host = {{ bind_host }} -public_workers = {{ workers }} -admin_workers = {{ workers }} - -[database] -{% if database_host -%} -connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} -{% else -%} -connection = sqlite:////var/lib/keystone/keystone.db -{% endif -%} -connection_recycle_time = 200 - -[identity] -driver = keystone.identity.backends.{{ identity_backend }}.Identity - -[credential] -driver = keystone.credential.backends.sql.Credential - -[trust] -driver = keystone.trust.backends.sql.Trust - -[os_inherit] - -[catalog] -driver = keystone.catalog.backends.sql.Catalog - -[endpoint_filter] - -[token] -driver = keystone.token.backends.sql.Token -provider = keystone.token.providers.uuid.Provider -expiration = {{ token_expiration }} - -{% include "parts/section-signing" %} - -[cache] - -[policy] -driver = keystone.policy.backends.sql.Policy - -[ec2] -driver = keystone.contrib.ec2.backends.sql.Ec2 - -[assignment] -driver = keystone.assignment.backends.{{ assignment_backend }}.Assignment - -[oauth1] - -[auth] -methods = external,password,token,oauth1 -password = keystone.auth.plugins.password.Password -token = keystone.auth.plugins.token.Token -oauth1 = keystone.auth.plugins.oauth1.OAuth - -[paste_deploy] -config_file = keystone-paste.ini - -[extra_headers] -Distribution = Ubuntu - -[ldap] -{% if identity_backend == 'ldap' -%} -url = {{ ldap_server }} -user = {{ ldap_user }} -password = {{ ldap_password }} -suffix = {{ ldap_suffix }} - -{% if ldap_config_flags -%} -{% for key, value in ldap_config_flags.items() -%} -{{ key }} = {{ value }} -{% endfor -%} -{% endif -%} - -{% if ldap_readonly -%} -user_allow_create = False -user_allow_update = False -user_allow_delete = False - -tenant_allow_create = False -tenant_allow_update = False -tenant_allow_delete = False - -role_allow_create = False -role_allow_update = False -role_allow_delete = False - -group_allow_create = False -group_allow_update = False -group_allow_delete = False -{% endif -%} -{% endif -%} diff --git a/templates/icehouse/logging.conf b/templates/icehouse/logging.conf deleted file mode 100644 index f84a4d73..00000000 --- a/templates/icehouse/logging.conf +++ /dev/null @@ -1,49 +0,0 @@ -# icehouse - -[loggers] -keys=root - -[formatters] -keys=normal,normal_with_name,debug - -[handlers] -keys=production,file,devel - -[logger_root] -{% if root_level -%} -level={{ root_level }} -{% else -%} -level=WARNING -{% endif -%} -handlers=file,production - -[handler_production] -class=handlers.SysLogHandler -{% if log_level -%} -level={{ log_level }} -{% else -%} -level=ERROR -{% endif -%} -formatter=normal_with_name -args=(('/dev/log'), handlers.SysLogHandler.LOG_USER) - -[handler_file] -class=FileHandler -level=DEBUG -formatter=normal_with_name -args=('/var/log/keystone/keystone.log', 'a') - -[handler_devel] -class=StreamHandler -level=NOTSET -formatter=debug -args=(sys.stdout,) - -[formatter_normal] -format=%(asctime)s %(levelname)s %(message)s - -[formatter_normal_with_name] -format=(%(name)s): %(asctime)s %(levelname)s %(message)s - -[formatter_debug] -format=(%(name)s): %(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s diff --git a/templates/kilo/keystone.conf b/templates/kilo/keystone.conf deleted file mode 100644 index fec774b8..00000000 --- a/templates/kilo/keystone.conf +++ /dev/null @@ -1,121 +0,0 @@ -# kilo -############################################################################### -# [ WARNING ] -# Configuration file maintained by Juju. Local changes may be overwritten. -############################################################################### -[DEFAULT] -admin_token = {{ token }} -use_syslog = {{ use_syslog }} -log_config = /etc/keystone/logging.conf -debug = {{ debug }} -verbose = {{ verbose }} -public_endpoint = {{ public_endpoint }} -admin_endpoint = {{ admin_endpoint }} - -[eventlet_server] -admin_bind_host = {{ bind_host }} -public_bind_host = {{ bind_host }} -public_workers = {{ workers }} -admin_workers = {{ workers }} -admin_port = {{ admin_port }} -public_port = {{ public_port }} - -[database] -{% if database_host -%} -connection = {{ database_type }}://{{ database_user }}:{{ database_password }}@{{ database_host }}/{{ database }}{% if database_ssl_ca %}?ssl_ca={{ database_ssl_ca }}{% if database_ssl_cert %}&ssl_cert={{ database_ssl_cert }}&ssl_key={{ database_ssl_key }}{% endif %}{% endif %} -{% else -%} -connection = sqlite:////var/lib/keystone/keystone.db -{% endif -%} -connection_recycle_time = 200 - -[identity] -driver = keystone.identity.backends.{{ identity_backend }}.Identity -{% if default_domain_id -%} -default_domain_id = {{ default_domain_id }} -{% endif -%} - -{% if api_version == 3 -%} -domain_specific_drivers_enabled = True -domain_config_dir = /etc/keystone/domains -{% endif -%} - -[credential] -driver = keystone.credential.backends.sql.Credential - -[trust] -driver = keystone.trust.backends.sql.Trust - -[os_inherit] - -[catalog] -driver = keystone.catalog.backends.sql.Catalog - -[endpoint_filter] - -[token] -driver = keystone.token.persistence.backends.sql.Token -provider = keystone.token.providers.uuid.Provider -expiration = {{ token_expiration }} - -{% include "parts/section-signing" %} - -[cache] - -[policy] -driver = keystone.policy.backends.sql.Policy - -[ec2] -driver = keystone.contrib.ec2.backends.sql.Ec2 - -[assignment] -driver = keystone.assignment.backends.{{ assignment_backend }}.Assignment - -[oauth1] - -[auth] -methods = external,password,token,oauth1 -password = keystone.auth.plugins.password.Password -token = keystone.auth.plugins.token.Token -oauth1 = keystone.auth.plugins.oauth1.OAuth - -[paste_deploy] -config_file = /etc/keystone/keystone-paste.ini - -[extra_headers] -Distribution = Ubuntu - -[ldap] -{% if identity_backend == 'ldap' -%} -url = {{ ldap_server }} -user = {{ ldap_user }} -password = {{ ldap_password }} -suffix = {{ ldap_suffix }} - -{% if ldap_config_flags -%} -{% for key, value in ldap_config_flags.items() -%} -{{ key }} = {{ value }} -{% endfor -%} -{% endif -%} - -{% if ldap_readonly -%} -user_allow_create = False -user_allow_update = False -user_allow_delete = False - -tenant_allow_create = False -tenant_allow_update = False -tenant_allow_delete = False - -role_allow_create = False -role_allow_update = False -role_allow_delete = False - -group_allow_create = False -group_allow_update = False -group_allow_delete = False -{% endif -%} -{% endif -%} - -[oslo_middleware] -# Bug #1819134 -max_request_body_size = 114688 \ No newline at end of file diff --git a/templates/liberty/policy.json b/templates/liberty/policy.json deleted file mode 100644 index 8821ba06..00000000 --- a/templates/liberty/policy.json +++ /dev/null @@ -1,382 +0,0 @@ -{% if api_version == 3 -%} -{ - "admin_required": "role:{{ admin_role }}", - "cloud_admin": "rule:admin_required and domain_id:{{ admin_domain_id }}", - "service_role": "role:service", - "service_or_admin": "rule:admin_required or rule:service_role", - "owner" : "user_id:%(user_id)s or user_id:%(target.token.user_id)s", - "admin_or_owner": "(rule:admin_required and domain_id:%(target.token.user.domain.id)s) or rule:owner", - "admin_or_cloud_admin": "rule:admin_required or rule:cloud_admin", - "admin_and_matching_domain_id": "rule:admin_required and domain_id:%(domain_id)s", - "service_admin_or_owner": "rule:service_or_admin or rule:owner", - - "default": "rule:admin_required", - - "identity:get_region": "", - "identity:list_regions": "", - "identity:create_region": "rule:cloud_admin", - "identity:update_region": "rule:cloud_admin", - "identity:delete_region": "rule:cloud_admin", - - "identity:get_service": "rule:admin_or_cloud_admin", - "identity:list_services": "rule:admin_or_cloud_admin", - "identity:create_service": "rule:cloud_admin", - "identity:update_service": "rule:cloud_admin", - "identity:delete_service": "rule:cloud_admin", - - "identity:get_endpoint": "rule:admin_or_cloud_admin", - "identity:list_endpoints": "rule:admin_or_cloud_admin", - "identity:create_endpoint": "rule:cloud_admin", - "identity:update_endpoint": "rule:cloud_admin", - "identity:delete_endpoint": "rule:cloud_admin", - - "identity:get_domain": "rule:cloud_admin or rule:admin_and_matching_domain_id", - "identity:list_domains": "rule:cloud_admin", - "identity:create_domain": "rule:cloud_admin", - "identity:update_domain": "rule:cloud_admin", - "identity:delete_domain": "rule:cloud_admin", - - "admin_and_matching_target_project_domain_id": "rule:admin_required and domain_id:%(target.project.domain_id)s", - "admin_and_matching_project_domain_id": "rule:admin_required and domain_id:%(project.domain_id)s", - "identity:get_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id", - "identity:list_projects": "rule:cloud_admin or rule:admin_and_matching_domain_id", - "identity:list_user_projects": "rule:owner or rule:admin_and_matching_domain_id", - "identity:create_project": "rule:cloud_admin or rule:admin_and_matching_project_domain_id", - "identity:update_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id", - "identity:delete_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id", - - "admin_and_matching_target_user_domain_id": "rule:admin_required and domain_id:%(target.user.domain_id)s", - "admin_and_matching_user_domain_id": "rule:admin_required and domain_id:%(user.domain_id)s", - "identity:get_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id", - "identity:list_users": "rule:cloud_admin or rule:admin_and_matching_domain_id", - "identity:create_user": "rule:cloud_admin or rule:admin_and_matching_user_domain_id", - "identity:update_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id", - "identity:delete_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id", - - "admin_and_matching_target_group_domain_id": "rule:admin_required and domain_id:%(target.group.domain_id)s", - "admin_and_matching_group_domain_id": "rule:admin_required and domain_id:%(group.domain_id)s", - "identity:get_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", - "identity:list_groups": "rule:cloud_admin or rule:admin_and_matching_domain_id", - "identity:list_groups_for_user": "rule:owner or rule:admin_and_matching_domain_id", - "identity:create_group": "rule:cloud_admin or rule:admin_and_matching_group_domain_id", - "identity:update_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", - "identity:delete_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", - "identity:list_users_in_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", - "identity:remove_user_from_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", - "identity:check_user_in_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", - "identity:add_user_to_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", - - "identity:get_credential": "rule:admin_required", - "identity:list_credentials": "rule:admin_required or user_id:%(user_id)s", - "identity:create_credential": "rule:admin_required", - "identity:update_credential": "rule:admin_required", - "identity:delete_credential": "rule:admin_required", - - "identity:ec2_get_credential": "rule:admin_or_cloud_admin or (rule:owner and user_id:%(target.credential.user_id)s)", - "identity:ec2_list_credentials": "rule:admin_or_cloud_admin or rule:owner", - "identity:ec2_create_credential": "rule:admin_or_cloud_admin or rule:owner", - "identity:ec2_delete_credential": "rule:admin_or_cloud_admin or (rule:owner and user_id:%(target.credential.user_id)s)", - - "identity:get_role": "rule:admin_or_cloud_admin", - "identity:list_roles": "rule:admin_or_cloud_admin", - "identity:create_role": "rule:cloud_admin", - "identity:update_role": "rule:cloud_admin", - "identity:delete_role": "rule:cloud_admin", - - "domain_admin_for_grants": "rule:admin_required and (domain_id:%(domain_id)s or domain_id:%(target.project.domain_id)s)", - "project_admin_for_grants": "rule:admin_required and project_id:%(project_id)s", - "identity:check_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants", - "identity:list_grants": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants", - "identity:create_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants", - "identity:revoke_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants", - - "admin_on_domain_filter" : "rule:admin_required and domain_id:%(scope.domain.id)s", - "admin_on_project_filter" : "rule:admin_required and project_id:%(scope.project.id)s", - "identity:list_role_assignments": "rule:cloud_admin or rule:admin_on_domain_filter or rule:admin_on_project_filter", - - "identity:get_policy": "rule:cloud_admin", - "identity:list_policies": "rule:cloud_admin", - "identity:create_policy": "rule:cloud_admin", - "identity:update_policy": "rule:cloud_admin", - "identity:delete_policy": "rule:cloud_admin", - - "identity:change_password": "rule:owner", - "identity:check_token": "rule:admin_or_owner", - "identity:validate_token": "rule:service_admin_or_owner", - "identity:validate_token_head": "rule:service_or_admin", - "identity:revocation_list": "rule:service_or_admin", - "identity:revoke_token": "rule:admin_or_owner", - - "identity:create_trust": "user_id:%(trust.trustor_user_id)s", - "identity:list_trusts": "", - "identity:list_roles_for_trust": "", - "identity:get_role_for_trust": "", - "identity:delete_trust": "", - - "identity:create_consumer": "rule:admin_required", - "identity:get_consumer": "rule:admin_required", - "identity:list_consumers": "rule:admin_required", - "identity:delete_consumer": "rule:admin_required", - "identity:update_consumer": "rule:admin_required", - - "identity:authorize_request_token": "rule:admin_required", - "identity:list_access_token_roles": "rule:admin_required", - "identity:get_access_token_role": "rule:admin_required", - "identity:list_access_tokens": "rule:admin_required", - "identity:get_access_token": "rule:admin_required", - "identity:delete_access_token": "rule:admin_required", - - "identity:list_projects_for_endpoint": "rule:admin_required", - "identity:add_endpoint_to_project": "rule:admin_required", - "identity:check_endpoint_in_project": "rule:admin_required", - "identity:list_endpoints_for_project": "rule:admin_required", - "identity:remove_endpoint_from_project": "rule:admin_required", - - "identity:create_endpoint_group": "rule:admin_required", - "identity:list_endpoint_groups": "rule:admin_required", - "identity:get_endpoint_group": "rule:admin_required", - "identity:update_endpoint_group": "rule:admin_required", - "identity:delete_endpoint_group": "rule:admin_required", - "identity:list_projects_associated_with_endpoint_group": "rule:admin_required", - "identity:list_endpoints_associated_with_endpoint_group": "rule:admin_required", - "identity:get_endpoint_group_in_project": "rule:admin_required", - "identity:list_endpoint_groups_for_project": "rule:admin_required", - "identity:add_endpoint_group_to_project": "rule:admin_required", - "identity:remove_endpoint_group_from_project": "rule:admin_required", - - "identity:create_identity_provider": "rule:cloud_admin", - "identity:list_identity_providers": "rule:cloud_admin", - "identity:get_identity_providers": "rule:cloud_admin", - "identity:update_identity_provider": "rule:cloud_admin", - "identity:delete_identity_provider": "rule:cloud_admin", - - "identity:create_protocol": "rule:cloud_admin", - "identity:update_protocol": "rule:cloud_admin", - "identity:get_protocol": "rule:cloud_admin", - "identity:list_protocols": "rule:cloud_admin", - "identity:delete_protocol": "rule:cloud_admin", - - "identity:create_mapping": "rule:cloud_admin", - "identity:get_mapping": "rule:cloud_admin", - "identity:list_mappings": "rule:cloud_admin", - "identity:delete_mapping": "rule:cloud_admin", - "identity:update_mapping": "rule:cloud_admin", - - "identity:create_service_provider": "rule:cloud_admin", - "identity:list_service_providers": "rule:cloud_admin", - "identity:get_service_provider": "rule:cloud_admin", - "identity:update_service_provider": "rule:cloud_admin", - "identity:delete_service_provider": "rule:cloud_admin", - - "identity:get_auth_catalog": "", - "identity:get_auth_projects": "", - "identity:get_auth_domains": "", - - "identity:list_projects_for_groups": "", - "identity:list_domains_for_groups": "", - - "identity:list_revoke_events": "", - - "identity:create_policy_association_for_endpoint": "rule:cloud_admin", - "identity:check_policy_association_for_endpoint": "rule:cloud_admin", - "identity:delete_policy_association_for_endpoint": "rule:cloud_admin", - "identity:create_policy_association_for_service": "rule:cloud_admin", - "identity:check_policy_association_for_service": "rule:cloud_admin", - "identity:delete_policy_association_for_service": "rule:cloud_admin", - "identity:create_policy_association_for_region_and_service": "rule:cloud_admin", - "identity:check_policy_association_for_region_and_service": "rule:cloud_admin", - "identity:delete_policy_association_for_region_and_service": "rule:cloud_admin", - "identity:get_policy_for_endpoint": "rule:cloud_admin", - "identity:list_endpoints_for_policy": "rule:cloud_admin", - - "identity:create_domain_config": "rule:cloud_admin", - "identity:get_domain_config": "rule:cloud_admin", - "identity:update_domain_config": "rule:cloud_admin", - "identity:delete_domain_config": "rule:cloud_admin" -} -{% else -%} -{ - "admin_required": "role:admin or is_admin:1", - "service_role": "role:service", - "service_or_admin": "rule:admin_required or rule:service_role", - "owner" : "user_id:%(user_id)s", - "admin_or_owner": "rule:admin_required or rule:owner", - "token_subject": "user_id:%(target.token.user_id)s", - "admin_or_token_subject": "rule:admin_required or rule:token_subject", - "service_admin_or_token_subject": "rule:service_or_admin or rule:token_subject", - - "default": "rule:admin_required", - - "identity:get_region": "", - "identity:list_regions": "", - "identity:create_region": "rule:admin_required", - "identity:update_region": "rule:admin_required", - "identity:delete_region": "rule:admin_required", - - "identity:get_service": "rule:admin_required", - "identity:list_services": "rule:admin_required", - "identity:create_service": "rule:admin_required", - "identity:update_service": "rule:admin_required", - "identity:delete_service": "rule:admin_required", - - "identity:get_endpoint": "rule:admin_required", - "identity:list_endpoints": "rule:admin_required", - "identity:create_endpoint": "rule:admin_required", - "identity:update_endpoint": "rule:admin_required", - "identity:delete_endpoint": "rule:admin_required", - - "identity:get_domain": "rule:admin_required", - "identity:list_domains": "rule:admin_required", - "identity:create_domain": "rule:admin_required", - "identity:update_domain": "rule:admin_required", - "identity:delete_domain": "rule:admin_required", - - "identity:get_project": "rule:admin_required", - "identity:list_projects": "rule:admin_required", - "identity:list_user_projects": "rule:admin_or_owner", - "identity:create_project": "rule:admin_required", - "identity:update_project": "rule:admin_required", - "identity:delete_project": "rule:admin_required", - - "identity:get_user": "rule:admin_required", - "identity:list_users": "rule:admin_required", - "identity:create_user": "rule:admin_required", - "identity:update_user": "rule:admin_required", - "identity:delete_user": "rule:admin_required", - "identity:change_password": "rule:admin_or_owner", - - "identity:get_group": "rule:admin_required", - "identity:list_groups": "rule:admin_required", - "identity:list_groups_for_user": "rule:admin_or_owner", - "identity:create_group": "rule:admin_required", - "identity:update_group": "rule:admin_required", - "identity:delete_group": "rule:admin_required", - "identity:list_users_in_group": "rule:admin_required", - "identity:remove_user_from_group": "rule:admin_required", - "identity:check_user_in_group": "rule:admin_required", - "identity:add_user_to_group": "rule:admin_required", - - "identity:get_credential": "rule:admin_required", - "identity:list_credentials": "rule:admin_required", - "identity:create_credential": "rule:admin_required", - "identity:update_credential": "rule:admin_required", - "identity:delete_credential": "rule:admin_required", - - "identity:ec2_get_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)", - "identity:ec2_list_credentials": "rule:admin_or_owner", - "identity:ec2_create_credential": "rule:admin_or_owner", - "identity:ec2_delete_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)", - - "identity:get_role": "rule:admin_required", - "identity:list_roles": "rule:admin_required", - "identity:create_role": "rule:admin_required", - "identity:update_role": "rule:admin_required", - "identity:delete_role": "rule:admin_required", - - "identity:check_grant": "rule:admin_required", - "identity:list_grants": "rule:admin_required", - "identity:create_grant": "rule:admin_required", - "identity:revoke_grant": "rule:admin_required", - - "identity:list_role_assignments": "rule:admin_required", - - "identity:get_policy": "rule:admin_required", - "identity:list_policies": "rule:admin_required", - "identity:create_policy": "rule:admin_required", - "identity:update_policy": "rule:admin_required", - "identity:delete_policy": "rule:admin_required", - - "identity:check_token": "rule:admin_or_token_subject", - "identity:validate_token": "rule:service_admin_or_token_subject", - "identity:validate_token_head": "rule:service_or_admin", - "identity:revocation_list": "rule:service_or_admin", - "identity:revoke_token": "rule:admin_or_token_subject", - - "identity:create_trust": "user_id:%(trust.trustor_user_id)s", - "identity:list_trusts": "", - "identity:list_roles_for_trust": "", - "identity:get_role_for_trust": "", - "identity:delete_trust": "", - - "identity:create_consumer": "rule:admin_required", - "identity:get_consumer": "rule:admin_required", - "identity:list_consumers": "rule:admin_required", - "identity:delete_consumer": "rule:admin_required", - "identity:update_consumer": "rule:admin_required", - - "identity:authorize_request_token": "rule:admin_required", - "identity:list_access_token_roles": "rule:admin_required", - "identity:get_access_token_role": "rule:admin_required", - "identity:list_access_tokens": "rule:admin_required", - "identity:get_access_token": "rule:admin_required", - "identity:delete_access_token": "rule:admin_required", - - "identity:list_projects_for_endpoint": "rule:admin_required", - "identity:add_endpoint_to_project": "rule:admin_required", - "identity:check_endpoint_in_project": "rule:admin_required", - "identity:list_endpoints_for_project": "rule:admin_required", - "identity:remove_endpoint_from_project": "rule:admin_required", - - "identity:create_endpoint_group": "rule:admin_required", - "identity:list_endpoint_groups": "rule:admin_required", - "identity:get_endpoint_group": "rule:admin_required", - "identity:update_endpoint_group": "rule:admin_required", - "identity:delete_endpoint_group": "rule:admin_required", - "identity:list_projects_associated_with_endpoint_group": "rule:admin_required", - "identity:list_endpoints_associated_with_endpoint_group": "rule:admin_required", - "identity:get_endpoint_group_in_project": "rule:admin_required", - "identity:list_endpoint_groups_for_project": "rule:admin_required", - "identity:add_endpoint_group_to_project": "rule:admin_required", - "identity:remove_endpoint_group_from_project": "rule:admin_required", - - "identity:create_identity_provider": "rule:admin_required", - "identity:list_identity_providers": "rule:admin_required", - "identity:get_identity_providers": "rule:admin_required", - "identity:update_identity_provider": "rule:admin_required", - "identity:delete_identity_provider": "rule:admin_required", - - "identity:create_protocol": "rule:admin_required", - "identity:update_protocol": "rule:admin_required", - "identity:get_protocol": "rule:admin_required", - "identity:list_protocols": "rule:admin_required", - "identity:delete_protocol": "rule:admin_required", - - "identity:create_mapping": "rule:admin_required", - "identity:get_mapping": "rule:admin_required", - "identity:list_mappings": "rule:admin_required", - "identity:delete_mapping": "rule:admin_required", - "identity:update_mapping": "rule:admin_required", - - "identity:create_service_provider": "rule:admin_required", - "identity:list_service_providers": "rule:admin_required", - "identity:get_service_provider": "rule:admin_required", - "identity:update_service_provider": "rule:admin_required", - "identity:delete_service_provider": "rule:admin_required", - - "identity:get_auth_catalog": "", - "identity:get_auth_projects": "", - "identity:get_auth_domains": "", - - "identity:list_projects_for_groups": "", - "identity:list_domains_for_groups": "", - - "identity:list_revoke_events": "", - - "identity:create_policy_association_for_endpoint": "rule:admin_required", - "identity:check_policy_association_for_endpoint": "rule:admin_required", - "identity:delete_policy_association_for_endpoint": "rule:admin_required", - "identity:create_policy_association_for_service": "rule:admin_required", - "identity:check_policy_association_for_service": "rule:admin_required", - "identity:delete_policy_association_for_service": "rule:admin_required", - "identity:create_policy_association_for_region_and_service": "rule:admin_required", - "identity:check_policy_association_for_region_and_service": "rule:admin_required", - "identity:delete_policy_association_for_region_and_service": "rule:admin_required", - "identity:get_policy_for_endpoint": "rule:admin_required", - "identity:list_endpoints_for_policy": "rule:admin_required", - - "identity:create_domain_config": "rule:admin_required", - "identity:get_domain_config": "rule:admin_required", - "identity:update_domain_config": "rule:admin_required", - "identity:delete_domain_config": "rule:admin_required" -} -{% endif -%} diff --git a/templates/mitaka/keystone.conf b/templates/mitaka/keystone.conf index 6f728f0e..f6b46ed5 100644 --- a/templates/mitaka/keystone.conf +++ b/templates/mitaka/keystone.conf @@ -4,7 +4,6 @@ # Configuration file maintained by Juju. Local changes may be overwritten. ############################################################################### [DEFAULT] -admin_token = {{ token }} use_syslog = {{ use_syslog }} log_config_append = /etc/keystone/logging.conf debug = {{ debug }} @@ -111,4 +110,4 @@ admin_project_name = admin {% include "section-oslo-middleware" %} # This goes in the section above, selectively # Bug #1819134 -max_request_body_size = 114688 \ No newline at end of file +max_request_body_size = 114688 diff --git a/templates/kilo/logging.conf b/templates/mitaka/logging.conf similarity index 100% rename from templates/kilo/logging.conf rename to templates/mitaka/logging.conf diff --git a/templates/mitaka/policy.json b/templates/mitaka/policy.json index 086bb9fd..34d3cd96 100644 --- a/templates/mitaka/policy.json +++ b/templates/mitaka/policy.json @@ -1,7 +1,11 @@ {% if api_version == 3 -%} { "admin_required": "role:{{ admin_role }}", - "cloud_admin": "rule:admin_required and (token.is_admin_project:True or domain_id:{{ admin_domain_id }} or project_id:{{ service_tenant_id }})", +{% if transitional_charm_user_id %} + "cloud_admin": "rule:admin_required and (user_id:{{ transitional_charm_user_id }} or token.is_admin_project:True or domain_id:{{ admin_domain_id }} or project_id:{{ service_tenant_id }})", +{% else %} + "cloud_admin": "rule:admin_required", +{% endif %} "service_role": "role:service", "service_or_admin": "rule:admin_required or rule:service_role", "owner" : "user_id:%(user_id)s or user_id:%(target.token.user_id)s", diff --git a/templates/liberty/policy.json.v2 b/templates/mitaka/policy.json.v2 similarity index 100% rename from templates/liberty/policy.json.v2 rename to templates/mitaka/policy.json.v2 diff --git a/templates/newton/policy.json b/templates/newton/policy.json index d288cb02..c92c5f01 100644 --- a/templates/newton/policy.json +++ b/templates/newton/policy.json @@ -1,7 +1,11 @@ {% if api_version == 3 -%} { "admin_required": "role:{{ admin_role }}", - "cloud_admin": "rule:admin_required and (token.is_admin_project:True or domain_id:{{ admin_domain_id }} or project_id:{{ service_tenant_id }})", +{% if transitional_charm_user_id %} + "cloud_admin": "rule:admin_required and (user_id:{{ transitional_charm_user_id }} or token.is_admin_project:True or domain_id:{{ admin_domain_id }} or project_id:{{ service_tenant_id }})", +{% else %} + "cloud_admin": "rule:admin_required", +{% endif %} "service_role": "role:service", "service_or_admin": "rule:admin_required or rule:service_role", "owner" : "user_id:%(user_id)s or user_id:%(target.token.user_id)s", diff --git a/templates/ocata/keystone.conf b/templates/ocata/keystone.conf index 5640df96..bef9e835 100644 --- a/templates/ocata/keystone.conf +++ b/templates/ocata/keystone.conf @@ -4,7 +4,6 @@ # Configuration file maintained by Juju. Local changes may be overwritten. ############################################################################### [DEFAULT] -admin_token = {{ token }} use_syslog = {{ use_syslog }} log_config_append = {{ log_config }} debug = {{ debug }} diff --git a/templates/ocata/policy.json b/templates/ocata/policy.json index 1053fd6a..526ea08a 100644 --- a/templates/ocata/policy.json +++ b/templates/ocata/policy.json @@ -1,7 +1,11 @@ {% if api_version == 3 -%} { "admin_required": "role:{{ admin_role }}", - "cloud_admin": "rule:admin_required and (is_admin_project:True or domain_id:{{ admin_domain_id }} or project_id:{{ service_tenant_id }})", +{% if transitional_charm_user_id %} + "cloud_admin": "rule:admin_required and (user_id:{{ transitional_charm_user_id }} or token.is_admin_project:True or domain_id:{{ admin_domain_id }} or project_id:{{ service_tenant_id }})", +{% else %} + "cloud_admin": "rule:admin_required", +{% endif %} "service_role": "role:service", "service_or_admin": "rule:admin_required or rule:service_role", "owner" : "user_id:%(user_id)s or user_id:%(target.token.user_id)s", diff --git a/templates/queens/keystone.conf b/templates/queens/keystone.conf index c699921e..748178c3 100644 --- a/templates/queens/keystone.conf +++ b/templates/queens/keystone.conf @@ -4,7 +4,6 @@ # Configuration file maintained by Juju. Local changes may be overwritten. ############################################################################### [DEFAULT] -admin_token = {{ token }} use_syslog = {{ use_syslog }} log_config_append = {{ log_config }} debug = {{ debug }} diff --git a/templates/rocky/keystone.conf b/templates/rocky/keystone.conf index e2f870eb..54651e20 100644 --- a/templates/rocky/keystone.conf +++ b/templates/rocky/keystone.conf @@ -4,7 +4,6 @@ # Configuration file maintained by Juju. Local changes may be overwritten. ############################################################################### [DEFAULT] -admin_token = {{ token }} use_syslog = {{ use_syslog }} log_config_append = {{ log_config }} debug = {{ debug }} diff --git a/unit_tests/test_keystone_hooks.py b/unit_tests/test_keystone_hooks.py index 11212cc3..4e829a5d 100644 --- a/unit_tests/test_keystone_hooks.py +++ b/unit_tests/test_keystone_hooks.py @@ -582,6 +582,7 @@ class KeystoneRelationTests(CharmTestCase): cmd = ['a2dissite', 'openstack_https_frontend'] self.check_call.assert_called_with(cmd) + @patch.object(hooks, 'bootstrap_keystone') @patch.object(hooks, 'ensure_all_service_accounts_protected_for_pci_dss_options') @patch.object(hooks, 'maybe_do_policyd_overrides') @@ -601,7 +602,8 @@ class KeystoneRelationTests(CharmTestCase): os_release, update, mock_maybe_do_policyd_overrides, - mock_protect_service_accounts): + mock_protect_service_accounts, + mock_bootstrap_keystone): os_release.return_value = 'havana' mock_is_db_initialised.return_value = True mock_is_db_ready.return_value = True @@ -615,11 +617,13 @@ class KeystoneRelationTests(CharmTestCase): self.assertTrue(update.called) self.remove_old_packages.assert_called_once_with() self.service_restart.assert_called_with('apache2') + mock_bootstrap_keystone.assert_called_once_with(configs=ANY) mock_stop_manager_instance.assert_called_once_with() mock_maybe_do_policyd_overrides.assert_called_once_with( ANY, "keystone") mock_protect_service_accounts.assert_called_once_with() + @patch.object(hooks, 'bootstrap_keystone') @patch.object(hooks, 'ensure_all_service_accounts_protected_for_pci_dss_options') @patch.object(hooks, 'maybe_do_policyd_overrides') @@ -639,7 +643,8 @@ class KeystoneRelationTests(CharmTestCase): os_release, update, mock_maybe_do_policyd_overrides, - mock_protect_service_accounts): + mock_protect_service_accounts, + mock_bootstrap_keystone): os_release.return_value = 'havana' mock_is_db_initialised.return_value = True mock_is_db_ready.return_value = True @@ -653,15 +658,17 @@ class KeystoneRelationTests(CharmTestCase): self.assertTrue(update.called) self.remove_old_packages.assert_called_once_with() self.service_restart.assert_called_with('apache2') + mock_bootstrap_keystone.assert_called_once_with(configs=ANY) mock_stop_manager_instance.assert_called_once_with() mock_maybe_do_policyd_overrides.assert_called_once_with( ANY, "keystone") mock_protect_service_accounts.assert_called_once_with() + @patch.object(hooks, 'bootstrap_keystone') @patch.object(hooks, 'update_all_identity_relation_units') @patch.object(hooks, 'is_db_initialised') def test_leader_init_db_if_ready(self, is_db_initialized, - update): + update, mock_bootstrap_keystone): """ Verify leader initilaizes db """ self.is_elected_leader.return_value = True is_db_initialized.return_value = False @@ -670,6 +677,7 @@ class KeystoneRelationTests(CharmTestCase): hooks.leader_init_db_if_ready() self.is_db_ready.assert_called_with(use_current_context=False) self.migrate_database.assert_called_with() + mock_bootstrap_keystone.assert_called_once_with(configs=ANY) update.assert_called_with(check_db_ready=False) @patch.object(hooks, 'update_all_identity_relation_units') @@ -803,6 +811,7 @@ class KeystoneRelationTests(CharmTestCase): # Still updates relations self.assertTrue(self.relation_ids.called) + @patch.object(hooks, 'bootstrap_keystone') @patch.object(hooks, 'maybe_do_policyd_overrides') @patch.object(hooks, 'update_all_identity_relation_units') @patch.object(utils, 'os_release') @@ -814,7 +823,8 @@ class KeystoneRelationTests(CharmTestCase): mock_relation_ids, mock_log, os_release, update, - mock_maybe_do_policyd_overrides): + mock_maybe_do_policyd_overrides, + mock_bootstrap_keystone): os_release.return_value = 'havana' self.filter_installed_packages.return_value = ['something'] @@ -823,10 +833,12 @@ class KeystoneRelationTests(CharmTestCase): self.assertTrue(self.apt_install.called) self.assertTrue(self.log.called) self.assertFalse(update.called) + mock_bootstrap_keystone.assert_called_once_with(configs=ANY) mock_stop_manager_instance.assert_called_once() mock_maybe_do_policyd_overrides.assert_called_once_with( ANY, "keystone") + @patch.object(hooks, 'bootstrap_keystone') @patch.object(hooks, 'maybe_do_policyd_overrides') @patch.object(hooks, 'update_all_identity_relation_units') @patch.object(utils, 'os_release') @@ -840,7 +852,8 @@ class KeystoneRelationTests(CharmTestCase): mock_log, os_release, update, - mock_maybe_do_policyd_overrides + mock_maybe_do_policyd_overrides, + mock_bootstrap_keystone, ): os_release.return_value = 'havana' @@ -850,6 +863,7 @@ class KeystoneRelationTests(CharmTestCase): self.assertFalse(self.apt_install.called) self.assertTrue(self.log.called) self.assertFalse(update.called) + mock_bootstrap_keystone.assert_called_once_with(configs=ANY) mock_stop_manager_instance.assert_called_once() mock_maybe_do_policyd_overrides.assert_called_once_with( ANY, "keystone") diff --git a/unit_tests/test_keystone_utils.py b/unit_tests/test_keystone_utils.py index fd08fe6c..a976498f 100644 --- a/unit_tests/test_keystone_utils.py +++ b/unit_tests/test_keystone_utils.py @@ -15,7 +15,7 @@ import builtins import collections import copy -from mock import patch, call, MagicMock, mock_open, Mock +from mock import ANY, patch, call, MagicMock, mock_open, Mock import json import os import subprocess @@ -23,6 +23,8 @@ import time from test_utils import CharmTestCase +import keystone_types as ks_types + os.environ['JUJU_UNIT_NAME'] = 'keystone' with patch('charmhelpers.core.hookenv.config') as config, \ patch('charmhelpers.contrib.openstack.' @@ -42,7 +44,6 @@ TO_PATCH = [ 'create_role', 'create_service_entry', 'create_endpoint_template', - 'get_admin_token', 'get_local_endpoint', 'get_requested_roles', 'get_service_password', @@ -242,6 +243,7 @@ class TestKeystoneUtils(CharmTestCase): if p.startswith('python-')] + ['python-keystone', 'python-memcache']) + @patch.object(utils, 'bootstrap_keystone') @patch.object(utils, 'is_elected_leader') @patch.object(utils, 'disable_unused_apache_sites') @patch('os.path.exists') @@ -251,7 +253,7 @@ class TestKeystoneUtils(CharmTestCase): def test_openstack_upgrade_leader( self, migrate_database, determine_packages, run_in_apache, os_path_exists, disable_unused_apache_sites, - mock_is_elected_leader): + mock_is_elected_leader, mock_bootstrap_keystone): configs = MagicMock() self.test_config.set('openstack-origin', 'cloud:xenial-newton') self.os_release.return_value = 'ocata' @@ -285,6 +287,7 @@ class TestKeystoneUtils(CharmTestCase): self.assertTrue(configs.set_release.called) self.assertTrue(configs.write_all.called) self.assertTrue(migrate_database.called) + mock_bootstrap_keystone.assert_called_once_with(configs=ANY) disable_unused_apache_sites.assert_called_with() self.reset_os_release.assert_called() @@ -374,7 +377,6 @@ class TestKeystoneUtils(CharmTestCase): leader_get.return_value = None relation_id = 'identity-service:0' remote_unit = 'unit/0' - self.get_admin_token.return_value = 'token' self.get_service_password.return_value = 'password' self.test_config.set('service-tenant', 'tenant') self.test_config.set('admin-role', 'Admin') @@ -418,7 +420,6 @@ class TestKeystoneUtils(CharmTestCase): publicurl='10.0.0.1', adminurl='10.0.0.2', internalurl='192.168.1.2') - self.assertTrue(self.get_admin_token.called) self.get_service_password.assert_called_with('keystone') create_user.assert_called_with('keystone', 'password', domain=service_domain, @@ -435,7 +436,7 @@ class TestKeystoneUtils(CharmTestCase): 'admin_user_id': admin_user_id, 'admin_project_id': admin_project_id, 'auth_host': '10.0.0.3', - 'service_host': '10.0.0.3', 'admin_token': 'token', + 'service_host': '10.0.0.3', 'service_port': 81, 'auth_port': 80, 'service_username': 'keystone', 'service_password': 'password', @@ -1830,3 +1831,78 @@ class TestKeystoneUtils(CharmTestCase): self.assertEqual(csum, ('d938ff5656d3d9b50345e8061b4c73c8' '116a9c7fbc087765ce2e3a4a5df7cb17')) + + @patch.object(utils, 'leader_get') + def test_get_charm_credentials(self, mock_leader_get): + expect = ks_types.CharmCredentials( + '_charm-keystone-admin', 'fakepassword', + 'all', 'admin', 'default', 'default') + mock_leader_get.return_value = 'fakepassword' + self.assertEquals(utils.get_charm_credentials(), expect) + mock_leader_get.assert_called_once_with(expect.username + '_passwd') + mock_leader_get.retrun_value = None + mock_leader_get.side_effect = [None] + with self.assertRaises(RuntimeError): + utils.get_charm_credentials() + + @patch.object(utils, 'leader_get') + def test_is_bootstrapped(self, mock_leader_get): + mock_leader_get.side_effect = [None, None] + self.assertFalse(utils.is_bootstrapped()) + mock_leader_get.assert_called_once_with('keystone-bootstrapped') + mock_leader_get.side_effect = [True, None] + self.assertFalse(utils.is_bootstrapped()) + mock_leader_get.side_effect = [True, 'fakepassword'] + self.assertTrue(utils.is_bootstrapped()) + mock_leader_get.assert_called_with('_charm-keystone-admin_passwd') + + @patch.object(utils, 'get_manager') + @patch.object(utils, 'leader_set') + @patch.object(utils, 'resolve_address') + @patch.object(utils, 'endpoint_url') + @patch.object(utils, 'pwgen') + @patch.object(utils, 'leader_get') + @patch.object(utils, 'get_api_suffix') + def test_bootstrap_keystone( + self, + mock_get_api_suffix, + mock_leader_get, + mock_pwgen, + mock_endpoint_url, + mock_resolve_address, + mock_leader_set, + mock_get_manager): + configs = MagicMock() + mock_get_api_suffix.return_value = 'suffix' + mock_resolve_address.side_effect = lambda x: x + mock_endpoint_url.side_effect = ( + lambda x, y, z: 'http://{}:{}/{}'.format(x, y, z)) + mock_leader_get.return_value = 'fakepassword' + mock_get_manager().resolve_user_id.return_value = 'fakeid' + self.os_release.return_value = 'queens' + utils.bootstrap_keystone(configs=configs) + self.subprocess.check_call.assert_called_once_with( + ('keystone-manage', 'bootstrap', + '--bootstrap-username', '_charm-keystone-admin', + '--bootstrap-password', 'fakepassword', + '--bootstrap-project-name', 'admin', + '--bootstrap-role-name', 'Admin', + '--bootstrap-service-name', 'keystone', + '--bootstrap-admin-url', 'http://admin:35357/suffix', + '--bootstrap-public-url', 'http://public:5000/suffix', + '--bootstrap-internal-url', 'http://int:5000/suffix', + '--bootstrap-region-id', 'RegionOne'), + ) + mock_leader_set.assert_called_once_with({ + 'keystone-bootstrapped': True, + '_charm-keystone-admin_passwd': 'fakepassword'}) + self.assertFalse(configs.write_all.called) + mock_leader_set.reset_mock() + self.os_release.return_value = 'pike' + utils.bootstrap_keystone(configs=configs) + mock_leader_set.assert_has_calls([ + call({'keystone-bootstrapped': True, + '_charm-keystone-admin_passwd': 'fakepassword'}), + call({'transitional_charm_user_id': 'fakeid'}), + ]) + configs.write_all.assert_called_once_with()