Skip to content

Commit

Permalink
Implement Keystone V3 authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarolopez committed Jun 11, 2018
1 parent ddac178 commit 43e2281
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 209 deletions.
198 changes: 32 additions & 166 deletions atrope/dispatcher/glance.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,11 @@
# under the License.

import json
from six.moves.urllib.parse import urlparse

import glanceclient
import glanceclient.client
from glanceclient import exc as glance_exc
from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient.auth.identity import v3 as v3_auth
from keystoneclient import discover
from keystoneclient.openstack.common.apiclient import exceptions as ks_exc
from keystoneclient import session
from keystoneclient.v3 import client
from keystoneauth1 import loading
from keystoneclient.v3 import client as ks_client_v3
from oslo_config import cfg
from oslo_log import log

Expand All @@ -34,42 +29,20 @@
default='etc/voms.json',
help='File containing the VO <-> tenant mapping for image '
'lists private to VOs'),
cfg.StrOpt('username',
default=None,
help='Glance user name that will upload the images.'),
cfg.StrOpt('user_id',
default=None,
help='Glance user UUID that will upload the images.'),
cfg.StrOpt('password',
default=None,
help='Password for the glance user.'),
cfg.StrOpt('tenant_name',
default=None,
help='Tenant name for the user.'),
cfg.StrOpt('tenant_id',
default=None,
help='Tenant UUID for the user.'),
cfg.StrOpt('auth_url',
default=None,
help='URL of the identity service to authenticate with.'),
cfg.StrOpt('endpoint',
default=None,
help='URL of the image service to upload images to. '
'If this option is not specified, the image service will'
'be obtained from the identity service.'),
cfg.BoolOpt('insecure',
default=False,
help='Explicitly allow us to perform '
'\"insecure SSL\" (https) requests. The server\'s '
'certificate will not be verified against any '
'certificate authorities. This option should '
'be used with caution.'),
]

CFG_GROUP="glance"
CONF = cfg.CONF
CONF.import_opt("prefix", "atrope.dispatcher.base", group="dispatchers")
CONF.register_opts(opts, group="glance")

loading.register_auth_conf_options(CONF, CFG_GROUP)
loading.register_session_conf_options(CONF, CFG_GROUP)

opts += (loading.get_auth_common_conf_options() +
loading.get_session_conf_options() +
loading.get_auth_plugin_conf_options('password'))

LOG = log.getLogger(__name__)


