From c6fdc969c21878f73b32bc3c2af976071e208e26 Mon Sep 17 00:00:00 2001 From: IK Date: Wed, 9 Sep 2026 07:29:39 +0700 Subject: [PATCH] fix: separate admin page and API authentication --- README.md | 4 +-- deploy/Caddyfile | 5 ++-- deploy/README.md | 2 +- deploy/probe-admin-auth.py | 44 ++++++++++++++++++++++++++++++++ deploy/test-proxy-routing.sh | 2 ++ docs/AUDIT_FIXES.md | 2 ++ docs/PROJECT_AUDIT_2026-09-08.md | 2 +- docs/ROADMAP.md | 4 +-- docs/security-review.md | 8 +++--- 9 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 deploy/probe-admin-auth.py diff --git a/README.md b/README.md index 36e3633..155f5ad 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Web Docker-образ устанавливает зависимости чере Идёт исправление аудита: актуальные изменения и ограничения перечислены в [AUDIT_FIXES.md](docs/AUDIT_FIXES.md). Production Compose включает community scheduler; страницы rules/privacy реализованы. Для запуска остаются сервер, DNS/TLS, секреты, внешний backup и контакты. Шкала 72 часов использует полную выборку по времени поступления; одинаковые поля разных источников больше не считаются доказательством одного события. Фоновая публикация обновляет кэш API в пределах TTL, не мгновенно. -Полный аудит 8 сентября: [отчёт](docs/PROJECT_AUDIT_2026-09-08.md), [приоритетный план](docs/ROADMAP.md#ближайший-рабочий-пакет). T01 выполнен: Caddy-маршруты формы исправлены и проверены через временный proxy командой `sh deploy/test-proxy-routing.sh` на запущенном локальном Compose. Перед production остаются конфликт Basic/Bearer admin-аутентификации и исходящая сеть community scheduler. Затем — клиентский rate limit, надёжность парсеров, согласованность фильтров/оценок и HTTP/SEO-контракт ошибок. +Полный аудит 8 сентября: [отчёт](docs/PROJECT_AUDIT_2026-09-08.md), [приоритетный план](docs/ROADMAP.md#ближайший-рабочий-пакет). T01/T02 выполнены: исправлены маршруты формы и конфликт admin-аутентификации. Проверка `sh deploy/test-proxy-routing.sh` на запущенном локальном Compose проверяет Astro redirects, API, Basic-доступ к страницам и Bearer-доступ к API, включая отказы. Следующий блокер — исходящая сеть community scheduler (T03). Затем — клиентский rate limit, надёжность парсеров, согласованность фильтров/оценок и HTTP/SEO-контракт ошибок. На ширинах 320, 390, 768 и 1280 px ранее проверено отсутствие горизонтального переполнения основных страниц. Это не полная визуальная приёмка: аудит обнаружил неверную desktop-компоновку фильтров; наполненные карточки, длинные названия, клавиатура и zoom остаются отдельной задачей. @@ -59,7 +59,7 @@ FastAPI ─ PostgreSQL 17 └ MinIO/S3 ``` -Наружу production-профиль публикует только Caddy. PostgreSQL, API, Astro и MinIO находятся во внутренних Docker-сетях. Caddy завершает TLS и защищает административные маршруты Basic Auth; административный API дополнительно требует bearer token. +Наружу production-профиль публикует только Caddy. PostgreSQL, API, Astro и MinIO находятся в Docker-сетях. Caddy завершает TLS и защищает административные страницы Basic Auth; административный API отдельно проверяет Bearer-токен в FastAPI. Basic не накладывается на API-запросы. Gitea Actions workflow `.gitea/workflows/ci.yml` на каждый push и pull request проверяет Python, миграции на чистой PostgreSQL, Astro build и полный Compose/Playwright-сценарий. При падении E2E сохраняются логи контейнеров и Playwright-артефакты. diff --git a/deploy/Caddyfile b/deploy/Caddyfile index 849477f..55cb5e1 100644 --- a/deploy/Caddyfile +++ b/deploy/Caddyfile @@ -25,9 +25,8 @@ @adminApi path /api/v1/admin/* handle @adminApi { - basic_auth { - {$ADMIN_BASIC_USER} {$ADMIN_BASIC_PASSWORD_HASH} - } + # FastAPI verifies the Bearer token. Basic Auth here would reject the + # browser's Bearer Authorization header before it reached the API. reverse_proxy api:8000 } diff --git a/deploy/README.md b/deploy/README.md index ea50d93..93a1818 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1,6 +1,6 @@ # Развёртывание открытой альфы rf4spotter.ru -Production-контур рассчитан на один Linux-сервер с Docker Compose. Наружу публикуются только Caddy `80/443`; PostgreSQL, FastAPI и MinIO не имеют host-портов. Административные страницы защищены одновременно Caddy Basic Auth и API bearer token. +Production-контур рассчитан на один Linux-сервер с Docker Compose. Наружу публикуются только Caddy `80/443`; PostgreSQL, FastAPI и MinIO не имеют host-портов. Административные страницы защищены Caddy Basic Auth, административный API — Bearer-токеном FastAPI. API-запросы не требуют Basic: обе схемы используют заголовок Authorization и не могут накладываться на один запрос. Текущее состояние публичных DNS/TLS и незакрытые инфраструктурные действия ведутся в [`docs/deployment-status.md`](../docs/deployment-status.md). diff --git a/deploy/probe-admin-auth.py b/deploy/probe-admin-auth.py new file mode 100644 index 0000000..329c641 --- /dev/null +++ b/deploy/probe-admin-auth.py @@ -0,0 +1,44 @@ +"""Read-only checks through a temporary proxy, run inside the local API container.""" +import base64 +import json +import os +from urllib.error import HTTPError +from urllib.request import Request, urlopen + + +base = os.environ["RF4_PROXY_TEST_URL"] +token = os.environ["ADMIN_TOKEN"] +basic = "Basic " + base64.b64encode(b"route-test:hiccup").decode() + + +def probe(path, expected, authorization=None, method="GET", data=None): + headers = {"Host": "localhost:8080"} + if authorization: + headers["Authorization"] = authorization + if data is not None: + headers["Content-Type"] = "application/json" + request = Request(base + path, headers=headers, method=method, data=data) + try: + response = urlopen(request, timeout=15) + except HTTPError as error: + response = error + with response: + assert response.status == expected, f"{method} {path}: expected {expected}, got {response.status}" + if path.startswith("/api/v1/admin/") and expected == 200: + assert "no-store" in response.headers.get("Cache-Control", "") + assert isinstance(json.load(response), dict) + + +probe("/admin/moderation", 401) +probe("/admin/moderation", 200, basic) +probe("/admin/moderation", 401, "Bearer " + token) +for path in ("/api/v1/admin/diagnostics", "/api/v1/admin/catch-reports"): + probe(path, 401) + probe(path, 401, basic) + probe(path, 401, "Bearer invalid-routing-test-token") +probe("/api/v1/admin/diagnostics", 200, "Bearer " + token) +# Invalid UUID prevents writes while checking the moderation request path. +probe("/api/v1/admin/catch-reports/not-a-uuid", 401, method="PATCH", data=b'{"status":"approved"}') +probe("/api/v1/admin/catch-reports/not-a-uuid", 422, "Bearer " + token, + method="PATCH", data=b'{"status":"approved"}') +print("Admin auth passed: Basic pages, Bearer API, rejected missing/wrong credentials") diff --git a/deploy/test-proxy-routing.sh b/deploy/test-proxy-routing.sh index 7fb217e..778b366 100644 --- a/deploy/test-proxy-routing.sh +++ b/deploy/test-proxy-routing.sh @@ -44,4 +44,6 @@ check_redirect /api/report '/report?state=create_error' check_redirect /api/report-screenshot '/report?state=screenshot_error&report_id=' curl -fsS --max-time 15 -H 'Host: localhost:8080' "$base/api/v1/fishes?limit=1" >/dev/null curl -fsS --max-time 15 -H 'Host: localhost:8080' "$base/ready" >/dev/null +docker compose exec -T -e "RF4_PROXY_TEST_URL=http://$container:8080" api \ + python - < "$repo/deploy/probe-admin-auth.py" echo "Proxy routing passed: Astro form redirects, FastAPI catalog and health/readiness" diff --git a/docs/AUDIT_FIXES.md b/docs/AUDIT_FIXES.md index 9fe2beb..415fda4 100644 --- a/docs/AUDIT_FIXES.md +++ b/docs/AUDIT_FIXES.md @@ -4,6 +4,8 @@ ## Исправлено в рабочей версии +- [x] T02: разделены Basic для административных страниц и Bearer для admin API. 9 сентября проверены положительные/отрицательные сценарии через Caddy; API сохраняет обязательную проверку токена и no-store. + - [x] T01 полного аудита: маршруты `/api/report` и `/api/report-screenshot` достигают Astro через Caddy; FastAPI получает `/api/v1/*`, health/readiness. 9 сентября: `sh deploy/test-proxy-routing.sh` проходит на локальном стенде с корректным Origin; создание реальной заявки через production proxy остаётся частью T06. - [x] Очередь внешних наблюдений фильтруется в SQL до limit/offset; UI показывает по 50 записей с навигацией, опубликованные записи не вытесняют ожидающие проверки. diff --git a/docs/PROJECT_AUDIT_2026-09-08.md b/docs/PROJECT_AUDIT_2026-09-08.md index 734fe68..4ea6ba1 100644 --- a/docs/PROJECT_AUDIT_2026-09-08.md +++ b/docs/PROJECT_AUDIT_2026-09-08.md @@ -1,6 +1,6 @@ # Аудит RF4 Spotter — 8 сентября 2026 -Обновление 9 сентября: T01 исправлен (`bb61d21`) и проверен скриптом `deploy/test-proxy-routing.sh`. Ниже сохранены исходные находки аудита; актуальные отметки выполнения — в ROADMAP. T02/T03 остаются блокерами production. +Обновление 9 сентября: T01 исправлен (`bb61d21`), T02 закрыт разделением Basic для страниц и Bearer для API; оба проверены скриптом `deploy/test-proxy-routing.sh`. Ниже сохранены исходные находки аудита; актуальные отметки выполнения — в ROADMAP. T03 остаётся блокером production. База проверки: commit `486b4e9`. Аудит охватывает production-конфигурацию, API, импорт и публикацию данных, Astro, пользовательские сценарии, визуальную идентичность и SEO. Это отчёт о текущем состоянии, а не подтверждение готовности к запуску. Исправления приложения в этот пакет не входят. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 309bf82..704413e 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -180,12 +180,12 @@ ## Ближайший рабочий пакет -Production-контур требует исправлений до запуска. Доказательства и критерии приёмки каждого ID находятся в [полном аудите](PROJECT_AUDIT_2026-09-08.md). Следующая задача — **T02**, затем T03. Инфраструктурные изменения можно объединить в один проверочный Compose-прогон. +Production-контур требует исправлений до запуска. Доказательства и критерии приёмки каждого ID находятся в [полном аудите](PROJECT_AUDIT_2026-09-08.md). Следующая задача — **T03**. Инфраструктурные изменения можно объединить в один проверочный Compose-прогон. ### P0 — блокеры production - [x] T01: Caddy направляет в FastAPI только `/api/v1/*`, `/health`, `/ready`, обработчики формы остаются в Astro (правка `bb61d21`). Проверено 9 сентября: `sh deploy/test-proxy-routing.sh`, оба POST дают ожидаемый 303, каталог и readiness доступны. Тест использует временный proxy и не создаёт уловов; успешная отправка с сохранением входит в T06. -- [ ] T02: согласовать Basic/Bearer admin-аутентификацию через production proxy. +- [x] T02: Basic применяется к admin-страницам, Bearer — к admin API, конфликт Authorization устранён. 9 сентября: `sh deploy/test-proxy-routing.sh` проверяет доступ к странице/диагностике, 401 без правильных credentials и авторизованный PATCH с невалидным UUID (422, без записи). Полный цикл модерации — T06. - [ ] T03: обеспечить исходящую сеть community scheduler при изоляции БД/MinIO. ### P1 — до открытой альфы diff --git a/docs/security-review.md b/docs/security-review.md index 63033d8..33fa481 100644 --- a/docs/security-review.md +++ b/docs/security-review.md @@ -7,7 +7,7 @@ | Область | Реализация | |---|---| | Внешняя поверхность | наружу публикуются только Caddy `80/443`; PostgreSQL, API, web и MinIO находятся во внутренних сетях | -| Admin web/API | `/admin/*` и `/api/v1/admin/*` требуют Caddy Basic Auth; API дополнительно требует длинный bearer token | +| Admin web/API | `/admin/*` требует Caddy Basic Auth; `/api/v1/admin/*` требует длинный Bearer-токен в FastAPI. Схемы разделены по маршрутам, поскольку используют один заголовок Authorization (T02, 9 сентября) | | Сравнение токена | constant-time `hmac.compare_digest` | | CORS | production принимает только явно заданные HTTPS origins, wildcard запрещён конфигурацией | | Чувствительные ответы | административные ответы и создание заявки получают `Cache-Control: no-store` | @@ -31,8 +31,10 @@ 1. Убедиться, что `ss -lntup` не показывает наружу порты `4321`, `8000`, `9000`, `9001`, `5432`. 2. Проверить `stat -c '%a %U:%G' .env.production`: ожидается `600 rf4spotter:rf4spotter`. -3. Без Basic Auth запрос к `/admin/moderation` и `/api/v1/admin/catch-reports` должен вернуть `401` от Caddy. -4. С Basic Auth, но без bearer token административный API должен вернуть `401` от FastAPI. +3. Без Basic Auth запрос к `/admin/moderation` должен вернуть `401` от Caddy; с правильным Basic — `200`. Bearer не открывает административную страницу. +4. Admin API без Bearer, с неверным Bearer или только с Basic должен вернуть `401` от FastAPI; правильный Bearer даёт доступ и `Cache-Control: no-store`. Владение API-токеном достаточно для API-доступа; дополнительная Basic-проверка к этим запросам не применяется. 5. Проверить security headers через `curl -I https://rf4spotter.ru/`. 6. Убедиться, что `https://files.rf4spotter.ru/` не открывает Console, list buckets или анонимную запись. 7. После проверки удалить команды с секретами из shell history либо вводить их через защищённый password manager. + +Локальная проверка: `sh deploy/test-proxy-routing.sh` запускает временный Caddy с production-маршрутами перед работающим Compose. `probe-admin-auth.py` читает токен из окружения API-контейнера и не печатает его. Проверяются успешное чтение, отказы и PATCH с невалидным UUID без изменения данных; полный цикл реальной модерации через proxy относится к T06.