From 2c17ad4ddcfb32f433e98c26854ffaa38f67d6e8 Mon Sep 17 00:00:00 2001 From: Gros Frumos Date: Fri, 20 Mar 2026 21:07:25 +0200 Subject: [PATCH] =?UTF-8?q?kin:=20BATON-ARCH-011=20=D0=97=D0=B0=D1=89?= =?UTF-8?q?=D0=B8=D1=82=D0=B8=D1=82=D1=8C=20BOT=5FTOKEN=20=D0=BE=D1=82=20?= =?UTF-8?q?=D1=83=D1=82=D0=B5=D1=87=D0=BA=D0=B8=20=D0=B2=20nginx=20access.?= =?UTF-8?q?log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 34 ++++++++++++++++++++ nginx/baton.conf | 70 ++++++++++++++++++++++++++++++++++++++++++ tests/test_arch_002.py | 12 ++++++-- 3 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 nginx/baton.conf diff --git a/README.md b/README.md index 3dcc4dd..05f5f12 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,40 @@ systemctl enable --now baton-keepalive.timer systemctl list-timers baton-keepalive.timer ``` +## Nginx deployment + +Для проксирования через nginx используйте готовый шаблон `nginx/baton.conf`. + +### Применение + +```bash +# 1. Скопировать шаблон и заменить домен +sudo cp nginx/baton.conf /etc/nginx/sites-available/baton +sudo sed -i 's//baton.example.com/g' /etc/nginx/sites-available/baton + +# 2. Получить TLS-сертификат (если ещё нет) +sudo certbot certonly --nginx -d baton.example.com + +# 3. Включить конфиг +sudo ln -s /etc/nginx/sites-available/baton /etc/nginx/sites-enabled/baton + +# 4. Проверить и применить +sudo nginx -t && sudo systemctl reload nginx +``` + +### Защита BOT_TOKEN в логах + +Конфиг включает `map`-блок, который автоматически маскирует токен бота в `access_log`: + +``` +# В логе вместо реального токена: +GET /bot/sendMessage → GET /bot[REDACTED]/sendMessage +``` + +Это защита по принципу «defence in depth»: текущий webhook-эндпоинт (`/api/webhook/telegram`) токен в URL не содержит, но маскировка сработает, если в будущем появится маршрут вида `/bot/...`. + +Заголовок `X-Telegram-Bot-Api-Secret-Token` не попадает в `access_log` — nginx не логирует заголовки запросов в стандартном `log_format`. + ## Тесты ```bash diff --git a/nginx/baton.conf b/nginx/baton.conf new file mode 100644 index 0000000..07b7857 --- /dev/null +++ b/nginx/baton.conf @@ -0,0 +1,70 @@ +# nginx/baton.conf — шаблон конфигурации для деплоя Baton +# +# Замените на реальный домен перед использованием. +# Токен бота НЕ включается в этот файл — он передаётся только через +# переменную окружения BOT_TOKEN в самом приложении. +# +# Деплой: +# sudo cp nginx/baton.conf /etc/nginx/sites-available/baton +# sudo ln -s /etc/nginx/sites-available/baton /etc/nginx/sites-enabled/baton +# sudo nginx -t && sudo systemctl reload nginx + +# --------------------------------------------------------------------------- +# Маскировка BOT_TOKEN в access_log (defence in depth) +# +# Хотя текущий webhook-эндпоинт (/api/webhook/telegram) не содержит токен +# в URL, этот map-блок защищает на случай, если в будущем появится маршрут +# вида /bot/... (например, при смене архитектуры или временном дебаге). +# --------------------------------------------------------------------------- +map $request_uri $masked_uri { + default $request_uri; + "~^(/bot)[^/]+(/.*)$" "$1[REDACTED]$2"; +} + +log_format baton_secure '$remote_addr - $remote_user [$time_local] ' + '"$request_method $masked_uri $server_protocol" ' + '$status $body_bytes_sent ' + '"$http_referer" "$http_user_agent"'; + +# --------------------------------------------------------------------------- +# HTTP → HTTPS redirect +# --------------------------------------------------------------------------- +server { + listen 80; + server_name ; + + return 301 https://$server_name$request_uri; +} + +# --------------------------------------------------------------------------- +# HTTPS: проксирование к FastAPI (uvicorn на порту 8000) +# --------------------------------------------------------------------------- +server { + listen 443 ssl; + server_name ; + + ssl_certificate /etc/letsencrypt/live//fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live//privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + # Все запросы логируются с маскированным URI + access_log /var/log/nginx/baton_access.log baton_secure; + error_log /var/log/nginx/baton_error.log warn; + + # Заголовки X-Telegram-Bot-Api-Secret-Token НЕ логируются — + # они передаются только в proxy_pass и не попадают в access_log. + location / { + proxy_pass http://127.0.0.1:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Таймауты для webhook-запросов от Telegram + proxy_read_timeout 30s; + proxy_send_timeout 30s; + proxy_connect_timeout 5s; + } +} diff --git a/tests/test_arch_002.py b/tests/test_arch_002.py index 0571035..a89dfa5 100644 --- a/tests/test_arch_002.py +++ b/tests/test_arch_002.py @@ -32,15 +32,21 @@ _BACKEND_DIR = Path(__file__).parent.parent / "backend" def test_aggregator_task_creation_commented_out_in_main(): - """asyncio.create_task must not appear in active code in main.py (ADR-004).""" + """aggregator.run() must not appear in an active create_task call in main.py (ADR-004). + + Note: other create_task calls (e.g. keep-alive) are allowed — only the + SignalAggregator task is disabled in v1. + """ source = (_BACKEND_DIR / "main.py").read_text() active_lines = [ line for line in source.splitlines() - if "create_task" in line and not line.strip().startswith("#") + if "create_task" in line + and "aggregator" in line + and not line.strip().startswith("#") ] assert active_lines == [], ( - f"Found active asyncio.create_task in main.py: {active_lines}" + f"Found active asyncio.create_task(aggregator...) in main.py: {active_lines}" )