Support V3 authentication with swift

This is the easiest way to support v3 authentication in the swift backend. It
allows us to specify that we want v3 authentication and provide the extra
authentication attributes to swiftclient.

It allows specifying auth_version via individual authentication references so
that you can mix v2 and v3 authentication.

Ideally in future we would move to a session orientated solution. In which case
we will need to handle the existing options in a backwards compatible way and
so I don't think that the extra arguments will cause any additional problems
there.

Change-Id: Ifb7c9be805689e938ff0e99a18fa220993862001
This commit is contained in:
Jamie Lennox 2015-06-19 10:50:18 +08:00
parent cd04b9217f
commit b529f0a782
4 changed files with 121 additions and 13 deletions

View File

@ -48,10 +48,6 @@ DEFAULT_LARGE_OBJECT_CHUNK_SIZE = 200 # 200M
ONE_MB = units.k * units.Ki # Here we used the mixed meaning of MB
_SWIFT_OPTS = [
cfg.StrOpt('swift_store_auth_version', default='2',
help=_('Version of the authentication service to use. '
'Valid versions are 2 for keystone and 1 for swauth '
'and rackspace. (deprecated)')),
cfg.BoolOpt('swift_store_auth_insecure', default=False,
help=_('If True, swiftclient won\'t check for a valid SSL '
'certificate when authenticating.')),
@ -728,8 +724,14 @@ class SingleTenantStore(BaseStore):
self.ref_params = sutils.SwiftParams(self.conf).params
def configure(self, re_raise_bsc=False):
super(SingleTenantStore, self).configure(re_raise_bsc=re_raise_bsc)
# set configuration before super so configure_add can override
self.auth_version = self._option_get('swift_store_auth_version')
self.user_domain_id = None
self.user_domain_name = None
self.project_domain_id = None
self.project_domain_name = None
super(SingleTenantStore, self).configure(re_raise_bsc=re_raise_bsc)
def configure_add(self):
default_ref = self.conf.glance_store.default_swift_reference
@ -746,8 +748,15 @@ class SingleTenantStore(BaseStore):
else:
self.scheme = 'swift+https'
self.container = self.conf.glance_store.swift_store_container
self.auth_version = default_swift_reference.get('auth_version')
self.user = default_swift_reference.get('user')
self.key = default_swift_reference.get('key')
self.user_domain_id = default_swift_reference.get('user_domain_id')
self.user_domain_name = default_swift_reference.get('user_domain_name')
self.project_domain_id = default_swift_reference.get(
'project_domain_id')
self.project_domain_name = default_swift_reference.get(
'project_domain_name')
if not (self.user or self.key):
reason = _("A value for swift_store_ref_params is required.")
@ -811,7 +820,7 @@ class SingleTenantStore(BaseStore):
if not auth_url.endswith('/'):
auth_url += '/'
if self.auth_version == '2':
if self.auth_version in ('2', '3'):
try:
tenant_name, user = location.user.split(':')
except ValueError:
@ -828,6 +837,14 @@ class SingleTenantStore(BaseStore):
os_options['region_name'] = self.region
os_options['endpoint_type'] = self.endpoint_type
os_options['service_type'] = self.service_type
if self.user_domain_id:
os_options['user_domain_id'] = self.user_domain_id
if self.user_domain_name:
os_options['user_domain_name'] = self.user_domain_name
if self.project_domain_id:
os_options['project_domain_id'] = self.project_domain_id
if self.project_domain_name:
os_options['project_domain_name'] = self.project_domain_name
return swiftclient.Connection(
auth_url, user, location.key, preauthurl=self.conf_endpoint,

View File

@ -27,23 +27,38 @@ swift_opts = [
default="ref1",
help=i18n._('The reference to the default swift account/backing'
' store parameters to use for adding new images.')),
cfg.StrOpt('swift_store_auth_version', default='2',
help=i18n._('Version of the authentication service to use. '
'Valid versions are 2 and 3 for keystone and 1 '
'(deprecated) for swauth and rackspace. '
'(deprecated - use "auth_version" in '
'swift_store_config_file)')),
cfg.StrOpt('swift_store_auth_address',
help=i18n._('The address where the Swift authentication '
'service is listening.(deprecated)')),
'service is listening. (deprecated - use '
'"auth_address" in swift_store_config_file)')),
cfg.StrOpt('swift_store_user', secret=True,
help=i18n._('The user to authenticate against the Swift '
'authentication service (deprecated)')),
'authentication service (deprecated - use "user" '
'in swift_store_config_file)')),
cfg.StrOpt('swift_store_key', secret=True,
help=i18n._('Auth key for the user authenticating against the '
'Swift authentication service. (deprecated)')),
'Swift authentication service. (deprecated - use '
'"key" in swift_store_config_file)')),
cfg.StrOpt('swift_store_config_file', secret=True,
help=i18n._('The config file that has the swift account(s)'
'configs.')),
]
_config_defaults = {'user_domain_id': None,
'user_domain_name': None,
'project_domain_id': None,
'project_domain_name': None}
# NOTE(bourke): The default dict_type is collections.OrderedDict in py27, but
# we must set manually for compatibility with py26
CONFIG = configparser.SafeConfigParser(dict_type=OrderedDict)
CONFIG = configparser.SafeConfigParser(defaults=_config_defaults,
dict_type=OrderedDict)
LOG = logging.getLogger(__name__)
@ -74,6 +89,11 @@ class SwiftParams(object):
default['user'] = glance_store.swift_store_user
default['key'] = glance_store.swift_store_key
default['auth_address'] = glance_store.swift_store_auth_address
default['project_domain_id'] = None
default['project_domain_name'] = None
default['user_domain_id'] = None
default['user_domain_name'] = None
default['auth_version'] = glance_store.swift_store_auth_version
return {glance_store.default_swift_reference: default}
return {}
@ -92,12 +112,25 @@ class SwiftParams(object):
reason=msg)
account_params = {}
account_references = CONFIG.sections()
for ref in account_references:
reference = {}
try:
reference['auth_address'] = CONFIG.get(ref, 'auth_address')
reference['user'] = CONFIG.get(ref, 'user')
reference['key'] = CONFIG.get(ref, 'key')
for param in ('auth_address',
'user',
'key',
'project_domain_id',
'project_domain_name',
'user_domain_id',
'user_domain_name'):
reference[param] = CONFIG.get(ref, param)
try:
reference['auth_version'] = CONFIG.get(ref, 'auth_version')
except configparser.NoOptionError:
av = self.conf.glance_store.swift_store_auth_version
reference['auth_version'] = av
account_params[ref] = reference
except (ValueError, SyntaxError, configparser.NoOptionError) as e:
LOG.exception(i18n._("Invalid format of swift store config"

View File

@ -6,6 +6,9 @@ auth_address = example.com
[ref2]
user = user2
key = key2
user_domain_id = default
project_domain_id = default
auth_version = 3
auth_address = http://example.com
[store_2]

View File

@ -1041,6 +1041,15 @@ class TestStoreAuthV2(TestStoreAuthV1):
self.assertEqual('swift', loc.store_name)
class TestStoreAuthV3(TestStoreAuthV1):
def getConfig(self):
conf = super(TestStoreAuthV3, self).getConfig()
conf['swift_store_auth_version'] = '3'
conf['swift_store_user'] = 'tenant:user1'
return conf
class FakeConnection(object):
def __init__(self, authurl, user, key, retries=5, preauthurl=None,
preauthtoken=None, starting_backoff=1, tenant_name=None,
@ -1202,6 +1211,52 @@ class TestSingleTenantStoreConnections(base.StoreBaseTest):
self.location.parse_uri,
self.location.uri)
def test_ref_overrides_defaults(self):
self.config(swift_store_auth_version='2',
swift_store_user='testuser',
swift_store_key='testpass',
swift_store_auth_address='testaddress',
swift_store_endpoint_type='internalURL',
swift_store_config_file='somefile')
self.store.ref_params = {'ref1': {'auth_address': 'authurl.com',
'auth_version': '3',
'user': 'user:pass',
'user_domain_id': 'default',
'user_domain_name': 'ignored',
'project_domain_id': 'default',
'project_domain_name': 'ignored'}}
self.store.configure()
self.assertEqual('user:pass', self.store.user)
self.assertEqual('3', self.store.auth_version)
self.assertEqual('authurl.com', self.store.auth_address)
self.assertEqual('default', self.store.user_domain_id)
self.assertEqual('ignored', self.store.user_domain_name)
self.assertEqual('default', self.store.project_domain_id)
self.assertEqual('ignored', self.store.project_domain_name)
def test_with_v3_auth(self):
self.store.ref_params = {'ref1': {'auth_address': 'authurl.com',
'auth_version': '3',
'user': 'user:pass',
'key': 'password',
'user_domain_id': 'default',
'user_domain_name': 'ignored',
'project_domain_id': 'default',
'project_domain_name': 'ignored'}}
self.store.configure()
connection = self.store.get_connection(self.location)
self.assertEqual('3', connection.auth_version)
self.assertEqual(connection.os_options,
{'service_type': 'object-store',
'endpoint_type': 'publicURL',
'user_domain_id': 'default',
'user_domain_name': 'ignored',
'project_domain_id': 'default',
'project_domain_name': 'ignored'})
class TestMultiTenantStoreConnections(base.StoreBaseTest):
def setUp(self):