Add plugin to support token-endpoint auth
The ksc auth plugins do not have support for the original token-endpoint (aka token flow) auth where the user supplies a token (possibly the Keystone admin_token) and an API endpoint. This is used for bootstrapping Keystone but also has other uses when a scoped user token is provided. The api.auth:TokenEndpoint class is required to provide the same interface methods so all of the special-case code branches to support token-endpoint can be removed. Some additional cleanups related to ClientManager and creating the Compute client also were done to streamline using sessions. Change-Id: I1a6059afa845a591eff92567ca346c09010a93af
This commit is contained in:
parent
0c77a9fe8b
commit
c3c6edbe8a
@ -18,6 +18,8 @@ import logging
|
|||||||
|
|
||||||
import stevedore
|
import stevedore
|
||||||
|
|
||||||
|
from oslo.config import cfg
|
||||||
|
|
||||||
from keystoneclient.auth import base
|
from keystoneclient.auth import base
|
||||||
|
|
||||||
from openstackclient.common import exceptions as exc
|
from openstackclient.common import exceptions as exc
|
||||||
@ -53,14 +55,14 @@ for plugin in PLUGIN_LIST:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _guess_authentication_method(options):
|
def select_auth_plugin(options):
|
||||||
"""If no auth plugin was specified, pick one based on other options"""
|
"""If no auth plugin was specified, pick one based on other options"""
|
||||||
|
|
||||||
if options.os_url:
|
|
||||||
# service token authentication, do nothing
|
|
||||||
return
|
|
||||||
auth_plugin = None
|
auth_plugin = None
|
||||||
if options.os_password:
|
if options.os_url and options.os_token:
|
||||||
|
# service token authentication
|
||||||
|
auth_plugin = 'token_endpoint'
|
||||||
|
elif options.os_password:
|
||||||
if options.os_identity_api_version == '3':
|
if options.os_identity_api_version == '3':
|
||||||
auth_plugin = 'v3password'
|
auth_plugin = 'v3password'
|
||||||
elif options.os_identity_api_version == '2.0':
|
elif options.os_identity_api_version == '2.0':
|
||||||
@ -83,14 +85,13 @@ def _guess_authentication_method(options):
|
|||||||
)
|
)
|
||||||
LOG.debug("No auth plugin selected, picking %s from other "
|
LOG.debug("No auth plugin selected, picking %s from other "
|
||||||
"options" % auth_plugin)
|
"options" % auth_plugin)
|
||||||
options.os_auth_plugin = auth_plugin
|
return auth_plugin
|
||||||
|
|
||||||
|
|
||||||
def build_auth_params(cmd_options):
|
def build_auth_params(cmd_options):
|
||||||
auth_params = {}
|
auth_params = {}
|
||||||
if cmd_options.os_url:
|
|
||||||
return {'token': cmd_options.os_token}
|
|
||||||
if cmd_options.os_auth_plugin:
|
if cmd_options.os_auth_plugin:
|
||||||
|
LOG.debug('auth_plugin: %s', cmd_options.os_auth_plugin)
|
||||||
auth_plugin = base.get_plugin_class(cmd_options.os_auth_plugin)
|
auth_plugin = base.get_plugin_class(cmd_options.os_auth_plugin)
|
||||||
plugin_options = auth_plugin.get_options()
|
plugin_options = auth_plugin.get_options()
|
||||||
for option in plugin_options:
|
for option in plugin_options:
|
||||||
@ -110,6 +111,7 @@ def build_auth_params(cmd_options):
|
|||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
LOG.debug('no auth_plugin')
|
||||||
# delay the plugin choice, grab every option
|
# delay the plugin choice, grab every option
|
||||||
plugin_options = set([o.replace('-', '_') for o in OPTIONS_LIST])
|
plugin_options = set([o.replace('-', '_') for o in OPTIONS_LIST])
|
||||||
for option in plugin_options:
|
for option in plugin_options:
|
||||||
@ -178,3 +180,54 @@ def build_auth_plugins_option_parser(parser):
|
|||||||
help=argparse.SUPPRESS,
|
help=argparse.SUPPRESS,
|
||||||
)
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
class TokenEndpoint(base.BaseAuthPlugin):
|
||||||
|
"""Auth plugin to handle traditional token/endpoint usage
|
||||||
|
|
||||||
|
Implements the methods required to handle token authentication
|
||||||
|
with a user-specified token and service endpoint; no Identity calls
|
||||||
|
are made for re-scoping, service catalog lookups or the like.
|
||||||
|
|
||||||
|
The purpose of this plugin is to get rid of the special-case paths
|
||||||
|
in the code to handle this authentication format. Its primary use
|
||||||
|
is for bootstrapping the Keystone database.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, url, token, **kwargs):
|
||||||
|
"""A plugin for static authentication with an existing token
|
||||||
|
|
||||||
|
:param string url: Service endpoint
|
||||||
|
:param string token: Existing token
|
||||||
|
"""
|
||||||
|
super(TokenEndpoint, self).__init__()
|
||||||
|
self.endpoint = url
|
||||||
|
self.token = token
|
||||||
|
|
||||||
|
def get_endpoint(self, session, **kwargs):
|
||||||
|
"""Return the supplied endpoint"""
|
||||||
|
return self.endpoint
|
||||||
|
|
||||||
|
def get_token(self, session):
|
||||||
|
"""Return the supplied token"""
|
||||||
|
return self.token
|
||||||
|
|
||||||
|
def get_auth_ref(self, session, **kwargs):
|
||||||
|
"""Stub this method for compatibility"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Override this because it needs to be a class method...
|
||||||
|
@classmethod
|
||||||
|
def get_options(self):
|
||||||
|
options = super(TokenEndpoint, self).get_options()
|
||||||
|
|
||||||
|
options.extend([
|
||||||
|
# Maintain name 'url' for compatibility
|
||||||
|
cfg.StrOpt('url',
|
||||||
|
help='Specific service endpoint to use'),
|
||||||
|
cfg.StrOpt('token',
|
||||||
|
secret=True,
|
||||||
|
help='Authentication token to use'),
|
||||||
|
])
|
||||||
|
|
||||||
|
return options
|
||||||
|
@ -54,9 +54,10 @@ class ClientManager(object):
|
|||||||
return self._auth_params[name[1:]]
|
return self._auth_params[name[1:]]
|
||||||
|
|
||||||
def __init__(self, auth_options, api_version=None, verify=True):
|
def __init__(self, auth_options, api_version=None, verify=True):
|
||||||
|
# If no plugin is named by the user, select one based on
|
||||||
|
# the supplied options
|
||||||
if not auth_options.os_auth_plugin:
|
if not auth_options.os_auth_plugin:
|
||||||
auth._guess_authentication_method(auth_options)
|
auth_options.os_auth_plugin = auth.select_auth_plugin(auth_options)
|
||||||
|
|
||||||
self._auth_plugin = auth_options.os_auth_plugin
|
self._auth_plugin = auth_options.os_auth_plugin
|
||||||
self._url = auth_options.os_url
|
self._url = auth_options.os_url
|
||||||
@ -66,7 +67,7 @@ class ClientManager(object):
|
|||||||
self._service_catalog = None
|
self._service_catalog = None
|
||||||
self.timing = auth_options.timing
|
self.timing = auth_options.timing
|
||||||
|
|
||||||
# For compatability until all clients can be updated
|
# For compatibility until all clients can be updated
|
||||||
if 'project_name' in self._auth_params:
|
if 'project_name' in self._auth_params:
|
||||||
self._project_name = self._auth_params['project_name']
|
self._project_name = self._auth_params['project_name']
|
||||||
elif 'tenant_name' in self._auth_params:
|
elif 'tenant_name' in self._auth_params:
|
||||||
@ -86,8 +87,6 @@ class ClientManager(object):
|
|||||||
root_logger = logging.getLogger('')
|
root_logger = logging.getLogger('')
|
||||||
LOG.setLevel(root_logger.getEffectiveLevel())
|
LOG.setLevel(root_logger.getEffectiveLevel())
|
||||||
|
|
||||||
self.session = None
|
|
||||||
if not self._url:
|
|
||||||
LOG.debug('Using auth plugin: %s' % self._auth_plugin)
|
LOG.debug('Using auth plugin: %s' % self._auth_plugin)
|
||||||
auth_plugin = base.get_plugin_class(self._auth_plugin)
|
auth_plugin = base.get_plugin_class(self._auth_plugin)
|
||||||
self.auth = auth_plugin.load_from_options(**self._auth_params)
|
self.auth = auth_plugin.load_from_options(**self._auth_params)
|
||||||
@ -100,13 +99,13 @@ class ClientManager(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.auth_ref = None
|
self.auth_ref = None
|
||||||
if not self._auth_plugin.endswith("token") and not self._url:
|
if 'token' not in self._auth_params:
|
||||||
LOG.debug("Populate other password flow attributes")
|
LOG.debug("Get service catalog")
|
||||||
self.auth_ref = self.session.auth.get_auth_ref(self.session)
|
self.auth_ref = self.auth.get_auth_ref(self.session)
|
||||||
self._token = self.session.auth.get_token(self.session)
|
|
||||||
self._service_catalog = self.auth_ref.service_catalog
|
self._service_catalog = self.auth_ref.service_catalog
|
||||||
else:
|
|
||||||
self._token = self._auth_params.get('token')
|
# This begone when clients no longer need it...
|
||||||
|
self._token = self.auth.get_token(self.session)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -44,33 +44,20 @@ def make_client(instance):
|
|||||||
|
|
||||||
extensions = [extension.Extension('list_extensions', list_extensions)]
|
extensions = [extension.Extension('list_extensions', list_extensions)]
|
||||||
client = compute_client(
|
client = compute_client(
|
||||||
username=instance._username,
|
session=instance.session,
|
||||||
api_key=instance._password,
|
|
||||||
project_id=instance._project_name,
|
|
||||||
auth_url=instance._auth_url,
|
|
||||||
cacert=instance._cacert,
|
|
||||||
insecure=instance._insecure,
|
|
||||||
region_name=instance._region_name,
|
|
||||||
# FIXME(dhellmann): get endpoint_type from option?
|
|
||||||
endpoint_type='publicURL',
|
|
||||||
extensions=extensions,
|
extensions=extensions,
|
||||||
service_type=API_NAME,
|
|
||||||
# FIXME(dhellmann): what is service_name?
|
|
||||||
service_name='',
|
|
||||||
http_log_debug=http_log_debug,
|
http_log_debug=http_log_debug,
|
||||||
timings=instance.timing,
|
timings=instance.timing,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Populate the Nova client to skip another auth query to Identity
|
# Populate the Nova client to skip another auth query to Identity
|
||||||
if instance._url:
|
if 'token' not in instance._auth_params:
|
||||||
# token flow
|
|
||||||
client.client.management_url = instance._url
|
|
||||||
else:
|
|
||||||
# password flow
|
# password flow
|
||||||
client.client.management_url = instance.get_endpoint_for_service_type(
|
client.client.management_url = instance.get_endpoint_for_service_type(
|
||||||
API_NAME)
|
API_NAME)
|
||||||
client.client.service_catalog = instance._service_catalog
|
client.client.service_catalog = instance._service_catalog
|
||||||
client.client.auth_token = instance._token
|
client.client.auth_token = instance.auth.get_token(instance.session)
|
||||||
|
|
||||||
return client
|
return client
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,6 +76,31 @@ class TestClientManager(utils.TestCase):
|
|||||||
url=fakes.AUTH_URL,
|
url=fakes.AUTH_URL,
|
||||||
verb='GET')
|
verb='GET')
|
||||||
|
|
||||||
|
def test_client_manager_token_endpoint(self):
|
||||||
|
|
||||||
|
client_manager = clientmanager.ClientManager(
|
||||||
|
auth_options=FakeOptions(os_token=fakes.AUTH_TOKEN,
|
||||||
|
os_url=fakes.AUTH_URL,
|
||||||
|
os_auth_plugin='token_endpoint'),
|
||||||
|
api_version=API_VERSION,
|
||||||
|
verify=True
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
fakes.AUTH_URL,
|
||||||
|
client_manager._url,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
fakes.AUTH_TOKEN,
|
||||||
|
client_manager._token,
|
||||||
|
)
|
||||||
|
self.assertIsInstance(
|
||||||
|
client_manager.auth,
|
||||||
|
auth.TokenEndpoint,
|
||||||
|
)
|
||||||
|
self.assertFalse(client_manager._insecure)
|
||||||
|
self.assertTrue(client_manager._verify)
|
||||||
|
|
||||||
def test_client_manager_token(self):
|
def test_client_manager_token(self):
|
||||||
|
|
||||||
client_manager = clientmanager.ClientManager(
|
client_manager = clientmanager.ClientManager(
|
||||||
@ -176,8 +201,7 @@ class TestClientManager(utils.TestCase):
|
|||||||
self.assertTrue(client_manager._verify)
|
self.assertTrue(client_manager._verify)
|
||||||
self.assertEqual('cafile', client_manager._cacert)
|
self.assertEqual('cafile', client_manager._cacert)
|
||||||
|
|
||||||
def _client_manager_guess_auth_plugin(self, auth_params,
|
def _select_auth_plugin(self, auth_params, api_version, auth_plugin):
|
||||||
api_version, auth_plugin):
|
|
||||||
auth_params['os_auth_plugin'] = auth_plugin
|
auth_params['os_auth_plugin'] = auth_plugin
|
||||||
auth_params['os_identity_api_version'] = api_version
|
auth_params['os_identity_api_version'] = api_version
|
||||||
client_manager = clientmanager.ClientManager(
|
client_manager = clientmanager.ClientManager(
|
||||||
@ -190,25 +214,25 @@ class TestClientManager(utils.TestCase):
|
|||||||
client_manager._auth_plugin,
|
client_manager._auth_plugin,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_client_manager_guess_auth_plugin(self):
|
def test_client_manager_select_auth_plugin(self):
|
||||||
# test token auth
|
# test token auth
|
||||||
params = dict(os_token=fakes.AUTH_TOKEN,
|
params = dict(os_token=fakes.AUTH_TOKEN,
|
||||||
os_auth_url=fakes.AUTH_URL)
|
os_auth_url=fakes.AUTH_URL)
|
||||||
self._client_manager_guess_auth_plugin(params, '2.0', 'v2token')
|
self._select_auth_plugin(params, '2.0', 'v2token')
|
||||||
self._client_manager_guess_auth_plugin(params, '3', 'v3token')
|
self._select_auth_plugin(params, '3', 'v3token')
|
||||||
self._client_manager_guess_auth_plugin(params, 'XXX', 'token')
|
self._select_auth_plugin(params, 'XXX', 'token')
|
||||||
# test service auth
|
# test token/endpoint auth
|
||||||
params = dict(os_token=fakes.AUTH_TOKEN, os_url='test')
|
params = dict(os_token=fakes.AUTH_TOKEN, os_url='test')
|
||||||
self._client_manager_guess_auth_plugin(params, 'XXX', '')
|
self._select_auth_plugin(params, 'XXX', 'token_endpoint')
|
||||||
# test password auth
|
# test password auth
|
||||||
params = dict(os_auth_url=fakes.AUTH_URL,
|
params = dict(os_auth_url=fakes.AUTH_URL,
|
||||||
os_username=fakes.USERNAME,
|
os_username=fakes.USERNAME,
|
||||||
os_password=fakes.PASSWORD)
|
os_password=fakes.PASSWORD)
|
||||||
self._client_manager_guess_auth_plugin(params, '2.0', 'v2password')
|
self._select_auth_plugin(params, '2.0', 'v2password')
|
||||||
self._client_manager_guess_auth_plugin(params, '3', 'v3password')
|
self._select_auth_plugin(params, '3', 'v3password')
|
||||||
self._client_manager_guess_auth_plugin(params, 'XXX', 'password')
|
self._select_auth_plugin(params, 'XXX', 'password')
|
||||||
|
|
||||||
def test_client_manager_guess_auth_plugin_failure(self):
|
def test_client_manager_select_auth_plugin_failure(self):
|
||||||
self.assertRaises(exc.CommandError,
|
self.assertRaises(exc.CommandError,
|
||||||
clientmanager.ClientManager,
|
clientmanager.ClientManager,
|
||||||
auth_options=FakeOptions(os_auth_plugin=''),
|
auth_options=FakeOptions(os_auth_plugin=''),
|
||||||
|
@ -27,6 +27,9 @@ packages =
|
|||||||
console_scripts =
|
console_scripts =
|
||||||
openstack = openstackclient.shell:main
|
openstack = openstackclient.shell:main
|
||||||
|
|
||||||
|
keystoneclient.auth.plugin =
|
||||||
|
token_endpoint = openstackclient.api.auth:TokenEndpoint
|
||||||
|
|
||||||
openstack.cli =
|
openstack.cli =
|
||||||
module_list = openstackclient.common.module:ListModule
|
module_list = openstackclient.common.module:ListModule
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user