From d45161b12008ea3ee7e0be35298b567d133804f3 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Mon, 12 Feb 2018 14:56:01 -0500 Subject: [PATCH] Add a worker to automatically GC expired app specific tokens Fixes https://jira.coreos.com/browse/QUAY-822 --- .../expiredappspecifictokenworker/log/run | 4 ++ .../batch/expiredappspecifictokenworker/run | 9 ++++ config.py | 4 ++ data/model/appspecifictoken.py | 6 +-- data/model/test/test_appspecifictoken.py | 4 +- util/config/schema.py | 6 +++ workers/expiredappspecifictokenworker.py | 49 +++++++++++++++++++ 7 files changed, 77 insertions(+), 5 deletions(-) create mode 100755 conf/init/service/batch/expiredappspecifictokenworker/log/run create mode 100755 conf/init/service/batch/expiredappspecifictokenworker/run create mode 100644 workers/expiredappspecifictokenworker.py diff --git a/conf/init/service/batch/expiredappspecifictokenworker/log/run b/conf/init/service/batch/expiredappspecifictokenworker/log/run new file mode 100755 index 000000000..a8881fc51 --- /dev/null +++ b/conf/init/service/batch/expiredappspecifictokenworker/log/run @@ -0,0 +1,4 @@ +#!/bin/sh + +# Start the logger +exec logger -i -t expiredappspecifictokenworker diff --git a/conf/init/service/batch/expiredappspecifictokenworker/run b/conf/init/service/batch/expiredappspecifictokenworker/run new file mode 100755 index 000000000..3436f4432 --- /dev/null +++ b/conf/init/service/batch/expiredappspecifictokenworker/run @@ -0,0 +1,9 @@ +#! /bin/bash + +echo 'Starting Expired app specific token GC worker' + +QUAYPATH=${QUAYPATH:-"."} +cd ${QUAYDIR:-"/"} +PYTHONPATH=$QUAYPATH venv/bin/python -m workers.expiredappspecifictokenworker 2>&1 + +echo 'Expired app specific token GC exited' diff --git a/config.py b/config.py index 48216d1e3..26c0635f9 100644 --- a/config.py +++ b/config.py @@ -501,5 +501,9 @@ class DefaultConfig(ImmutableConfig): # Feature Flag: If enabled, users can create and use app specific tokens to login via the CLI. FEATURE_APP_SPECIFIC_TOKENS = True + # How long expired app specific tokens should remain visible to users before being automatically + # deleted. Set to None to turn off garbage collection. + EXPIRED_APP_SPECIFIC_TOKEN_GC = '1d' + # The size of pages returned by the Docker V2 API. V2_PAGINATION_SIZE = 50 diff --git a/data/model/appspecifictoken.py b/data/model/appspecifictoken.py index 282fa458c..2ff92f56f 100644 --- a/data/model/appspecifictoken.py +++ b/data/model/appspecifictoken.py @@ -63,11 +63,11 @@ def get_expiring_tokens(user, soon): AppSpecificAuthToken.expiration <= soon_datetime)) -def gc_expired_tokens(user): - """ Deletes all expired tokens owned by the given user. """ +def gc_expired_tokens(expiration_window): + """ Deletes all expired tokens outside of the expiration window. """ (AppSpecificAuthToken .delete() - .where(AppSpecificAuthToken.user == user, AppSpecificAuthToken.expiration < datetime.now()) + .where(AppSpecificAuthToken.expiration < (datetime.now() - expiration_window)) .execute()) diff --git a/data/model/test/test_appspecifictoken.py b/data/model/test/test_appspecifictoken.py index ef0498cc0..b811a8678 100644 --- a/data/model/test/test_appspecifictoken.py +++ b/data/model/test/test_appspecifictoken.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timedelta from mock import patch import pytest @@ -36,7 +36,7 @@ def test_gc(expiration, initialized_db): token = create_token(user, 'Some token', expiration=expiration_date) # GC tokens. - gc_expired_tokens(user) + gc_expired_tokens(timedelta(seconds=0)) # Ensure the token was GCed if expired and not if it wasn't. assert (access_valid_token(token.token_code) is None) == is_expired diff --git a/util/config/schema.py b/util/config/schema.py index 7fcc11d9c..ba300d5ba 100644 --- a/util/config/schema.py +++ b/util/config/schema.py @@ -736,6 +736,12 @@ CONFIG_SCHEMA = { 'pattern': '^[0-9]+(w|m|d|h|s)$', }, + 'EXPIRED_APP_SPECIFIC_TOKEN_GC': { + 'type': ['string', 'null'], + 'description': 'Duration of time expired external app tokens will remain before being garbage collected. Defaults to 1d.', + 'pattern': '^[0-9]+(w|m|d|h|s)$', + }, + # Feature Flag: Permanent Sessions. 'FEATURE_PERMANENT_SESSIONS': { 'type': 'boolean', diff --git a/workers/expiredappspecifictokenworker.py b/workers/expiredappspecifictokenworker.py new file mode 100644 index 000000000..9db1aed7f --- /dev/null +++ b/workers/expiredappspecifictokenworker.py @@ -0,0 +1,49 @@ +import logging +import time + +import features + +from app import app # This is required to initialize the database. +from data import model +from workers.worker import Worker +from util.log import logfile_path +from util.timedeltastring import convert_to_timedelta + +POLL_PERIOD_SECONDS = 60 * 60 # 1 hour + +logger = logging.getLogger(__name__) + +class ExpiredAppSpecificTokenWorker(Worker): + def __init__(self): + super(ExpiredAppSpecificTokenWorker, self).__init__() + + expiration_window = app.config.get('EXPIRED_APP_SPECIFIC_TOKEN_GC', '1d') + self.expiration_window = convert_to_timedelta(expiration_window) + + logger.debug('Found expiration window: %s', expiration_window) + self.add_operation(self._gc_expired_tokens, POLL_PERIOD_SECONDS) + + def _gc_expired_tokens(self): + """ Garbage collects any expired app specific tokens outside of the configured + window. """ + logger.debug('Garbage collecting expired app specific tokens with window: %s', + self.expiration_window) + model.appspecifictoken.gc_expired_tokens(self.expiration_window) + return True + +if __name__ == "__main__": + logging.config.fileConfig(logfile_path(debug=False), disable_existing_loggers=False) + + if not features.APP_SPECIFIC_TOKENS: + logger.debug('App specific tokens disabled; skipping') + while True: + time.sleep(100000) + + if app.config.get('EXPIRED_APP_SPECIFIC_TOKEN_GC') is None: + logger.debug('GC of App specific tokens is disabled; skipping') + while True: + time.sleep(100000) + + logger.debug('Starting expired app specific token GC worker') + worker = ExpiredAppSpecificTokenWorker() + worker.start()