Разработана первая версия приложений

This commit is contained in:
mi
2026-07-10 18:06:14 +03:00
parent aa8761d1b3
commit 8c7b4074c4
162 changed files with 12178 additions and 16 deletions
+274
View File
@@ -0,0 +1,274 @@
# module-03. Проектная спецификация корневого `nginx`
> Статус: целевая спецификация полностью рабочего edge-контура MVP.
> Источники: [`README.md`](README.md), [`arch-00-glossary.md`](arch-00-glossary.md), [`arch-01-system-architecture.md`](arch-01-system-architecture.md), [`arch-02-api-contracts.md`](arch-02-api-contracts.md), [`arch-03-docker-compose-blueprint.md`](arch-03-docker-compose-blueprint.md), [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), [`arch-05-agent-development-process.md`](arch-05-agent-development-process.md), [`module-01-api-backend.md`](module-01-api-backend.md).
## 1. Назначение и обязательная топология
В production-like контуре существует ровно один корневой nginx. Только он публикует host-порты `80/443`, завершает TLS, раздаёт SPA и проксирует публичные маршруты. Контейнеры API, Keycloak, Redis, Safety, Bitrix и OTEL используют только `expose`/Docker networks.
Если перед VM есть внешний WAF/LB, доверенные proxy CIDR задаются явно; nginx не доверяет произвольному `X-Forwarded-For`. Другой nginx на host не должен маршрутизировать сервисы по отдельности.
## 2. Routing matrix
Порядок location критичен: exact/longest public routes до общего `/bitrix/`.
| Внешний путь | Upstream | Режим |
|---|---|---|
| `/api/` | `api-backend:8000` | REST; `/api/v1/realtime` WS |
| `/auth/` | `keycloak:8080` | OIDC/OTP, prefix/hostname согласован с issuer |
| `/bitrix/sync/webhook/contact` | `bitrix-sync:8080` | public HTTPS POST, no cache; отсутствует, пока действует stub module-07 |
| `/bitrix/handler`, `/bitrix/install`, `/bitrix/placement` | `bitrix-local-app:8080` | public HTTPS |
| exact `/health/live`, `/health/ready` | `bitrix-local-app:8080` | по умолчанию не публикуются; только при явно выбранной ops/monitoring policy |
| `/` | static SPA либо Expo dev upstream | `try_files` fallback |
`/internal/`, `/_internal/`, Redis/OTLP/admin/status/config files запрещены exact prefix response `404` (допустим `403`, но единообразно выбран `404`). Никакого fallback internal path в SPA или общий proxy. `message-safety` не имеет публичного route.
## 3. Upstreams
Именованные upstream: `api_backend`, `keycloak`, `bitrix_local`, `bitrix_sync`, опционально `frontend_dev`. Для одной replica допустим `server service:port`; `keepalive` включён. Docker DNS resolver задаётся с коротким `valid` и `resolve` там, где поддерживает выбранная nginx edition; иначе контейнер перезапускается при смене IP upstream.
Upstream failures не перенаправляются на другой сервис и не попадают в SPA. API возвращает `502/504` с безопасным nginx body и `X-Request-ID`; custom JSON error допустим для `/api`, но не имитирует backend domain code.
## 4. HTTP/HTTPS и TLS
- единый web host `:80` обслуживает только ACME challenge и `308 https://$host$request_uri`;
- выделенный API host, если появится, не имеет listener `:80`;
- `:443 ssl http2`, TLS 1.2/1.3, современные cipher suites, session tickets по ops policy;
- сертификат доверенного CA, private key read-only и недоступен приложению;
- OCSP stapling при поддержке CA/DNS;
- HSTS включается только после успешной проверки HTTPS: `max-age` из env, затем по решению ops `includeSubDomains`; preload не включать автоматически;
- OIDC redirects, cookies и external URLs всегда HTTPS.
## 5. ACME lifecycle
Выбран webroot Certbot/ACME client с общими named volumes:
```text
nginx-certs -> /etc/letsencrypt (rw у certbot, ro у nginx)
nginx-acme-webroot -> /var/www/certbot
frontend-static -> /usr/share/nginx/html:ro
```
Bootstrap:
1. DNS указывает на VM; 80/443 разрешены.
2. Запустить временный HTTP config с `/.well-known/acme-challenge/`.
3. Выпустить certificate без остановки nginx.
4. Проверить `nginx -t`, атомарно активировать TLS config, reload.
Renew container/host timer выполняет `certbot renew` минимум дважды в сутки; после фактического renewal — `nginx -s reload`. Reload допускается только после `nginx -t`; при ошибке остаётся старый worker/config/cert и срабатывает alert. Контролируются expiry days и последняя успешная попытка. Staging CA используется в rehearsal, чтобы не исчерпать лимиты.
## 6. Request ID и forwarded headers
На edge формируется trusted request id. Базовый nginx не генерирует UUID штатной переменной, поэтому используется njs/Lua либо модуль request-id, включённый в закреплённый image. Входящий `X-Request-ID` принимается только если соответствует UUID/ULID и длине; иначе генерируется новый.
Upstream получает:
```text
Host: original host
X-Real-IP: trusted real client IP
X-Forwarded-For: normalized proxy chain
X-Forwarded-Proto: https
X-Forwarded-Host: original host
X-Forwarded-Port: 443
X-Request-ID: edge request id
traceparent: входной валидный либо новый согласно OTEL integration
```
Ответ всегда содержит `X-Request-ID`. Клиентские `X-Forwarded-*` от недоверенного адреса перезаписываются. `Authorization`, `Cookie`, query string и body не попадают в access log.
## 7. WebSocket
Только exact `location = /api/v1/realtime`:
- `proxy_http_version 1.1`;
- `Upgrade $http_upgrade`, `Connection` через `map`;
- buffering и response cache выключены;
- read timeout больше ping interval (ориентир 75 с), send timeout bounded;
- rate limit handshake и `limit_conn` на IP;
- query `access_token` вырезается/редактируется из логов;
- subprotocol передаётся;
- при shutdown nginx позволяет grace reconnect, frontend восстанавливается polling.
## 8. Timeouts и body limits
Общие ориентиры:
| Группа | connect/send/read |
|---|---|
| обычный API | 3s / 30s / 30s |
| auth | 3s / 30s / 60s |
| Bitrix callback | 3s / 30s / 60s |
| WS | 3s / 30s / 75s+ |
| message POST | 3s / 30s / `MESSAGE_SAFETY_TASK_POLL_MAX_SEC + 30s` минимум |
При default safety max 300 сек message read timeout не меньше 330 сек. Значение генерируется из env template до startup; nginx не выполняет арифметику env runtime.
`client_max_body_size` global 8m по arch-04, но JSON API locations получают более строгие limits, где возможно. Байты вложения не проходят через nginx/API: клиент PUT напрямую в S3. Buffering request допустим для малого JSON; для callback устанавливается bounded temp storage. Header count/size ограничены.
## 9. Edge rate limits
`limit_req_zone` использует binary remote address и отдельные зоны:
- `auth`: `NGINX_RATE_LIMIT_AUTH`, малый burst, без большого nodelay;
- `public`: config/content, 60/min/IP;
- `api`: общий API;
- `polling`: GET messages fallback;
- `downloads`: issuance URL;
- `bitrix_callbacks`: мягкий burst для повторов;
- `ws_connect`: handshake;
- `connections`: `limit_conn`.
Ответ превышения — `429`, `Retry-After` (статический/вычисляемый для зоны) и request id. Edge не реализует user-level бизнес-лимит; это делает API/Redis. OPTIONS не должен расходовать auth budget чрезмерно. Bitrix webhook retries имеют отдельный достаточный burst и всё равно проверяют application token в сервисе.
## 10. Static SPA и dev mode
Production:
- root `${FRONTEND_STATIC_PATH}`;
- существующие hashed assets — `Cache-Control: public, max-age=31536000, immutable`;
- `index.html`, manifest/service worker — `no-cache` либо короткая revalidation;
- `try_files $uri $uri/ /index.html`;
- dotfiles, source maps (если не предназначены), config/env artifacts запрещены;
- API/Bitrix/auth/internal locations объявлены до SPA и никогда в неё не fallback.
Dev: при `FRONTEND_DEV_PROXY_ENABLED=true` `/` проксируется на allow-listed `EXPO_DEV_SERVER_URL`, с WS/HMR. Этот режим запрещён при `APP_ENV=production-like|production`; startup template validator fail-fast.
## 11. Public caching
`GET /api/v1/public/app-config` и `/content` кэшируются только для GET/HEAD, с key `scheme+host+uri+accept-encoding` (и locale query, если контракт его использует). Backend `Cache-Control`/ETag учитываются. Базовый TTL — `security.public_cache.max_age_seconds`/3600.
- `Set-Cookie` не кэшируется;
- Authorization request bypass cache;
- stale-if-error допускается ограниченно и маркируется `Warning`;
- mutation, auth, Bitrix, profile, dialogs, downloads и errors не кэшируются;
- cache status пишется в log, но наружу технологический header опционален.
## 12. CORS, CSP и security headers
CORS — exact allow-list из согласованного deploy config; application CORS остаётся последней инстанцией. Wildcard с credentials запрещён. Allowed headers: `Authorization`, `Content-Type`, `X-Request-ID`, `X-Ux-Session-Id`, `Idempotency-Key`, `traceparent`; методы соответствуют OpenAPI. Preflight получает bounded max-age.
Для SPA:
- CSP default-src `'self'`;
- connect-src `'self'` `https:` к разрешённому S3 endpoint и `wss:` текущего host;
- img-src `'self' data: blob:` и разрешённые signed HTTPS resources;
- object-src `'none'`, base-uri `'self'`, frame-ancestors `'none'`;
- script-src без `unsafe-eval` production; nonce/hash при необходимости;
- style-src policy согласовать с Expo build, постепенно исключить unsafe-inline.
Также: `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy`, frame protection через CSP, корректный COOP/CORP без поломки Keycloak redirect/S3. `Server` tokens скрыты; upstream `X-Powered-By` удаляется.
Bitrix placement может требовать embedding: для exact `/bitrix/placement` CSP `frame-ancestors` задаётся отдельным allow-list Bitrix24, а не ослабляет SPA.
## 13. Health
- внутренний `GET /nginx-health/live` возвращает static 200 и доступен Docker healthcheck;
- внешний health публикуется только если нужен мониторингу, с allow-list;
- nginx health не утверждает готовность upstream;
- внешняя synthetic проверка отдельно проверяет TLS, redirect, public API, auth discovery и callback route;
- upstream `/health/ready` не агрегируется публично без решения ops.
## 14. Логи и OTEL correlation
JSON access log: timestamp, request_id, trace_id (если извлечён), remote IP/hashed policy, host, method, route class, normalized URI без sensitive query, status, bytes, duration, upstream addr/status/time, cache status, TLS protocol/cipher, user agent при принятой retention.
Не логируются Authorization, cookies, request/response body, OTP, tokens, query token, presigned query, PII. Error log структурирован настолько, насколько позволяет nginx; debug выключен production.
Nginx передаёт W3C trace context; native OTEL module допустим при закреплённой версии. Если edge создаёт span, request id остаётся отдельным correlation key. Логи идут stdout/stderr; Docker/collector отвечает за доставку и rotation.
## 15. Layout конфигурации
```text
nginx/
Dockerfile
docker-compose.yml
nginx.conf
templates/
00-maps.conf.template
10-upstreams.conf.template
20-http-redirect.conf.template
30-https-site.conf.template
snippets/
proxy-common.conf
security-headers.conf
tls.conf
rate-limits.conf
websocket.conf
njs/request_id.js
scripts/{render,validate,reload-after-renew}.sh
tests/
```
Image и modules pin по digest/version. Render использует allow-list env и fail-fast для пустых host/cert/upstream/timeouts. Секреты в rendered config не требуются.
## 16. Docker Compose
`nginx` подключён к `public` и `backend`, публикует `${NGINX_HTTP_PORT}:80`, `${NGINX_HTTPS_PORT}:443`; filesystem read-only, tmpfs для cache/run/temp, non-root где позволяет bind ports/capabilities. Cert/static volumes read-only. ACME client имеет только необходимые volumes/network.
`depends_on` health не заменяет retry: nginx может стартовать при временно недоступном upstream и отдавать 502, затем восстановиться без reload. Resource/FD limits учитывают WS.
## 17. Failure behavior
- API/Keycloak upstream down: bounded 502/504, без SPA fallback.
- Safety slow: nginx ждёт message budget ≥ max+30, затем 504; backend checkpoint продолжает recovery.
- Redis down не влияет на запуск nginx; app решает degraded policy.
- cert renewal failed: текущий cert продолжает работу, alert до expiry.
- invalid new config: reload отменяется, старые workers остаются.
- disk/cache full: public cache bypass/evict, requests продолжаются где безопасно.
- DNS upstream changed: resolver/restart policy восстанавливает адрес.
- overload: 429/503 на edge, bounded queues; не накапливать неограниченные connections.
## 18. Валидация и тесты
Команды acceptance:
```text
docker compose config
docker compose exec nginx nginx -t
curl -I http://tohin.ru/
curl -vk https://tohin.ru/api/v1/public/app-config
openssl s_client -connect tohin.ru:443 -servername tohin.ru
curl -i https://tohin.ru/internal/safety/v1/messages/check
```
Автоматические тесты:
- Test::Nginx/containers для route precedence, methods, 404 internal;
- TLS scan: только 1.2/1.3, chain/hostname/expiry;
- redirect и ACME challenge;
- request-id valid/invalid/generation/propagation;
- forwarded spoof rejection;
- WS handshake, ping idle и reconnect;
- message request длительнее safety max не обрывается до budget;
- body/header limits;
- rate zones/429/Retry-After;
- public cache HIT/MISS/bypass/no private cache;
- CSP/CORS preflight и Bitrix placement exception;
- upstream down/timeout, failed reload, renewal rehearsal;
- logs не содержат secrets/query tokens.
## 19. Definition of Done
- единственный root nginx публикует только 80/443;
- TLS/ACME bootstrap, renewal и safe reload испытаны;
- routing matrix и route precedence покрыты;
- internal endpoints/ports извне недоступны;
- WS работает на `/api/v1/realtime`;
- message timeout равен safety max + минимум 30 секунд;
- request id и trusted forwarded headers корректны;
- limits, public cache, CSP/CORS/security headers проверены;
- static production и dev proxy guard работают;
- JSON logs коррелируют request/trace и не содержат секретов;
- health/synthetic checks и failure tests проходят;
- image non-root/read-only насколько возможно, versions pinned;
- runbooks для cert, reload, upstream outage и rollback готовы.
## 20. Решения, допущения и TBD
**Решения:** один nginx; njs/module для UUID; internal → 404; webroot ACME; отдельная CSP для Bitrix placement; public cache только allow-listed endpoints.
**Допущения:** MVP использует единый host `tohin.ru`; upstream service names стабильны в Compose; S3 CORS настраивается отдельно.
**TBD:** N1 доверенные WAF CIDR; N2 production cipher suite/OCSP; N3 нужен ли публичный health; N4 точный CSP Expo build; N5 Bitrix frame ancestor domains; N6 финальные burst/connection limits; N7 certbot vs другой ACME client после ops review.