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:
parent
d2ec027e22
commit
a5df15005b
@ -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
|
||||
|
@ -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
|
||||
|
@ -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],
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user