service key worker to refresh automatic keys

This commit is contained in:
Evan Cordell 2016-04-29 10:51:22 -05:00 committed by Jimmy Zelinskie
parent 2242c6773d
commit a6f6a114c2
6 changed files with 82 additions and 18 deletions

33
boot.py
View file

@ -1,30 +1,20 @@
#!/usr/bin/env python #!/usr/bin/env python
from datetime import datetime from datetime import datetime, timedelta
from urlparse import urlunparse from urlparse import urlunparse
from jinja2 import Template from jinja2 import Template
from cachetools import lru_cache
import release import release
from app import app from app import app
from data.model.release import set_region_release from data.model.release import set_region_release
from util.config.database import sync_database_with_config from util.config.database import sync_database_with_config
from util.generatepresharedkey import generate_key from util.generatepresharedkey import generate_key
def create_quay_service_key(): @lru_cache(maxsize=1)
"""
Creates a service key for quay to use in the jwtproxy
"""
quay_key, key_id = generate_key('quay', 'quay')
with open('/conf/quay.pem', mode='w') as f:
f.truncate(0)
f.write(quay_key.exportKey())
return key_id
def get_audience(): def get_audience():
audience = app.config.get('JWTPROXY_AUDIENCE') audience = app.config.get('JWTPROXY_AUDIENCE')
@ -47,6 +37,21 @@ def get_audience():
return urlunparse((scheme, hostname + ':' + port, '', '', '', '')) return urlunparse((scheme, hostname + ':' + port, '', '', '', ''))
def create_quay_service_key():
"""
Creates a service key for quay to use in the jwtproxy
"""
minutes_until_expiration = app.config.get('QUAY_SERVICE_KEY_EXPIRATION', 120)
expiration = timedelta(minutes=minutes_until_expiration)
quay_key, key_id = generate_key('quay', get_audience(), datetime.now() + expiration)
with open('/conf/quay.pem', mode='w') as f:
f.truncate(0)
f.write(quay_key.exportKey())
return key_id
def create_jwtproxy_conf(quay_key_id): def create_jwtproxy_conf(quay_key_id):
""" """
Generates the jwtproxy conf from the jinja template Generates the jwtproxy conf from the jinja template

View file

@ -0,0 +1,2 @@
#!/bin/sh
exec logger -i -t service_key_worker

View file

@ -0,0 +1,8 @@
#! /bin/bash
echo 'Starting service key worker'
cd /
venv/bin/python -m workers.service_key_worker 2>&1
echo 'Service key worker exited'

View file

@ -320,5 +320,7 @@ class DefaultConfig(object):
# lowest user in the database will be used. # lowest user in the database will be used.
SERVICE_LOG_ACCOUNT_ID = None SERVICE_LOG_ACCOUNT_ID = None
# Quay's service key expiration in seconds # Quay's service key expiration in minutes
QUAY_SERVICE_KEY_EXPIRATION = 500 QUAY_SERVICE_KEY_EXPIRATION = 120
# Number of minutes between expiration refresh in minutes
QUAY_SERVICE_KEY_REFRESH = 60

View file