Expand Down Expand Up @@ -97,23 +70,9 @@ class Dispatcher(base.BaseDispatcher):
"""
def __init__(self):
if not (CONF.glance.username or CONF.glance.user_id):
raise exception.GlanceMissingConfiguration(flags=["username",
"user_id"])
elif not (CONF.glance.tenant_name or CONF.glance.tenant_id):
raise exception.GlanceMissingConfiguration(flags=["tenant_name",
"tenant_id"])
elif not (CONF.glance.endpoint or CONF.glance.auth_url):
raise exception.GlanceMissingConfiguration(flags=["endpoint",
"auth_url"])

self.ks_session = None
self.ks_client = None
self.token, self.endpoint = self._get_token_and_endpoint()

self.client = glanceclient.Client('2', endpoint=self.endpoint,
token=self.token,
insecure=CONF.glance.insecure)
self.client = self._get_glance_client()
self.ks_client = self._get_ks_client()

try:
self.json_mapping = json.loads(
open(CONF.glance.mapping_file).read())
Expand All @@ -131,117 +90,23 @@ def __init__(self):
"the container and the image format are. I cannot "
"promise anything.")

def _discover_auth_versions(self, session, auth_url):
# discover the API versions the server is supporting base on the
# given URL
v2_auth_url = None
v3_auth_url = None
try:
ks_discover = discover.Discover(session=session, auth_url=auth_url)
v2_auth_url = ks_discover.url_for('2.0')
v3_auth_url = ks_discover.url_for('3.0')
except ks_exc.ClientException as e:
# Identity service may not support discover API version.
# Lets trying to figure out the API version from the original 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:
# not enough information to determine the auth version
msg = ('Unable to determine the Keystone version '
'to authenticate with using the given '
'auth_url. Identity service may not support API '
'version discovery. Please provide a versioned '
'auth_url instead. error=%s') % (e)
raise exception.GlanceError(msg)

return (v2_auth_url, v3_auth_url)

def _get_ks_session(self, **kwargs):
ks_session = session.Session.construct(kwargs)

# discover the supported keystone versions using the given auth url
auth_url = kwargs.pop('auth_url', None)
(v2_auth_url, v3_auth_url) = self._discover_auth_versions(
session=ks_session,
auth_url=auth_url)

# Determine which authentication plugin to use. First inspect the
# auth_url to see the supported version. If both v3 and v2 are
# supported, then use the highest version if possible.
user_id = kwargs.pop('user_id', None)
username = kwargs.pop('username', None)
password = kwargs.pop('password', None)
user_domain_name = kwargs.pop('user_domain_name', None)
user_domain_id = kwargs.pop('user_domain_id', None)
# project and tenant can be used interchangeably
project_id = (kwargs.pop('project_id', None) or
kwargs.pop('tenant_id', None))
project_name = (kwargs.pop('project_name', None) or
kwargs.pop('tenant_name', None))
project_domain_id = kwargs.pop('project_domain_id', None)
project_domain_name = kwargs.pop('project_domain_name', 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:
auth = v3_auth.Password(
v3_auth_url,
user_id=user_id,
username=username,
password=password,
user_domain_id=user_domain_id,
user_domain_name=user_domain_name,
project_id=project_id,
project_name=project_name,
project_domain_id=project_domain_id,
project_domain_name=project_domain_name)
elif use_v2:
auth = v2_auth.Password(
v2_auth_url,
username,
password,
tenant_id=project_id,
tenant_name=project_name)
def _get_ks_client(self):
auth_plugin = loading.load_auth_from_conf_options(CONF, CFG_GROUP)
sess = loading.load_session_from_conf_options(CONF, CFG_GROUP,
auth=auth_plugin)
return ks_client_v3.Client(session=sess)

def _get_glance_client(self, tenant=None):
if tenant:
auth_plugin = loading.load_auth_from_conf_options(
CONF, CFG_GROUP, project_name=tenant)
else:
# if we get here it means domain information is provided
# (caller meant to use Keystone V3) but the auth url is
# actually Keystone V2. Obviously we can't authenticate a V3
# user using V2.
exception.GlanceError("Credential and auth_url mismatch. The given"
" auth_url is using Keystone V2 endpoint, "
" which may not able to handle Keystone V3 "
"credentials. Please provide a correct "
"Keystone V3 auth_url.")

ks_session.auth = auth
return ks_session

def _get_token_and_endpoint(self):
kwargs = {
"username": CONF.glance.username,
"user_id": CONF.glance.user_id,
"password": CONF.glance.password,
"tenant_name": CONF.glance.tenant_name,
"tenant_id": CONF.glance.tenant_id,
"auth_url": CONF.glance.auth_url,
"insecure": CONF.glance.insecure,
}
if self.ks_session is None:
self.ks_session = self._get_ks_session(**kwargs)
endpoint = CONF.glance.endpoint or self.ks_session.get_endpoint(
service_type='image',
endpoint_type='public')
self.ks_client = client.Client(session=self.ks_session)
return self.ks_session.get_token(), endpoint
auth_plugin = loading.load_auth_from_conf_options(CONF, CFG_GROUP)

session = loading.load_session_from_conf_options(CONF, CFG_GROUP,
auth=auth_plugin)
return glanceclient.client.Client(2, session=session)


def _get_vo_tenant_mapping(self, vo):
tenant = self.json_mapping.get(vo, {}).get("tenant", None)
Expand Down Expand Up @@ -323,7 +188,8 @@ def dispatch(self, image_name, image, is_public, **kwargs):
if tenant is not None:
try:
self.client.image_members.create(glance_image.id, tenant)
self.client.image_members.update(glance_image.id, tenant, 'accepted')
client = self._get_glance_client(tenant=tenant)
client.image_members.update(glance_image.id, tenant, 'accepted')
except glance_exc.HTTPConflict:
pass
LOG.info("Image '%s' associated with VO '%s', tenant '%s'",
Expand Down
Loading

0 comments on commit 43e2281

Please sign in to comment.