Use the instance service key for registry JWT signing
This commit is contained in:
parent
a4aa5cc02a
commit
8887f09ba8
26 changed files with 457 additions and 278 deletions
|
@ -1,17 +1,80 @@
|
|||
import time
|
||||
import jwt
|
||||
import logging
|
||||
|
||||
from cachetools import lru_cache
|
||||
from util.security import jwtutil
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ANONYMOUS_SUB = '(anonymous)'
|
||||
ALGORITHM = 'RS256'
|
||||
|
||||
|
||||
def generate_jwt_object(audience, subject, context, access, lifetime_s, app_config):
|
||||
""" Generates a compact encoded JWT with the values specified.
|
||||
class InvalidBearerTokenException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def decode_bearer_token(bearer_token, instance_keys):
|
||||
""" decode_bearer_token decodes the given bearer token that contains both a Key ID as well as the
|
||||
encoded JWT and returns the decoded and validated JWT. On any error, raises an
|
||||
InvalidBearerTokenException with the reason for failure.
|
||||
"""
|
||||
app_config = instance_keys.app.config
|
||||
|
||||
# Extract the jwt token from the header
|
||||
match = jwtutil.TOKEN_REGEX.match(bearer_token)
|
||||
if match is None:
|
||||
raise InvalidBearerTokenException('Invalid bearer token format')
|
||||
|
||||
encoded_jwt = match.group(1)
|
||||
logger.debug('encoded JWT: %s', encoded_jwt)
|
||||
|
||||
# Decode the key ID.
|
||||
headers = jwt.get_unverified_header(encoded_jwt)
|
||||
kid = headers.get('kid', None)
|
||||
if kid is None:
|
||||
logger.error('Missing kid header on encoded JWT: %s', encoded_jwt)
|
||||
raise InvalidBearerTokenException('Missing kid header')
|
||||
|
||||
# Find the matching public key.
|
||||
public_key = instance_keys.get_service_key_public_key(kid)
|
||||
if public_key is None:
|
||||
logger.error('Could not find requested service key %s', kid)
|
||||
raise InvalidBearerTokenException('Unknown service key')
|
||||
|
||||
# Load the JWT returned.
|
||||
try:
|
||||
expected_issuer = instance_keys.service_name
|
||||
audience = app_config['SERVER_HOSTNAME']
|
||||
max_signed_s = app_config.get('REGISTRY_JWT_AUTH_MAX_FRESH_S', 3660)
|
||||
|
||||
max_exp = jwtutil.exp_max_s_option(max_signed_s)
|
||||
payload = jwtutil.decode(encoded_jwt, public_key, algorithms=[ALGORITHM], audience=audience,
|
||||
issuer=expected_issuer, options=max_exp)
|
||||
except jwtutil.InvalidTokenError as ite:
|
||||
logger.exception('Invalid token reason: %s', ite)
|
||||
raise InvalidBearerTokenException(ite)
|
||||
|
||||
if not 'sub' in payload:
|
||||
raise InvalidBearerTokenException('Missing sub field in JWT')
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
def generate_bearer_token(audience, subject, context, access, lifetime_s, instance_keys):
|
||||
""" Generates a registry bearer token (without the 'Bearer ' portion) based on the given
|
||||
information.
|
||||
"""
|
||||
return _generate_jwt_object(audience, subject, context, access, lifetime_s,
|
||||
instance_keys.service_name, instance_keys.local_key_id,
|
||||
instance_keys.local_private_key)
|
||||
|
||||
|
||||
def _generate_jwt_object(audience, subject, context, access, lifetime_s, issuer, key_id,
|
||||
private_key):
|
||||
""" Generates a compact encoded JWT with the values specified. """
|
||||
token_data = {
|
||||
'iss': app_config['JWT_AUTH_TOKEN_ISSUER'],
|
||||
'iss': issuer,
|
||||
'aud': audience,
|
||||
'nbf': int(time.time()),
|
||||
'iat': int(time.time()),
|
||||
|
@ -21,15 +84,11 @@ def generate_jwt_object(audience, subject, context, access, lifetime_s, app_conf
|
|||
'context': context,
|
||||
}
|
||||
|
||||
certificate = _load_certificate_bytes(app_config['JWT_AUTH_CERTIFICATE_PATH'])
|
||||
|
||||
token_headers = {
|
||||
'x5c': [certificate],
|
||||
'kid': key_id,
|
||||
}
|
||||
|
||||
private_key = _load_private_key(app_config['JWT_AUTH_PRIVATE_KEY_PATH'])
|
||||
|
||||
return jwt.encode(token_data, private_key, 'RS256', headers=token_headers)
|
||||
return jwt.encode(token_data, private_key, ALGORITHM, headers=token_headers)
|
||||
|
||||
|
||||
def build_context_and_subject(user, token, oauthtoken):
|
||||
|
@ -64,14 +123,3 @@ def build_context_and_subject(user, token, oauthtoken):
|
|||
return (context, ANONYMOUS_SUB)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_certificate_bytes(certificate_file_path):
|
||||
with open(certificate_file_path) as cert_file:
|
||||
cert_lines = cert_file.readlines()[1:-1]
|
||||
return ''.join([cert_line.rstrip('\n') for cert_line in cert_lines])
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_private_key(private_key_file_path):
|
||||
with open(private_key_file_path) as private_key_file:
|
||||
return private_key_file.read()
|
||||
|
|
Reference in a new issue