Repository URL to install this package:
|
Version:
4.1.94.1.dev5 ▾
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013 TrilioData, Inc.
# All Rights Reserved.
"""Keystone Client functionality for use by resources."""
import collections
from keystoneclient.auth.identity import v3 as kc_auth_v3
import keystoneclient.exceptions as kc_exception
from keystoneclient import session
from keystoneclient.v3 import client as kc_v3
from oslo_config import cfg
from oslo_log import log as logging
from keystoneauth1.identity.generic import password as passMod
from keystoneclient import client
from novaclient import client as novaclient
from workloadmgr.common import context
from workloadmgr import exception
from workloadmgr.common.i18n import _LE
from workloadmgr.common.i18n import _LW
#from workloadmgr.vault import vault
from workloadmgr.db.workloadmgrdb import WorkloadMgrDB
LOG = logging.getLogger('workloadmgr.common.keystoneclient')
#AccessKey = collections.namedtuple('AccessKey', ['id', 'access', 'secret'])
_default_keystone_backend = "workloadmgr.common.workloadmgr_keystoneclient.KeystoneClientV3"
keystone_opts = [
cfg.StrOpt('keystone_backend',
default=_default_keystone_backend,
help="Fully qualified class name to use as a keystone backend.")
]
cfg.CONF.register_opts(keystone_opts)
CONF = cfg.CONF
class KeystoneClientBase(object):
"""Wrap keystone client so we can encapsulate logic used in resources.
Note this is intended to be initialized from a resource on a per-session
basis, so the session context is passed in on initialization
Also note that an instance of this is created in each request context as
part of a lazy-loaded cloud backend and it can be easily referenced in
each resource as ``self.keystone()``, so there should not be any need to
directly instantiate instances of this class inside resources themselves.
"""
def __init__(self, context):
# If a trust_id is specified in the context, we immediately
# authenticate so we can populate the context with a trust token
# otherwise, we delay client authentication until needed to avoid
# unnecessary calls to keystone.
#
# Note that when you obtain a token using a trust, it cannot be
# used to reauthenticate and get another token, so we have to
# get a new trust-token even if context.auth_token is set.
#
# - context.auth_url is expected to contain a versioned keystone
# path, we will work with either a v2.0 or v3 path
self.context = context
self._client = None
self._client_instance = None
self._nova_client = None
self._admin_auth = None
self._domain_admin_auth = None
self._domain_admin_client = None
self.session = session.Session.construct(self._ssl_options())
try:
self.v3_endpoint = self.context.keystone_v3_endpoint
except BaseException:
self.v3_endpoint = None
try:
if self.context.trust_id:
# Create a client with the specified trust_id, this
# populates self.context.auth_token with a trust-scoped token
self._client = self._v3_client_init()
except BaseException:
self._client = self._v3_client_init()
@property
def client(self):
if not self._client:
# Create connection to v3 API
self._client = self._v3_client_init()
return self._client
@property
def client_instance(self):
if not self._client_instance:
# Create connection to API
self._client_instance = self._common_client_init()
return self._client_instance
@property
def nova_client(self):
if not self._nova_client:
self._nova_client = self._common_client_init(nova_client=True)
return self._nova_client
def _common_client_init(self, nova_client=False):
try:
username = CONF.get('keystone_authtoken').username
except BaseException:
username = CONF.get('keystone_authtoken').admin_user
try:
password = CONF.get('keystone_authtoken').password
except BaseException:
password = CONF.get('keystone_authtoken').admin_password
try:
tenant_name = CONF.get('keystone_authtoken').admin_tenant_name
except BaseException:
project_id = context.project_id
context.project_id = 'Configurator'
tenant_name = WorkloadMgrDB().db.setting_get(
context, 'service_tenant_name', get_hidden=True).value
context.project_id = project_id
auth_url = CONF.keystone_endpoint_url
if CONF.keystone_auth_version == '3':
username = CONF.get('nova_admin_username')
password = CONF.get('nova_admin_password')
if username == 'triliovault':
domain_id = CONF.get('triliovault_user_domain_id')
else:
domain_id = CONF.get('domain_name')
cloud_admin_project = CONF.get('neutron_admin_tenant_name', None)
if nova_client is True:
auth = passMod.Password(auth_url=auth_url,
username=username,
password=password,
user_domain_id=domain_id,
project_name=cloud_admin_project,
project_domain_id=domain_id,
)
else:
auth = passMod.Password(auth_url=auth_url,
username=username,
password=password,
user_domain_id=domain_id,
domain_id=domain_id,
)
else:
auth = passMod.Password(auth_url=auth_url,
username=username,
password=password,
project_name=tenant_name,
)
sess = session.Session(auth=auth, verify=False)
if nova_client is True:
return novaclient.Client("2", session=sess,
endpoint_type=CONF.clients.endpoint_type)
return client.Client(session=sess, auth_url=auth_url, insecure=True,
endpoint_type=CONF.clients.endpoint_type)
def _v3_client_init(self):
client = kc_v3.Client(session=self.session,
auth=self.context.auth_plugin)
if hasattr(self.context.auth_plugin, 'get_access'):
# NOTE(jamielennox): get_access returns the current token without
# reauthenticating if it's present and valid.
try:
auth_ref = self.context.auth_plugin.get_access(self.session)
except kc_exception.Unauthorized:
LOG.error(_LE("Keystone client authentication failed"))
raise exception.AuthorizationFailure()
if self.context.trust_id:
# Sanity check
if not auth_ref.trust_scoped:
LOG.error(_LE("trust token re-scoping failed!"))
raise exception.AuthorizationFailure()
# Sanity check that impersonation is effective
if self.context.trustor_user_id != auth_ref.user_id:
LOG.error(_LE("Trust impersonation failed"))
raise exception.AuthorizationFailure()
return client
def _ssl_options(self):
opts = {'cacert': cfg.CONF.keystone_authtoken.cafile,
'insecure': cfg.CONF.keystone_authtoken.insecure,
'cert': cfg.CONF.keystone_authtoken.certfile,
'key': cfg.CONF.keystone_authtoken.keyfile}
return opts
def create_trust_context(self):
"""Create a trust using the trustor identity in the current context.
The trust is created with the trustee as the heat service user.
If the current context already contains a trust_id, we do nothing
and return the current context.
Returns a context containing the new trust_id.
"""
if self.context.trust_id:
return self.context
# We need the service admin user ID (not name), as the trustor user
# can't lookup the ID in keystoneclient unless they're admin
# workaround this by getting the user_id from admin_client
try:
trustee_user_id = self.context.trusts_auth_plugin.get_user_id(
self.session)
except kc_exception.Unauthorized:
LOG.error(_LE("Domain admin client authentication failed"))
raise exception.AuthorizationFailure()
trustor_user_id = self.context.trustor_user_id
trustor_proj_id = self.context.tenant_id
# inherit the roles of the trustor
roles = self.context.roles
try:
trust = self.client.trusts.create(trustor_user=trustor_user_id,
trustee_user=trustee_user_id,
project=trustor_proj_id,
impersonation=True,
role_names=roles)
except kc_exception.NotFound as ex:
LOG.exception(ex)
LOG.debug("Failed to find roles %s for user %s"
% (roles, trustor_user_id))
raise exception.MissingCredentialError(
message="Invalid roles %s" % roles)
trust_context = context.RequestContext.from_dict(
self.context.to_dict())
trust_context.trust_id = trust.id
trust_context.trustor_user_id = trustor_user_id
return trust_context
def delete_trust(self, trust_id):
"""Delete the specified trust."""
try:
self.client.trusts.delete(trust_id)
except kc_exception.NotFound:
pass
def _get_username(self, username):
if(len(username) > 64):
LOG.warning(_LW("Truncating the username %s to the last 64 "
"characters."), username)
# get the last 64 characters of the username
return username[-64:]
def url_for(self, **kwargs):
default_region_name = (self.context.region_name or
cfg.CONF.region_name_for_services)
kwargs.setdefault('region_name', default_region_name)
return self.context.auth_plugin.get_endpoint(self.session, **kwargs)
@property
def auth_token(self):
return self.context.auth_plugin.get_token(self.session)
@property
def auth_ref(self):
return self.context.auth_plugin.get_access(self.session)
class KeystoneClientV3(KeystoneClientBase):
def __init__(self, context):
super(KeystoneClientV3, self).__init__(context)
def user_exist_in_tenant(self, project_id, user_id):
try:
output = None
user = self.client.users.get(user_id)
project = self.client.projects.get(project_id)
output = self.client.role_assignments.list(
user=user, project=project, effective=True)
if output:
return True
else:
user_groups = self.client.groups.list()
for grp in user_groups:
user_grp_check = False
try:
user_grp_check = self.client.users.check_in_group(user, grp)
except Exception as ex:
LOG.exception("User {} is not a member of a group {}. Checking in next group".format(
user_id, grp.id))
pass
if user_grp_check:
LOG.info("User {} is present in a group {}".format(user_id, grp.id))
output = self.client.role_assignments.list(project=project, group=grp)
if output:
return True
return False
except Exception as ex:
LOG.exception(ex)
raise ex
def check_user_role(self, project_id, user_id, context=None):
try:
roles = []
if context:
roles = context.roles
else:
# Fetch role of a user in a project
roles = [role.name for role in self.client.roles.list(
user=user_id, project=project_id)]
# Fetch role of user group in a project
user_groups = self.client.groups.list()
grp_roles = []
user = self.client.users.get(user_id)
for grp in user_groups:
user_grp_check = False
try:
user_grp_check = self.client.users.check_in_group(user, grp)
except Exception as ex:
LOG.exception("User {} is not a member of a group {}. Checking in next group".format(
user_id, grp.id))
pass
if user_grp_check:
LOG.info("User {} is present in a group {}".format(user_id, grp.id))
grp_roles.extend(
self.client.roles.list(project=project_id, group=grp)
)
"""
If the roles is not assigned to the project on user level or group level.
Check if the roles assigned to the project by any user/group through inheritance on project/Domain Level.
"""
if not grp_roles:
grp_roles = self.client.role_assignments.list(user=self.client.users.get(user_id), project=self.client.projects.get(project_id), effective=True)
try:
roles.extend([role.name for role in grp_roles])
except Exception as ex:
LOG.info('Roles has been assigned through inheritance on a project via group:- {0}'.format(ex))
roles.extend([str(self.client.roles.get(role=str(role.role.get('id'))).name) for role in grp_roles])
if not roles:
return False
trustee_role = CONF.trustee_role
for role in roles:
if (trustee_role == role) or (role.lower() == 'admin') or (role.lower() == 'administrator'):
return True
return False
except Exception as ex:
LOG.exception(ex)
raise ex
def get_project_list_for_import(self, context):
try:
res = []
if context.user_id == CONF.get('cloud_admin_user_id'):
res = self.client.projects.list()
else:
project_list = []
user = self.client.users.get(context.user_id)
# check if user is domain admin and then fetch domain projects
user_domain = self.client.domains.get(user.domain_id)
user_role = self.client.roles.list(user=user, domain=user_domain)
role_list = [role.name for role in user_role]
is_domain_admin = False
for role in role_list:
if (role.lower() == 'admin') or (role.lower() == 'administrator'):
is_domain_admin = True
break
if is_domain_admin:
all_projects = self.client.projects.list()
res = [prj for prj in all_projects if prj.domain_id == user.domain_id]
else:
# Or check if user is belongs to a group and then fetch projects
project_list = self.client.role_assignments.list(user=user)
user_groups = self.client.groups.list()
for grp in user_groups:
projects = []
user_grp_check = False
try:
user_grp_check = self.client.users.check_in_group(user, grp)
except Exception as ex:
LOG.exception("User {} is not a member of a group {}. Checking in next group".format(
user.id, grp.id))
pass
if user_grp_check:
LOG.info("User {} is present in a group {}".format(user.id, grp.id))
projects = self.client.role_assignments.list(group=grp)
if projects:
project_list.extend(projects)
if project_list:
for proj in project_list:
if hasattr(proj, 'scope'):
project_id = proj.scope.get('project', {}).get('id')
if project_id:
project = self.client.projects.get(project_id)
res.append(project)
return res
except Exception as ex:
LOG.exception(ex)
raise ex
class KeystoneClientV2(KeystoneClientBase):
def __init__(self, context):
super(KeystoneClientV2, self).__init__(context)
def user_exist_in_tenant(self, project_id, user_id):
try:
users = self.client_instance.users.list(project_id)
user_ids = [user.id for user in users]
if user_id in user_ids:
return True
else:
return False
except Exception as ex:
LOG.exception(ex)
def check_user_role(self, project_id, user_id):
try:
roles = self.client_instance.tenants.role_manager.roles_for_user(
user_id, project_id)
trustee_role = CONF.trustee_role
for role in roles:
if (trustee_role == role.name) or (role.name == 'admin'):
return True
return False
except Exception as ex:
LOG.exception(ex)
def get_project_list_for_import(self, context):
try:
projects = self.client_instance.tenants.list()
return projects
except Exception as ex:
LOG.exception(ex)
class KeystoneClient(object):
"""Keystone Auth Client.
"""
_instance = None
def __new__(class_, *args, **kwargs):
if not isinstance(class_._instance, class_):
class_._instance = object.__new__(class_)
return class_._instance
def __init__(self, context):
keystone_version = CONF.keystone_auth_version
if keystone_version == '3':
self.client = KeystoneClientV3(context)
else:
self.client = KeystoneClientV2(context)
def get_user_to_get_email_address(self, context):
user = self.client.client.users.get(context.user_id)
if not hasattr(user, 'email'):
user.email = None
return user
def get_user_list(self):
users = self.client.client.users.list()
return users
def create_flavor(self, name, ram, vcpus, disk, ephemeral=0, swap=0):
nova = self.client.nova_client
return nova.flavors.create(
name, ram, vcpus, disk, ephemeral=ephemeral, swap=swap)
def list_opts():
yield None, keystone_opts