Fix copy requests to service accounts in Keystone
In case of a COPY request the swift_owner was already set to True, and the following PUT request was granted access no matter if a service token was used or not. This allowed to copy data to service accounts without any service token. Service token unit tests have been added to verify that when swift_owner is set to True in a request environ, this setting is ignored when authorizing another request based on the same environ. Applying only this test change on master fails currently, and only passes with the fix in this patch. Tempauth seems to be not affected, however a small doc update has been added to make it more clear that a service token is not needed to access a service account when an ACL is used. Further details with an example are available in the bug report (https://bugs.launchpad.net/swift/+bug/1483007). Co-Authored-By: Alistair Coles <alistair.coles@hp.com> Co-Authored-By: Hisashi Osanai <osanai.hisashi@jp.fujitsu.com> Co-Authored-By: Donagh McCabe <donagh.mccabe@hp.com> Closes-Bug: 1483007 Change-Id: I1207b911f018b855362b1078f68c38615be74bbd
This commit is contained in:
parent
25d5e686a1
commit
4b8f52b153
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user