diff --git a/docs/install/settings.md b/docs/install/settings.md index ad7c49bf..bc698599 100644 --- a/docs/install/settings.md +++ b/docs/install/settings.md @@ -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 (``). -| Setting Name | Default Value | Description | -|---------------------------|--------------------------|---------------------------------------------------------------------------------------------------| -| `CONFIG_TIMEZONE` | `UTC` | The application timezone. | -| `CONFIG_STATIC_DIR` | `/static_files` | Where to store internal static files required by the application. | -| `CONFIG_UPLOAD_DIR` | `/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` | `/static_files` | Where to store internal static files required by the application. | +| `CONFIG_UPLOAD_DIR` | `/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 diff --git a/keystone_api/apps/logging/tasks.py b/keystone_api/apps/logging/tasks.py index c3a505f8..42661402 100644 --- a/keystone_api/apps/logging/tasks.py +++ b/keystone_api/apps/logging/tasks.py @@ -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() diff --git a/keystone_api/apps/logging/tests/test_tasks/test_rotate_log_files.py b/keystone_api/apps/logging/tests/test_tasks/test_rotate_log_files.py index c6f63610..cb51ce34 100644 --- a/keystone_api/apps/logging/tests/test_tasks/test_rotate_log_files.py +++ b/keystone_api/apps/logging/tests/test_tasks/test_rotate_log_files.py @@ -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): @@ -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()) @@ -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()) diff --git a/keystone_api/apps/scheduler/celery.py b/keystone_api/apps/scheduler/celery.py index 6558680e..5ffecdb9 100644 --- a/keystone_api/apps/scheduler/celery.py +++ b/keystone_api/apps/scheduler/celery.py @@ -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.' }, diff --git a/keystone_api/main/settings.py b/keystone_api/main/settings.py index 7c26360a..a11bdd9f 100644 --- a/keystone_api/main/settings.py +++ b/keystone_api/main/settings.py @@ -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 @@ -50,6 +56,7 @@ INSTALLED_APPS = [ 'jazzmin', + 'corsheaders', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -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', @@ -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': { @@ -139,9 +147,6 @@ # REST API settings REST_FRAMEWORK = { - 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework_simplejwt.authentication.JWTAuthentication', - ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated' ], @@ -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/' @@ -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,