From d5770ee21401468c49391eee3e8e8a724e92425f Mon Sep 17 00:00:00 2001 From: Clay Gerrard Date: Thu, 16 Sep 2010 16:44:44 -0500 Subject: [PATCH] trying to make sense of auth middleware and reseller prefix --- doc/source/development_auth.rst | 7 +- swift/auth/server.py | 4 +- swift/common/middleware/auth.py | 109 ++++++++++++++++++-------------- test/unit/auth/test_server.py | 3 +- 4 files changed, 70 insertions(+), 53 deletions(-) diff --git a/doc/source/development_auth.rst b/doc/source/development_auth.rst index 410312f3db..afbe776bab 100644 --- a/doc/source/development_auth.rst +++ b/doc/source/development_auth.rst @@ -60,9 +60,10 @@ Example Authentication with DevAuth: * The external DevAuth server responds with "X-Auth-Groups: test:tester,test,AUTH_storage_xyz" * Now this user will have full access (via authorization procedures later) - to the AUTH_storage_xyz Swift storage account and access to other storage - accounts with the same `AUTH_` reseller prefix and has an ACL specifying - at least one of those three groups returned. + to the AUTH_storage_xyz Swift storage account and access to containers in + other storage accounts, provided the storage account begins with the same + `AUTH_` reseller prefix and the container has an ACL specifying at least + one of those three groups returned. Authorization is performed through callbacks by the Swift Proxy server to the WSGI environment's swift.authorize value, if one is set. The swift.authorize diff --git a/swift/auth/server.py b/swift/auth/server.py index e8214db354..24acda6196 100644 --- a/swift/auth/server.py +++ b/swift/auth/server.py @@ -454,7 +454,7 @@ YOU HAVE A FEW OPTIONS: if create_reseller_admin and ( request.headers.get('X-Auth-Admin-User') != '.super_admin' or request.headers.get('X-Auth-Admin-Key') != self.super_admin_key): - return HTTPForbidden(request=request) + return HTTPUnauthorized(request=request) create_account_admin = \ request.headers.get('x-auth-user-admin') == 'true' if create_account_admin and \ @@ -484,7 +484,7 @@ YOU HAVE A FEW OPTIONS: """ if request.headers.get('X-Auth-Admin-User') != '.super_admin' or \ request.headers.get('X-Auth-Admin-Key') != self.super_admin_key: - return HTTPForbidden(request=request) + return HTTPUnauthorized(request=request) result = self.recreate_accounts() return Response(result, 200, request=request) diff --git a/swift/common/middleware/auth.py b/swift/common/middleware/auth.py index f5e7a2c9d0..05939d06ac 100644 --- a/swift/common/middleware/auth.py +++ b/swift/common/middleware/auth.py @@ -20,7 +20,7 @@ from webob.exc import HTTPForbidden, HTTPUnauthorized from swift.common.bufferedhttp import http_connect_raw as http_connect from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed -from swift.common.utils import cache_from_env, split_path +from swift.common.utils import cache_from_env, split_path, TRUE_VALUES class DevAuth(object): @@ -35,9 +35,33 @@ class DevAuth(object): self.auth_host = conf.get('ip', '127.0.0.1') self.auth_port = int(conf.get('port', 11000)) self.ssl = \ - conf.get('ssl', 'false').lower() in ('true', 'on', '1', 'yes') + conf.get('ssl', 'false').lower() in TRUE_VALUES self.timeout = int(conf.get('node_timeout', 10)) + def get_groups(self, token): + memcache_client = cache_from_env(env) + key = '%s/token/%s' % (self.reseller_prefix, token) + cached_auth_data = memcache_client.get(key) + if cached_auth_data: + start, expiration, groups = cached_auth_data + if time() - start > expiration: + groups = None + if not groups: + with Timeout(self.timeout): + conn = http_connect(self.auth_host, self.auth_port, 'GET', + '/token/%s' % token, ssl=self.ssl) + resp = conn.getresponse() + resp.read() + conn.close() + if resp.status // 100 != 2: + return None + + expiration = float(resp.getheader('x-auth-ttl')) + groups = resp.getheader('x-auth-groups') + memcache_client.set(key, (time(), expiration, groups), + timeout=expiration) + return groups + def __call__(self, env, start_response): """ Accepts a standard WSGI application call, authenticating the request @@ -45,57 +69,48 @@ class DevAuth(object): validation. For an authenticated request, REMOTE_USER will be set to a comma separated list of the user's groups. """ + token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) - if token and token.startswith(self.reseller_prefix): - groups = None - memcache_client = cache_from_env(env) - key = '%s/token/%s' % (self.reseller_prefix, token) - cached_auth_data = memcache_client.get(key) - if cached_auth_data: - start, expiration, groups = cached_auth_data - if time() - start > expiration: - groups = None - if not groups: - with Timeout(self.timeout): - conn = http_connect(self.auth_host, self.auth_port, 'GET', - '/token/%s' % token, ssl=self.ssl) - resp = conn.getresponse() - resp.read() - conn.close() - if resp.status // 100 != 2: - if self.reseller_prefix: - return HTTPUnauthorized()(env, start_response) - else: - # If we have no reseller prefix, we can't deny the - # request just yet because another auth middleware - # might be able to approve. - if 'swift.authorize' not in env: - env['swift.authorize'] = self.denied_response - return self.app(env, start_response) - expiration = float(resp.getheader('x-auth-ttl')) - groups = resp.getheader('x-auth-groups') - memcache_client.set(key, (time(), expiration, groups), - timeout=expiration) - env['REMOTE_USER'] = groups + + if not self.reseller_prefix: + # all requests belong to me + if token: + # I should attempt to auth any token + groups = self.get_groups(token) + else: + groups = None # no token is same as an unauthorized token + if groups: + env['REMOTE_USER'] = groups + user = groups and groups.split(',', 1)[0] or '' + env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token) env['swift.authorize'] = self.authorize env['swift.clean_acl'] = clean_acl - # We know the proxy logs the token, so we augment it just a bit to - # also log the authenticated user. - user = groups and groups.split(',', 1)[0] or '' - env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token) else: - version, rest = split_path(env.get('PATH_INFO', ''), 1, 2, True) - if rest and rest.startswith(self.reseller_prefix): - # If we don't have a reseller prefix we have no way of knowing - # if we should be handling the request, so we only set - # swift.authorize if it isn't set already (or we have a - # reseller prefix that matches so we know we should handle the - # request). - if self.reseller_prefix or 'swift.authorize' not in env: + # as a reseller, I must respect that just can my auth can't provide + # groups for a token, others may + if token and token.startswith(self.reseller_prefix):: + # attempt to auth my token with my auth server + groups = self.get_groups(token) + if groups: + # authenticated! + env['REMOTE_USER'] = groups + user = groups and groups.split(',', 1)[0] or '' + env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token) + env['swift.authorize'] = self.authorize + env['swift.clean_acl'] = clean_acl + else: + # I can't claim this token, but I might claim the annoynomous request + version, rest = split_path(env.get('PATH_INFO', ''), 1, 2, True) + if rest and rest.startswith(self.reseller_prefix): + # annoynomous access to my reseller's accounts env['swift.authorize'] = self.authorize env['swift.clean_acl'] = clean_acl - elif 'swift.authorize' not in env: - env['swift.authorize'] = self.denied_response + else: + # not my token, not my account + # good idea regardless... + if 'swift.authorize' not in env: + env['swift.authorize'] = self.denied_response + return self.app(env, start_response) def authorize(self, req): diff --git a/test/unit/auth/test_server.py b/test/unit/auth/test_server.py index 6ab8b76095..d63f843abe 100644 --- a/test/unit/auth/test_server.py +++ b/test/unit/auth/test_server.py @@ -685,7 +685,8 @@ class TestAuthServer(unittest.TestCase): conf = {'swift_dir': self.testdir, 'log_name': 'auth'} self.assertRaises(ValueError, auth_server.AuthController, conf) conf['super_admin_key'] = 'testkey' - auth_server.AuthController(conf) + controller = auth_server.AuthController(conf) + self.assertEquals(controller.super_admin_key, conf['super_admin_key']) def test_add_storage_account(self): auth_server.http_connect = fake_http_connect(201)