trying to make sense of auth middleware and reseller prefix
This commit is contained in:
parent
01059884b3
commit
d5770ee214
doc/source
swift
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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user