tempauth: Add .reseller_reader group

Change-Id: I8c5197ed327fbb175c8a2c0e788b1ae14e6dfe23
This commit is contained in:
Tim Burke
2021-02-08 12:37:17 -08:00
parent 0c072e244c
commit cf4f320644
3 changed files with 75 additions and 8 deletions

View File

@@ -388,8 +388,9 @@ use = egg:swift#tempauth
# user64_<account_b64>_<user_b64> = <key> [group] [group] [...] [storage_url] # user64_<account_b64>_<user_b64> = <key> [group] [group] [...] [storage_url]
# There are special groups of: # There are special groups of:
# .reseller_admin = can do anything to any account for this auth # .reseller_admin = can do anything to any account for this auth
# .reseller_reader = can GET/HEAD anything in any account for this auth
# .admin = can do anything within the account # .admin = can do anything within the account
# If neither of these groups are specified, the user can only access containers # If none of these groups are specified, the user can only access containers
# that have been explicitly allowed for them by a .admin or .reseller_admin. # that have been explicitly allowed for them by a .admin or .reseller_admin.
# The trailing optional storage_url allows you to specify an alternate url to # The trailing optional storage_url allows you to specify an alternate url to
# hand back to the user upon authentication. If not specified, this defaults to # hand back to the user upon authentication. If not specified, this defaults to
@@ -397,6 +398,7 @@ use = egg:swift#tempauth
# to what the requester would need to use to reach this host. # to what the requester would need to use to reach this host.
# Here are example entries, required for running the tests: # Here are example entries, required for running the tests:
user_admin_admin = admin .admin .reseller_admin user_admin_admin = admin .admin .reseller_admin
user_admin_auditor = admin_ro .reseller_reader
user_test_tester = testing .admin user_test_tester = testing .admin
user_test_tester2 = testing2 .admin user_test_tester2 = testing2 .admin
user_test_tester3 = testing3 user_test_tester3 = testing3

View File

@@ -54,12 +54,13 @@ in a line like this::
user64_<account_b64>_<user_b64> = <key> [group] [...] [storage_url] user64_<account_b64>_<user_b64> = <key> [group] [...] [storage_url]
There are two special groups: There are three special groups:
* ``.reseller_admin`` -- can do anything to any account for this auth * ``.reseller_admin`` -- can do anything to any account for this auth
* ``.reseller_reader`` -- can GET/HEAD anything in any account for this auth
* ``.admin`` -- can do anything within the account * ``.admin`` -- can do anything within the account
If neither of these groups are specified, the user can only access If none of these groups are specified, the user can only access
containers that have been explicitly allowed for them by a ``.admin`` or containers that have been explicitly allowed for them by a ``.admin`` or
``.reseller_admin``. ``.reseller_admin``.
@@ -124,8 +125,8 @@ and ``X-Service-Token`` is from the ``glance`` user::
user_maryacct_mary = marypw .admin user_maryacct_mary = marypw .admin
user_glance_glance = glancepw .service user_glance_glance = glancepw .service
The name ``.service`` is an example. Unlike ``.admin`` and The name ``.service`` is an example. Unlike ``.admin``, ``.reseller_admin``,
``.reseller_admin`` it is not a reserved name. ``.reseller_reader`` it is not a reserved name.
Please note that ACLs can be set on service accounts and are matched 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 against the identity validated by ``X-Auth-Token``. As such ACLs can grant
@@ -569,6 +570,14 @@ class TempAuth(object):
% account_user) % account_user)
return None return None
if '.reseller_reader' in user_groups and \
account not in self.reseller_prefixes and \
not self._dot_account(account) and \
req.method in ('GET', 'HEAD'):
self.logger.debug("User %s has reseller reader authorizing."
% account_user)
return None
if wsgi_to_str(account) in user_groups and \ if wsgi_to_str(account) in user_groups and \
(req.method not in ('DELETE', 'PUT') or container): (req.method not in ('DELETE', 'PUT') or container):
# The user is admin for the account and is not trying to do an # The user is admin for the account and is not trying to do an

View File

