summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaru Newby <mnewby@internap.com>2012-03-20 22:19:36 -0700
committerMaru Newby <mnewby@internap.com>2012-03-30 17:26:54 -0700
commit6ec1782dcc13b77eba14d7ff1ace6c9bca997dc5 (patch)
tree8141670f61b3985bf90a25d6b72c8a7012ed327a
parentf9c787148747c75f739d9ca555fa26c541752344 (diff)
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
-rw-r--r--doc/source/configuringservices.rst3
-rw-r--r--keystone/middleware/swift_auth.py106
-rw-r--r--tests/test_swift_auth_middleware.py56
3 files changed, 104 insertions, 61 deletions
diff --git a/doc/source/configuringservices.rst b/doc/source/configuringservices.rst
index 3782f360..2b0a5d2c 100644
--- a/doc/source/configuringservices.rst
+++ b/doc/source/configuringservices.rst
@@ -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
diff --git a/keystone/middleware/swift_auth.py b/keystone/middleware/swift_auth.py
index df62a834..b5f0a9b1 100644
--- a/keystone/middleware/swift_auth.py
+++ b/keystone/middleware/swift_auth.py
@@ -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.
diff --git a/tests/test_swift_auth_middleware.py b/tests/test_swift_auth_middleware.py
index 04f806bb..2a79d0ee 100644
--- a/tests/test_swift_auth_middleware.py
+++ b/tests/test_swift_auth_middleware.py
@@ -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)
-
- 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)
+ resp = req.get_response(self._get_successful_middleware())
+ self.assertEqual(resp.status_int, 200)
- 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)
+ def test_confirmed_identity_is_not_authorized(self):
+ headers = self._get_identity_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)
- #NOTE(chmou): This should fail when we are going to add anonymous
- #access back.
- def test_default_forbidden(self):
- headers = self._get_identity_headers()
- req = self._make_request('/v1/AUTH_acct', headers)
+ 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.assertEquals(resp.status_int, 403)
+ 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):