trying to make sense of auth middleware and reseller prefix

This commit is contained in:
Clay Gerrard 2010-09-16 16:44:44 -05:00
parent 01059884b3
commit d5770ee214
4 changed files with 70 additions and 53 deletions
doc/source
swift
auth
common/middleware
test/unit/auth

@ -60,9 +60,10 @@ Example Authentication with DevAuth:
* The external DevAuth server responds with "X-Auth-Groups: * The external DevAuth server responds with "X-Auth-Groups:
test:tester,test,AUTH_storage_xyz" test:tester,test,AUTH_storage_xyz"
* Now this user will have full access (via authorization procedures later) * Now this user will have full access (via authorization procedures later)
to the AUTH_storage_xyz Swift storage account and access to other storage to the AUTH_storage_xyz Swift storage account and access to containers in
accounts with the same `AUTH_` reseller prefix and has an ACL specifying other storage accounts, provided the storage account begins with the same
at least one of those three groups returned. `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 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 WSGI environment's swift.authorize value, if one is set. The swift.authorize

@ -454,7 +454,7 @@ YOU HAVE A FEW OPTIONS:
if create_reseller_admin and ( if create_reseller_admin and (
request.headers.get('X-Auth-Admin-User') != '.super_admin' or request.headers.get('X-Auth-Admin-User') != '.super_admin' or
request.headers.get('X-Auth-Admin-Key') != self.super_admin_key): request.headers.get('X-Auth-Admin-Key') != self.super_admin_key):
return HTTPForbidden(request=request) return HTTPUnauthorized(request=request)
create_account_admin = \ create_account_admin = \
request.headers.get('x-auth-user-admin') == 'true' request.headers.get('x-auth-user-admin') == 'true'
if create_account_admin and \ 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 \ if request.headers.get('X-Auth-Admin-User') != '.super_admin' or \
request.headers.get('X-Auth-Admin-Key') != self.super_admin_key: request.headers.get('X-Auth-Admin-Key') != self.super_admin_key:
return HTTPForbidden(request=request) return HTTPUnauthorized(request=request)
result = self.recreate_accounts() result = self.recreate_accounts()
return Response(result, 200, request=request) return Response(result, 200, request=request)

@ -20,7 +20,7 @@ from webob.exc import HTTPForbidden, HTTPUnauthorized
from swift.common.bufferedhttp import http_connect_raw as http_connect 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.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): class DevAuth(object):
@ -35,9 +35,33 @@ class DevAuth(object):
self.auth_host = conf.get('ip', '127.0.0.1') self.auth_host = conf.get('ip', '127.0.0.1')
self.auth_port = int(conf.get('port', 11000)) self.auth_port = int(conf.get('port', 11000))
self.ssl = \ 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)) 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): def __call__(self, env, start_response):
""" """
Accepts a standard WSGI application call, authenticating the request 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 validation. For an authenticated request, REMOTE_USER will be set to a
comma separated list of the user's groups. comma separated list of the user's groups.
""" """
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
if token and token.startswith(self.reseller_prefix):
groups = None if not self.reseller_prefix:
memcache_client = cache_from_env(env) # all requests belong to me
key = '%s/token/%s' % (self.reseller_prefix, token) if token:
cached_auth_data = memcache_client.get(key) # I should attempt to auth any token
if cached_auth_data: groups = self.get_groups(token)
start, expiration, groups = cached_auth_data else:
if time() - start > expiration: groups = None # no token is same as an unauthorized token
groups = None if groups:
if not groups: env['REMOTE_USER'] = groups
with Timeout(self.timeout): user = groups and groups.split(',', 1)[0] or ''
conn = http_connect(self.auth_host, self.auth_port, 'GET', env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token)
'/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
env['swift.authorize'] = self.authorize env['swift.authorize'] = self.authorize
env['swift.clean_acl'] = clean_acl 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: else:
version, rest = split_path(env.get('PATH_INFO', ''), 1, 2, True) # as a reseller, I must respect that just can my auth can't provide
if rest and rest.startswith(self.reseller_prefix): # groups for a token, others may
# If we don't have a reseller prefix we have no way of knowing if token and token.startswith(self.reseller_prefix)::
# if we should be handling the request, so we only set # attempt to auth my token with my auth server
# swift.authorize if it isn't set already (or we have a groups = self.get_groups(token)
# reseller prefix that matches so we know we should handle the if groups:
# request). # authenticated!
if self.reseller_prefix or 'swift.authorize' not in env: 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.authorize'] = self.authorize
env['swift.clean_acl'] = clean_acl env['swift.clean_acl'] = clean_acl
elif 'swift.authorize' not in env: else:
env['swift.authorize'] = self.denied_response # 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) return self.app(env, start_response)
def authorize(self, req): def authorize(self, req):

@ -685,7 +685,8 @@ class TestAuthServer(unittest.TestCase):
conf = {'swift_dir': self.testdir, 'log_name': 'auth'} conf = {'swift_dir': self.testdir, 'log_name': 'auth'}
self.assertRaises(ValueError, auth_server.AuthController, conf) self.assertRaises(ValueError, auth_server.AuthController, conf)
conf['super_admin_key'] = 'testkey' 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): def test_add_storage_account(self):
auth_server.http_connect = fake_http_connect(201) auth_server.http_connect = fake_http_connect(201)