[CLI] Fix token auth type

There are 2 known issues which breaks token auth method in CLI:

* The wrong check of flag (the check should be for --os-token since
  arguments are not parsed at that moment) is performed in CLI inner method
  `_append_global_identity_args`. It led to usage of "password" auth
  type by default[1] even if `--os-token` cli argument is specified.
  If `--os-auth-type` is specified to token, keystoneauth1 library makes
  the right decision[2].

* Based on an auth type, keystoneauth library registers different CLI
  arguments[3]. It means that `--os-username` argument is available only
  in password auth type, `--os-token` is available only in token auth
  type, etc.
  It also affects the way in which the python code should access such
  arguments. The arguments which are unrelated to the selected auth type
  are omitted from the parsed arguments object.
  That sounds reasonable, but unfortunately the code assumes the
  unrelated arguments are always present which leads to an
  AttributeError.

Combination of these 2 issues made token auth type broken in CLI layer.

[1] ee2221f052/novaclient/shell.py (L255-L257)
[2] 14dd37b34c/keystoneauth1/loading/cli.py (L51-L52)
[3] 14dd37b34c/keystoneauth1/loading/cli.py (L65-L73)

Closes-Bug: #1659015
Change-Id: Ibc861d396b71fe105288d8336623cc22cf92523e
This commit is contained in:
Andrey Kurilin 2017-01-23 19:22:14 +02:00 committed by Matt Riedemann
parent 8cabab774c
commit 6049be67c0
3 changed files with 31 additions and 35 deletions

View File

@ -249,11 +249,11 @@ class OpenStackComputeShell(object):
def _append_global_identity_args(self, parser, argv):
# Register the CLI arguments that have moved to the session object.
loading.register_session_argparse_arguments(parser)
# Peek into argv to see if os-auth-token or os-token were given,
# Peek into argv to see if os-token was given,
# in which case, the token auth plugin is what the user wants
# else, we'll default to password
default_auth_plugin = 'password'
if 'os-token' in argv:
if "--os-token" in argv:
default_auth_plugin = 'token'
loading.register_auth_argparse_arguments(
parser, argv, default=default_auth_plugin)
@ -513,8 +513,10 @@ class OpenStackComputeShell(object):
api_version = api_versions.get_api_version(
args.os_compute_api_version)
os_username = args.os_username
os_user_id = args.os_user_id
auth_token = getattr(args, "os_token", None)
os_username = getattr(args, "os_username", None)
os_user_id = getattr(args, "os_user_id", None)
os_password = None # Fetched and set later as needed
os_project_name = getattr(
args, 'os_project_name', getattr(args, 'os_tenant_name', None))
@ -529,13 +531,16 @@ class OpenStackComputeShell(object):
if (not args.os_project_domain_id and
not args.os_project_domain_name):
setattr(args, "os_project_domain_id", "default")
if not args.os_user_domain_id and not args.os_user_domain_name:
# os_user_domain_id is redundant in case of Token auth type
if not auth_token and (not args.os_user_domain_id and
not args.os_user_domain_name):
setattr(args, "os_user_domain_id", "default")
os_project_domain_id = args.os_project_domain_id
os_project_domain_name = args.os_project_domain_name
os_user_domain_id = args.os_project_domain_id
os_user_domain_name = args.os_project_domain_name
os_user_domain_id = getattr(args, "os_user_domain_id", None)
os_user_domain_name = getattr(args, "os_user_domain_name", None)
endpoint_type = args.endpoint_type
insecure = args.insecure
@ -550,13 +555,6 @@ class OpenStackComputeShell(object):
keystone_session = None
keystone_auth = None
# We may have either, both or none of these.
# If we have both, we don't need USERNAME, PASSWORD etc.
# Finally, authenticate unless we have both.
# Note if we don't auth we probably don't have a tenant ID so we can't
# cache the token.
auth_token = getattr(args, 'os_token', None)
if not endpoint_type:
endpoint_type = DEFAULT_NOVA_ENDPOINT_TYPE
@ -579,11 +577,11 @@ class OpenStackComputeShell(object):
# for os_username or os_password but for compatibility it is not.
if must_auth and not skip_auth:
if not os_username and not os_user_id:
if not any([auth_token, os_username, os_user_id]):
raise exc.CommandError(
_("You must provide a username "
"or user ID via --os-username, --os-user-id, "
"env[OS_USERNAME] or env[OS_USER_ID]"))
_("You must provide a user name/id (via --os-username, "
"--os-user-id, env[OS_USERNAME] or env[OS_USER_ID]) or "
"an auth token (via --os-token)."))
if not any([os_project_name, os_project_id]):
raise exc.CommandError(_("You must provide a project name or"

View File

@ -51,17 +51,15 @@ class TestAuthentication(base.ClientTestBase):
project_name=self.project_name, **kw)
nova.servers.list()
# NOTE(andreykurilin): token auth is completely broken in CLI
# flags = ('--os-username %s --os-tenant-name %s --os-auth-token %s '
# '--os-auth-url %s --os-endpoint-type publicURL' % (
# self.cli_clients.username,
# self.cli_clients.tenant_name,
# token, auth_url))
# if self.cli_clients.insecure:
# flags += ' --insecure '
#
# return tempest.lib.cli.base.execute(
# "nova", action, flags, cli_dir=self.cli_clients.cli_dir)
flags = ('--os-tenant-name %(project_name)s --os-token %(token)s '
'--os-auth-url %(auth_url)s --os-endpoint-type publicURL'
% {"project_name": self.project_name,
"token": token, "auth_url": auth_url})
if self.cli_clients.insecure:
flags += ' --insecure '
tempest.lib.cli.base.execute(
"nova", "list", flags, cli_dir=self.cli_clients.cli_dir)
def test_auth_via_keystone_v2(self):
session = self.keystone.session

View File

@ -467,9 +467,9 @@ class ShellTest(utils.TestCase):
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
def test_no_username(self):
required = ('You must provide a username or user ID'
' via --os-username, --os-user-id,'
' env[OS_USERNAME] or env[OS_USER_ID]')
required = ('You must provide a user name/id (via --os-username, '
'--os-user-id, env[OS_USERNAME] or env[OS_USER_ID]) or '
'an auth token (via --os-token).')
self.make_env(exclude='OS_USERNAME')
try:
self.shell('list')
@ -479,9 +479,9 @@ class ShellTest(utils.TestCase):
self.fail('CommandError not raised')
def test_no_user_id(self):
required = ('You must provide a username or user ID'
' via --os-username, --os-user-id,'
' env[OS_USERNAME] or env[OS_USER_ID]')
required = ('You must provide a user name/id (via --os-username, '
'--os-user-id, env[OS_USERNAME] or env[OS_USER_ID]) or '
'an auth token (via --os-token).')
self.make_env(exclude='OS_USER_ID', fake_env=FAKE_ENV2)
try:
self.shell('list')