Last active
June 2, 2017 22:40
Token Provider Migration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from oslo_log import log | |
from keystone.token import persistence | |
from keystone.token.providers import fernet | |
from keystone.token.providers import uuid | |
LOG = log.getLogger(__name__) | |
class Provider(fernet.Provider): | |
"""Hybrid token provider to aid in migrating token formats w/o downtime. | |
This token provider should be rolled out after all nodes in the deployment | |
have started using the HybridUuidProvider. Once this provider is in place | |
on all keystone nodes, we can guarantee that no UUID tokens will be issued | |
in the deployment. There might be some that are still in use and valid | |
though. We should be able to validate those tokens and see the frequency of | |
those validations through logging. | |
We can replace this token provider with the upstream Fernet provider after | |
it has been in the deployment for the duration of the token lifespan. This | |
bit is important because we guarantee that any UUID tokens that might come | |
through should already be expired and would fail validation anyway. This | |
means we're safe to switch to the upstream Fernet provider. | |
""" | |
def __init__(self): | |
self.uuid_provider = uuid.Provider() | |
self.persistence_manager = persistence.PersistenceManager() | |
super(Provider, self).__init__() | |
def _is_uuid_like(self, token): | |
return len(token) == 32 | |
def needs_persistence(self, token=None): | |
"""Determine if the token should be persisted.""" | |
if token and isinstance(token, dict): | |
# If we've been passed a token here, then we should check and see | |
# if it is UUID-like. If it is, then we know we need to go to | |
# persistent storage in order to validate it, so return True. We | |
# can tell if this is a UUID token because it would be a | |
# dictionary, specifically a token reference, instead of a string. | |
return True | |
else: | |
super(Provider, self).needs_persistence() | |
def validate_token(self, token): | |
"""Validate the given UUID or Fernet token. | |
:param token: token ID to validate | |
:type token: str | |
:returns: token data | |
:raises keystone.exception.TokenNotFound: If the token doesn't exist. | |
""" | |
if self._is_uuid_like(token): | |
# Let's leave some useful logs when we do this that way we can | |
# monitor the frequency of UUID token validation through the | |
# migration. Once we stop seeing this, ideally after the maximum | |
# token expiration lifespan, we can stop using this hybrid provider | |
# and default to the upstream Fernet provider. | |
LOG.debug('Validating a UUID token') | |
# We know we're dealing with a UUID token since Fernet tokens are | |
# strings at this point. Let's reach into the persistence manager | |
# before validation since UUID validation will assume the manager | |
# has already pulled this reference. | |
token_ref = self.persistence_manager.get_token(token) | |
return self.uuid_provider.validate_token(token_ref) | |
else: | |
# Otherwise, validate this token in the same way we would validate | |
# a Fernet token using the Fernet provider. | |
return super(Provider, self).validate_token(token) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from oslo_log import log | |
from keystone.token import persistence | |
from keystone.token.providers import fernet | |
from keystone.token.providers import uuid | |
LOG = log.getLogger(__name__) | |
class Provider(uuid.Provider): | |
"""Hybrid token provider to aid in migrating token formats w/o downtime. | |
This provider uses the UUID provider to issue tokens and has the ability to | |
validate both Fernet and UUID tokens. The goal of this provider is to | |
replace the stock UUID provider. The reason why this is useful is because | |
it has the ability to validate Fernet tokens if it sees one. | |
The next step in the migration is to start rolling out another custom | |
provider that defaults to issuing Fernet tokens but has the ability to | |
validate UUID tokens. This provider will be compatible with that switch | |
since it can validate Fernet tokens. | |
""" | |
def __init__(self): | |
self.fernet_provider = fernet.Provider() | |
self.persistence_manager = persistence.PersistenceManager() | |
super(Provider, self).__init__() | |
def _is_fernet_like(self, token): | |
return len(token) > 32 | |
def needs_persistence(self, token=None): | |
"""Determine if the token should be persisted.""" | |
if token and isinstance(token, dict): | |
# If we've been passed a token here, then we should check and see | |
# if it is UUID-like. If it is, then we know we need to go to | |
# persistent storage in order to validate it, so return True. We | |
# can tell if this is a UUID token because it would be a | |
# dictionary, specifically a token reference, instead of a string. | |
return True | |
else: | |
super(Provider, self).needs_persistence() | |
def validate_token(self, token): | |
"""Validate the given UUID or Fernet token. | |
:param token: token ID to validate | |
:type token: str | |
:returns: token data | |
:raises keystone.exception.TokenNotFound: If the token doesn't exist. | |
""" | |
if self._is_fernet_like(token): | |
LOG.debug('Validating a Fernet token') | |
return self.fernet_provider.validate_token(token) | |
else: | |
token_ref = self.persistence_manager.get_token(token) | |
return super(Provider, self).validate_token(token_ref) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment