Merge "Fix copy requests to service accounts in Keystone"

This commit is contained in:
Jenkins 2015-10-02 03:10:18 +00:00 committed by Gerrit Code Review
commit fc7c8c23c9
3 changed files with 73 additions and 29 deletions

View File

@ -129,12 +129,12 @@ class KeystoneAuth(object):
SERVICE_operator_roles = admin, swiftoperator
SERVICE_service_roles = service
The keystoneauth middleware supports cross-tenant access control using
the syntax ``<tenant>:<user>`` to specify a grantee in container Access
Control Lists (ACLs). For a request to be granted by an ACL, the grantee
The keystoneauth middleware supports cross-tenant access control using the
syntax ``<tenant>:<user>`` to specify a grantee in container Access Control
Lists (ACLs). For a request to be granted by an ACL, the grantee
``<tenant>`` must match the UUID of the tenant to which the request
token is scoped and the grantee ``<user>`` must match the UUID of the
user authenticated by the request token.
X-Auth-Token is scoped and the grantee ``<user>`` must match the UUID of
the user authenticated by that token.
Note that names must no longer be used in cross-tenant ACLs because with
the introduction of domains in keystone names are no longer globally
@ -143,7 +143,7 @@ class KeystoneAuth(object):
For backwards compatibility, ACLs using names will be granted by
keystoneauth when it can be established that the grantee tenant,
the grantee user and the tenant being accessed are either not yet in a
domain (e.g. the request token has been obtained via the keystone v2
domain (e.g. the X-Auth-Token has been obtained via the keystone v2
API) or are all in the default domain to which legacy accounts would
have been migrated. The default domain is identified by its UUID,
which by default has the value ``default``. This can be changed by
@ -406,6 +406,10 @@ class KeystoneAuth(object):
return None
def authorize(self, env_identity, req):
# Cleanup - make sure that a previously set swift_owner setting is
# cleared now. This might happen for example with COPY requests.
req.environ.pop('swift_owner', None)
tenant_id, tenant_name = env_identity['tenant']
user_id, user_name = env_identity['user']
referrers, roles = swift_acl.parse_acl(getattr(req, 'acl', None))
@ -473,6 +477,7 @@ class KeystoneAuth(object):
# in operator_roles? service_roles? in service_roles? swift_owner?
# ------------------ -------------- -------------------- ------------
# yes yes yes yes
# yes yes no no
# yes no don't care yes
# no don't care don't care no
# ------------------ -------------- -------------------- ------------
@ -483,14 +488,16 @@ class KeystoneAuth(object):
service_roles = self.account_rules[account_prefix]['service_roles']
have_service_role = set(service_roles).intersection(
set(user_service_roles))
allowed = False
if have_operator_role and (service_roles and have_service_role):
req.environ['swift_owner'] = True
allowed = True
elif have_operator_role and not service_roles:
req.environ['swift_owner'] = True
if req.environ.get('swift_owner'):
allowed = True
if allowed:
log_msg = 'allow user with role(s) %s as account admin'
self.logger.debug(log_msg, ','.join(have_operator_role.union(
have_service_role)))
req.environ['swift_owner'] = True
return
# If user is of the same name of the tenant then make owner of it.

View File

@ -90,29 +90,34 @@ class TempAuth(object):
to access an account. If you have several reseller prefix items, prefix
the ``require_group`` parameter with the appropriate prefix.
X-Service-Token:
X-Service-Token:
If an X-Service-Token is presented in the request headers, the groups
derived from the token are appended to the roles derived form
X-Auth-Token. If X-Auth-Token is missing or invalid, X-Service-Token
is not processed.
If an X-Service-Token is presented in the request headers, the groups
derived from the token are appended to the roles derived from
X-Auth-Token. If X-Auth-Token is missing or invalid, X-Service-Token
is not processed.
The X-Service-Token is useful when combined with multiple reseller prefix
items. In the following configuration, accounts prefixed SERVICE_
are only accessible if X-Auth-Token is form the end-user and
X-Service-Token is from the ``glance`` user::
The X-Service-Token is useful when combined with multiple reseller prefix
items. In the following configuration, accounts prefixed SERVICE_
are only accessible if X-Auth-Token is from the end-user and
X-Service-Token is from the ``glance`` user::
[filter:tempauth]
use = egg:swift#tempauth
reseller_prefix = AUTH, SERVICE
SERVICE_require_group = .service
user_admin_admin = admin .admin .reseller_admin
user_joeacct_joe = joepw .admin
user_maryacct_mary = marypw .admin
user_glance_glance = glancepw .service
[filter:tempauth]
use = egg:swift#tempauth
reseller_prefix = AUTH, SERVICE
SERVICE_require_group = .service
user_admin_admin = admin .admin .reseller_admin
user_joeacct_joe = joepw .admin
user_maryacct_mary = marypw .admin
user_glance_glance = glancepw .service
The name .service is an example. Unlike .admin and .reseller_admin
it is not a reserved name.
The name .service is an example. Unlike .admin and .reseller_admin
it is not a reserved name.
Please note that ACLs can be set on service accounts and are matched
against the identity validated by X-Auth-Token. As such ACLs can grant
access to a service account's container without needing to provide a
service token, just like any other cross-reseller request using ACLs.
Account ACLs:
If a swift_owner issues a POST or PUT to the account, with the

View File

@ -358,7 +358,8 @@ class SwiftAuthMultiple(SwiftAuth):
class ServiceTokenFunctionality(unittest.TestCase):
def _make_authed_request(self, conf, project_id, path, method='GET',
user_role='admin', service_role=None):
user_role='admin', service_role=None,
environ=None):
"""Make a request with keystoneauth as auth
By default, acts as though the user had presented a token
@ -371,6 +372,8 @@ class ServiceTokenFunctionality(unittest.TestCase):
:param method: the method (defaults to GET)
:param user_role: the role of X-Auth-Token (defaults to 'admin')
:param service_role: the role in X-Service-Token (defaults to none)
:param environ: a dict of items to be added to the request environ
(defaults to none)
:returns: response object
"""
@ -381,6 +384,8 @@ class ServiceTokenFunctionality(unittest.TestCase):
_, info_key = _get_cache_key(account, None)
env = {info_key: {'status': 0, 'sysmeta': {}},
'keystone.token_info': _fake_token_info(version='2')}
if environ:
env.update(environ)
req = Request.blank(path, environ=env, headers=headers)
req.method = method
fake_app = FakeApp(iter([('200 OK', {}, '')]))
@ -388,6 +393,33 @@ class ServiceTokenFunctionality(unittest.TestCase):
resp = req.get_response(test_auth)
return resp
def test_existing_swift_owner_ignored(self):
# a request without admin role is denied
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/AUTH_12345678',
environ={'swift_owner': False},
user_role='something_else')
self.assertEqual(resp.status_int, 403)
# ... even when swift_owner has previously been set True in request env
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/AUTH_12345678',
environ={'swift_owner': True},
user_role='something_else')
self.assertEqual(resp.status_int, 403)
# a request with admin role but to different account prefix is denied
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/SERVICE_12345678',
environ={'swift_owner': False})
self.assertEqual(resp.status_int, 403)
# ... even when swift_owner has previously been set True in request env
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'}, '12345678', '/v1/SERVICE_12345678',
environ={'swift_owner': True})
self.assertEqual(resp.status_int, 403)
def test_unknown_prefix(self):
resp = self._make_authed_request({}, '12345678', '/v1/BLAH_12345678')
self.assertEqual(resp.status_int, 403)