set is_admin
on tokens for admin project
Adds two new configuration value: admin_project_name admin_project_domain_name If both values are set, and tokens requested for projects (only, not domains) that match both will have an additional value in them; `is_admin_project=true` DocImpact -- Configuration changes need documentation APIImpact -- Adds optional return values in token validation calls SecurityImpact -- Should be helpful in making access control decisions Implements: blueprint is-admin-project Partial-Bug: #968696 Change-Id: Ic9cf9862739381a30130b4be87075f726736ff88
This commit is contained in:
parent
933e118eee
commit
e7023697a8
@ -386,6 +386,17 @@ FILE_OPTIONS = {
|
|||||||
group='assignment')],
|
group='assignment')],
|
||||||
help='Maximum number of entities that will be returned '
|
help='Maximum number of entities that will be returned '
|
||||||
'in a resource collection.'),
|
'in a resource collection.'),
|
||||||
|
cfg.StrOpt('admin_project_domain_name',
|
||||||
|
help='Name of the domain that contains the special '
|
||||||
|
'project for performing administrative operations on '
|
||||||
|
'remote services. Tokens scoped to this project will '
|
||||||
|
'contain the key/value `is_admin_project=true`. Defaults '
|
||||||
|
'to None.'),
|
||||||
|
cfg.StrOpt('admin_project_name',
|
||||||
|
help='Special project for performing administrative '
|
||||||
|
'operations on remote services. Tokens scoped to '
|
||||||
|
'this project will contain the key/value '
|
||||||
|
'`is_admin_project=true`. Defaults to None.'),
|
||||||
],
|
],
|
||||||
'domain_config': [
|
'domain_config': [
|
||||||
cfg.StrOpt('driver',
|
cfg.StrOpt('driver',
|
||||||
|
@ -572,6 +572,7 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase,
|
|||||||
require_catalog = kwargs.pop('require_catalog', True)
|
require_catalog = kwargs.pop('require_catalog', True)
|
||||||
endpoint_filter = kwargs.pop('endpoint_filter', False)
|
endpoint_filter = kwargs.pop('endpoint_filter', False)
|
||||||
ep_filter_assoc = kwargs.pop('ep_filter_assoc', 0)
|
ep_filter_assoc = kwargs.pop('ep_filter_assoc', 0)
|
||||||
|
is_admin_project = kwargs.pop('is_admin_project', False)
|
||||||
token = self.assertValidTokenResponse(r, *args, **kwargs)
|
token = self.assertValidTokenResponse(r, *args, **kwargs)
|
||||||
|
|
||||||
if require_catalog:
|
if require_catalog:
|
||||||
@ -599,6 +600,11 @@ class RestfulTestCase(unit.SQLDriverOverrides, rest.RestfulTestCase,
|
|||||||
self.assertIn('id', role)
|
self.assertIn('id', role)
|
||||||
self.assertIn('name', role)
|
self.assertIn('name', role)
|
||||||
|
|
||||||
|
if is_admin_project:
|
||||||
|
self.assertIs(True, token['is_admin_project'])
|
||||||
|
else:
|
||||||
|
self.assertNotIn('is_admin_project', token)
|
||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def assertValidProjectScopedTokenResponse(self, r, *args, **kwargs):
|
def assertValidProjectScopedTokenResponse(self, r, *args, **kwargs):
|
||||||
|
@ -413,6 +413,76 @@ class TokenAPITests(object):
|
|||||||
headers={'X-Subject-Token': v3_token})
|
headers={'X-Subject-Token': v3_token})
|
||||||
self.assertValidProjectScopedTokenResponse(r, require_catalog=False)
|
self.assertValidProjectScopedTokenResponse(r, require_catalog=False)
|
||||||
|
|
||||||
|
def test_is_admin_token_by_ids(self):
|
||||||
|
self.config_fixture.config(
|
||||||
|
group='resource',
|
||||||
|
admin_project_domain_name=self.domain['name'],
|
||||||
|
admin_project_name=self.project['name'])
|
||||||
|
r = self.v3_create_token(self.build_authentication_request(
|
||||||
|
user_id=self.user['id'],
|
||||||
|
password=self.user['password'],
|
||||||
|
project_id=self.project['id']))
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=True)
|
||||||
|
v3_token = r.headers.get('X-Subject-Token')
|
||||||
|
r = self.get('/auth/tokens', headers={'X-Subject-Token': v3_token})
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=True)
|
||||||
|
|
||||||
|
def test_is_admin_token_by_names(self):
|
||||||
|
self.config_fixture.config(
|
||||||
|
group='resource',
|
||||||
|
admin_project_domain_name=self.domain['name'],
|
||||||
|
admin_project_name=self.project['name'])
|
||||||
|
r = self.v3_create_token(self.build_authentication_request(
|
||||||
|
user_id=self.user['id'],
|
||||||
|
password=self.user['password'],
|
||||||
|
project_domain_name=self.domain['name'],
|
||||||
|
project_name=self.project['name']))
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=True)
|
||||||
|
v3_token = r.headers.get('X-Subject-Token')
|
||||||
|
r = self.get('/auth/tokens', headers={'X-Subject-Token': v3_token})
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=True)
|
||||||
|
|
||||||
|
def test_token_for_non_admin_project_is_not_admin(self):
|
||||||
|
self.config_fixture.config(
|
||||||
|
group='resource',
|
||||||
|
admin_project_domain_name=self.domain['name'],
|
||||||
|
admin_project_name=uuid.uuid4().hex)
|
||||||
|
r = self.v3_create_token(self.build_authentication_request(
|
||||||
|
user_id=self.user['id'],
|
||||||
|
password=self.user['password'],
|
||||||
|
project_id=self.project['id']))
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=False)
|
||||||
|
v3_token = r.headers.get('X-Subject-Token')
|
||||||
|
r = self.get('/auth/tokens', headers={'X-Subject-Token': v3_token})
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=False)
|
||||||
|
|
||||||
|
def test_token_for_non_admin_domain_same_project_name_is_not_admin(self):
|
||||||
|
self.config_fixture.config(
|
||||||
|
group='resource',
|
||||||
|
admin_project_domain_name=uuid.uuid4().hex,
|
||||||
|
admin_project_name=self.project['name'])
|
||||||
|
r = self.v3_create_token(self.build_authentication_request(
|
||||||
|
user_id=self.user['id'],
|
||||||
|
password=self.user['password'],
|
||||||
|
project_id=self.project['id']))
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=False)
|
||||||
|
v3_token = r.headers.get('X-Subject-Token')
|
||||||
|
r = self.get('/auth/tokens', headers={'X-Subject-Token': v3_token})
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=False)
|
||||||
|
|
||||||
|
def test_only_admin_project_set_acts_as_non_admin(self):
|
||||||
|
self.config_fixture.config(
|
||||||
|
group='resource',
|
||||||
|
admin_project_name=self.project['name'])
|
||||||
|
r = self.v3_create_token(self.build_authentication_request(
|
||||||
|
user_id=self.user['id'],
|
||||||
|
password=self.user['password'],
|
||||||
|
project_id=self.project['id']))
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=False)
|
||||||
|
v3_token = r.headers.get('X-Subject-Token')
|
||||||
|
r = self.get('/auth/tokens', headers={'X-Subject-Token': v3_token})
|
||||||
|
self.assertValidProjectScopedTokenResponse(r, is_admin_project=False)
|
||||||
|
|
||||||
|
|
||||||
class AllowRescopeScopedTokenDisabledTests(test_v3.RestfulTestCase):
|
class AllowRescopeScopedTokenDisabledTests(test_v3.RestfulTestCase):
|
||||||
def config_overrides(self):
|
def config_overrides(self):
|
||||||
|
@ -253,6 +253,16 @@ class V3TokenDataHelper(object):
|
|||||||
return filtered_project
|
return filtered_project
|
||||||
|
|
||||||
def _populate_scope(self, token_data, domain_id, project_id):
|
def _populate_scope(self, token_data, domain_id, project_id):
|
||||||
|
# TODO(ayoung): Support the ability for a project acting as a domain
|
||||||
|
# to be the admin project once the rest of the code for domains
|
||||||
|
# acting as projects is merged. Code will likely be:
|
||||||
|
# (r.admin_project_name == None and project['is_domain'] == True
|
||||||
|
# and project['name'] == r.admin_project_domain_name)
|
||||||
|
def _is_admin_project(project):
|
||||||
|
r = CONF.resource
|
||||||
|
return (project['name'] == r.admin_project_name and
|
||||||
|
project['domain']['name'] == r.admin_project_domain_name)
|
||||||
|
|
||||||
if 'domain' in token_data or 'project' in token_data:
|
if 'domain' in token_data or 'project' in token_data:
|
||||||
# scope already exist, no need to populate it again
|
# scope already exist, no need to populate it again
|
||||||
return
|
return
|
||||||
@ -261,6 +271,8 @@ class V3TokenDataHelper(object):
|
|||||||
token_data['domain'] = self._get_filtered_domain(domain_id)
|
token_data['domain'] = self._get_filtered_domain(domain_id)
|
||||||
if project_id:
|
if project_id:
|
||||||
token_data['project'] = self._get_filtered_project(project_id)
|
token_data['project'] = self._get_filtered_project(project_id)
|
||||||
|
if _is_admin_project(token_data['project']):
|
||||||
|
token_data['is_admin_project'] = True
|
||||||
|
|
||||||
def _get_roles_for_user(self, user_id, domain_id, project_id):
|
def _get_roles_for_user(self, user_id, domain_id, project_id):
|
||||||
roles = []
|
roles = []
|
||||||
|
14
releasenotes/notes/is-admin-24b34238c83b3a82.yaml
Normal file
14
releasenotes/notes/is-admin-24b34238c83b3a82.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- >
|
||||||
|
[`bug 96869 <https://bugs.launchpad.net/keystone/+bug/968696>`_]
|
||||||
|
A pair of configuration options have been added to the ``[resource]``
|
||||||
|
section to specify a special ``admin`` project:
|
||||||
|
``admin_project_domain_name`` and ``admin_project_name``. If these are
|
||||||
|
defined, any scoped token issued for that project will have an additional
|
||||||
|
identifier ``is_admin_project`` added to the token. This identifier can then
|
||||||
|
be checked by the policy rules in the policy files of the services when
|
||||||
|
evaluating access control policy for an API. Keystone does not yet
|
||||||
|
support the ability for a project acting as a domain to be the
|
||||||
|
admin project. That will be added once the rest of the code for
|
||||||
|
domains acting as projects is merged.
|
Loading…
Reference in New Issue
Block a user