service keys: do all the right stuff
This commit is contained in:
parent
6ecff950ab
commit
4079dba167
7 changed files with 149 additions and 41 deletions
|
@ -1,5 +1,7 @@
|
|||
from datetime import datetime
|
||||
|
||||
import jwt
|
||||
|
||||
from flask import Blueprint, jsonify, abort, request, make_response
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
@ -16,26 +18,42 @@ JWT_HEADER_NAME = 'Authorization'
|
|||
JWT_AUDIENCE = 'quay'
|
||||
|
||||
|
||||
def _validate_JWT(jwt, service, kid):
|
||||
try:
|
||||
service_key = data.model.service_keys.get_service_keys(service, kid=kid)
|
||||
except data.model.ServiceKeyDoesNotExist:
|
||||
abort(404)
|
||||
|
||||
public_key = RSAPublicNumbers(e=service_key.jwk.e,
|
||||
n=service_key.jwk.n).public_key(default_backend())
|
||||
def _validate_JWT(encoded_jwt, jwk, service):
|
||||
public_key = RSAPublicNumbers(e=jwk.e,
|
||||
n=jwk.n).public_key(default_backend())
|
||||
|
||||
try:
|
||||
strictjwt.decode(jwt, public_key, algorithms=['RS256'], audience=JWT_AUDIENCE, issuer=service)
|
||||
strictjwt.decode(encoded_jwt, public_key, algorithms=['RS256'],
|
||||
audience=JWT_AUDIENCE, issuer=service)
|
||||
except strictjwt.InvalidTokenError:
|
||||
abort(400)
|
||||
|
||||
|
||||
def _signer_jwk(encoded_jwt, jwk, service, kid):
|
||||
decoded_jwt = jwt.decode(encoded_jwt, verify=False)
|
||||
|
||||
signer_kid = decoded_jwt.get('signer_kid', '')
|
||||
# If we already have our own JWK and it's the signer, short-circuit.
|
||||
if (signer_kid == kid or signer_kid == '') and jwk is not None:
|
||||
return jwk
|
||||
else:
|
||||
try:
|
||||
service_key = data.model.service_keys.get_service_keys(service, kid=signer_kid)
|
||||
except data.model.ServiceKeyDoesNotExist:
|
||||
abort(404)
|
||||
|
||||
return service_key.jwk
|
||||
|
||||
|
||||
@key_server.route('/services/<service>/keys', methods=['GET'])
|
||||
def get_service_keys(service):
|
||||
keys = data.model.service_keys.get_service_keys(service)
|
||||
jwks = [key.jwk for key in keys]
|
||||
return jsonify({'keys': jwks})
|
||||
kid = request.args.get('kid', None)
|
||||
if kid is not None:
|
||||
keys = data.model.service_keys.get_service_keys(service, kid=kid)
|
||||
else:
|
||||
keys = data.model.service_keys.get_service_keys(service)
|
||||
|
||||
return jsonify({'keys': [key.jwk for key in keys]})
|
||||
|
||||
|
||||
@key_server.route('/services/<service>/keys/<kid>', methods=['PUT'])
|
||||
|
@ -47,11 +65,6 @@ def put_service_keys(service, kid):
|
|||
except ValueError:
|
||||
abort(400)
|
||||
|
||||
jwt = request.headers.get(JWT_HEADER_NAME, None)
|
||||
if not jwt:
|
||||
abort(400)
|
||||
_validate_JWT(jwt, service, kid)
|
||||
|
||||
try:
|
||||
jwk = request.json()
|
||||
except ValueError:
|
||||
|
@ -60,6 +73,9 @@ def put_service_keys(service, kid):
|
|||
if 'kty' not in jwk:
|
||||
abort(400)
|
||||
|
||||
if 'kid' not in jwk or jwk['kid'] != kid:
|
||||
abort(400)
|
||||
|
||||
if jwk['kty'] == 'EC':
|
||||
if 'x' not in jwk or 'y' not in jwk:
|
||||
abort(400)
|
||||
|
@ -69,15 +85,30 @@ def put_service_keys(service, kid):
|
|||
else:
|
||||
abort(400)
|
||||
|
||||
data.model.service_keys.upsert_service_key(kid, service, jwk, expiration_date)
|
||||
encoded_jwt = request.headers.get(JWT_HEADER_NAME, None)
|
||||
if not encoded_jwt:
|
||||
abort(400)
|
||||
|
||||
signer_jwk = _signer_jwk(encoded_jwt, jwk, service, kid)
|
||||
_validate_JWT(encoded_jwt, signer_jwk, service)
|
||||
|
||||
metadata = {
|
||||
'ip': request.remote_addr,
|
||||
'signer_jwk': signer_jwk,
|
||||
}
|
||||
|
||||
|
||||
data.model.service_keys.upsert_service_key('', kid, service, jwk, metadata, expiration_date)
|
||||
|
||||
|
||||
@key_server.route('/services/<service>/keys/<kid>', methods=['DELETE'])
|
||||
def delete_service_key(service, kid):
|
||||
jwt = request.headers.get(JWT_HEADER_NAME, None)
|
||||
if not jwt:
|
||||
encoded_jwt = request.headers.get(JWT_HEADER_NAME, None)
|
||||
if not encoded_jwt:
|
||||
abort(400)
|
||||
_validate_JWT(jwt, service, kid)
|
||||
|
||||
signer_jwk = _signer_jwk(encoded_jwt, None, service, kid)
|
||||
_validate_JWT(encoded_jwt, signer_jwk, service)
|
||||
|
||||
try:
|
||||
data.model.service_keys.delete_service_key(service, kid)
|
||||
|
|
Reference in a new issue