From 1db3824cb44be986f6ccd2280b341706ed890c72 Mon Sep 17 00:00:00 2001 From: embeddevio Date: Wed, 21 Oct 2026 23:29:36 +0300 Subject: [PATCH] =?UTF-8?q?docs(#16):=20ADR-007=20(web-=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B5=D1=80)=20=D0=B8=20ADR-008=20(=D0=B1=D0=B5=D0=B7?= =?UTF-8?q?=D0=BE=D0=BF=D0=B0=D1=81=D0=BD=D0=BE=D1=81=D1=82=D1=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/adr/007-embedded-web-server.md | 26 ++++++++++++++++++++++++ docs/adr/008-api-ui-security.md | 31 +++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 docs/adr/007-embedded-web-server.md create mode 100644 docs/adr/008-api-ui-security.md diff --git a/docs/adr/007-embedded-web-server.md b/docs/adr/007-embedded-web-server.md new file mode 100644 index 0000000..84be090 --- /dev/null +++ b/docs/adr/007-embedded-web-server.md @@ -0,0 +1,26 @@ +# 007. Web-сервер: встроенный uvicorn, без nginx + +- **Статус:** принято +- **Контекст/issue:** #15, #31 + +## Контекст + +UI — SPA, API — REST и WebSocket, всё в одном контейнере. Нагрузка: LAN, единичный пользователь. +Установка должна быть в одну команду `docker run` без дополнительных контейнеров. + +## Решение + +Один uvicorn-процесс `awg-api` отдаёт и API, и собранную статику UI, и WebSocket. Сборка UI выполняется +на этапе билда образа и попадает в `ui/dist`. Закладывается совместимость с внешним reverse-proxy: +поддержка base-path и заголовков `X-Forwarded-*`, без обязательной настройки хоста. + +## Альтернативы + +- **nginx в образе** — плюс к размеру и к конфигу, преимуществ при такой нагрузке нет. +- **Отдельный контейнер для UI** — нарушает требование единого образа. + +## Последствия + +- Заголовки кэширования статики и `index.html` настраиваются в FastAPI (иначе UI кэшируется после обновления). +- WebSocket идёт через тот же порт: учитывается при настройке reverse-proxy в runbook (#45). +- Docker HEALTHCHECK опирается на `/api/v1/health` того же процесса (#31). diff --git a/docs/adr/008-api-ui-security.md b/docs/adr/008-api-ui-security.md new file mode 100644 index 0000000..098fc8d --- /dev/null +++ b/docs/adr/008-api-ui-security.md @@ -0,0 +1,31 @@ +# 008. Безопасность API/UI: токен, CSRF, rate limit, маскировка секретов + +- **Статус:** принято +- **Контекст/issue:** #15, #34 + +## Контекст + +Приложение управляет VPN-туннелем и принимает приватные ключи AmneziaWG. Оно доступно из LAN, +а в перспективе может быть выставлено через reverse-proxy. В ТЗ этот слой не описан, но без него +загрузка `.conf` через веб-интерфейс превращается в дыру. + +## Решение + +- API — токен доступа в заголовке (Bearer), токен только в виде хеша в БД; +- UI — сессия + CSRF-токен на мутирующие запросы; +- приватные ключи (`PrivateKey`, `PresharedKey`) — `writeOnly` в OpenAPI, маска в UI и логах, + файлы 0600 в отдельном каталоге; +- rate limit и lockout на `/auth`, ограничение допустимых источников, bind по умолчанию на LAN; +- audit log всех изменений конфигурации. + +## Альтернативы + +- **Без аутентификации, доступ только по LAN** — не защищает от заражённого устройства в сети и от + ошибочной публикации порта. +- **OIDC-провайдер** — не для локального однопользовательского приложения. + +## Последствия + +- Начальный токен генерируется при первом старте и выводится в лог один раз — это шаг в runbook (#45). +- Маскировка секретов проверяется автотестами (#41) и security-прогоном (#44). +- UI обязан корректно вести себя при 401 (переход на форму входа) — часть AC #35.