@ -7,7 +7,7 @@ from peewee import JOIN_LEFT_OUTER
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from jwkest.jwk import RSAKey from jwkest.jwk import RSAKey
from data.database import db_for_update, User, ServiceKey, ServiceKeyApproval from data.database import db_for_update, User, ServiceKey, ServiceKeyApproval, ServiceKeyApprovalType
from data.model import (ServiceKeyDoesNotExist, ServiceKeyAlreadyApproved, ServiceNameInvalid, from data.model import (ServiceKeyDoesNotExist, ServiceKeyAlreadyApproved, ServiceNameInvalid,
db_transaction, config) db_transaction, config)
from data.model.notification import create_notification, delete_all_notifications_by_path_prefix from data.model.notification import create_notification, delete_all_notifications_by_path_prefix
@ -20,9 +20,11 @@ def _expired_keys_clause(service):
return ((ServiceKey.service == service) & return ((ServiceKey.service == service) &
(ServiceKey.expiration_date <= datetime.utcnow())) (ServiceKey.expiration_date <= datetime.utcnow()))
def _stale_expired_keys_service_clause(service): def _stale_expired_keys_service_clause(service):
return ((ServiceKey.service == service) & _stale_expired_keys_clause()) return ((ServiceKey.service == service) & _stale_expired_keys_clause())
def _stale_expired_keys_clause(): def _stale_expired_keys_clause():
expired_ttl = timedelta(seconds=config.app_config['EXPIRED_SERVICE_KEY_TTL_SEC']) expired_ttl = timedelta(seconds=config.app_config['EXPIRED_SERVICE_KEY_TTL_SEC'])
return (ServiceKey.expiration_date <= (datetime.utcnow() - expired_ttl)) return (ServiceKey.expiration_date <= (datetime.utcnow() - expired_ttl))
@ -34,6 +36,10 @@ def _stale_unapproved_keys_clause(service):
(ServiceKey.created_date <= (datetime.utcnow() - unapproved_ttl))) (ServiceKey.created_date <= (datetime.utcnow() - unapproved_ttl)))
def _unexpired_clause():
return ServiceKey.expiration_date >= datetime.utcnow()
def _gc_expired(service): def _gc_expired(service):
ServiceKey.delete().where(_stale_expired_keys_service_clause(service) | ServiceKey.delete().where(_stale_expired_keys_service_clause(service) |
_stale_unapproved_keys_clause(service)).execute() _stale_unapproved_keys_clause(service)).execute()
@ -141,6 +147,16 @@ def set_key_expiration(kid, expiration_date):
service_key.save() service_key.save()
def refresh_automatic_service_keys(extension):
"""
Finds all unexpired automatic keys and sets their
expiration to `now + extension`
"""
for service_key in list(_list_service_keys_query(approval_type=ServiceKeyApprovalType.AUTOMATIC).where(_unexpired_clause())):
service_key.expiration_date = datetime.now() + extension
service_key.save()
def approve_service_key(kid, approver, approval_type, notes=''): def approve_service_key(kid, approver, approval_type, notes=''):
try: try:
with db_transaction(): with db_transaction():
@ -159,12 +175,15 @@ def approve_service_key(kid, approver, approval_type, notes=''):
return key return key
def _list_service_keys_query(kid=None, service=None, approved_only=False): def _list_service_keys_query(kid=None, service=None, approved_only=False, approval_type=None):
query = ServiceKey.select().join(ServiceKeyApproval, JOIN_LEFT_OUTER) query = ServiceKey.select().join(ServiceKeyApproval, JOIN_LEFT_OUTER)
if approved_only: if approved_only:
query = query.where(~(ServiceKey.approval >> None)) query = query.where(~(ServiceKey.approval >> None))
if approval_type is not None:
query = query.where(ServiceKeyApproval.approval_type == approval_type)
if service is not None: if service is not None:
query = query.where(ServiceKey.service == service) query = query.where(ServiceKey.service == service)
query = query.where(~(_expired_keys_clause(service)) | query = query.where(~(_expired_keys_clause(service)) |

View file

@ -0,0 +1,28 @@
import logging
from datetime import timedelta
from app import app
from data.model.service_keys import refresh_automatic_service_keys
from workers.worker import Worker
logger = logging.getLogger(__name__)
class ServiceKeyWorker(Worker):
def __init__(self):
super(ServiceKeyWorker, self).__init__()
self.add_operation(self._refresh_service_keys,
app.config.get('QUAY_SERVICE_KEY_REFRESH', 60)*60)
def _refresh_service_keys(self):
""" Refreshes active service keys so they don't get garbage collected. """
minutes_until_expiration = app.config.get('QUAY_SERVICE_KEY_EXPIRATION', 120)
expiration = timedelta(minutes=minutes_until_expiration)
logger.debug('Starting refresh of automatic service keys')
refresh_automatic_service_keys(expiration)
logger.debug('Finished refresh of automatic service keys')
if __name__ == "__main__":
worker = ServiceKeyWorker()
worker.start()