diff --git a/requirements.txt b/requirements.txt index 1b195775..f2d8172a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,5 +11,5 @@ jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT oslo.i18n>=2.1.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0 -python-keystoneclient!=2.1.0,>=2.0.0 # Apache-2.0 +keystoneauth1>=2.10.0 # Apache-2.0 osc-lib>=1.0.2 # Apache-2.0 diff --git a/zaqarclient/auth/keystone.py b/zaqarclient/auth/keystone.py index 03f4fa84..f23fb149 100644 --- a/zaqarclient/auth/keystone.py +++ b/zaqarclient/auth/keystone.py @@ -13,14 +13,21 @@ # See the License for the specific language governing permissions and # limitations under the License. -from keystoneclient.v2_0 import client as ksclient +import six.moves.urllib.parse as urlparse + +from keystoneauth1 import discover +from keystoneauth1 import exceptions as ka_exc +from keystoneauth1.identity import v2 as v2_auth +from keystoneauth1.identity import v3 as v3_auth +from keystoneauth1 import session from zaqarclient.auth import base +from zaqarclient import errors # NOTE(flaper87): Some of the code below # was brought to you by the very unique -# work of ceilometerclient. +# work of ceilometerclient and glanceclient. class KeystoneAuth(base.AuthBackend): """Keystone Auth backend @@ -38,24 +45,132 @@ class KeystoneAuth(base.AuthBackend): :type conf: `dict` """ - def _get_ksclient(self, **kwargs): - """Get an endpoint and auth token from Keystone. + def _get_keystone_session(self, **kwargs): + cacert = kwargs.pop('cacert', None) + cert = kwargs.pop('cert', None) + key = kwargs.pop('key', None) + insecure = kwargs.pop('insecure', False) + auth_url = kwargs.pop('auth_url', None) + project_id = kwargs.pop('project_id', None) + project_name = kwargs.pop('project_name', None) + token = kwargs.get('token') - :param kwargs: keyword args containing credentials: - * username: name of user - * password: user's password - * auth_url: endpoint to authenticate against - * insecure: allow insecure SSL (no cert verification) - * project_{name|id}: name or ID of project - * region_name: Name of a region - * cacert:CA certificate + if insecure: + verify = False + else: + verify = cacert or True - """ - return ksclient.Client(**kwargs) + if cert and key: + # passing cert and key together is deprecated in favour of the + # requests lib form of having the cert and key as a tuple + cert = (cert, key) - def _get_endpoint(self, client, **extra): - """Get an endpoint using the provided keystone client.""" - return client.service_catalog.url_for(**extra) + # create the keystone client session + ks_session = session.Session(verify=verify, cert=cert) + v2_auth_url, v3_auth_url = self._discover_auth_versions(ks_session, + auth_url) + + username = kwargs.pop('username', None) + user_id = kwargs.pop('user_id', None) + user_domain_name = kwargs.pop('user_domain_name', None) + user_domain_id = kwargs.pop('user_domain_id', None) + project_domain_name = kwargs.pop('project_domain_name', None) + project_domain_id = kwargs.pop('project_domain_id', None) + auth = None + + use_domain = (user_domain_id or user_domain_name or + project_domain_id or project_domain_name) + use_v3 = v3_auth_url and (use_domain or (not v2_auth_url)) + use_v2 = v2_auth_url and not use_domain + + if use_v3 and token: + auth = v3_auth.Token( + v3_auth_url, + token=token, + project_name=project_name, + project_id=project_id, + project_domain_name=project_domain_name, + project_domain_id=project_domain_id) + elif use_v2 and token: + auth = v2_auth.Token( + v2_auth_url, + token=token, + tenant_id=project_id, + tenant_name=project_name) + elif use_v3: + # The auth_url as v3 specified + # e.g. http://no.where:5000/v3 + # Keystone will return only v3 as viable option + auth = v3_auth.Password( + v3_auth_url, + username=username, + password=kwargs.pop('password', None), + user_id=user_id, + user_domain_name=user_domain_name, + user_domain_id=user_domain_id, + project_name=project_name, + project_id=project_id, + project_domain_name=project_domain_name, + project_domain_id=project_domain_id) + elif use_v2: + # The auth_url as v2 specified + # e.g. http://no.where:5000/v2.0 + # Keystone will return only v2 as viable option + auth = v2_auth.Password( + v2_auth_url, + username, + kwargs.pop('password', None), + tenant_id=project_id, + tenant_name=project_name) + + else: + raise errors.ZaqarError('Unable to determine the Keystone version ' + 'to authenticate with using the given ' + 'auth_url.') + + ks_session.auth = auth + return ks_session + + def _discover_auth_versions(self, session, auth_url): + # Discover the API versions the server is supporting based on the + # given URL + v2_auth_url = None + v3_auth_url = None + try: + ks_discover = discover.Discover(session=session, url=auth_url) + v2_auth_url = ks_discover.url_for('2.0') + v3_auth_url = ks_discover.url_for('3.0') + except ka_exc.DiscoveryFailure: + raise + except ka_exc.ClientException: + # Identity service may not support discovery. In that case, + # try to determine version from auth_url + url_parts = urlparse.urlparse(auth_url) + (scheme, netloc, path, params, query, fragment) = url_parts + path = path.lower() + if path.startswith('/v3'): + v3_auth_url = auth_url + elif path.startswith('/v2'): + v2_auth_url = auth_url + else: + raise errors.ZaqarError('Unable to determine the Keystone ' + 'version to authenticate with ' + 'using the given auth_url.') + return v2_auth_url, v3_auth_url + + def _get_endpoint(self, ks_session, **kwargs): + """Get an endpoint using the provided keystone session.""" + + # Set service specific endpoint types + endpoint_type = kwargs.get('endpoint_type') or 'publicURL' + service_type = kwargs.get('service_type') or 'messaging' + region_name = kwargs.get('region_name') + + endpoint = ks_session.get_endpoint(service_type=service_type, + interface=endpoint_type, + region_name=region_name) + + return endpoint def authenticate(self, api_version, request): """Get an authtenticated client using credentials in the keyword args. @@ -70,30 +185,20 @@ class KeystoneAuth(base.AuthBackend): token = get_options('auth_token') if not token or not request.endpoint: - # NOTE(flaper87): Lets assume all the - # required information was provided - # either through env variables or CLI - # params. Let keystoneclient fail otherwise. - ks_kwargs = {} keys = ("username", "password", "project_id", "project_name", "auth_url", "insecure", - "cacert", "region_name") + "cacert", "region_name", "user_domain_name", + "user_domain_id", "project_domain_name", + "project_domain_id") for k in keys: ks_kwargs.update({k: get_options(k)}) - _ksclient = self._get_ksclient(**ks_kwargs) + ks_session = self._get_keystone_session(**ks_kwargs) if not token: - token = _ksclient.auth_token - + token = ks_session.get_token() if not request.endpoint: - extra = { - 'service_type': self.conf.get('os_service_type', - 'messaging'), - 'endpoint_type': self.conf.get('os_endpoint_type', - 'publicURL'), - } - request.endpoint = self._get_endpoint(_ksclient, **extra) + request.endpoint = self._get_endpoint(ks_session, **ks_kwargs) # NOTE(flaper87): Update the request spec # with the final token.