summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--keystone/common/policies/token.py82
-rw-r--r--keystone/tests/unit/protection/v3/test_tokens.py526
-rw-r--r--lower-constraints.txt2
-rw-r--r--releasenotes/notes/bug-1750676-cf70c1a27b2c8de3.yaml35
-rw-r--r--requirements.txt2
5 files changed, 614 insertions, 33 deletions
diff --git a/keystone/common/policies/token.py b/keystone/common/policies/token.py
index 49165f42e..6db9913ec 100644
--- a/keystone/common/policies/token.py
+++ b/keystone/common/policies/token.py
@@ -10,54 +10,74 @@
# License for the specific language governing permissions and limitations
# under the License.
+from oslo_log import versionutils
from oslo_policy import policy
from keystone.common.policies import base
+DEPRECATED_REASON = """
+As of the Train release, the token API now understands how to handle
+system-scoped tokens, making the API more accessible to users without
+compromising security or manageability for administrators. This support
+includes a read-only role by default.
+"""
+
+deprecated_check_token = policy.DeprecatedRule(
+ name=base.IDENTITY % 'check_token',
+ check_str=base.RULE_ADMIN_OR_TOKEN_SUBJECT
+)
+deprecated_validate_token = policy.DeprecatedRule(
+ name=base.IDENTITY % 'validate_token',
+ check_str=base.RULE_SERVICE_ADMIN_OR_TOKEN_SUBJECT
+)
+deprecated_revoke_token = policy.DeprecatedRule(
+ name=base.IDENTITY % 'revoke_token',
+ check_str=base.RULE_ADMIN_OR_TOKEN_SUBJECT
+)
+
+SYSTEM_ADMIN_OR_TOKEN_SUBJECT = (
+ '(role:admin and system_scope:all) or rule:token_subject' # nosec
+)
+SYSTEM_USER_OR_TOKEN_SUBJECT = (
+ '(role:reader and system_scope:all) or rule:token_subject' # nosec
+)
+SYSTEM_USER_OR_SERVICE_OR_TOKEN_SUBJECT = (
+ '(role:reader and system_scope:all) ' # nosec
+ 'or rule:service_role or rule:token_subject' # nosec
+)
+
+
token_policies = [
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'check_token',
- check_str=base.RULE_ADMIN_OR_TOKEN_SUBJECT,
- # FIXME(lbragstad): Token validation should be handled within keystone,
- # but it makes sense to have this be a system-level operation and a
- # project-level operation. If this API is called by a system-level
- # administrator, they should be able to check any token. If this API
- # is called by a project administrator, then the token should be
- # checked with respect to the project the administrator has a role on.
- # Otherwise it would be possible for administrators in one project to
- # validate tokens scoped to another project, which is a security
- # concern. Note the following line should be uncommented once keystone
- # supports the ability for project administrators to validate tokens
- # only within their project.
- # scope_types=['system', 'project'],
+ check_str=SYSTEM_USER_OR_TOKEN_SUBJECT,
+ scope_types=['system', 'domain', 'project'],
description='Check a token.',
operations=[{'path': '/v3/auth/tokens',
- 'method': 'HEAD'}]),
+ 'method': 'HEAD'}],
+ deprecated_rule=deprecated_check_token,
+ deprecated_reason=DEPRECATED_REASON,
+ deprecated_since=versionutils.deprecated.TRAIN),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'validate_token',
- check_str=base.RULE_SERVICE_ADMIN_OR_TOKEN_SUBJECT,
- # FIXME(lbragstad): See the comment above about why this is commented
- # out. If this weren't commented out and the `enforce_scope` were set
- # to True, then users with project-scoped tokens would no longer be
- # able to validate them by setting the same token as the X-Auth-Header
- # and X-Subject-Token.
- # scope_types=['system', 'project'],
+ check_str=SYSTEM_USER_OR_SERVICE_OR_TOKEN_SUBJECT,
+ scope_types=['system', 'domain', 'project'],
description='Validate a token.',
operations=[{'path': '/v3/auth/tokens',
- 'method': 'GET'}]),
+ 'method': 'GET'}],
+ deprecated_rule=deprecated_validate_token,
+ deprecated_reason=DEPRECATED_REASON,
+ deprecated_since=versionutils.deprecated.TRAIN),
policy.DocumentedRuleDefault(
name=base.IDENTITY % 'revoke_token',
- check_str=base.RULE_ADMIN_OR_TOKEN_SUBJECT,
- # FIXME(lbragstad): System administrators should be able to revoke any
- # valid token. Project administrators should only be able to invalidate
- # tokens scoped to the project they administer. Users should be able to
- # invalidate their own tokens. If we uncommented this line without
- # adding support for each of these cases in code, we'd be breaking the
- # ability for users to invalidate their own tokens.
- # scope_types=['system', 'project'],
+ check_str=SYSTEM_ADMIN_OR_TOKEN_SUBJECT,
+ scope_types=['system', 'domain', 'project'],
description='Revoke a token.',
operations=[{'path': '/v3/auth/tokens',
- 'method': 'DELETE'}])
+ 'method': 'DELETE'}],
+ deprecated_rule=deprecated_revoke_token,
+ deprecated_reason=DEPRECATED_REASON,
+ deprecated_since=versionutils.deprecated.TRAIN)
]
diff --git a/keystone/tests/unit/protection/v3/test_tokens.py b/keystone/tests/unit/protection/v3/test_tokens.py
new file mode 100644
index 000000000..a4379838e
--- /dev/null
+++ b/keystone/tests/unit/protection/v3/test_tokens.py
@@ -0,0 +1,526 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+
+from six.moves import http_client
+
+from keystone.common import provider_api
+import keystone.conf
+from keystone.tests.common import auth as common_auth
+from keystone.tests import unit
+from keystone.tests.unit import base_classes
+from keystone.tests.unit import ksfixtures
+
+CONF = keystone.conf.CONF
+PROVIDERS = provider_api.ProviderAPIs
+
+
+class _SystemUserTokenTests(object):
+
+ def test_user_can_validate_system_scoped_token(self):
+ user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_system_grant_for_user(
+ user['id'], self.bootstrapper.reader_role_id
+ )
+
+ system_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ system=True
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=system_auth)
+ system_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = system_token
+ c.get('/v3/auth/tokens', headers=self.headers)
+
+ def test_user_can_validate_domain_scoped_token(self):
+ domain = PROVIDERS.resource_api.create_domain(
+ uuid.uuid4().hex, unit.new_domain_ref()
+ )
+
+ user = unit.new_user_ref(domain_id=domain['id'])
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.reader_role_id, user_id=user['id'],
+ domain_id=domain['id']
+ )
+
+ domain_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ domain_id=domain['id']
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=domain_auth)
+ domain_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = domain_token
+ c.get('/v3/auth/tokens', headers=self.headers)
+
+ def test_user_can_validate_project_scoped_token(self):
+ project = PROVIDERS.resource_api.create_project(
+ uuid.uuid4().hex,
+ unit.new_project_ref(domain_id=CONF.identity.default_domain_id)
+ )
+
+ user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.reader_role_id, user_id=user['id'],
+ project_id=project['id']
+ )
+
+ project_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ project_id=project['id']
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=project_auth)
+ project_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = project_token
+ c.get('/v3/auth/tokens', headers=self.headers)
+
+
+class _SystemMemberAndReaderTokenTests(object):
+
+ def test_user_cannot_revoke_a_system_scoped_token(self):
+ user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_system_grant_for_user(
+ user['id'], self.bootstrapper.reader_role_id
+ )
+
+ system_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ system=True
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=system_auth)
+ system_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = system_token
+ c.delete(
+ '/v3/auth/tokens', headers=self.headers,
+ expected_status_code=http_client.FORBIDDEN
+ )
+
+ def test_user_cannot_revoke_a_domain_scoped_token(self):
+ domain = PROVIDERS.resource_api.create_domain(
+ uuid.uuid4().hex, unit.new_domain_ref()
+ )
+
+ user = unit.new_user_ref(domain_id=domain['id'])
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.reader_role_id, user_id=user['id'],
+ domain_id=domain['id']
+ )
+
+ domain_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ domain_id=domain['id']
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=domain_auth)
+ domain_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = domain_token
+ c.delete(
+ '/v3/auth/tokens', headers=self.headers,
+ expected_status_code=http_client.FORBIDDEN
+ )
+
+ def test_user_cannot_revoke_a_project_scoped_token(self):
+ project = PROVIDERS.resource_api.create_project(
+ uuid.uuid4().hex,
+ unit.new_project_ref(domain_id=CONF.identity.default_domain_id)
+ )
+
+ user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.reader_role_id, user_id=user['id'],
+ project_id=project['id']
+ )
+
+ project_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ project_id=project['id']
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=project_auth)
+ project_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = project_token
+ c.delete(
+ '/v3/auth/tokens', headers=self.headers,
+ expected_status_code=http_client.FORBIDDEN
+ )
+
+
+class SystemReaderTests(base_classes.TestCaseWithBootstrap,
+ common_auth.AuthTestMixin,
+ _SystemUserTokenTests,
+ _SystemMemberAndReaderTokenTests):
+
+ def setUp(self):
+ super(SystemReaderTests, self).setUp()
+ self.loadapp()
+ self.useFixture(ksfixtures.Policy(self.config_fixture))
+ self.config_fixture.config(group='oslo_policy', enforce_scope=True)
+
+ system_reader = unit.new_user_ref(
+ domain_id=CONF.identity.default_domain_id
+ )
+ self.user_id = PROVIDERS.identity_api.create_user(
+ system_reader
+ )['id']
+ PROVIDERS.assignment_api.create_system_grant_for_user(
+ self.user_id, self.bootstrapper.reader_role_id
+ )
+
+ auth = self.build_authentication_request(
+ user_id=self.user_id, password=system_reader['password'],
+ system=True
+ )
+
+ # Grab a token using the persona we're testing and prepare headers
+ # for requests we'll be making in the tests.
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=auth)
+ self.token_id = r.headers['X-Subject-Token']
+ self.headers = {'X-Auth-Token': self.token_id}
+
+
+class SystemMemberTests(base_classes.TestCaseWithBootstrap,
+ common_auth.AuthTestMixin,
+ _SystemUserTokenTests,
+ _SystemMemberAndReaderTokenTests):
+
+ def setUp(self):
+ super(SystemMemberTests, self).setUp()
+ self.loadapp()
+ self.useFixture(ksfixtures.Policy(self.config_fixture))
+ self.config_fixture.config(group='oslo_policy', enforce_scope=True)
+
+ system_reader = unit.new_user_ref(
+ domain_id=CONF.identity.default_domain_id
+ )
+ self.user_id = PROVIDERS.identity_api.create_user(
+ system_reader
+ )['id']
+ PROVIDERS.assignment_api.create_system_grant_for_user(
+ self.user_id, self.bootstrapper.reader_role_id
+ )
+
+ auth = self.build_authentication_request(
+ user_id=self.user_id, password=system_reader['password'],
+ system=True
+ )
+
+ # Grab a token using the persona we're testing and prepare headers
+ # for requests we'll be making in the tests.
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=auth)
+ self.token_id = r.headers['X-Subject-Token']
+ self.headers = {'X-Auth-Token': self.token_id}
+
+
+class SystemAdminTests(base_classes.TestCaseWithBootstrap,
+ common_auth.AuthTestMixin,
+ _SystemUserTokenTests):
+
+ def setUp(self):
+ super(SystemAdminTests, self).setUp()
+ self.loadapp()
+ self.useFixture(ksfixtures.Policy(self.config_fixture))
+ self.config_fixture.config(group='oslo_policy', enforce_scope=True)
+
+ self.user_id = self.bootstrapper.admin_user_id
+ auth = self.build_authentication_request(
+ user_id=self.user_id,
+ password=self.bootstrapper.admin_password,
+ system=True
+ )
+
+ # Grab a token using the persona we're testing and prepare headers
+ # for requests we'll be making in the tests.
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=auth)
+ self.token_id = r.headers['X-Subject-Token']
+ self.headers = {'X-Auth-Token': self.token_id}
+
+ def test_user_can_revoke_a_system_scoped_token(self):
+ user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_system_grant_for_user(
+ user['id'], self.bootstrapper.reader_role_id
+ )
+
+ system_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ system=True
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=system_auth)
+ system_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = system_token
+ c.delete('/v3/auth/tokens', headers=self.headers)
+
+ def test_user_can_revoke_a_domain_scoped_token(self):
+ domain = PROVIDERS.resource_api.create_domain(
+ uuid.uuid4().hex, unit.new_domain_ref()
+ )
+
+ user = unit.new_user_ref(domain_id=domain['id'])
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.reader_role_id, user_id=user['id'],
+ domain_id=domain['id']
+ )
+
+ domain_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ domain_id=domain['id']
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=domain_auth)
+ domain_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = domain_token
+ c.delete('/v3/auth/tokens', headers=self.headers)
+
+ def test_user_can_revoke_a_project_scoped_token(self):
+ project = PROVIDERS.resource_api.create_project(
+ uuid.uuid4().hex,
+ unit.new_project_ref(domain_id=CONF.identity.default_domain_id)
+ )
+
+ user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.reader_role_id, user_id=user['id'],
+ project_id=project['id']
+ )
+
+ project_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ project_id=project['id']
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=project_auth)
+ project_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = project_token
+ c.delete('/v3/auth/tokens', headers=self.headers)
+
+
+class _DomainAndProjectUserTests(object):
+
+ def test_user_can_validate_their_own_tokens(self):
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = self.token_id
+ c.get('/v3/auth/tokens', headers=self.headers)
+
+ def test_user_cannot_validate_system_scoped_token(self):
+ user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_system_grant_for_user(
+ user['id'], self.bootstrapper.reader_role_id
+ )
+
+ system_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ system=True
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=system_auth)
+ system_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = system_token
+ c.get(
+ '/v3/auth/tokens', headers=self.headers,
+ expected_status_code=http_client.FORBIDDEN
+ )
+
+ def test_user_cannot_validate_domain_scoped_token(self):
+ domain = PROVIDERS.resource_api.create_domain(
+ uuid.uuid4().hex, unit.new_domain_ref()
+ )
+
+ user = unit.new_user_ref(domain_id=domain['id'])
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.reader_role_id, user_id=user['id'],
+ domain_id=domain['id']
+ )
+
+ domain_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ domain_id=domain['id']
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=domain_auth)
+ domain_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = domain_token
+ c.get(
+ '/v3/auth/tokens', headers=self.headers,
+ expected_status_code=http_client.FORBIDDEN
+ )
+ pass
+
+ def test_user_cannot_validate_project_scoped_token(self):
+ project = PROVIDERS.resource_api.create_project(
+ uuid.uuid4().hex,
+ unit.new_project_ref(domain_id=CONF.identity.default_domain_id)
+ )
+
+ user = unit.new_user_ref(domain_id=CONF.identity.default_domain_id)
+ user['id'] = PROVIDERS.identity_api.create_user(user)['id']
+
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.reader_role_id, user_id=user['id'],
+ project_id=project['id']
+ )
+
+ project_auth = self.build_authentication_request(
+ user_id=user['id'], password=user['password'],
+ project_id=project['id']
+ )
+
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=project_auth)
+ project_token = r.headers['X-Subject-Token']
+
+ with self.test_client() as c:
+ self.headers['X-Subject-Token'] = project_token
+ c.get(
+ '/v3/auth/tokens', headers=self.headers,
+ expected_status_code=http_client.FORBIDDEN
+ )
+
+
+class DomainUserTests(base_classes.TestCaseWithBootstrap,
+ common_auth.AuthTestMixin,
+ _DomainAndProjectUserTests):
+
+ def setUp(self):
+ super(DomainUserTests, self).setUp()
+ self.loadapp()
+ self.useFixture(ksfixtures.Policy(self.config_fixture))
+ self.config_fixture.config(group='oslo_policy', enforce_scope=True)
+
+ domain = PROVIDERS.resource_api.create_domain(
+ uuid.uuid4().hex, unit.new_domain_ref()
+ )
+ self.domain_id = domain['id']
+ domain_user = unit.new_user_ref(domain_id=self.domain_id)
+ self.domain_user_id = PROVIDERS.identity_api.create_user(
+ domain_user
+ )['id']
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.member_role_id, user_id=self.domain_user_id,
+ domain_id=self.domain_id
+ )
+
+ auth = self.build_authentication_request(
+ user_id=self.domain_user_id, password=domain_user['password'],
+ domain_id=self.domain_id
+ )
+
+ # Grab a token using the persona we're testing and prepare headers
+ # for requests we'll be making in the tests.
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=auth)
+ self.token_id = r.headers['X-Subject-Token']
+ self.headers = {'X-Auth-Token': self.token_id}
+
+
+class ProjectUserTests(base_classes.TestCaseWithBootstrap,
+ common_auth.AuthTestMixin,
+ _DomainAndProjectUserTests):
+
+ def setUp(self):
+ super(ProjectUserTests, self).setUp()
+ self.loadapp()
+ self.useFixture(ksfixtures.Policy(self.config_fixture))
+ self.config_fixture.config(group='oslo_policy', enforce_scope=True)
+
+ domain = PROVIDERS.resource_api.create_domain(
+ uuid.uuid4().hex, unit.new_domain_ref()
+ )
+ self.domain_id = domain['id']
+
+ project_reader = unit.new_user_ref(domain_id=self.domain_id)
+ project_reader_id = PROVIDERS.identity_api.create_user(
+ project_reader
+ )['id']
+ project = unit.new_project_ref(domain_id=self.domain_id)
+ project_id = PROVIDERS.resource_api.create_project(
+ project['id'], project
+ )['id']
+
+ PROVIDERS.assignment_api.create_grant(
+ self.bootstrapper.reader_role_id, user_id=project_reader_id,
+ project_id=project_id
+ )
+
+ auth = self.build_authentication_request(
+ user_id=project_reader_id,
+ password=project_reader['password'],
+ project_id=project_id
+ )
+
+ # Grab a token using the persona we're testing and prepare headers
+ # for requests we'll be making in the tests.
+ with self.test_client() as c:
+ r = c.post('/v3/auth/tokens', json=auth)
+ self.token_id = r.headers['X-Subject-Token']
+ self.headers = {'X-Auth-Token': self.token_id}
diff --git a/lower-constraints.txt b/lower-constraints.txt
index dab11af99..307e9246d 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -31,7 +31,7 @@ oslo.config==5.2.0
oslo.context==2.22.0
oslo.db==4.27.0
oslo.i18n==3.15.3
-oslo.log==3.38.0
+oslo.log==3.44.0
oslo.messaging==5.29.0
oslo.middleware==3.31.0
oslo.policy==1.43.1
diff --git a/releasenotes/notes/bug-1750676-cf70c1a27b2c8de3.yaml b/releasenotes/notes/bug-1750676-cf70c1a27b2c8de3.yaml
new file mode 100644
index 000000000..293e14949
--- /dev/null
+++ b/releasenotes/notes/bug-1750676-cf70c1a27b2c8de3.yaml
@@ -0,0 +1,35 @@
+---
+features:
+ - |
+ [`bug 1750676 <https://bugs.launchpad.net/keystone/+bug/1750676>`_]
+ [`bug 1818844 <https://bugs.launchpad.net/keystone/+bug/1818844>`_]
+ The token API now supports the ``admin``, ``member``, and ``reader``
+ default roles.
+upgrade:
+ - |
+ [`bug 1750676 <https://bugs.launchpad.net/keystone/+bug/1750676>`_]
+ [`bug 1818844 <https://bugs.launchpad.net/keystone/+bug/1818844>`_]
+ The token API uses new default policies that make it easier for system
+ users to delegate functionality in a secure way. Please consider the new
+ policies if your deployment overrides the token policies.
+deprecations:
+ - |
+ [`bug 1750676 <https://bugs.launchpad.net/keystone/+bug/1750676>`_]
+ [`bug 1818844 <https://bugs.launchpad.net/keystone/+bug/1818844>`_]
+ The ``identity:check_token`` policy now uses ``(role:reader and
+ system_scope:all) or rule:token_subject`` instead of ``rule:admin_required
+ or rule:token_subject``. The ``identity:validate_token`` policy now uses
+ ``(role:reader and system_scope:all) or rule:service_role or
+ rule:token_subject`` instead or ``rule:service_or_admin or
+ rule:token_subject``. The ``identity:revoke_token`` policy now uses
+ ``(role:admin and system_scope:all) or rule:token_subject`` instead of
+ ``rule:admin_or_token_subject``. These new defaults automatically account
+ for a read-only role by default and allow more granular access to the API.
+ Please consider these new defaults if your deployment overrides the token
+ policies.
+security:
+ - |
+ [`bug 1750676 <https://bugs.launchpad.net/keystone/+bug/1750676>`_]
+ [`bug 1818844 <https://bugs.launchpad.net/keystone/+bug/1818844>`_]
+ The token API now uses system-scope and default roles properly to provide
+ more granular access to the token API.
diff --git a/requirements.txt b/requirements.txt
index 90e0375b6..b46f3cfe4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -27,7 +27,7 @@ oslo.context>=2.22.0 # Apache-2.0
oslo.messaging>=5.29.0 # Apache-2.0
oslo.db>=4.27.0 # Apache-2.0
oslo.i18n>=3.15.3 # Apache-2.0
-oslo.log>=3.38.0 # Apache-2.0
+oslo.log>=3.44.0 # Apache-2.0
oslo.middleware>=3.31.0 # Apache-2.0
oslo.policy>=1.43.1 # Apache-2.0
oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0