Skip to content

Commit

Permalink
Allow request and application logs to have separate retention policies
Browse files Browse the repository at this point in the history
  • Loading branch information
djperrefort committed Sep 12, 2024
1 parent 9a41184 commit 97b7da0
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 36 deletions.
15 changes: 8 additions & 7 deletions docs/install/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ Improperly configured settings can introduce dangerous vulnerabilities and may d
Keystone uses various static files and user content to facilitate operation.
By default, these files are stored in subdirectories of the installed application directory (`<app>`).

| Setting Name | Default Value | Description |
|---------------------------|--------------------------|---------------------------------------------------------------------------------------------------|
| `CONFIG_TIMEZONE` | `UTC` | The application timezone. |
| `CONFIG_STATIC_DIR` | `<app>/static_files` | Where to store internal static files required by the application. |
| `CONFIG_UPLOAD_DIR` | `<app>/upload_files` | Where to store file data uploaded by users. |
| `CONFIG_LOG_RETENTION` | 30 days | How long to store log records in seconds. Set to 0 to keep all records. |
| `CONFIG_LOG_LEVEL` | `WARNING` | Only record logs above this level (`CRITICAL`, `ERROR`, `WARNING`, `INFO`, `DEBUG`, or `NOTSET`). |
| Setting Name | Default Value | Description |
|----------------------------|----------------------|-------------------------------------------------------------------------------------------------------------|
| `CONFIG_TIMEZONE` | `UTC` | The timezone to use when rendering date/time values. |
| `CONFIG_STATIC_DIR` | `<app>/static_files` | Where to store internal static files required by the application. |
| `CONFIG_UPLOAD_DIR` | `<app>/upload_files` | Where to store file data uploaded by users. |
| `CONFIG_LOG_LEVEL` | `WARNING` | Only record application logs above this level (accepts `CRITICAL`, `ERROR`, `WARNING`, `INFO`, or `DEBUG`). |
| `CONFIG_LOG_RETENTION` | `2592000` (30 days) | How long to store application logs in seconds. Set to 0 to keep all records. |
| `CONFIG_REQUEST_RETENTION` | `2592000` (30 days) | How long to store request logs in seconds. Set to 0 to keep all records. |

## API Throttling

Expand Down
15 changes: 8 additions & 7 deletions keystone_api/apps/logging/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@


@shared_task()
def rotate_log_files() -> None:
"""Delete old log files."""
def clear_log_files() -> None:
"""Delete request and application logs according to retention policies set in application settings."""

if settings.LOG_RECORD_ROTATION == 0:
return
if settings.CONFIG_LOG_RETENTION > 0:
max_app_log_age = timezone.now() - timedelta(seconds=settings.CONFIG_LOG_RETENTION)
AppLog.objects.filter(time__lt=max_app_log_age).delete()

max_record_age = timezone.now() - timedelta(seconds=settings.LOG_RECORD_ROTATION)
AppLog.objects.filter(time__lt=max_record_age).delete()
RequestLog.objects.filter(time__lt=max_record_age).delete()
if settings.CONFIG_REQUEST_RETENTION > 0:
max_request_log_age = timezone.now() - timedelta(seconds=settings.CONFIG_REQUEST_RETENTION)
RequestLog.objects.filter(time__lt=max_request_log_age).delete()
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from django.utils.timezone import now

from apps.logging.models import AppLog, RequestLog
from apps.logging.tasks import rotate_log_files
from apps.logging.tasks import clear_log_files


class LogFileDeletion(TestCase):
Expand Down Expand Up @@ -61,7 +61,7 @@ def test_log_files_rotated(self, mock_now: Mock) -> None:
self.assertEqual(2, RequestLog.objects.count())

# Run rotation
rotate_log_files()
clear_log_files()

# Assert only the newer records remain
self.assertEqual(1, AppLog.objects.count())
Expand All @@ -73,6 +73,6 @@ def test_rotation_disabled(self) -> None:

self.create_dummy_records(now())