@@ -537,7 +537,7 @@ class TestAuth(unittest.TestCase):
def test_account_put_permissions(self): def test_account_put_permissions(self):
self.test_auth = auth.filter_factory({})( self.test_auth = auth.filter_factory({})(
FakeApp(iter(NO_CONTENT_RESP * 4))) FakeApp(iter(NO_CONTENT_RESP * 5)))
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'PUT'}) environ={'REQUEST_METHOD': 'PUT'})
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
@@ -563,6 +563,12 @@ class TestAuth(unittest.TestCase):
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertIsNone(resp) self.assertIsNone(resp)
req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'PUT'})
req.remote_user = 'act:usr,act,.reseller_reader'
resp = self.test_auth.authorize(req)
self.assertEqual(resp.status_int, 403)
# .super_admin is not something the middleware should ever see or care # .super_admin is not something the middleware should ever see or care
# about # about
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
@@ -573,7 +579,7 @@ class TestAuth(unittest.TestCase):
def test_account_delete_permissions(self): def test_account_delete_permissions(self):
self.test_auth = auth.filter_factory({})( self.test_auth = auth.filter_factory({})(
FakeApp(iter(NO_CONTENT_RESP * 4))) FakeApp(iter(NO_CONTENT_RESP * 5)))
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'DELETE'}) environ={'REQUEST_METHOD': 'DELETE'})
req.remote_user = 'act:usr,act' req.remote_user = 'act:usr,act'
@@ -599,6 +605,12 @@ class TestAuth(unittest.TestCase):
resp = self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertIsNone(resp) self.assertIsNone(resp)
req = self._make_request('/v1/AUTH_new',
environ={'REQUEST_METHOD': 'DELETE'})
req.remote_user = 'act:usr,act,.reseller_reader'
resp = self.test_auth.authorize(req)
self.assertEqual(resp.status_int, 403)
# .super_admin is not something the middleware should ever see or care # .super_admin is not something the middleware should ever see or care
# about # about
req = self._make_request('/v1/AUTH_new', req = self._make_request('/v1/AUTH_new',
@@ -824,9 +836,18 @@ class TestAuth(unittest.TestCase):
req = self._make_request('/v1/AUTH_cfa', req = self._make_request('/v1/AUTH_cfa',
headers={'X-Auth-Token': 'AUTH_t'}) headers={'X-Auth-Token': 'AUTH_t'})
req.remote_user = '.reseller_admin' req.remote_user = '.reseller_admin'
self.test_auth.authorize(req) resp = self.test_auth.authorize(req)
self.assertIsNone(resp)
self.assertEqual(owner_values, [True]) self.assertEqual(owner_values, [True])
owner_values = []
req = self._make_request('/v1/AUTH_cfa',
headers={'X-Auth-Token': 'AUTH_t'})
req.remote_user = '.reseller_reader'
resp = self.test_auth.authorize(req)
self.assertIsNone(resp)
self.assertEqual(owner_values, [False])
def test_admin_is_owner(self): def test_admin_is_owner(self):
orig_authorize = self.test_auth.authorize orig_authorize = self.test_auth.authorize
owner_values = [] owner_values = []
@@ -1172,12 +1193,17 @@ class TestParseUserCreation(unittest.TestCase):
'user_test_tester3': 'testing', 'user_test_tester3': 'testing',
'user_has_url': 'urlly .admin http://a.b/v1/DEF_has', 'user_has_url': 'urlly .admin http://a.b/v1/DEF_has',
'user_admin_admin': 'admin .admin .reseller_admin', 'user_admin_admin': 'admin .admin .reseller_admin',
'user_admin_auditor': 'admin_ro .reseller_reader',
})(FakeApp()) })(FakeApp())
self.assertEqual(auth_filter.users, { self.assertEqual(auth_filter.users, {
'admin:admin': { 'admin:admin': {
'url': '$HOST/v1/ABC_admin', 'url': '$HOST/v1/ABC_admin',
'groups': ['.admin', '.reseller_admin'], 'groups': ['.admin', '.reseller_admin'],
'key': 'admin' 'key': 'admin'
}, 'admin:auditor': {
'url': '$HOST/v1/ABC_admin',
'groups': ['.reseller_reader'],
'key': 'admin_ro'
}, 'test:tester3': { }, 'test:tester3': {
'url': '$HOST/v1/ABC_test', 'url': '$HOST/v1/ABC_test',
'groups': [], 'groups': [],
@@ -1612,6 +1638,16 @@ class ServiceTokenFunctionality(unittest.TestCase):
{'reseller_prefix': 'AUTH'}, 'acct:joe,acct,AUTH_acct', {'reseller_prefix': 'AUTH'}, 'acct:joe,acct,AUTH_acct',
'/v1/AUTH_acct/c', method='PUT') '/v1/AUTH_acct/c', method='PUT')
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'},
'admin:mary,admin,AUTH_admin,.reseller_reader',
'/v1/AUTH_acct', method='GET')
self.assertEqual(resp.status_int, 200)
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'},
'admin:mary,admin,AUTH_admin,.reseller_reader',
'/v1/AUTH_acct/c', method='GET')
self.assertEqual(resp.status_int, 200)
resp = self._make_authed_request( resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'}, {'reseller_prefix': 'AUTH'},
'admin:mary,admin,AUTH_admin,.reseller_admin', 'admin:mary,admin,AUTH_admin,.reseller_admin',
@@ -1641,6 +1677,26 @@ class ServiceTokenFunctionality(unittest.TestCase):
'/v1/AUTH_acct', '/v1/AUTH_acct',
method='DELETE') method='DELETE')
self.assertEqual(resp.status_int, 403) self.assertEqual(resp.status_int, 403)
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'},
'admin:mary,admin,.admin,.reseller_reader',
'/v1/AUTH_acct', method='PUT')
self.assertEqual(resp.status_int, 403)
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'},
'admin:mary,admin,.admin,.reseller_reader',
'/v1/AUTH_acct', method='DELETE')
self.assertEqual(resp.status_int, 403)
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'},
'admin:mary,admin,.admin,.reseller_reader',
'/v1/AUTH_acct/c', method='PUT')
self.assertEqual(resp.status_int, 403)
resp = self._make_authed_request(
{'reseller_prefix': 'AUTH'},
'admin:mary,admin,.admin,.reseller_reader',
'/v1/AUTH_acct/c', method='DELETE')
self.assertEqual(resp.status_int, 403)
def test_authed_for_primary_path_multiple(self): def test_authed_for_primary_path_multiple(self):
resp = self._make_authed_request( resp = self._make_authed_request(