Support v3 application credentials auth.
Use keystoneauth1 application credential plugin and session to fetch a token and endpoint catalog url. $ swift --os-auth-url http://172.16.1.2:5000/v3 --auth-version 3\ --os-application-credential-id THE_ID \ --os-application-credential-secret THE_SECRET \ --os-auth-type v3applicationcredential auth Change-Id: I9190e5e7e24b6a741970fa0d0ac792deccf73d25 Closes-Bug: 1843901 Closes-Bug: 1856635
This commit is contained in:
parent
c36616292f
commit
02b637cdca
@ -70,6 +70,9 @@ except ImportError:
|
||||
pass
|
||||
try:
|
||||
from keystoneclient.v3 import client as ksclient_v3
|
||||
from keystoneauth1.identity import v3
|
||||
from keystoneauth1 import session
|
||||
from keystoneauth1 import exceptions as ksauthexceptions
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@ -615,6 +618,46 @@ Auth versions 2.0 and 3 require python-keystoneclient, install it or use Auth
|
||||
version 1.0 which requires ST_AUTH, ST_USER, and ST_KEY environment
|
||||
variables to be set or overridden with -A, -U, or -K.''')
|
||||
|
||||
filter_kwargs = {}
|
||||
service_type = os_options.get('service_type') or 'object-store'
|
||||
endpoint_type = os_options.get('endpoint_type') or 'publicURL'
|
||||
if os_options.get('region_name'):
|
||||
filter_kwargs['attr'] = 'region'
|
||||
filter_kwargs['filter_value'] = os_options['region_name']
|
||||
|
||||
if os_options.get('auth_type') == 'v3applicationcredential':
|
||||
try:
|
||||
v3
|
||||
except NameError:
|
||||
raise ClientException('Auth v3applicationcredential requires '
|
||||
'python-keystoneclient>=2.0.0')
|
||||
|
||||
try:
|
||||
auth = v3.ApplicationCredential(
|
||||
auth_url=auth_url,
|
||||
application_credential_secret=os_options.get(
|
||||
'application_credential_secret'),
|
||||
application_credential_id=os_options.get(
|
||||
'application_credential_id'))
|
||||
sses = session.Session(auth=auth)
|
||||
token = sses.get_token()
|
||||
except ksauthexceptions.Unauthorized:
|
||||
msg = 'Unauthorized. Check application credential id and secret.'
|
||||
raise ClientException(msg)
|
||||
except ksauthexceptions.AuthorizationFailure as err:
|
||||
raise ClientException('Authorization Failure. %s' % err)
|
||||
|
||||
try:
|
||||
endpoint = sses.get_endpoint_data(service_type=service_type,
|
||||
endpoint_type=endpoint_type,
|
||||
**filter_kwargs)
|
||||
|
||||
return endpoint.catalog_url, token
|
||||
except ksauthexceptions.EndpointNotFound:
|
||||
raise ClientException(
|
||||
'Endpoint for %s not found - '
|
||||
'have you specified a region?' % service_type)
|
||||
|
||||
try:
|
||||
_ksclient = ksclient.Client(
|
||||
username=user,
|
||||
@ -642,13 +685,8 @@ variables to be set or overridden with -A, -U, or -K.''')
|
||||
raise ClientException(msg)
|
||||
except ksexceptions.AuthorizationFailure as err:
|
||||
raise ClientException('Authorization Failure. %s' % err)
|
||||
service_type = os_options.get('service_type') or 'object-store'
|
||||
endpoint_type = os_options.get('endpoint_type') or 'publicURL'
|
||||
|
||||
try:
|
||||
filter_kwargs = {}
|
||||
if os_options.get('region_name'):
|
||||
filter_kwargs['attr'] = 'region'
|
||||
filter_kwargs['filter_value'] = os_options['region_name']
|
||||
endpoint = _ksclient.service_catalog.url_for(
|
||||
service_type=service_type,
|
||||
endpoint_type=endpoint_type,
|
||||
@ -717,9 +755,12 @@ def get_auth(auth_url, user, key, **kwargs):
|
||||
if kwargs.get('tenant_name'):
|
||||
os_options['tenant_name'] = kwargs['tenant_name']
|
||||
|
||||
if not (os_options.get('tenant_name') or os_options.get('tenant_id') or
|
||||
os_options.get('project_name') or
|
||||
os_options.get('project_id')):
|
||||
if os_options.get('auth_type') == 'v3applicationcredential':
|
||||
pass
|
||||
elif not (os_options.get('tenant_name') or
|
||||
os_options.get('tenant_id') or
|
||||
os_options.get('project_name') or
|
||||
os_options.get('project_id')):
|
||||
if auth_version in AUTH_VERSIONS_V2:
|
||||
raise ClientException('No tenant specified')
|
||||
raise ClientException('No project name or project id specified.')
|
||||
|
@ -110,6 +110,9 @@ def process_options(options):
|
||||
else:
|
||||
options['auth_version'] = '2.0'
|
||||
|
||||
if options.get('os_auth_type', None) == 'v3applicationcredential':
|
||||
options['auth_version'] == '3'
|
||||
|
||||
# Use new-style args if old ones not present
|
||||
if not options['auth'] and options['os_auth_url']:
|
||||
options['auth'] = options['os_auth_url']
|
||||
@ -134,6 +137,11 @@ def process_options(options):
|
||||
'auth_token': options['os_auth_token'],
|
||||
'object_storage_url': options['os_storage_url'],
|
||||
'region_name': options['os_region_name'],
|
||||
'auth_type': options['os_auth_type'],
|
||||
'application_credential_id':
|
||||
options['os_application_credential_id'],
|
||||
'application_credential_secret':
|
||||
options['os_application_credential_secret'],
|
||||
}
|
||||
|
||||
|
||||
@ -162,6 +170,11 @@ def _build_default_global_options():
|
||||
"os_project_domain_id": environ.get('OS_PROJECT_DOMAIN_ID'),
|
||||
"os_auth_url": environ.get('OS_AUTH_URL'),
|
||||
"os_auth_token": environ.get('OS_AUTH_TOKEN'),
|
||||
"os_auth_type": environ.get('OS_AUTH_TYPE'),
|
||||
"os_application_credential_id":
|
||||
environ.get('OS_APPLICATION_CREDENTIAL_ID'),
|
||||
"os_application_credential_secret":
|
||||
environ.get('OS_APPLICATION_CREDENTIAL_SECRET'),
|
||||
"os_storage_url": environ.get('OS_STORAGE_URL'),
|
||||
"os_region_name": environ.get('OS_REGION_NAME'),
|
||||
"os_service_type": environ.get('OS_SERVICE_TYPE'),
|
||||
|
@ -1651,16 +1651,27 @@ def parse_args(parser, args, enforce_requires=True):
|
||||
return options, args
|
||||
|
||||
if enforce_requires:
|
||||
if options['auth_version'] == '3':
|
||||
if options['os_auth_type'] == 'v3applicationcredential':
|
||||
if not (options['os_application_credential_id'] and
|
||||
options['os_application_credential_secret']):
|
||||
exit('Auth version 3 (application credential) requires '
|
||||
'OS_APPLICATION_CREDENTIAL_ID and '
|
||||
'OS_APPLICATION_CREDENTIAL_SECRET to be set or '
|
||||
'overridden with --os-application-credential-id and '
|
||||
'--os-application-credential-secret respectively.')
|
||||
elif options['os_auth_type']:
|
||||
exit('Only "v3applicationcredential" is supported for '
|
||||
'--os-auth-type')
|
||||
elif options['auth_version'] == '3':
|
||||
if not options['auth']:
|
||||
exit('Auth version 3 requires OS_AUTH_URL to be set or ' +
|
||||
exit('Auth version 3 requires OS_AUTH_URL to be set or '
|
||||
'overridden with --os-auth-url')
|
||||
if not (options['user'] or options['os_user_id']):
|
||||
exit('Auth version 3 requires either OS_USERNAME or ' +
|
||||
'OS_USER_ID to be set or overridden with ' +
|
||||
exit('Auth version 3 requires either OS_USERNAME or '
|
||||
'OS_USER_ID to be set or overridden with '
|
||||
'--os-username or --os-user-id respectively.')
|
||||
if not options['key']:
|
||||
exit('Auth version 3 requires OS_PASSWORD to be set or ' +
|
||||
exit('Auth version 3 requires OS_PASSWORD to be set or '
|
||||
'overridden with --os-password')
|
||||
elif not (options['auth'] and options['user'] and options['key']):
|
||||
exit('''
|
||||
@ -1831,6 +1842,29 @@ def add_default_args(parser):
|
||||
'env[OS_AUTH_URL].')
|
||||
os_grp.add_argument('--os_auth_url',
|
||||
help=argparse.SUPPRESS)
|
||||
os_grp.add_argument('--os-auth-type',
|
||||
metavar='<auth-type>',
|
||||
default=environ.get('OS_AUTH_TYPE'),
|
||||
help='OpenStack auth type for v3. Defaults to '
|
||||
'env[OS_AUTH_TYPE].')
|
||||
os_grp.add_argument('--os_auth_type',
|
||||
help=argparse.SUPPRESS)
|
||||
os_grp.add_argument('--os-application-credential-id',
|
||||
metavar='<auth-application-credential-id>',
|
||||
default=environ.get('OS_APPLICATION_CREDENTIAL_ID'),
|
||||
help='OpenStack appplication credential id. '
|
||||
'Defaults to env[OS_APPLICATION_CREDENTIAL_ID].')
|
||||
os_grp.add_argument('--os_application_credential_id',
|
||||
help=argparse.SUPPRESS)
|
||||
os_grp.add_argument('--os-application-credential-secret',
|
||||
metavar='<auth-application-credential-secret>',
|
||||
default=environ.get(
|
||||
'OS_APPLICATION_CREDENTIAL_SECRET'),
|
||||
help='OpenStack appplication credential secret. '
|
||||
'Defaults to '
|
||||
'env[OS_APPLICATION_CREDENTIAL_SECRET].')
|
||||
os_grp.add_argument('--os_application_credential_secret',
|
||||
help=argparse.SUPPRESS)
|
||||
os_grp.add_argument('--os-auth-token',
|
||||
metavar='<auth-token>',
|
||||
default=environ.get('OS_AUTH_TOKEN'),
|
||||
@ -1915,6 +1949,11 @@ def main(arguments=None):
|
||||
[--os-project-domain-name <auth-project-domain-name>]
|
||||
[--os-auth-url <auth-url>]
|
||||
[--os-auth-token <auth-token>]
|
||||
[--os-auth-type <os-auth-type>]
|
||||
[--os-application-credential-id
|
||||
<auth-application-credential-id>]
|
||||
[--os-application-credential-secret
|
||||
<auth-application-credential-secret>]
|
||||
[--os-storage-url <storage-url>]
|
||||
[--os-region-name <region-name>]
|
||||
[--os-service-type <service-type>]
|
||||
@ -1967,6 +2006,11 @@ Examples:
|
||||
--os-user-id abcdef0123456789abcdef0123456789 \\
|
||||
--os-password password list
|
||||
|
||||
%(prog)s --os-auth-url https://api.example.com/v3 --auth-version 3\\
|
||||
--os-application-credential-id d78683c92f0e4f9b9b02a2e208039412 \\
|
||||
--os-application-credential-secret APPLICTION_CREDENTIAL_SECRET \\
|
||||
--os-auth-type v3applicationcredential list
|
||||
|
||||
%(prog)s --os-auth-token 6ee5eb33efad4e45ab46806eac010566 \\
|
||||
--os-storage-url https://10.1.5.2:8080/v1/AUTH_ced809b6a4baea7aeab61a \\
|
||||
list
|
||||
|
@ -2395,6 +2395,8 @@ class TestParsing(TestBase):
|
||||
'object_storage_url', 'project_domain_id',
|
||||
'user_id', 'user_domain_id', 'tenant_id',
|
||||
'service_type', 'project_id', 'auth_token',
|
||||
'auth_type', 'application_credential_id',
|
||||
'application_credential_secret',
|
||||
'project_domain_name']
|
||||
for key in expected_os_opts_keys:
|
||||
self.assertIn(key, actual_os_opts_dict)
|
||||
@ -2686,6 +2688,50 @@ class TestParsing(TestBase):
|
||||
swiftclient.shell.main(args)
|
||||
self.assertIn('Auth version 3 requires OS_AUTH_URL', str(cm.exception))
|
||||
|
||||
def test_command_args_v3applicationcredential(self):
|
||||
result = [None, None]
|
||||
fake_command = self._make_fake_command(result)
|
||||
opts = {"auth_version": "3"}
|
||||
os_opts = {
|
||||
"auth_type": "v3applicationcredential",
|
||||
"application_credential_id": "proejct_id",
|
||||
"application_credential_secret": "secret",
|
||||
"auth_url": "http://example.com:5000/v3"}
|
||||
|
||||
args = _make_args("stat", opts, os_opts)
|
||||
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
||||
swiftclient.shell.main(args)
|
||||
self.assertEqual(['stat'], result[1])
|
||||
with mock.patch('swiftclient.shell.st_stat', fake_command):
|
||||
args = args + ["container_name"]
|
||||
swiftclient.shell.main(args)
|
||||
self.assertEqual(["stat", "container_name"], result[1])
|
||||
|
||||
def test_insufficient_args_v3applicationcredential(self):
|
||||
opts = {"auth_version": "3"}
|
||||
os_opts = {
|
||||
"auth_type": "v3applicationcredential",
|
||||
"application_credential_secret": "secret",
|
||||
"auth_url": "http://example.com:5000/v3"}
|
||||
|
||||
args = _make_args("stat", opts, os_opts)
|
||||
with self.assertRaises(SystemExit) as cm:
|
||||
swiftclient.shell.main(args)
|
||||
self.assertIn('Auth version 3 (application credential) requires',
|
||||
str(cm.exception))
|
||||
|
||||
os_opts = {
|
||||
"auth_type": "v3password",
|
||||
"application_credential_id": "proejct_id",
|
||||
"application_credential_secret": "secret",
|
||||
"auth_url": "http://example.com:5000/v3"}
|
||||
|
||||
args = _make_args("stat", opts, os_opts)
|
||||
with self.assertRaises(SystemExit) as cm:
|
||||
swiftclient.shell.main(args)
|
||||
self.assertIn('Only "v3applicationcredential" is supported for',
|
||||
str(cm.exception))
|
||||
|
||||
def test_password_prompt(self):
|
||||
def do_test(opts, os_opts, auth_version):
|
||||
args = _make_args("stat", opts, os_opts)
|
||||
|
@ -562,6 +562,63 @@ class TestGetAuth(MockHttpTest):
|
||||
self.assertTrue(url.startswith("http"))
|
||||
self.assertTrue(token)
|
||||
|
||||
def test_auth_v3applicationcredential(self):
|
||||
from keystoneauth1 import exceptions as ksauthexceptions
|
||||
|
||||
os_options = {
|
||||
"auth_type": "v3applicationcredential",
|
||||
"application_credential_id": "proejct_id",
|
||||
"application_credential_secret": "secret"}
|
||||
|
||||
class FakeEndpointData(object):
|
||||
catalog_url = 'http://swift.cluster/v1/KEY_project_id'
|
||||
|
||||
class FakeKeystoneuth1v3Session(object):
|
||||
|
||||
def __init__(self, auth):
|
||||
self.auth = auth
|
||||
self.token = 'token'
|
||||
|
||||
def get_token(self):
|
||||
if self.auth.auth_url == 'http://keystone:5000/v3':
|
||||
return self.token
|
||||
elif self.auth.auth_url == 'http://keystone:9000/v3':
|
||||
raise ksauthexceptions.AuthorizationFailure
|
||||
else:
|
||||
raise ksauthexceptions.Unauthorized
|
||||
|
||||
def get_endpoint_data(self, service_type, endpoint_type, **kwargs):
|
||||
return FakeEndpointData()
|
||||
|
||||
mock_sess = FakeKeystoneuth1v3Session
|
||||
with mock.patch('keystoneauth1.session.Session', mock_sess):
|
||||
url, token = c.get_auth('http://keystone:5000', '', '',
|
||||
os_options=os_options,
|
||||
auth_version="3")
|
||||
|
||||
self.assertTrue(url.startswith("http"))
|
||||
self.assertEqual(url, 'http://swift.cluster/v1/KEY_project_id')
|
||||
self.assertEqual(token, 'token')
|
||||
|
||||
with mock.patch('keystoneauth1.session.Session', mock_sess):
|
||||
with self.assertRaises(c.ClientException) as exc_mgr:
|
||||
url, token = c.get_auth('http://keystone:9000', '', '',
|
||||
os_options=os_options,
|
||||
auth_version="3")
|
||||
|
||||
body = 'Unauthorized. Check application credential id and secret.'
|
||||
body = 'Authorization Failure. Cannot authorize API client.'
|
||||
self.assertEqual(exc_mgr.exception.__str__()[-89:], body)
|
||||
|
||||
with mock.patch('keystoneauth1.session.Session', mock_sess):
|
||||
with self.assertRaises(c.ClientException) as exc_mgr:
|
||||
url, token = c.get_auth('http://keystone:5000', '', '',
|
||||
os_options=os_options,
|
||||
auth_version="2")
|
||||
|
||||
body = 'Unauthorized. Check application credential id and secret.'
|
||||
self.assertEqual(exc_mgr.exception.__str__()[-89:], body)
|
||||
|
||||
def test_get_keystone_client_2_0(self):
|
||||
# check the correct auth version is passed to get_auth_keystone
|
||||
os_options = {'tenant_name': 'asdf'}
|
||||
|
Loading…
x
Reference in New Issue
Block a user