Add a worker to automatically GC expired app specific tokens

Fixes https://jira.coreos.com/browse/QUAY-822
This commit is contained in:
Joseph Schorr 2018-02-12 14:56:01 -05:00
parent 06d25816be
commit d45161b120
7 changed files with 77 additions and 5 deletions

View file

@ -0,0 +1,4 @@
#!/bin/sh
# Start the logger
exec logger -i -t expiredappspecifictokenworker

View file

@ -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'

View file

@ -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 Flag: If enabled, users can create and use app specific tokens to login via the CLI.
FEATURE_APP_SPECIFIC_TOKENS = True 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. # The size of pages returned by the Docker V2 API.
V2_PAGINATION_SIZE = 50 V2_PAGINATION_SIZE = 50

View file

@ -63,11 +63,11 @@ def get_expiring_tokens(user, soon):
AppSpecificAuthToken.expiration <= soon_datetime)) AppSpecificAuthToken.expiration <= soon_datetime))
def gc_expired_tokens(user): def gc_expired_tokens(expiration_window):
""" Deletes all expired tokens owned by the given user. """ """ Deletes all expired tokens outside of the expiration window. """
(AppSpecificAuthToken (AppSpecificAuthToken
.delete() .delete()
.where(AppSpecificAuthToken.user == user, AppSpecificAuthToken.expiration < datetime.now()) .where(AppSpecificAuthToken.expiration < (datetime.now() - expiration_window))
.execute()) .execute())

View file

@ -1,4 +1,4 @@
from datetime import datetime from datetime import datetime, timedelta
from mock import patch from mock import patch
import pytest import pytest
@ -36,7 +36,7 @@ def test_gc(expiration, initialized_db):
token = create_token(user, 'Some token', expiration=expiration_date) token = create_token(user, 'Some token', expiration=expiration_date)
# GC tokens. # 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. # Ensure the token was GCed if expired and not if it wasn't.
assert (access_valid_token(token.token_code) is None) == is_expired assert (access_valid_token(token.token_code) is None) == is_expired

View file

@ -736,6 +736,12 @@ CONFIG_SCHEMA = {
'pattern': '^[0-9]+(w|m|d|h|s)$', '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 Flag: Permanent Sessions.
'FEATURE_PERMANENT_SESSIONS': { 'FEATURE_PERMANENT_SESSIONS': {
'type': 'boolean', 'type': 'boolean',

View file

@ -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()