openstack-virtual-baremetal/openstack_virtual_baremetal/auth.py
Ben Nemec 857b532d21 Cache OS_CLOUD on import
Older versions of os-client-config pop OS_CLOUD from the environment
when make_client is called.  This has been fixed in [1], but to
support older versions as well, let's cache it on import so even if
someone messes with os.environ later we still have the right value.

1: 990cfa3ce2
2017-06-05 13:06:52 -05:00

206 lines
8.0 KiB
Python

# Copyright 2017 Red Hat Inc.
#
# 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.
import os
import sys
from keystoneclient.v2_0 import client as keystone_client
from keystoneclient.v3 import client as keystone_v3_client
# Older versions of os-client-config pop this from the environment when
# make_client is called. Cache it on import so we know what the original
# value was, regardless of any funny business that happens later.
OS_CLOUD = os.environ.get('OS_CLOUD')
def _validate_auth_parameters(username, password, tenant, auth_url,
project, user_domain, project_domain):
"""Validate that the necessary auth parameters are set
Depending on the version of keystone in use, certain parameters are
required for successful keystone authentication. If the combination
passed to this function is not valid, it will print an error message and
exit with a return code of 1 immediately.
"""
if '/v3' not in auth_url:
if not username or not password or not tenant or not auth_url:
print('Source an appropriate rc file first')
sys.exit(1)
else:
if (not username or not password or not auth_url or not project or
not user_domain or not project_domain):
print('Source an appropriate rc file first')
sys.exit(1)
def _create_auth_parameters():
"""Read keystone auth parameters from appropriate source
If the environment variable OS_CLOUD is set, read the auth information
from os_client_config. Otherwise, read it from environment variables.
When reading from the environment, also validate that all of the required
values are set.
:returns: A dict containing the following keys: os_user, os_password,
os_tenant, os_auth_url, os_project, os_user_domain,
os_project_domain.
"""
if OS_CLOUD:
import os_client_config
config = os_client_config.OpenStackConfig().get_one_cloud(OS_CLOUD)
auth = config.config['auth']
username = auth['username']
password = auth['password']
# os_client_config seems to always call this project_name
tenant = auth['project_name']
auth_url = auth['auth_url']
project = auth['project_name']
user_domain = (auth.get('user_domain_name') or
auth.get('user_domain_id', ''))
project_domain = (auth.get('project_domain_name') or
auth.get('project_domain_id', ''))
else:
username = os.environ.get('OS_USERNAME')
password = os.environ.get('OS_PASSWORD')
tenant = os.environ.get('OS_TENANT_NAME', '')
auth_url = os.environ.get('OS_AUTH_URL', '')
project = os.environ.get('OS_PROJECT_NAME', '')
user_domain = (os.environ.get('OS_USER_DOMAIN_ID') or
os.environ.get('OS_USER_DOMAIN_NAME', ''))
project_domain = (os.environ.get('OS_PROJECT_DOMAIN_ID') or
os.environ.get('OS_PROJECT_DOMAIN_NAME', ''))
_validate_auth_parameters(username, password, tenant, auth_url,
project, user_domain, project_domain)
return {'os_user': username,
'os_password': password,
'os_tenant': tenant,
'os_auth_url': auth_url,
'os_project': project,
'os_user_domain': user_domain,
'os_project_domain': project_domain,
}
def _get_keystone_session(auth_data):
"""Get a new keystone session object
:param auth_data: Dict of authentication parameters as returned from
_create_auth_parameters.
:returns: keystoneclient Session
"""
username = auth_data['os_user']
password = auth_data['os_password']
tenant = auth_data['os_tenant']
auth_url = auth_data['os_auth_url']
project = auth_data['os_project']
user_domain = auth_data['os_user_domain']
project_domain = auth_data['os_project_domain']
from keystoneauth1.identity import v3
from keystoneauth1 import session
password_auth = v3.Password(auth_url=auth_url,
username=username,
password=password,
project_name=project,
user_domain_name=user_domain,
project_domain_name=project_domain)
return session.Session(auth=password_auth)
def _get_keystone_client(auth_data):
"""Get an instance of keystoneclient.Client
Abstracts away the version-specific logic of getting a new instance of
the keystone client.
:param auth_data: Dict of authentication parameters as returned from
_create_auth_parameters.
:returns: A new keystoneclient Client instance
"""
username = auth_data['os_user']
password = auth_data['os_password']
tenant = auth_data['os_tenant']
auth_url = auth_data['os_auth_url']
if '/v3' not in auth_url:
return keystone_client.Client(username=username, password=password,
tenant_name=tenant, auth_url=auth_url)
else:
sess = _get_keystone_session(auth_data)
return keystone_v3_client.Client(session=sess)
def _get_keystone_token():
"""Get a raw keystone token
This is a wrapper around the keystoneclient
get_raw_token_from_identity_service that handles both keystone v2 and v3.
:returns: Keystone token data structure
"""
auth_data = _create_auth_parameters()
username = auth_data['os_user']
password = auth_data['os_password']
tenant = auth_data['os_tenant']
auth_url = auth_data['os_auth_url']
project = auth_data['os_project']
user_domain = auth_data['os_user_domain']
project_domain = auth_data['os_project_domain']
kclient = _get_keystone_client(auth_data)
if '/v3' not in auth_url:
return kclient.get_raw_token_from_identity_service(username=username,
password=password,
tenant_name=tenant,
auth_url=auth_url)
else:
return kclient.get_raw_token_from_identity_service(
username=username,
password=password,
project_name=project,
auth_url=auth_url,
user_domain_name=user_domain,
project_domain_name=project_domain)
def _get_token_and_endpoint(name):
"""Return a token id and endpoint url for the specified service
:param name: The name of the service. heat, glance, etc.
:returns: A tuple of (token_id, service_endpoint)
"""
auth_data = _create_auth_parameters()
auth_url = auth_data['os_auth_url']
# Get token for service to use
if '/v3' not in auth_url:
token_data = _get_keystone_token()
token_id = token_data['token']['id']
catalog_key = 'serviceCatalog'
else:
token_data = _get_keystone_token()
token_id = token_data['auth_token']
catalog_key = 'catalog'
# Get service endpoint
for endpoint in token_data[catalog_key]:
if endpoint['name'] == name:
try:
# TODO: What if there's more than one endpoint?
service_endpoint = endpoint['endpoints'][0]['publicURL']
except KeyError:
# Keystone v3 endpoint data looks different
service_endpoint = [e for e in endpoint['endpoints']
if e['interface'] == 'public'][0]['url']
return token_id, service_endpoint