Skip to content

Instantly share code, notes, and snippets.

@lbragstad
Last active June 2, 2017 22:40
Token Provider Migration
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)
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