rotate_log_files()
clear_log_files()
self.assertEqual(1, AppLog.objects.count())
self.assertEqual(1, RequestLog.objects.count())
4 changes: 2 additions & 2 deletions keystone_api/apps/scheduler/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
'schedule': crontab(minute='0'),
'description': 'This task synchronizes user data against LDAP. This task does nothing if LDAP is disabled.'
},
'apps.logging.tasks.rotate_log_files': {
'task': 'apps.logging.tasks.rotate_log_files',
'apps.logging.tasks.clear_log_files': {
'task': 'apps.logging.tasks.clear_log_files',
'schedule': crontab(hour='0', minute='0'),
'description': 'This task deletes old log entries according to application settings.'
},
Expand Down
35 changes: 18 additions & 17 deletions keystone_api/main/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,23 @@
SECRET_KEY = os.environ.get('SECURE_SECRET_KEY', get_random_secret_key())
ALLOWED_HOSTS = env.list("SECURE_ALLOWED_HOSTS", default=["localhost", "127.0.0.1"])

_SECURE_SESSION_TOKENS = env.bool("SECURE_SESSION_TOKENS", False)
SESSION_COOKIE_SECURE = _SECURE_SESSION_TOKENS
CSRF_COOKIE_SECURE = _SECURE_SESSION_TOKENS
_SECURE_SSL_TOKENS = env.bool("SECURE_SSL_TOKENS", False)
SESSION_COOKIE_SECURE = _SECURE_SSL_TOKENS
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_AGE = env.int("SECURE_SESSION_AGE", 1209600) # 2 weeks

CSRF_TRUSTED_ORIGINS = env.list("SECURE_CSRF_ORIGINS", default=[])
CSRF_COOKIE_SECURE = _SECURE_SSL_TOKENS
CSRF_COOKIE_HTTPONLY = False
CSRF_COOKIE_SAMESITE = "Lax"

SECURE_SSL_REDIRECT = env.bool("SECURE_SSL_REDIRECT", False)
SECURE_HSTS_PRELOAD = env.bool("SECURE_HSTS_PRELOAD", False)
SECURE_HSTS_SECONDS = env.int("SECURE_HSTS_SECONDS", 0)
SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool("SECURE_HSTS_SUBDOMAINS", False)

CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOWED_ORIGINS = env.list("SECURE_ALLOWED_ORIGINS", default=[])

# App Configuration

Expand All @@ -50,6 +56,7 @@

INSTALLED_APPS = [
'jazzmin',
'corsheaders',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
Expand All @@ -65,7 +72,8 @@
'health_check.contrib.celery_ping',
'health_check.contrib.redis',
'rest_framework',
'rest_framework_simplejwt.token_blacklist',
'rest_framework.authtoken',
'dj_rest_auth',
'django_celery_beat',
'django_celery_results',
'django_filters',
Expand All @@ -86,18 +94,18 @@
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'apps.logging.middleware.LogRequestMiddleware',
]

TEMPLATES = [
{ # The default backend rquired by Django builtins (e.g., the admin)
{ # The default backend required by Django builtins (e.g., the admin)
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'OPTIONS': {
Expand Down Expand Up @@ -139,9 +147,6 @@
# REST API settings

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated'
],
Expand Down Expand Up @@ -257,12 +262,6 @@
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
]

SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(seconds=env.int('SECURE_ACCESS_TOKEN_LIFETIME', 5 * 60)),
'REFRESH_TOKEN_LIFETIME': timedelta(seconds=env.int('SECURE_REFRESH_TOKEN_LIFETIME', 24 * 60 * 60)),
'UPDATE_LAST_LOGIN': True
}

# Static file handling (CSS, JavaScript, Images)

STATIC_URL = 'static/'
Expand All @@ -282,7 +281,9 @@

# Logging

LOG_RECORD_ROTATION = env.int('CONFIG_LOG_RETENTION', timedelta(days=30).total_seconds())
CONFIG_LOG_RETENTION = env.int('CONFIG_LOG_RETENTION', timedelta(days=30).total_seconds())
CONFIG_REQUEST_RETENTION = env.int('CONFIG_REQUEST_RETENTION', timedelta(days=30).total_seconds())

LOGGING = {
"version": 1,
"disable_existing_loggers": False,
Expand Down

0 comments on commit 97b7da0

Please sign in to comment.