Added support for Swift container sync feature. Fixes #7
This commit is contained in:
parent
3ef9425dec
commit
894f58a1bc
@ -38,6 +38,25 @@ from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
|
||||
from swift.common.utils import cache_from_env, get_logger, split_path, urlparse
|
||||
|
||||
|
||||
# NOTE: This should be removed after some time when anyone upgrading Swauth
|
||||
# should have also upgraded Swift.
|
||||
try:
|
||||
# Attempt to import get_remote_client; older versions of Swift don't have
|
||||
# this.
|
||||
from swift.common.utils import get_remote_client
|
||||
except ImportError:
|
||||
# Fall back to locally defined version.
|
||||
def get_remote_client(req):
|
||||
# remote host for zeus
|
||||
client = req.headers.get('x-cluster-client-ip')
|
||||
if not client and 'x-forwarded-for' in req.headers:
|
||||
# remote host for other lbs
|
||||
client = req.headers['x-forwarded-for'].split(',')[0].strip()
|
||||
if not client:
|
||||
client = req.remote_addr
|
||||
return client
|
||||
|
||||
|
||||
class Swauth(object):
|
||||
"""
|
||||
Scalable authentication and authorization system that uses Swift as its
|
||||
@ -101,6 +120,9 @@ class Swauth(object):
|
||||
self.timeout = int(conf.get('node_timeout', 10))
|
||||
self.itoken = None
|
||||
self.itoken_expires = None
|
||||
self.allowed_sync_hosts = [h.strip()
|
||||
for h in conf.get('allowed_sync_hosts', '127.0.0.1').split(',')
|
||||
if h.strip()]
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
"""
|
||||
@ -269,11 +291,20 @@ class Swauth(object):
|
||||
if '.reseller_admin' in user_groups and \
|
||||
account != self.reseller_prefix and \
|
||||
account[len(self.reseller_prefix)] != '.':
|
||||
req.environ['swift_owner'] = True
|
||||
return None
|
||||
if account in user_groups and \
|
||||
(req.method not in ('DELETE', 'PUT') or container):
|
||||
# If the user is admin for the account and is not trying to do an
|
||||
# account DELETE or PUT...
|
||||
req.environ['swift_owner'] = True
|
||||
return None
|
||||
if (req.environ.get('swift_sync_key') and
|
||||
req.environ['swift_sync_key'] ==
|
||||
req.headers.get('x-container-sync-key', None) and
|
||||
'x-timestamp' in req.headers and
|
||||
(req.remote_addr in self.allowed_sync_hosts or
|
||||
get_remote_client(req) in self.allowed_sync_hosts)):
|
||||
return None
|
||||
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
||||
if referrer_allowed(req.referer, referrers):
|
||||
|
@ -56,15 +56,21 @@ class FakeMemcache(object):
|
||||
|
||||
class FakeApp(object):
|
||||
|
||||
def __init__(self, status_headers_body_iter=None):
|
||||
def __init__(self, status_headers_body_iter=None, acl=None, sync_key=None):
|
||||
self.calls = 0
|
||||
self.status_headers_body_iter = status_headers_body_iter
|
||||
if not self.status_headers_body_iter:
|
||||
self.status_headers_body_iter = iter([('404 Not Found', {}, '')])
|
||||
self.acl = acl
|
||||
self.sync_key = sync_key
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
self.calls += 1
|
||||
self.request = Request.blank('', environ=env)
|
||||
if self.acl:
|
||||
self.request.acl = self.acl
|
||||
if self.sync_key:
|
||||
self.request.environ['swift_sync_key'] = self.sync_key
|
||||
if 'swift.authorize' in env:
|
||||
resp = env['swift.authorize'](self.request)
|
||||
if resp:
|
||||
@ -3260,6 +3266,173 @@ class TestAuth(unittest.TestCase):
|
||||
self._get_token_success_v1_0_encoded(
|
||||
'act:u s r', 'k:e:y', 'act%3au%20s%20r', 'k%3Ae%3ay')
|
||||
|
||||
def test_allowed_sync_hosts(self):
|
||||
a = auth.filter_factory({'super_admin_key': 'supertest'})(FakeApp())
|
||||
self.assertEquals(a.allowed_sync_hosts, ['127.0.0.1'])
|
||||
a = auth.filter_factory({'super_admin_key': 'supertest',
|
||||
'allowed_sync_hosts':
|
||||
'1.1.1.1,2.1.1.1, 3.1.1.1 , 4.1.1.1,, , 5.1.1.1'})(FakeApp())
|
||||
self.assertEquals(a.allowed_sync_hosts,
|
||||
['1.1.1.1', '2.1.1.1', '3.1.1.1', '4.1.1.1', '5.1.1.1'])
|
||||
|
||||
def test_reseller_admin_is_owner(self):
|
||||
orig_authorize = self.test_auth.authorize
|
||||
owner_values = []
|
||||
|
||||
def mitm_authorize(req):
|
||||
rv = orig_authorize(req)
|
||||
owner_values.append(req.environ.get('swift_owner', False))
|
||||
return rv
|
||||
|
||||
self.test_auth.authorize = mitm_authorize
|
||||
|
||||
self.test_auth.app = FakeApp(iter([
|
||||
('200 Ok', {},
|
||||
json.dumps({'account': 'other', 'user': 'other:usr',
|
||||
'account_id': 'AUTH_other',
|
||||
'groups': [{'name': 'other:usr'}, {'name': 'other'},
|
||||
{'name': '.reseller_admin'}],
|
||||
'expires': time() + 60})),
|
||||
('204 No Content', {}, '')]))
|
||||
req = Request.blank('/v1/AUTH_cfa', headers={'X-Auth-Token': 'AUTH_t'})
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 204)
|
||||
self.assertEquals(owner_values, [True])
|
||||
|
||||
def test_admin_is_owner(self):
|
||||
orig_authorize = self.test_auth.authorize
|
||||
owner_values = []
|
||||
|
||||
def mitm_authorize(req):
|
||||
rv = orig_authorize(req)
|
||||
owner_values.append(req.environ.get('swift_owner', False))
|
||||
return rv
|
||||
|
||||
self.test_auth.authorize = mitm_authorize
|
||||
|
||||
self.test_auth.app = FakeApp(iter([
|
||||
('200 Ok', {},
|
||||
json.dumps({'account': 'act', 'user': 'act:usr',
|
||||
'account_id': 'AUTH_cfa',
|
||||
'groups': [{'name': 'act:usr'}, {'name': 'act'},
|
||||
{'name': '.admin'}],
|
||||
'expires': time() + 60})),
|
||||
('204 No Content', {}, '')]))
|
||||
req = Request.blank('/v1/AUTH_cfa', headers={'X-Auth-Token': 'AUTH_t'})
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 204)
|
||||
self.assertEquals(owner_values, [True])
|
||||
|
||||
def test_regular_is_not_owner(self):
|
||||
orig_authorize = self.test_auth.authorize
|
||||
owner_values = []
|
||||
|
||||
def mitm_authorize(req):
|
||||
rv = orig_authorize(req)
|
||||
owner_values.append(req.environ.get('swift_owner', False))
|
||||
return rv
|
||||
|
||||
self.test_auth.authorize = mitm_authorize
|
||||
|
||||
self.test_auth.app = FakeApp(iter([
|
||||
('200 Ok', {},
|
||||
json.dumps({'account': 'act', 'user': 'act:usr',
|
||||
'account_id': 'AUTH_cfa',
|
||||
'groups': [{'name': 'act:usr'}, {'name': 'act'}],
|
||||
'expires': time() + 60})),
|
||||
('204 No Content', {}, '')]), acl='act:usr')
|
||||
req = Request.blank('/v1/AUTH_cfa/c',
|
||||
headers={'X-Auth-Token': 'AUTH_t'})
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 204)
|
||||
self.assertEquals(owner_values, [False])
|
||||
|
||||
def test_sync_request_success(self):
|
||||
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
|
||||
sync_key='secret')
|
||||
req = Request.blank('/v1/AUTH_cfa/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'x-container-sync-key': 'secret',
|
||||
'x-timestamp': '123.456'})
|
||||
req.remote_addr = '127.0.0.1'
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 204)
|
||||
|
||||
def test_sync_request_fail_key(self):
|
||||
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
|
||||
sync_key='secret')
|
||||
req = Request.blank('/v1/AUTH_cfa/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'x-container-sync-key': 'wrongsecret',
|
||||
'x-timestamp': '123.456'})
|
||||
req.remote_addr = '127.0.0.1'
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
|
||||
sync_key='othersecret')
|
||||
req = Request.blank('/v1/AUTH_cfa/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'x-container-sync-key': 'secret',
|
||||
'x-timestamp': '123.456'})
|
||||
req.remote_addr = '127.0.0.1'
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
|
||||
sync_key=None)
|
||||
req = Request.blank('/v1/AUTH_cfa/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'x-container-sync-key': 'secret',
|
||||
'x-timestamp': '123.456'})
|
||||
req.remote_addr = '127.0.0.1'
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
def test_sync_request_fail_no_timestamp(self):
|
||||
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
|
||||
sync_key='secret')
|
||||
req = Request.blank('/v1/AUTH_cfa/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'x-container-sync-key': 'secret'})
|
||||
req.remote_addr = '127.0.0.1'
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
def test_sync_request_fail_sync_host(self):
|
||||
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
|
||||
sync_key='secret')
|
||||
req = Request.blank('/v1/AUTH_cfa/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'x-container-sync-key': 'secret',
|
||||
'x-timestamp': '123.456'})
|
||||
req.remote_addr = '127.0.0.2'
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
def test_sync_request_success_lb_sync_host(self):
|
||||
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
|
||||
sync_key='secret')
|
||||
req = Request.blank('/v1/AUTH_cfa/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'x-container-sync-key': 'secret',
|
||||
'x-timestamp': '123.456',
|
||||
'x-forwarded-for': '127.0.0.1'})
|
||||
req.remote_addr = '127.0.0.2'
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 204)
|
||||
|
||||
self.test_auth.app = FakeApp(iter([('204 No Content', {}, '')]),
|
||||
sync_key='secret')
|
||||
req = Request.blank('/v1/AUTH_cfa/c/o',
|
||||
environ={'REQUEST_METHOD': 'DELETE'},
|
||||
headers={'x-container-sync-key': 'secret',
|
||||
'x-timestamp': '123.456',
|
||||
'x-cluster-client-ip': '127.0.0.1'})
|
||||
req.remote_addr = '127.0.0.2'
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 204)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
Loading…
Reference in New Issue
Block a user