Got rid of inter-reseller ACLs. Enforce ACLs to only work within a reseller space. Updated docs and tests. We can expand to inter-reseller in the future with ACLs like .x:RESELLER_group

This commit is contained in:
gholt 2010-09-09 10:24:25 -07:00
parent d2ec027e22
commit a5df15005b
8 changed files with 71 additions and 72 deletions

View File

@ -35,14 +35,15 @@ string. If the user does not have admin access to the account, the third group
will be omitted.
It is highly recommended that authentication server implementers prefix their
group names and tokens with a configurable reseller prefix (`AUTH_` by default
with the included DevAuth). This prefix will allow deconflicting with other
authentication servers that might be using the same Swift cluster.
tokens and Swift storage accounts they create with a configurable reseller
prefix (`AUTH_` by default with the included DevAuth). This prefix will allow
deconflicting with other authentication servers that might be using the same
Swift cluster. Otherwise, the Swift cluster will have to try all the resellers
until one validates a token or all fail.
The only other restriction is that no group name should begin with a period '.'
as that is reserved for internal Swift use (such as the .r for referrer
designations as you'll see later). This shouldn't be an issue if a reseller
prefix is in use and does not begin with a period.
A restriction with group names is that no group name should begin with a period
'.' as that is reserved for internal Swift use (such as the .r for referrer
designations as you'll see later).
Example Authentication with DevAuth:
@ -54,10 +55,11 @@ Example Authentication with DevAuth:
it matches the "tester" user within the "test" account for the storage
account "AUTH_storage_xyz".
* The external DevAuth server responds with "X-Auth-Groups:
AUTH_test:tester,AUTH_test,AUTH_storage_xyz"
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 anything with
ACLs specifying at least one of those three groups returned.
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.
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

@ -37,10 +37,12 @@ use = egg:swift#proxy
[filter:auth]
use = egg:swift#auth
# The reseller prefix, if set, will verify a token begins with this prefix
# before even attempting to validate it with the external reseller. Usefull if
# multiple auth systems are in use for one Swift cluster.
# reseller_prefix =
# The reseller prefix will verify a token begins with this prefix before even
# attempting to validate it with the external authentication server. Also, with
# authorization, only Swift storage accounts with this prefix will be
# authorized by this middleware. Useful if multiple auth systems are in use for
# one Swift cluster.
# reseller_prefix = AUTH
# ip = 127.0.0.1
# port = 11000
# ssl = false

View File

@ -361,9 +361,7 @@ class AuthController(object):
validation = self.validate_token(token)
if not validation:
return HTTPNotFound()
groups = [
'%s%s:%s' % (self.reseller_prefix, validation[1], validation[2]),
'%s%s' % (self.reseller_prefix, validation[1])]
groups = ['%s:%s' % (validation[1], validation[2]), validation[1]]
if validation[3]: # admin access to a cfaccount
groups.append(validation[3])
return HTTPNoContent(headers={'X-Auth-TTL': validation[0],

View File

@ -29,7 +29,7 @@ class DevAuth(object):
def __init__(self, app, conf):
self.app = app
self.conf = conf
self.reseller_prefix = conf.get('reseller_prefix', '').strip()
self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
if self.reseller_prefix and self.reseller_prefix[-1] != '_':
self.reseller_prefix += '_'
self.auth_host = conf.get('ip', '127.0.0.1')
@ -83,7 +83,7 @@ class DevAuth(object):
WSGI response callable if not.
"""
version, account, container, obj = split_path(req.path, 1, 4, True)
if not account:
if not account or not account.startswith(self.reseller_prefix):
return self.denied_response(req)
if req.remote_user and account in req.remote_user.split(','):
return None

View File

@ -2,7 +2,6 @@
auth_host = 127.0.0.1
auth_port = 11000
auth_ssl = no
auth_prefix = AUTH
# Primary functional test account (needs admin access to the account)
account = test

View File

@ -10,9 +10,6 @@ from swift.common.client import get_auth, http_connection
swift_test_auth = os.environ.get('SWIFT_TEST_AUTH')
swift_test_auth_prefix = os.environ.get('SWIFT_TEST_AUTH_PREFIX')
if swift_test_auth_prefix and swift_test_auth_prefix[-1] != '_':
swift_test_auth_prefix += '_'
swift_test_user = [os.environ.get('SWIFT_TEST_USER'), None, None]
swift_test_key = [os.environ.get('SWIFT_TEST_KEY'), None, None]
@ -35,9 +32,6 @@ if not all([swift_test_auth, swift_test_user[0], swift_test_key[0]]):
if conf.get('auth_ssl', 'no').lower() in ('yes', 'true', 'on', '1'):
swift_test_auth = 'https'
swift_test_auth += '://%(auth_host)s:%(auth_port)s/v1.0' % conf
swift_test_auth_prefix = conf.get('auth_prefix', 'AUTH')
if swift_test_auth_prefix and swift_test_auth_prefix[-1] != '_':
swift_test_auth_prefix += '_'
swift_test_user[0] = '%(account)s:%(username)s' % conf
swift_test_key[0] = conf['password']
try:

View File

@ -9,7 +9,7 @@ from swift.common.constraints import MAX_META_COUNT, MAX_META_NAME_LENGTH, \
MAX_META_OVERALL_SIZE, MAX_META_VALUE_LENGTH
from swift_testing import check_response, retry, skip, skip2, skip3, \
swift_test_auth_prefix, swift_test_user
swift_test_user
class TestContainer(unittest.TestCase):
@ -377,10 +377,9 @@ class TestContainer(unittest.TestCase):
self.assertEquals(resp.status, 403)
# Make the container accessible by the second account
def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '', {
'X-Auth-Token': token,
'X-Container-Read': '%stest2' % swift_test_auth_prefix,
'X-Container-Write': '%stest2' % swift_test_auth_prefix})
conn.request('POST', parsed.path + '/' + self.name, '',
{'X-Auth-Token': token, 'X-Container-Read': 'test2',
'X-Container-Write': 'test2'})
return check_response(conn)
resp = retry(post)
resp.read()
@ -446,9 +445,8 @@ class TestContainer(unittest.TestCase):
self.assertEquals(resp.status, 403)
# Now make the container also writeable by the second account
def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '', {
'X-Auth-Token': token,
'X-Container-Write': '%stest2' % swift_test_auth_prefix})
conn.request('POST', parsed.path + '/' + self.name, '',
{'X-Auth-Token': token, 'X-Container-Write': 'test2'})
return check_response(conn)
resp = retry(post)
resp.read()
@ -485,9 +483,7 @@ class TestContainer(unittest.TestCase):
# Make the container accessible by the third account
def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '',
{'X-Auth-Token': token,
'X-Container-Read': '%s%s' %
(swift_test_auth_prefix, swift_test_user[2])})
{'X-Auth-Token': token, 'X-Container-Read': swift_test_user[2]})
return check_response(conn)
resp = retry(post)
resp.read()
@ -508,8 +504,7 @@ class TestContainer(unittest.TestCase):
def post(url, token, parsed, conn):
conn.request('POST', parsed.path + '/' + self.name, '',
{'X-Auth-Token': token,
'X-Container-Write': '%s%s' %
(swift_test_auth_prefix, swift_test_user[2])})
'X-Container-Write': swift_test_user[2]})
return check_response(conn)
resp = retry(post)
resp.read()

View File

@ -109,7 +109,7 @@ class TestAuth(unittest.TestCase):
try:
auth.http_connect = mock_http_connect(404)
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': FakeMemcache()},
'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': FakeMemcache()},
lambda x, y: None))
self.assert_(result.startswith('401'), result)
finally:
@ -119,9 +119,9 @@ class TestAuth(unittest.TestCase):
old_http_connect = auth.http_connect
try:
auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'})
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': FakeMemcache()},
'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': FakeMemcache()},
lambda x, y: None))
self.assert_(result.startswith('204'), result)
finally:
@ -132,15 +132,15 @@ class TestAuth(unittest.TestCase):
try:
fake_memcache = FakeMemcache()
auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'})
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': fake_memcache},
'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
lambda x, y: None))
self.assert_(result.startswith('204'), result)
auth.http_connect = mock_http_connect(404)
# Should still be in memcache
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': fake_memcache},
'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
lambda x, y: None))
self.assert_(result.startswith('204'), result)
finally:
@ -151,15 +151,15 @@ class TestAuth(unittest.TestCase):
try:
fake_memcache = FakeMemcache()
auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '0', 'x-auth-groups': 'act:usr,act,cfa'})
{'x-auth-ttl': '0', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': fake_memcache},
'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
lambda x, y: None))
self.assert_(result.startswith('204'), result)
auth.http_connect = mock_http_connect(404)
# Should still be in memcache, but expired
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': fake_memcache},
'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
lambda x, y: None))
self.assert_(result.startswith('401'), result)
finally:
@ -169,12 +169,12 @@ class TestAuth(unittest.TestCase):
old_http_connect = auth.http_connect
try:
auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'})
req = Request.blank('/v/a/c/o', headers={'x-auth-token': 't'})
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
req = Request.blank('/v/a/c/o', headers={'x-auth-token': 'AUTH_t'})
req.environ['swift.cache'] = FakeMemcache()
result = ''.join(self.test_auth(req.environ, start_response))
self.assert_(result.startswith('204'), result)
self.assertEquals(req.remote_user, 'act:usr,act,cfa')
self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa')
finally:
auth.http_connect = old_http_connect
@ -182,7 +182,7 @@ class TestAuth(unittest.TestCase):
old_http_connect = auth.http_connect
try:
auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'})
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
req = Request.blank('/v/a/c/o')
req.environ['swift.cache'] = FakeMemcache()
result = ''.join(self.test_auth(req.environ, start_response))
@ -195,12 +195,13 @@ class TestAuth(unittest.TestCase):
old_http_connect = auth.http_connect
try:
auth.http_connect = mock_http_connect(204,
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,cfa'})
req = Request.blank('/v/a/c/o', headers={'x-storage-token': 't'})
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
req = Request.blank('/v/a/c/o',
headers={'x-storage-token': 'AUTH_t'})
req.environ['swift.cache'] = FakeMemcache()
result = ''.join(self.test_auth(req.environ, start_response))
self.assert_(result.startswith('204'), result)
self.assertEquals(req.remote_user, 'act:usr,act,cfa')
self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa')
finally:
auth.http_connect = old_http_connect
@ -209,73 +210,81 @@ class TestAuth(unittest.TestCase):
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('401'), resp)
req = Request.blank('/badpath')
req.remote_user = 'act:usr,act,cfa'
req.remote_user = 'act:usr,act,AUTH_cfa'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp)
def test_authorize_account_access(self):
req = Request.blank('/v1/cfa')
req.remote_user = 'act:usr,act,cfa'
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act,AUTH_cfa'
self.assertEquals(self.test_auth.authorize(req), None)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp)
def test_authorize_acl_group_access(self):
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
req.acl = 'act'
self.assertEquals(self.test_auth.authorize(req), None)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
req.acl = 'act:usr'
self.assertEquals(self.test_auth.authorize(req), None)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
req.acl = 'act2'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
req.acl = 'act:usr2'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp)
def test_deny_cross_reseller(self):
# Tests that cross-reseller is denied, even if ACLs/group names match
req = Request.blank('/v1/OTHER_cfa')
req.remote_user = 'act:usr,act,AUTH_cfa'
req.acl = 'act'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp)
def test_authorize_acl_referrer_access(self):
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
req.acl = '.r:*'
self.assertEquals(self.test_auth.authorize(req), None)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
req.acl = '.r:.example.com'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('403'), resp)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.remote_user = 'act:usr,act'
req.referer = 'http://www.example.com/index.html'
req.acl = '.r:.example.com'
self.assertEquals(self.test_auth.authorize(req), None)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('401'), resp)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.acl = '.r:*'
self.assertEquals(self.test_auth.authorize(req), None)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.acl = '.r:.example.com'
resp = str(self.test_auth.authorize(req))
self.assert_(resp.startswith('401'), resp)
req = Request.blank('/v1/cfa')
req = Request.blank('/v1/AUTH_cfa')
req.referer = 'http://www.example.com/index.html'
req.acl = '.r:.example.com'
self.assertEquals(self.test_auth.authorize(req), None)