diff --git a/config.py b/config.py index 9958f6c12..c4d59bba0 100644 --- a/config.py +++ b/config.py @@ -304,3 +304,7 @@ class DefaultConfig(object): # The timeout for service key approval. UNAPPROVED_SERVICE_KEY_TTL_SEC = 60 * 60 * 24 # One day + + # How long to wait before GCing an expired service key. + EXPIRED_SERVICE_KEY_TTL_SEC = 60 * 60 * 24 * 7 # One week + diff --git a/data/model/notification.py b/data/model/notification.py index 53e2eed78..194e2975b 100644 --- a/data/model/notification.py +++ b/data/model/notification.py @@ -79,7 +79,7 @@ def list_notifications(user, kind_name=None, id_filter=None, include_dismissed=F def delete_all_notifications_by_path_prefix(prefix): (Notification .delete() - .where(Notification.lookup_path % prefix + '%') + .where(Notification.lookup_path ** (prefix + '%')) .execute()) diff --git a/data/model/service_keys.py b/data/model/service_keys.py index 2b6b879e6..8face6aa6 100644 --- a/data/model/service_keys.py +++ b/data/model/service_keys.py @@ -15,6 +15,11 @@ def _expired_keys_clause(service): return ((ServiceKey.service == service) & (ServiceKey.expiration_date <= datetime.utcnow())) +def _stale_expired_keys_clause(service): + expired_ttl = timedelta(seconds=config.app_config['EXPIRED_SERVICE_KEY_TTL_SEC']) + return ((ServiceKey.service == service) & + (ServiceKey.expiration_date <= (datetime.utcnow() - expired_ttl))) + def _stale_unapproved_keys_clause(service): unapproved_ttl = timedelta(seconds=config.app_config['UNAPPROVED_SERVICE_KEY_TTL_SEC']) @@ -24,7 +29,7 @@ def _stale_unapproved_keys_clause(service): def _gc_expired(service): - ServiceKey.delete().where(_expired_keys_clause(service) | + ServiceKey.delete().where(_stale_expired_keys_clause(service) | _stale_unapproved_keys_clause(service)).execute() @@ -53,17 +58,18 @@ def create_service_key(name, kid, service, jwk, metadata, expiration_date, rotat _notify_superusers(key) _gc_expired(service) - return key -def generate_service_key(service, expiration_date, kid=None, name='', metadata=None): +def generate_service_key(service, expiration_date, kid=None, name='', metadata=None, + rotation_duration=None): private_key = RSA.generate(2048) jwk = RSAKey(key=private_key.publickey()).serialize() if kid is None: kid = canonical_kid(jwk) - key = create_service_key(name, kid, service, jwk, metadata or {}, expiration_date) + key = create_service_key(name, kid, service, jwk, metadata or {}, expiration_date, + rotation_duration=rotation_duration) return (private_key, key) diff --git a/endpoints/api/superuser.py b/endpoints/api/superuser.py index cf37fc88a..4eb614eeb 100644 --- a/endpoints/api/superuser.py +++ b/endpoints/api/superuser.py @@ -484,6 +484,7 @@ def key_view(key): 'metadata': key.metadata, 'created_date': key.created_date, 'expiration_date': key.expiration_date, + 'rotation_duration': key.rotation_duration, 'approval': approval_view(key.approval) if key.approval is not None else None, } @@ -562,6 +563,9 @@ class SuperUserServiceKeyManagement(ApiResource): except ValueError: abort(400) + if expiration_date <= datetime.now(): + abort(400) + # Create the metadata for the key. user = get_authenticated_user() metadata = body.get('metadata', {}) @@ -572,8 +576,8 @@ class SuperUserServiceKeyManagement(ApiResource): }) # Generate a key with a private key that we *never save*. - (private_key, key) = model.service_keys.generate_service_key(body['service'], metadata, - expiration_date, + (private_key, key) = model.service_keys.generate_service_key(body['service'], expiration_date, + metadata=metadata, name=body.get('name', '')) # Auto-approve the service key. model.service_keys.approve_service_key(key.kid, user, ServiceKeyApprovalType.SUPERUSER, @@ -670,6 +674,9 @@ class SuperUserServiceKey(ApiResource): except ValueError: abort(400) + if expiration_date <= datetime.now(): + abort(400) + key_log_metadata.update({ 'old_expiration_date': key.expiration_date, 'expiration_date': expiration_date, diff --git a/initdb.py b/initdb.py index c629b2d30..984cf972a 100644 --- a/initdb.py +++ b/initdb.py @@ -156,9 +156,10 @@ def __create_subtree(with_storage, repo, structure, creator_username, parent, ta def __generate_service_key(kid, name, user, timestamp, approval_type, expiration=None, - metadata=None, service='sample_service'): + metadata=None, service='sample_service', rotation_duration=None): _, key = model.service_keys.generate_service_key(service, expiration, kid=kid, - name=name, metadata=metadata) + name=name, metadata=metadata, + rotation_duration=rotation_duration) if approval_type is not None: model.service_keys.approve_service_key(key.kid, user, approval_type, @@ -660,7 +661,8 @@ def populate_database(minimal=False, with_storage=False): __generate_service_key('kid4', 'autorotatingkey', new_user_1, six_ago, ServiceKeyApprovalType.KEY_ROTATION, today + timedelta(1), - dict(rotation_ttl=timedelta(hours=12).total_seconds())) + rotation_duration=timedelta(hours=12).total_seconds()) + __generate_service_key('kid5', 'key for another service', new_user_1, today, ServiceKeyApprovalType.SUPERUSER, today + timedelta(14), service='different_sample_service') diff --git a/static/directives/service-keys-manager.html b/static/directives/service-keys-manager.html index 4815725ff..1b7b1d5ba 100644 --- a/static/directives/service-keys-manager.html +++ b/static/directives/service-keys-manager.html @@ -64,11 +64,11 @@