Merge "Add support to swift_auth for tokenless authz" into milestone-proposed

This commit is contained in:
Jenkins 2012-04-03 19:39:43 +00:00 committed by Gerrit Code Review
commit 0a0513d9fb
3 changed files with 105 additions and 62 deletions

View File

@ -145,6 +145,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

View File

@ -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.

View File

@ -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):