
This changes enables the Keystone v3 api. It can be toggled on and off via the preferred-api-version option. When services join the identity-service relation they will be presented with a new parameter api_version which is the maximum api version the keystone charm supports and matches what was set via preferred-api-version. If preferred-api-version is set to 3 then the charm will render a new policy.json which adds support for domains etc when keystone is checking authorisation. The new policy.json requires an admin domain to be created and specifies that a user is classed as an admin of the whole cloud if they have the admin role against that admin domain. The admin domain, called admin_domain, is created by the charm. The name of this domain is currently not user configurable. The role that enables a user to be classed as an admin is specified by the old charm option admin-role. The charm grants admin-role to the admin-user against the admin_domain. Switching a deployed cloud from preferred-api-version 2 to preferred-api-version 3 is supported. Switching from preferred-api-version 3 to preferred-api-version 2 should work from the charm point of view but may cause problems if there are duplicate users between domains or may have unintended consequences like escalating the privilege of some users so is not recommended. Change-Id: I8eec2a90e0acbf56ee72cb5036a0a21f4a77a2c3
253 lines
9.6 KiB
Python
253 lines
9.6 KiB
Python
#!/usr/bin/python
|
|
from keystoneclient.v2_0 import client
|
|
from keystoneclient.v3 import client as keystoneclient_v3
|
|
from keystoneclient.auth import token_endpoint
|
|
from keystoneclient import session
|
|
|
|
|
|
def _get_keystone_manager_class(endpoint, token, 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 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)
|
|
if api_version == 3:
|
|
return KeystoneManager3(endpoint, token)
|
|
raise ValueError('No manager found for api version {}'.format(api_version))
|
|
|
|
|
|
def get_keystone_manager(endpoint, token, 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
|
|
Use this manager to query the catalogue and determine which api version
|
|
should actually be being used. Return the correct client based on that
|
|
XXX I think the keystone client should be able to do version
|
|
detection automatically so the code below could be greatly
|
|
simplified
|
|
|
|
@param endpoint: the keystone endpoint to point client at
|
|
@param token: the keystone admin_token
|
|
@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)
|
|
else:
|
|
if 'v2.0' in endpoint.split('/'):
|
|
manager = _get_keystone_manager_class(endpoint, token, 2)
|
|
else:
|
|
manager = _get_keystone_manager_class(endpoint, token, 3)
|
|
if endpoint.endswith('/'):
|
|
base_ep = endpoint.rsplit('/', 2)[0]
|
|
else:
|
|
base_ep = endpoint.rsplit('/', 1)[0]
|
|
svc_id = None
|
|
for svc in manager.api.services.list():
|
|
if svc.type == 'identity':
|
|
svc_id = svc.id
|
|
version = None
|
|
for ep in manager.api.endpoints.list():
|
|
if ep.service_id == svc_id and hasattr(ep, 'adminurl'):
|
|
version = ep.adminurl.split('/')[-1]
|
|
if version and version == 'v2.0':
|
|
new_ep = base_ep + "/" + 'v2.0'
|
|
return _get_keystone_manager_class(new_ep, token, 2)
|
|
elif version and version == 'v3':
|
|
new_ep = base_ep + "/" + 'v3'
|
|
return _get_keystone_manager_class(new_ep, token, 3)
|
|
else:
|
|
return manager
|
|
|
|
|
|
class KeystoneManager(object):
|
|
|
|
def resolve_tenant_id(self, name):
|
|
"""Find the tenant_id of a given tenant"""
|
|
tenants = [t._info for t in self.api.tenants.list()]
|
|
for t in tenants:
|
|
if name == t['name']:
|
|
return t['id']
|
|
|
|
def resolve_domain_id(self, name):
|
|
pass
|
|
|
|
def resolve_role_id(self, name):
|
|
"""Find the role_id of a given role"""
|
|
roles = [r._info for r in self.api.roles.list()]
|
|
for r in roles:
|
|
if name == r['name']:
|
|
return r['id']
|
|
|
|
def resolve_service_id(self, name, service_type=None):
|
|
"""Find the service_id of a given service"""
|
|
services = [s._info for s in self.api.services.list()]
|
|
for s in services:
|
|
if service_type:
|
|
if name == s['name'] and service_type == s['type']:
|
|
return s['id']
|
|
else:
|
|
if name == s['name']:
|
|
return s['id']
|
|
|
|
def resolve_service_id_by_type(self, type):
|
|
"""Find the service_id of a given service"""
|
|
services = [s._info for s in self.api.services.list()]
|
|
for s in services:
|
|
if type == s['type']:
|
|
return s['id']
|
|
|
|
|
|
class KeystoneManager2(KeystoneManager):
|
|
|
|
def __init__(self, endpoint, token):
|
|
self.api_version = 2
|
|
self.api = client.Client(endpoint=endpoint, token=token)
|
|
|
|
def resolve_user_id(self, name, user_domain=None):
|
|
"""Find the user_id of a given user"""
|
|
users = [u._info for u in self.api.users.list()]
|
|
for u in users:
|
|
if name == u['name']:
|
|
return u['id']
|
|
|
|
def create_endpoints(self, region, service_id, publicurl, adminurl,
|
|
internalurl):
|
|
self.api.endpoints.create(region=region, service_id=service_id,
|
|
publicurl=publicurl, adminurl=adminurl,
|
|
internalurl=internalurl)
|
|
|
|
def tenants_list(self):
|
|
return self.api.tenants.list()
|
|
|
|
def create_tenant(self, tenant_name, description, domain='default'):
|
|
self.api.tenants.create(tenant_name=tenant_name,
|
|
description=description)
|
|
|
|
def delete_tenant(self, tenant_id):
|
|
self.api.tenants.delete(tenant_id)
|
|
|
|
def create_user(self, name, password, email, tenant_id=None,
|
|
domain_id=None):
|
|
self.api.users.create(name=name,
|
|
password=password,
|
|
email=email,
|
|
tenant_id=tenant_id)
|
|
|
|
def update_password(self, user, password):
|
|
self.api.users.update_password(user=user, password=password)
|
|
|
|
def roles_for_user(self, user_id, tenant_id=None, domain_id=None):
|
|
return self.api.roles.roles_for_user(user_id, tenant_id)
|
|
|
|
def add_user_role(self, user, role, tenant, domain):
|
|
self.api.roles.add_user_role(user=user, role=role, tenant=tenant)
|
|
|
|
|
|
class KeystoneManager3(KeystoneManager):
|
|
|
|
def __init__(self, endpoint, token):
|
|
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)
|
|
|
|
def resolve_tenant_id(self, name):
|
|
"""Find the tenant_id of a given tenant"""
|
|
tenants = [t._info for t in self.api.projects.list()]
|
|
for t in tenants:
|
|
if name == t['name']:
|
|
return t['id']
|
|
|
|
def resolve_domain_id(self, name):
|
|
"""Find the domain_id of a given domain"""
|
|
domains = [d._info for d in self.api.domains.list()]
|
|
for d in domains:
|
|
if name == d['name']:
|
|
return d['id']
|
|
|
|
def resolve_user_id(self, name, user_domain=None):
|
|
"""Find the user_id of a given user"""
|
|
if user_domain:
|
|
domain_id = self.resolve_domain_id(user_domain)
|
|
for user in self.api.users.list():
|
|
if name == user.name:
|
|
if user_domain:
|
|
if domain_id == user.domain_id:
|
|
return user.id
|
|
else:
|
|
return user.id
|
|
|
|
def create_endpoints(self, region, service_id, publicurl, adminurl,
|
|
internalurl):
|
|
self.api.endpoints.create(service_id, publicurl, interface='public',
|
|
region=region)
|
|
self.api.endpoints.create(service_id, adminurl, interface='admin',
|
|
region=region)
|
|
self.api.endpoints.create(service_id, internalurl,
|
|
interface='internal', region=region)
|
|
|
|
def tenants_list(self):
|
|
return self.api.projects.list()
|
|
|
|
def create_domain(self, domain_name, description):
|
|
self.api.domains.create(domain_name, description=description)
|
|
|
|
def create_tenant(self, tenant_name, description, domain='default'):
|
|
self.api.projects.create(tenant_name, domain, description=description)
|
|
|
|
def delete_tenant(self, tenant_id):
|
|
self.api.projects.delete(tenant_id)
|
|
|
|
def create_user(self, name, password, email, tenant_id=None,
|
|
domain_id=None):
|
|
if not domain_id:
|
|
domain_id = self.resolve_domain_id('default')
|
|
if tenant_id:
|
|
self.api.users.create(name,
|
|
domain=domain_id,
|
|
password=password,
|
|
email=email,
|
|
project=tenant_id)
|
|
else:
|
|
self.api.users.create(name,
|
|
domain=domain_id,
|
|
password=password,
|
|
email=email)
|
|
|
|
def update_password(self, user, password):
|
|
self.api.users.update(user, password=password)
|
|
|
|
def roles_for_user(self, user_id, tenant_id=None, domain_id=None):
|
|
# Specify either a domain or project, not both
|
|
if domain_id:
|
|
return self.api.roles.list(user_id, domain=domain_id)
|
|
else:
|
|
return self.api.roles.list(user_id, project=tenant_id)
|
|
|
|
def add_user_role(self, user, role, tenant, domain):
|
|
# Specify either a domain or project, not both
|
|
if domain:
|
|
self.api.roles.grant(role, user=user, domain=domain)
|
|
if tenant:
|
|
self.api.roles.grant(role, user=user, project=tenant)
|
|
|
|
def find_endpoint_v3(self, interface, service_id, region):
|
|
found_eps = []
|
|
for ep in self.api.endpoints.list():
|
|
if ep.service_id == service_id and ep.region == region and \
|
|
ep.interface == interface:
|
|
found_eps.append(ep)
|
|
return found_eps
|
|
|
|
def delete_old_endpoint_v3(self, interface, service_id, region, url):
|
|
eps = self.find_endpoint_v3(interface, service_id, region)
|
|
for ep in eps:
|
|
if getattr(ep, 'url') != url:
|
|
self.api.endpoints.delete(ep.id)
|
|
return True
|
|
return False
|