From 5ef655f1bc1b36514fb7414cc58b1f36a3c079eb Mon Sep 17 00:00:00 2001 From: emelinda Date: Thu, 23 Jul 2026 19:24:32 +0300 Subject: [PATCH] Add production backend configuration with classical JWT authentication, update provisioning tasks, and include `production.py` in `docker-compose` and Ansible workflows --- aero/roles/sarex_stack/tasks/main.yml | 9 +- backend/production.py | 125 ++++++++++++++++++++++++++ docker-compose.yaml | 4 + 3 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 backend/production.py diff --git a/aero/roles/sarex_stack/tasks/main.yml b/aero/roles/sarex_stack/tasks/main.yml index 621bbdb..dade3fd 100644 --- a/aero/roles/sarex_stack/tasks/main.yml +++ b/aero/roles/sarex_stack/tasks/main.yml @@ -23,11 +23,12 @@ dest: "{{ deploy_dir }}/nginx/templates/" mode: "0644" -# backend монтирует ./backend/uwsgi.ini (http-socket + 4 воркера) -- name: Скопировать конфиг uwsgi для backend +# backend монтирует ./backend/uwsgi.ini (http-socket + 4 воркера) и +# ./backend/production.py (классическая JWT-аутентификация) +- name: Скопировать конфиги backend (uwsgi.ini, production.py) ansible.builtin.copy: - src: "{{ deploy_src_root }}/backend/uwsgi.ini" - dest: "{{ deploy_dir }}/backend/uwsgi.ini" + src: "{{ deploy_src_root }}/backend/" + dest: "{{ deploy_dir }}/backend/" mode: "0644" # --- Секреты: генерация с персистом на control-node (идемпотентно) -------- diff --git a/backend/production.py b/backend/production.py new file mode 100644 index 0000000..fd50f8f --- /dev/null +++ b/backend/production.py @@ -0,0 +1,125 @@ +from .base import * + +from logging.handlers import SysLogHandler + +ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') + +FILE_UPLOAD_PERMISSIONS = 0o644 + +DEBUG = False + +CSRF_COOKIE_SECURE = True +SESSION_COOKIE_SECURE = True +SECURE_SSL_REDIRECT = False + +# STATIC_ROOT = env('STATIC_ROOT') +# MEDIA_ROOT = env('MEDIA_ROOT') + +# SECRET_KEY = env('SECRET_KEY') +SECRET_KEY = 't2=9+($2f%7ptsdy4!rby$)mcfl1l%o2e@vs^d(g&(wwi&%k1v' + +# DATABASE CONFIGURATION START +# ------------------------------------------------------------------------------ +POSTGRES_DATABASE = env('DJANGO_POSTGRES_DATABASE') +POSTGRES_USER = env('DJANGO_POSTGRES_USER') +POSTGRES_PASSWORD = env('DJANGO_POSTGRES_PASSWORD') +POSTGRES_HOST = env('DJANGO_POSTGRES_HOST') +POSTGRES_PORTS = env('DJANGO_POSTGRES_PORTS', default="5432") + +DATABASES = { + 'default': { + 'ENGINE': 'django_prometheus.db.backends.postgresql', + 'NAME': POSTGRES_DATABASE, + 'USER': POSTGRES_USER, + 'PASSWORD': POSTGRES_PASSWORD, + 'HOST': POSTGRES_HOST, + 'PORT': POSTGRES_PORTS, + } +} + +# DATABASE CONFIGURATION END +# ------------------------------------------------------------------------------ + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'formatters': { + 'verbose': { + 'format': '[contactor] %(levelname)s %(asctime)s %(message)s' + }, + }, + 'handlers': { + # Send all messages to console + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + }, + # critical errors are logged to sentry + 'sentry': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'logging.StreamHandler', + }, + }, + 'loggers': { + # This is the "catch all" logger + '': { + 'handlers': ['console', 'sentry'], + 'level': 'DEBUG', + 'propagate': False, + }, + } +} + +COMPARATOR_SECTION = env("COMPARATOR_SECTION", default="sarex-production-storage") + +SIMPLE_JWT = { + # 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5), + 'ACCESS_TOKEN_LIFETIME': timedelta(hours=1), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': False, + 'UPDATE_LAST_LOGIN': False, + + 'ALGORITHM': 'RS512', + 'SIGNING_KEY': env.str("JWT_PRIVATE_KEY").replace("\\n", "\n"), + 'VERIFYING_KEY': env.str("JWT_PUBLIC_KEY").replace("\\n", "\n"), + 'AUDIENCE': None, + 'ISSUER': SIMPLE_JWT_ISSUER, + + 'AUTH_HEADER_TYPES': ('Bearer',), + 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', + 'USER_ID_FIELD': 'id', + 'USER_ID_CLAIM': 'user_id', + + 'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',), + 'TOKEN_TYPE_CLAIM': 'token_type', + + 'JTI_CLAIM': 'jti', + + 'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', + # 'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5), + 'SLIDING_TOKEN_LIFETIME': timedelta(hours=1), + 'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), +} + +os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" + +COMPARISON_API_URL = f"{WORKFLOWSSETTINGS.host}/comparisons" +DOCUMENTATION_API_URL = f"{WORKFLOWSSETTINGS.host}/documentations" +PDM_FILES_API_URL = f"{WORKFLOWSSETTINGS.host}/files" + +# --- Override для self-hosted (auth_type=original) -------------------------- +# В base.py классическая SimpleJWT-аутентификация закомментирована (образ собран +# под Zitadel), поэтому Bearer-токен от /api/token/ отвергается защищёнными +# эндпоинтами. Возвращаем её первым классом; Session — для админки. +REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"] = [ + "rest_framework_simplejwt.authentication.JWTAuthentication", + "rest_framework.authentication.SessionAuthentication", + "rest_framework.authentication.BasicAuthentication", +] diff --git a/docker-compose.yaml b/docker-compose.yaml index 62fdd15..f09c7d3 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -302,6 +302,8 @@ services: volumes: # свой uwsgi.ini поверх образного: http-socket + 4 воркера (:z — SELinux/RedOS) - ./backend/uwsgi.ini:/opt/sarex/uwsgi.ini:ro,z + # production.py с включённой классической JWT-аутентификацией + - ./backend/production.py:/opt/sarex/config/settings/production.py:ro,z depends_on: postgres: condition: service_healthy @@ -321,6 +323,8 @@ services: entrypoint: celery -A config worker -l info -E -Q default -n default_worker.%h --concurrency=1 -B environment: <<: *sarex-backend-env + volumes: + - ./backend/production.py:/opt/sarex/config/settings/production.py:ro,z depends_on: postgres: condition: service_healthy