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

View File

@ -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

View File

@ -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)

View File

@ -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):

View File

@ -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)