Validate disabled domains and projects online

Keystone's performance degrades as the `revocation_event` table grows
in size. This patch reduces the total number of events written to the
table by not persisting events when a domain or project is disabled.

The main reason for persisting a revocation event when a project or
domain is disabled is to make sure tokens associated to those targets
are considered invalid. Instead of relying on revocation events, we
can check if the project or domain is enabled when we validate the
token. We take the same approach when we validate a user's role
assignments instead of relying on an ever-growing database table.

Co-Authored-By: Lance Bragstad <lbragstad@gmail.com>

Closes-Bug: 1524030
Change-Id: I76330567e0df2d9f2af88ef9b6b98b8c379e7406
This commit is contained in:
Jorge Munoz 2017-11-24 22:59:32 +00:00 committed by Lance Bragstad
parent 25b688e51d
commit 8eb29c37d1
4 changed files with 33 additions and 37 deletions

View File

@ -87,11 +87,6 @@ class Manager(manager.Manager):
self.revoke(
revoke_model.RevokeEvent(project_id=payload['resource_info']))
def _domain_callback(self, service, resource_type, operation,
payload):
self.revoke(
revoke_model.RevokeEvent(domain_id=payload['resource_info']))
def _trust_callback(self, service, resource_type, operation,
payload):
self.revoke(
@ -111,9 +106,7 @@ class Manager(manager.Manager):
['project', self._project_callback],
],
notifications.ACTIONS.disabled: [
['user', self._user_callback],
['project', self._project_callback],
['domain', self._domain_callback],
['user', self._user_callback]
],
notifications.ACTIONS.internal: [
[notifications.INVALIDATE_USER_TOKEN_PERSISTENCE,

View File

@ -3256,23 +3256,6 @@ class TestTokenRevokeApi(TestTokenRevokeById):
expected_response = {'events': [{'project_id': project_id}]}
self.assertEqual(expected_response, events_response)
def assertDomainAndProjectInList(self, events_response, domain_id):
events = events_response['events']
self.assertEqual(2, len(events))
self.assertEqual(domain_id, events[0]['project_id'])
self.assertEqual(domain_id, events[1]['domain_id'])
self.assertIsNotNone(events[0]['issued_before'])
self.assertIsNotNone(events[1]['issued_before'])
self.assertIsNotNone(events_response['links'])
del (events_response['events'][0]['issued_before'])
del (events_response['events'][1]['issued_before'])
del (events_response['events'][0]['revoked_at'])
del (events_response['events'][1]['revoked_at'])
del (events_response['links'])
expected_response = {'events': [{'project_id': domain_id},
{'domain_id': domain_id}]}
self.assertEqual(expected_response, events_response)
def assertValidRevokedTokenResponse(self, events_response, **kwargs):
events = events_response['events']
self.assertEqual(1, len(events))
@ -3318,18 +3301,6 @@ class TestTokenRevokeApi(TestTokenRevokeById):
self.assertValidDeletedProjectResponse(events_response,
self.projectA['id'])
def test_disable_domain_shows_in_event_list(self):
events = self.get('/OS-REVOKE/events').json_body['events']
self.assertEqual([], events)
disable_body = {'domain': {'enabled': False}}
self.patch(
'/domains/%(project_id)s' % {'project_id': self.domainA['id']},
body=disable_body)
events = self.get('/OS-REVOKE/events').json_body
self.assertDomainAndProjectInList(events, self.domainA['id'])
def assertEventDataInList(self, events, **kwargs):
found = False
for e in events:

View File

@ -93,11 +93,33 @@ class V3TokenDataHelper(object):
super(V3TokenDataHelper, self).__init__()
def _get_filtered_domain(self, domain_id):
"""Ensure the domain is enabled and return domain id and name.
:param domain_id: The ID of the domain to validate
:returns: A dictionary containing two keys, the `id` of the domain and
the `name` of the domain.
"""
domain_ref = self.resource_api.get_domain(domain_id)
if not domain_ref.get('enabled'):
msg = _('Unable to validate token because domain %(id)s is '
'disabled') % {'id': domain_ref['id']}
LOG.warning(msg)
raise exception.DomainNotFound(msg)
return {'id': domain_ref['id'], 'name': domain_ref['name']}
def _get_filtered_project(self, project_id):
"""Ensure the project and parent domain is enabled.
:param project_id: The ID of the project to validate
:return: A dictionary containing up to three keys, the `id` of the
project, the `name` of the project, and the parent `domain`.
"""
project_ref = self.resource_api.get_project(project_id)
if not project_ref.get('enabled'):
msg = _('Unable to validate token because project %(id)s is '
'disabled') % {'id': project_ref['id']}
LOG.warning(msg)
raise exception.ProjectNotFound(msg)
filtered_project = {
'id': project_ref['id'],
'name': project_ref['name']}

View File

@ -0,0 +1,10 @@
---
fixes:
- |
[`bug 1524030 <https://bugs.launchpad.net/keystone/+bug/1524030>`_]
Revocation records are no longer written to the ``revocation_event`` table
when a domain or project is disabled. These records were only ever used
during the token validation process. In favor of revocation events, the
project or domain will be validated online when the token is validated. This
results in less database bloat while maintaining security during token
validation.