88 lines
2.3 KiB
Python
88 lines
2.3 KiB
Python
|
from datetime import datetime
|
||
|
|
||
|
from flask import Blueprint, jsonify, abort, request, make_response
|
||
|
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
|
||
|
from cryptography.hazmat.backends import default_backend
|
||
|
|
||
|
import data.model
|
||
|
import data.model.service_keys
|
||
|
|
||
|
from util.security import strictjwt
|
||
|
|
||
|
|
||
|
key_server = Blueprint('key_server', __name__)
|
||
|
|
||
|
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())
|
||
|
|
||
|
try:
|
||
|
strictjwt.decode(jwt, public_key, algorithms=['RS256'], audience=JWT_AUDIENCE, issuer=service)
|
||
|
except strictjwt.InvalidTokenError:
|
||
|
abort(400)
|
||
|
|
||
|
|
||
|
@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})
|
||
|
|
||
|
|
||
|
@key_server.route('/services/<service>/keys/<kid>', methods=['PUT'])
|
||
|
def put_service_keys(service, kid):
|
||
|
expiration_date = request.args.get('expiration', None)
|
||
|
if expiration_date:
|
||
|
try:
|
||
|
expiration_date = datetime.utcfromtimestamp(float(expiration_date))
|
||
|
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:
|
||
|
abort(400)
|
||
|
|
||
|
if 'kty' not in jwk:
|
||
|
abort(400)
|
||
|
|
||
|
if jwk['kty'] == 'EC':
|
||
|
if 'x' not in jwk or 'y' not in jwk:
|
||
|
abort(400)
|
||
|
elif jwk['kty'] == 'RSA':
|
||
|
if 'e' not in jwk or 'n' not in jwk:
|
||
|
abort(400)
|
||
|
else:
|
||
|
abort(400)
|
||
|
|
||
|
data.model.service_keys.upsert_service_key(kid, service, jwk, 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:
|
||
|
abort(400)
|
||
|
_validate_JWT(jwt, service, kid)
|
||
|
|
||
|
try:
|
||
|
data.model.service_keys.delete_service_key(service, kid)
|
||
|
except data.model.ServiceKeyDoesNotExist:
|
||
|
abort(404)
|
||
|
|
||
|
return make_response('', 200)
|