From 2a488612487433cceeeedc05b5e3d8a858618037 Mon Sep 17 00:00:00 2001 From: Tim Burke Date: Mon, 10 Oct 2016 11:48:58 -0700 Subject: [PATCH] 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 --- etc/proxy-server.conf-sample | 5 ++ swift3/s3_token_middleware.py | 3 +- swift3/test/unit/test_s3_token_middleware.py | 74 +++++++++++++++++++- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/etc/proxy-server.conf-sample b/etc/proxy-server.conf-sample index 35734284..8979ad07 100644 --- a/etc/proxy-server.conf-sample +++ b/etc/proxy-server.conf-sample @@ -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 diff --git a/swift3/s3_token_middleware.py b/swift3/s3_token_middleware.py index f86c5ce6..d1646c32 100644 --- a/swift3/s3_token_middleware.py +++ b/swift3/s3_token_middleware.py @@ -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) diff --git a/swift3/test/unit/test_s3_token_middleware.py b/swift3/test/unit/test_s3_token_middleware.py index c7414bba..b99e3d96 100644 --- a/swift3/test/unit/test_s3_token_middleware.py +++ b/swift3/test/unit/test_s3_token_middleware.py @@ -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)