Make s3token work in a Keystone-V3-only world

Previously, we hardcoded a v2.0 path to use when validating requests
against Keystone. Now, the version to use may be specified in a new
auth_version config option.

In the future, we may want to implement some form of version
discovery, but that will be complicated by:

* trying to determine whether the S3 extension is actually enabled for a
  given version (particularly since the extensions endpoint [1] seems to
  have gone away in v3), and
* needing to be able to perform this detection as part of the
  client-request cycle, in case Keystone is down when the proxy is
  coming up.

[1] http://developer.openstack.org/api-ref/identity/v2/index.html?expanded=list-extensions-detail

Change-Id: I3a9c702123fd1b76d45214a89ec0583caf3719f0
This commit is contained in:
Tim Burke 2016-10-10 11:48:58 -07:00 committed by Kota Tsuyuzaki
parent 9ede5cddb9
commit 2a48861248
3 changed files with 78 additions and 4 deletions

View File

@ -156,6 +156,11 @@ delay_auth_decision = False
# Keystone server details
auth_uri = http://keystonehost:35357/
# Identity Service API version to use. This is appended to the auth_uri.
# Historically this defaulted to "2.0", but most modern deployments
# should use "3".
auth_version = 2.0
# Connect/read timeout to use when communicating with Keystone
http_timeout = 10.0

View File

@ -160,6 +160,7 @@ class S3Token(object):
if parsed.query or parsed.fragment or '@' in parsed.netloc:
raise ConfigFileError('Invalid auth_uri; must not include '
'username, query, or fragment')
self._request_uri += '/v%s/s3tokens' % conf.get('auth_version', '2.0')
# SSL
insecure = config_true_value(conf.get('insecure'))
@ -194,7 +195,7 @@ class S3Token(object):
def _json_request(self, creds_json):
headers = {'Content-Type': 'application/json'}
try:
response = requests.post('%s/v2.0/s3tokens' % self._request_uri,
response = requests.post(self._request_uri,
headers=headers, data=creds_json,
verify=self._verify,
timeout=self._timeout)

View File

@ -284,6 +284,28 @@ class S3TokenMiddlewareTestGood(S3TokenMiddlewareTestBase):
req.get_response(self.middleware)
self._assert_authorized(req)
def test_authorized_v3(self):
protocol = 'http'
host = 'fakehost'
port = 35357
self.requests_mock.post(
'%s://%s:%s/v3/s3tokens' % (protocol, host, port),
status_code=201, json=GOOD_RESPONSE_V2)
self.middleware = (
s3_token.filter_factory({'auth_protocol': 'http',
'auth_host': host,
'auth_port': port,
'auth_version': '3'})(self.app))
req = Request.blank('/v1/AUTH_cfa/c/o')
req.environ['swift3.auth_details'] = {
'access_key': u'access',
'signature': u'signature',
'string_to_sign': u'token',
}
req.get_response(self.middleware)
self._assert_authorized(req)
def test_authorized_trailing_slash(self):
self.middleware = s3_token.filter_factory({
'auth_uri': self.TEST_AUTH_URI + '/'})(self.app)
@ -355,20 +377,44 @@ class S3TokenMiddlewareTestGood(S3TokenMiddlewareTestBase):
middleware = s3_token.filter_factory(config)(self.app)
self.assertIs('false_ind', middleware._verify)
def test_auth_version(self):
for conf, expected in [
# if provided just host/scheme, tack on the default
# version/endpoint like before
({'auth_uri': 'https://example.com'},
'https://example.com/v2.0/s3tokens'),
# if provided a version-specific URI, trust it
({'auth_uri': 'https://example.com:5000',
'auth_version': '2.0'},
'https://example.com:5000/v2.0/s3tokens'),
({'auth_uri': 'http://example.com', 'auth_version': '3'},
'http://example.com/v3/s3tokens'),
# even try to allow for future versions
({'auth_uri': 'http://example.com', 'auth_version': '4.25'},
'http://example.com/v4.25/s3tokens'),
# keystone running under mod_wsgi often has a path prefix
({'auth_uri': 'https://example.com/identity'},
'https://example.com/identity/v2.0/s3tokens'),
# doesn't really work to include version in auth_uri
({'auth_uri': 'https://example.com/v2.0'},
'https://example.com/v2.0/v2.0/s3tokens')]:
middleware = s3_token.filter_factory(conf)(self.app)
self.assertEqual(expected, middleware._request_uri)
def test_ipv6_auth_host_option(self):
config = {}
ipv6_addr = '::FFFF:129.144.52.38'
identity_uri = 'https://[::FFFF:129.144.52.38]:35357'
request_uri = 'https://[::FFFF:129.144.52.38]:35357/v2.0/s3tokens'
# Raw IPv6 address should work
config['auth_host'] = ipv6_addr
middleware = s3_token.filter_factory(config)(self.app)
self.assertEqual(identity_uri, middleware._request_uri)
self.assertEqual(request_uri, middleware._request_uri)
# ...as should workarounds already in use
config['auth_host'] = '[%s]' % ipv6_addr
middleware = s3_token.filter_factory(config)(self.app)
self.assertEqual(identity_uri, middleware._request_uri)
self.assertEqual(request_uri, middleware._request_uri)
# ... with no config, we should get config error
del config['auth_host']
@ -742,6 +788,28 @@ class S3TokenMiddlewareTestV3(S3TokenMiddlewareTestBase):
req.get_response(self.middleware)
self._assert_authorized(req)
def test_authorized_v3(self):
protocol = 'http'
host = 'fakehost'
port = 35357
self.requests_mock.post(
'%s://%s:%s/v3/s3tokens' % (protocol, host, port),
status_code=201, json=GOOD_RESPONSE_V3)
self.middleware = (
s3_token.filter_factory({'auth_protocol': 'http',
'auth_host': host,
'auth_port': port,
'auth_version': '3'})(self.app))
req = Request.blank('/v1/AUTH_cfa/c/o')
req.environ['swift3.auth_details'] = {
'access_key': u'access',
'signature': u'signature',
'string_to_sign': u'token',
}
req.get_response(self.middleware)
self._assert_authorized(req)
def test_authorized_trailing_slash(self):
self.middleware = s3_token.filter_factory({
'auth_uri': self.TEST_AUTH_URI + '/'})(self.app)