service key server wip

This commit is contained in:
Jimmy Zelinskie 2016-03-16 15:49:25 -04:00 committed by Jimmy Zelinskie
parent fe5787ac28
commit 499bb16306
4 changed files with 167 additions and 13 deletions

View file

@ -1,20 +1,22 @@
import string
import logging
import uuid
import time
import toposort
import resumablehashlib
import sys
import inspect
import logging
import string
import sys
import time
import uuid
from random import SystemRandom
from datetime import datetime
from peewee import *
from data.read_slave import ReadSlaveModel
from data.fields import ResumableSHA256Field, ResumableSHA1Field, JSONField, Base64BinaryField
from sqlalchemy.engine.url import make_url
from collections import defaultdict
from datetime import datetime
from random import SystemRandom
import resumablehashlib
import toposort
from peewee import *
from sqlalchemy.engine.url import make_url
from data.fields import ResumableSHA256Field, ResumableSHA1Field, JSONField, Base64BinaryField
from data.read_slave import ReadSlaveModel
from util.names import urn_generator
@ -866,5 +868,22 @@ class TorrentInfo(BaseModel):
(('storage', 'piece_length'), True),
)
_ServiceKeyApproverProxy = Proxy()
class ServiceKeyApproval(BaseModel):
approver = ForeignKeyField(_ServiceKeyApproverProxy)
approval_type = CharField(index=True)
approved_date = DateTimeField(default=datetime.now)
_ServiceKeyApproverProxy.initialize(User)
class ServiceKey(BaseModel):
kid = CharField(unique=True, index=True)
service = CharField(index=True)
jwk = CharField(unique=True)
expiration_date = DateTimeField(null=True)
approval = ForeignKeyField(ServiceKeyApproval, index=True)
is_model = lambda x: inspect.isclass(x) and issubclass(x, BaseModel) and x is not BaseModel
all_models = [model[1] for model in inspect.getmembers(sys.modules[__name__], is_model)]

View file

@ -76,6 +76,10 @@ class InvalidManifestException(DataModelException):
pass
class ServiceKeyDoesNotExist(DataModelException):
pass
class TooManyLoginAttemptsException(Exception):
def __init__(self, message, retry_after):
super(TooManyLoginAttemptsException, self).__init__(message)

View file

@ -0,0 +1,44 @@
from datetime import datetime
from data.model import ServiceKeyDoesNotExist
from data.database import ServiceKey
def _gc_expired(service):
ServiceKey.delete().where(ServiceKey.service == service,
ServiceKey.expiration_date <= datetime.now).execute()
def upsert_service_key(kid, service, jwk, expiration_date):
_gc_expired(service)
try:
key = ServiceKey.select().where(ServiceKey.kid == kid).get()
key.service = service
key.jwk = jwk
key.expiration_date = expiration_date
key.save()
except ServiceKey.DoesNotExist:
ServiceKey.create(kid=kid, service=service, jwk=jwk, expiration_date=expiration_date)
def get_service_keys(service, kid=None):
_gc_expired(service)
try:
query = ServiceKey.select().where(ServiceKey.service == service,
~(ServiceKey.approval >> None))
if kid:
query.where(ServiceKey.kid == kid)
return query
except ServiceKey.DoesNotExist:
raise ServiceKeyDoesNotExist()
def delete_service_key(service, kid):
_gc_expired(service)
try:
ServiceKey.delete().where(ServiceKey.service == service,
ServiceKey.kid == kid).execute()
except ServiceKey.DoesNotExist:
raise ServiceKeyDoesNotExist()

87
endpoints/key_server.py Normal file
View file

@ -0,0 +1,87 @@
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)