From af1813ba4e9ad4362072e69d5841f9dff550bd1e Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Mon, 17 Jan 2011 15:42:20 +0900 Subject: [PATCH 1/2] s3api: fix AWSAccessKeyId We use cfaccount as AWSAccessKeyId (something like AUTH_89308df71f274e33af17779606f08fa0). However, users with the same account use the same cfaccount. In such case, we can't know which password should be used as a secret key to calculate the HMAC. This changes AWSAccessKeyId to the combination of account and user: Authorization: AWS test:tester:xQE0diMbLRepdf3YB+FIEXAMPLE= The auth validates the HMAC and sends a cfaccount back to the proxy. The proxy rewrites the path with the cfaccount. --- swift/auth/server.py | 20 +++++++++++--------- swift/common/middleware/auth.py | 10 ++++++++-- swift/common/middleware/swift3.py | 4 ++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/swift/auth/server.py b/swift/auth/server.py index a0bd31ccda..dac3a78a3e 100644 --- a/swift/auth/server.py +++ b/swift/auth/server.py @@ -243,18 +243,17 @@ YOU HAVE A FEW OPTIONS: raise err def validate_s3_sign(self, request, token): - cfaccount, sign = request.headers['Authorization'].split(' ')[-1].split(':') + account, user, sign = request.headers['Authorization'].split(' ')[-1].split(':') msg = base64.urlsafe_b64decode(unquote(token)) rv = False with self.get_conn() as conn: row = conn.execute(''' - SELECT account, user, password FROM account - WHERE cfaccount = ?''', - (cfaccount,)).fetchone() - rv = (84000, row[0], row[1], cfaccount) - + SELECT password, cfaccount FROM account + WHERE account = ? AND user = ?''', + (account, user)).fetchone() + rv = (84000, account, user, row[1]) if rv: - s = base64.encodestring(hmac.new(row[2], msg, sha1).digest()).strip() + s = base64.encodestring(hmac.new(row[0], msg, sha1).digest()).strip() self.logger.info("orig %s, calc %s" % (sign, s)) if sign != s: rv = False @@ -440,8 +439,10 @@ YOU HAVE A FEW OPTIONS: except ValueError: return HTTPBadRequest() # Retrieves (TTL, account, user, cfaccount) if valid, False otherwise + headers = {} if 'Authorization' in request.headers: validation = self.validate_s3_sign(request, token) + headers['X-Auth-Account-Suffix'] = validation[3] else: validation = self.validate_token(token) if not validation: @@ -451,8 +452,9 @@ YOU HAVE A FEW OPTIONS: # admin access to a cfaccount or ".reseller_admin" to access to all # accounts, including creating new ones. groups.append(validation[3]) - return HTTPNoContent(headers={'X-Auth-TTL': validation[0], - 'X-Auth-Groups': ','.join(groups)}) + headers['X-Auth-TTL'] = validation[0] + headers['X-Auth-Groups'] = ','.join(groups) + return HTTPNoContent(headers=headers) def handle_add_user(self, request): """ diff --git a/swift/common/middleware/auth.py b/swift/common/middleware/auth.py index 59cf83ddba..a2c71a3070 100644 --- a/swift/common/middleware/auth.py +++ b/swift/common/middleware/auth.py @@ -134,8 +134,7 @@ class DevAuth(object): headers = {} if env.get('HTTP_AUTHORIZATION'): groups = None - if env.get('HTTP_AUTHORIZATION'): - headers["Authorization"] = env.get('HTTP_AUTHORIZATION') + headers["Authorization"] = env.get('HTTP_AUTHORIZATION') if not groups: with Timeout(self.timeout): @@ -153,6 +152,13 @@ class DevAuth(object): if memcache_client: memcache_client.set(key, (time(), expiration, groups), timeout=expiration) + + if env.get('HTTP_AUTHORIZATION'): + account, user, sign = env['HTTP_AUTHORIZATION'].split(' ')[-1].split(':') + cfaccount = resp.getheader('x-auth-account-suffix') + path = env['PATH_INFO'] + env['PATH_INFO'] = path.replace("%s:%s" % (account, user), cfaccount, 1) + return groups def authorize(self, req): diff --git a/swift/common/middleware/swift3.py b/swift/common/middleware/swift3.py index 85f03902ac..f6a5126693 100644 --- a/swift/common/middleware/swift3.py +++ b/swift/common/middleware/swift3.py @@ -400,11 +400,11 @@ class Swift3Middleware(object): h += header.lower() + ":" + str(req.headers[header]) + "\n" h += req.path try: - account, _ = req.headers['Authorization'].split(' ')[-1].split(':') + account, user, _ = req.headers['Authorization'].split(' ')[-1].split(':') except: return None, None token = base64.urlsafe_b64encode(h) - return account, token + return '%s:%s' % (account, user), token def __call__(self, env, start_response): req = Request(env) From d7b59e0b94433ce928ccb7e5c7fd1a49b2d0ff62 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Mon, 17 Jan 2011 15:51:59 +0900 Subject: [PATCH 2/2] s3api: update unit tests for AWSAccessKeyId change --- test/unit/common/middleware/test_swift3.py | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test/unit/common/middleware/test_swift3.py b/test/unit/common/middleware/test_swift3.py index c7c974c965..f84a0ffe8a 100644 --- a/test/unit/common/middleware/test_swift3.py +++ b/test/unit/common/middleware/test_swift3.py @@ -209,7 +209,7 @@ class TestSwift3(unittest.TestCase): def test_bad_path(self): req = Request.blank('/bucket/object/bad', environ={'REQUEST_METHOD': 'GET'}, - headers={'Authorization': 'AUTH_something:hoge'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = self.app(req.environ, start_response) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.firstChild.nodeName, 'Error') @@ -219,7 +219,7 @@ class TestSwift3(unittest.TestCase): def test_bad_method(self): req = Request.blank('/', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Authorization': 'AUTH_something:hoge'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = self.app(req.environ, start_response) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.firstChild.nodeName, 'Error') @@ -230,7 +230,7 @@ class TestSwift3(unittest.TestCase): local_app = swift3.filter_factory({})(cl(status)) req = Request.blank(path, environ={'REQUEST_METHOD': method}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, start_response) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.firstChild.nodeName, 'Error') @@ -246,7 +246,7 @@ class TestSwift3(unittest.TestCase): local_app = swift3.filter_factory({})(FakeAppService()) req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, local_app.app.do_start_response) self.assertEquals(local_app.app.response_args[0].split()[0], '200') @@ -279,7 +279,7 @@ class TestSwift3(unittest.TestCase): bucket_name = 'junk' req = Request.blank('/%s' % bucket_name, environ={'REQUEST_METHOD': 'GET'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, local_app.app.do_start_response) self.assertEquals(local_app.app.response_args[0].split()[0], '200') @@ -307,7 +307,7 @@ class TestSwift3(unittest.TestCase): req = Request.blank('/%s' % bucket_name, environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'max-keys=3'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, local_app.app.do_start_response) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.getElementsByTagName('IsTruncated')[0]. @@ -316,7 +316,7 @@ class TestSwift3(unittest.TestCase): req = Request.blank('/%s' % bucket_name, environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'max-keys=2'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, local_app.app.do_start_response) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.getElementsByTagName('IsTruncated')[0]. @@ -335,7 +335,7 @@ class TestSwift3(unittest.TestCase): req = Request.blank('/%s' % bucket_name, environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'max-keys=5'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, lambda *args: None) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.getElementsByTagName('MaxKeys')[0]. @@ -346,7 +346,7 @@ class TestSwift3(unittest.TestCase): req = Request.blank('/%s' % bucket_name, environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'max-keys=5000'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, lambda *args: None) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.getElementsByTagName('MaxKeys')[0]. @@ -366,7 +366,7 @@ class TestSwift3(unittest.TestCase): req = Request.blank('/%s' % bucket_name, environ={'REQUEST_METHOD': 'GET', 'QUERY_STRING': 'delimiter=a&marker=b&prefix=c'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, lambda *args: None) dom = xml.dom.minidom.parseString("".join(resp)) self.assertEquals(dom.getElementsByTagName('Prefix')[0]. @@ -392,7 +392,7 @@ class TestSwift3(unittest.TestCase): local_app = swift3.filter_factory({})(FakeAppBucket(201)) req = Request.blank('/bucket', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, local_app.app.do_start_response) self.assertEquals(local_app.app.response_args[0].split()[0], '200') @@ -410,7 +410,7 @@ class TestSwift3(unittest.TestCase): local_app = swift3.filter_factory({})(FakeAppBucket(204)) req = Request.blank('/bucket', environ={'REQUEST_METHOD': 'DELETE'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, local_app.app.do_start_response) self.assertEquals(local_app.app.response_args[0].split()[0], '204') @@ -418,7 +418,7 @@ class TestSwift3(unittest.TestCase): local_app = swift3.filter_factory({})(FakeAppObject()) req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': method}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, local_app.app.do_start_response) self.assertEquals(local_app.app.response_args[0].split()[0], '200') @@ -468,7 +468,7 @@ class TestSwift3(unittest.TestCase): local_app = swift3.filter_factory({})(FakeAppObject(201)) req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Authorization': 'AUTH_who:password', + headers={'Authorization': 'AWS test:tester:hmac', 'x-amz-storage-class': 'REDUCED_REDUNDANCY', 'Content-MD5': 'Gyz1NfJ3Mcl0NDZFo5hTKA=='}) req.date = datetime.now() @@ -490,7 +490,7 @@ class TestSwift3(unittest.TestCase): local_app = swift3.filter_factory({})(app) req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': 'PUT'}, - headers={'Authorization': 'AUTH_who:password', + headers={'Authorization': 'AWS test:tester:hmac', 'X-Amz-Storage-Class': 'REDUCED_REDUNDANCY', 'X-Amz-Meta-Something': 'oh hai', 'X-Amz-Copy-Source': '/some/source', @@ -518,7 +518,7 @@ class TestSwift3(unittest.TestCase): local_app = swift3.filter_factory({})(FakeAppObject(204)) req = Request.blank('/bucket/object', environ={'REQUEST_METHOD': 'DELETE'}, - headers={'Authorization': 'AUTH_who:password'}) + headers={'Authorization': 'AWS test:tester:hmac'}) resp = local_app(req.environ, local_app.app.do_start_response) self.assertEquals(local_app.app.response_args[0].split()[0], '204')