Add support to swift_auth for tokenless authz
* Updates keystone.middleware.swift_auth to allow token-less (unauthenticated) access for container sync (bug 954030) and permitted referrers (bug 924578). Change-Id: Ieccf458c44dfe55f546dc15c79704800dad59ac0
This commit is contained in:
parent
f9c7871487
commit
6ec1782dcc
@ -201,6 +201,9 @@ rather than it's built in 'tempauth'.
|
||||
|
||||
[filter:authtoken]
|
||||
paste.filter_factory = keystone.middleware.auth_token:filter_factory
|
||||
# Delaying the auth decision is required to support token-less
|
||||
# usage for anonymous referrers ('.r:*').
|
||||
delay_auth_decision = true
|
||||
service_port = 5000
|
||||
service_host = 127.0.0.1
|
||||
auth_port = 35357
|
||||
|
@ -44,9 +44,12 @@ class SwiftAuth(object):
|
||||
pipeline = catch_errors cache authtoken swiftauth proxy-server
|
||||
|
||||
Make sure you have the authtoken middleware before the swiftauth
|
||||
middleware. The authtoken will take care of validating the user
|
||||
and swiftauth middleware will authorize it. See the documentation
|
||||
about how to configure the authtoken middleware.
|
||||
middleware. authtoken will take care of validating the user and
|
||||
swiftauth will authorize access. If support is required for
|
||||
unvalidated users (as with anonymous access), authtoken will need
|
||||
to be configured with delay_auth_decision set to true. See the
|
||||
documentation for more detail on how to configure the authtoken
|
||||
middleware.
|
||||
|
||||
Set account auto creation to true::
|
||||
|
||||
@ -95,15 +98,17 @@ class SwiftAuth(object):
|
||||
def __call__(self, environ, start_response):
|
||||
identity = self._keystone_identity(environ)
|
||||
|
||||
if not identity:
|
||||
environ['swift.authorize'] = self.denied_response
|
||||
return self.app(environ, start_response)
|
||||
if identity:
|
||||
self.logger.debug('Using identity: %r' % (identity))
|
||||
environ['keystone.identity'] = identity
|
||||
environ['REMOTE_USER'] = identity.get('tenant')
|
||||
environ['swift.authorize'] = self.authorize
|
||||
else:
|
||||
self.logger.debug('Authorizing as anonymous')
|
||||
environ['swift.authorize'] = self.authorize_anonymous
|
||||
|
||||
self.logger.debug("Using identity: %r" % (identity))
|
||||
environ['keystone.identity'] = identity
|
||||
environ['REMOTE_USER'] = identity.get('tenant')
|
||||
environ['swift.authorize'] = self.authorize
|
||||
environ['swift.clean_acl'] = swift_acl.clean_acl
|
||||
|
||||
return self.app(environ, start_response)
|
||||
|
||||
def _keystone_identity(self, environ):
|
||||
@ -119,9 +124,12 @@ class SwiftAuth(object):
|
||||
'roles': roles}
|
||||
return identity
|
||||
|
||||
def _get_account_for_tenant(self, tenant_id):
|
||||
return '%s%s' % (self.reseller_prefix, tenant_id)
|
||||
|
||||
def _reseller_check(self, account, tenant_id):
|
||||
"""Check reseller prefix."""
|
||||
return account == '%s%s' % (self.reseller_prefix, tenant_id)
|
||||
return account == self._get_account_for_tenant(tenant_id)
|
||||
|
||||
def authorize(self, req):
|
||||
env = req.environ
|
||||
@ -169,26 +177,13 @@ class SwiftAuth(object):
|
||||
req.environ['swift_owner'] = True
|
||||
return
|
||||
|
||||
# Allow container sync.
|
||||
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 swift_utils.get_remote_client(req)
|
||||
in self.allowed_sync_hosts)):
|
||||
log_msg = 'allowing proxy %s for container-sync' % req.remote_addr
|
||||
self.logger.debug(log_msg)
|
||||
return
|
||||
|
||||
# Check if referrer is allowed.
|
||||
referrers, roles = swift_acl.parse_acl(getattr(req, 'acl', None))
|
||||
if swift_acl.referrer_allowed(req.referer, referrers):
|
||||
#TODO(chmou): convert .rlistings to Keystone type role.
|
||||
if obj or '.rlistings' in roles:
|
||||
log_msg = 'authorizing %s via referer ACL' % req.referrer
|
||||
self.logger.debug(log_msg)
|
||||
return
|
||||
|
||||
authorized = self._authorize_unconfirmed_identity(req, obj, referrers,
|
||||
roles)
|
||||
if authorized:
|
||||
return
|
||||
elif authorized is not None:
|
||||
return self.denied_response(req)
|
||||
|
||||
# Allow ACL at individual user level (tenant:user format)
|
||||
@ -206,6 +201,57 @@ class SwiftAuth(object):
|
||||
|
||||
return self.denied_response(req)
|
||||
|
||||
def authorize_anonymous(self, req):
|
||||
"""
|
||||
Authorize an anonymous request.
|
||||
|
||||
:returns: None if authorization is granted, an error page otherwise.
|
||||
"""
|
||||
try:
|
||||
part = swift_utils.split_path(req.path, 1, 4, True)
|
||||
version, account, container, obj = part
|
||||
except ValueError:
|
||||
return webob.exc.HTTPNotFound(request=req)
|
||||
|
||||
is_authoritative_authz = (account and
|
||||
account.startswith(self.reseller_prefix))
|
||||
if not is_authoritative_authz:
|
||||
return self.denied_response(req)
|
||||
|
||||
referrers, roles = swift_acl.parse_acl(getattr(req, 'acl', None))
|
||||
authorized = self._authorize_unconfirmed_identity(req, obj, referrers,
|
||||
roles)
|
||||
if not authorized:
|
||||
return self.denied_response(req)
|
||||
|
||||
def _authorize_unconfirmed_identity(self, req, obj, referrers, roles):
|
||||
""""
|
||||
Perform authorization for access that does not require a
|
||||
confirmed identity.
|
||||
|
||||
:returns: A boolean if authorization is granted or denied. None if
|
||||
a determination could not be made.
|
||||
"""
|
||||
# Allow container sync.
|
||||
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 swift_utils.get_remote_client(req)
|
||||
in self.allowed_sync_hosts)):
|
||||
log_msg = 'allowing proxy %s for container-sync' % req.remote_addr
|
||||
self.logger.debug(log_msg)
|
||||
return True
|
||||
|
||||
# Check if referrer is allowed.
|
||||
if swift_acl.referrer_allowed(req.referer, referrers):
|
||||
if obj or '.rlistings' in roles:
|
||||
log_msg = 'authorizing %s via referer ACL' % req.referrer
|
||||
self.logger.debug(log_msg)
|
||||
return True
|
||||
return False
|
||||
|
||||
def denied_response(self, req):
|
||||
"""Deny WSGI Response.
|
||||
|
||||
|
@ -20,21 +20,15 @@ from keystone.middleware import swift_auth
|
||||
|
||||
|
||||
class FakeApp(object):
|
||||
def __init__(self, status_headers_body_iter=None, acl=None, sync_key=None):
|
||||
def __init__(self, status_headers_body_iter=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 = webob.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:
|
||||
@ -48,7 +42,9 @@ class SwiftAuth(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.test_auth = swift_auth.filter_factory({})(FakeApp())
|
||||
|
||||
def _make_request(self, path, headers=None, **kwargs):
|
||||
def _make_request(self, path=None, headers=None, **kwargs):
|
||||
if not path:
|
||||
path = '/v1/%s/c/o' % self.test_auth._get_account_for_tenant('foo')
|
||||
return webob.Request.blank(path, headers=headers, **kwargs)
|
||||
|
||||
def _get_identity_headers(self, status='Confirmed', tenant_id='1',
|
||||
@ -59,35 +55,34 @@ class SwiftAuth(unittest.TestCase):
|
||||
X_ROLE=role,
|
||||
X_USER=user)
|
||||
|
||||
def _get_successful_middleware(self):
|
||||
response_iter = iter([('200 OK', {}, '')])
|
||||
return swift_auth.filter_factory({})(FakeApp(response_iter))
|
||||
|
||||
def test_confirmed_identity_is_authorized(self):
|
||||
role = self.test_auth.reseller_admin_role
|
||||
headers = self._get_identity_headers(role=role)
|
||||
req = self._make_request('/v1/AUTH_acct/c', headers)
|
||||
response_iter = iter([('200 OK', {}, '')])
|
||||
test_auth = swift_auth.filter_factory({})(
|
||||
FakeApp(response_iter))
|
||||
resp = req.get_response(test_auth)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
resp = req.get_response(self._get_successful_middleware())
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
|
||||
def test_invalid_identity_is_not_authorized(self):
|
||||
headers = self._get_identity_headers(status='Invalid')
|
||||
req = self._make_request('/v1/AUTH_acct', headers)
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
def test_auth_deny_token_not_for_account(self):
|
||||
headers = self._get_identity_headers(role='AUTH_acct')
|
||||
req = self._make_request('/v1/AUTH_1', headers)
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
#NOTE(chmou): This should fail when we are going to add anonymous
|
||||
#access back.
|
||||
def test_default_forbidden(self):
|
||||
def test_confirmed_identity_is_not_authorized(self):
|
||||
headers = self._get_identity_headers()
|
||||
req = self._make_request('/v1/AUTH_acct', headers)
|
||||
req = self._make_request('/v1/AUTH_acct/c', headers)
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
self.assertEqual(resp.status_int, 403)
|
||||
|
||||
def test_anonymous_is_authorized_for_permitted_referrer(self):
|
||||
req = self._make_request(headers={'X_IDENTITY_STATUS': 'Invalid'})
|
||||
req.acl = '.r:*'
|
||||
resp = req.get_response(self._get_successful_middleware())
|
||||
self.assertEqual(resp.status_int, 200)
|
||||
|
||||
def test_anonymous_is_not_authorized_for_unknown_reseller_prefix(self):
|
||||
req = self._make_request(path='/v1/BLAH_foo/c/o',
|
||||
headers={'X_IDENTITY_STATUS': 'Invalid'})
|
||||
resp = req.get_response(self.test_auth)
|
||||
self.assertEqual(resp.status_int, 401)
|
||||
|
||||
def test_blank_reseller_prefix(self):
|
||||
conf = {'reseller_prefix': ''}
|
||||
@ -106,8 +101,7 @@ class TestAuthorize(unittest.TestCase):
|
||||
def _get_account(self, identity=None):
|
||||
if not identity:
|
||||
identity = self._get_identity()
|
||||
return '%s%s' % (self.test_auth.reseller_prefix,
|
||||
identity['tenant'][0])
|
||||
return self.test_auth._get_account_for_tenant(identity['tenant'][0])
|
||||
|
||||
def _get_identity(self, tenant_id='tenant_id',
|
||||
tenant_name='tenant_name', user='user', roles=None):
|
||||
|
Loading…
x
Reference in New Issue
Block a user