215 lines
13 KiB
Markdown
215 lines
13 KiB
Markdown
# module-03-vm1. Nginx ВМ1 HAN Chat
|
||
|
||
> Статус: целевая спецификация nginx на ВМ1.
|
||
> Канонический контракт (TLS/ACME, request id, internal 404, logs, reload) — [`arch-08-nginx.md`](../../architectory/arch-08-nginx.md).
|
||
> Обязательный host/container hardening baseline — [`arch-06-service-hosting-security.md`](../../architectory/arch-06-service-hosting-security.md).
|
||
> Контур ВМ2 — [`module-03-nginx-vm2.md`](../../VM2_services/documentation/module-03-nginx-vm2.md). Публичный трафик ВМ2 не проходит через этот nginx.
|
||
|
||
## 1. Назначение и границы
|
||
|
||
Nginx ВМ1 — публичная точка входа приложения: frontend, API, auth, Open Lines, SMS callback. Message Safety и CRM webhook на этой машине не публикуются.
|
||
|
||
Канонический вызов Safety: `api-backend` напрямую → private `8443` nginx ВМ2. Public `/internal/` на ВМ1 всегда `404`; upstream `processing_gateway` и proxy route Safety в nginx ВМ1 запрещены.
|
||
|
||
## 2. Routing matrix
|
||
|
||
Порядок location критичен. Prefix `/` — последним.
|
||
|
||
| Внешний путь | Upstream | Режим |
|
||
|---|---|---|
|
||
| `/api/` | `api-backend:8000` | REST; `/api/v1/realtime` WS |
|
||
| `/auth/` | `keycloak:8080` | OIDC/OTP, prefix/hostname согласован с issuer |
|
||
| exact `/callbacks/idgtl/sms` | `sms-service:8080` | public HTTPS POST Direct; IP allowlist + Basic auth в upstream |
|
||
| `/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 |
|
||
|
||
Notification paths внутри `/api/` имеют отдельные edge-зоны. `/internal/`, `/_internal/`, Redis/OTLP/admin/status/config files → `404` (arch-08). `/bitrix/sync/webhook/*` на ВМ1 не маршрутизируется.
|
||
|
||
## 3. Upstreams
|
||
|
||
Именованные upstream: `api_backend`, `keycloak`, `sms_service`, `bitrix_local`, опционально `frontend_dev`.
|
||
|
||
Upstream failures: `502/504` с безопасным body и `X-Request-ID`. Custom JSON error допустим для `/api`, но не имитирует backend domain code. Fallback в SPA запрещён.
|
||
|
||
## 4. Listeners и TLS
|
||
|
||
Public host ВМ1: `:80` только ACME + `308 https://$host$request_uri`; `:443 ssl http2` по arch-08. Собственный сертификат, не разделяется с ВМ2.
|
||
|
||
## 5. 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.
|
||
|
||
## 6. Timeouts ВМ1
|
||
|
||
Ориентиры arch-08 §6. Обязательно:
|
||
|
||
- message POST read timeout ≥ `MESSAGE_SAFETY_TASK_POLL_MAX_SEC + 30s`; при default safety max 300 сек — не меньше 330 сек;
|
||
- значение из env template до startup.
|
||
|
||
`client_max_body_size` global 8m; JSON API locations строже, где возможно. Байты вложения через nginx не идут.
|
||
|
||
## 7. Edge rate limits ВМ1
|
||
|
||
Зоны `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;
|
||
- `notifications_read`: list/counter/detail;
|
||
- `notifications_action`: read/hide/CTA/button;
|
||
- `notification_upload`: универсальные upload drafts;
|
||
- `notifications_public`: guest notifications и каталог видов;
|
||
- `bitrix_callbacks`: мягкий burst для повторов local app;
|
||
- `idgtl_callbacks`: отдельный bounded burst, учитывающий повтор каждые 5 минут в течение суток;
|
||
- `ws_connect`: handshake;
|
||
- `connections`: `limit_conn`.
|
||
|
||
Ответ превышения — `429`, `Retry-After`, request id. OPTIONS не должен расходовать auth budget чрезмерно. Resource/FD limits учитывают WS.
|
||
|
||
## 8. Static SPA и dev mode
|
||
|
||
Production:
|
||
|
||
- root `${FRONTEND_STATIC_PATH}`;
|
||
- volume `frontend-static` → `/usr/share/nginx/html:ro`;
|
||
- существующие 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.
|
||
|
||
## 9. 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 опционален.
|
||
|
||
## 10. 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'`;
|
||
- frame-src `'none'`: инструкция `install_app` всегда открывается в новой вкладке, iframe/модалка не поддерживается;
|
||
- script-src без `unsafe-eval` production; nonce/hash при необходимости;
|
||
- style-src policy согласовать с Expo build, постепенно исключить unsafe-inline.
|
||
|
||
Для Keycloak login endpoints под `/auth/` применяется отдельный CSP, не SPA-policy. При `KEYCLOAK_YANDEX_CAPTCHA_ENABLED=true` он точечно разрешает `https://smartcaptcha.cloud.yandex.ru` и необходимые static resources `https://yastatic.net` только в соответствующих directives; wildcard и ослабление CSP остальных `/auth/*` запрещены. При выключенной CAPTCHA эти origins отсутствуют. Env validator обязан согласовать CAPTCHA flag, CSP allow-list и ограниченный egress Keycloak.
|
||
|
||
Также: `X-Content-Type-Options: nosniff`, `Referrer-Policy: strict-origin-when-cross-origin`, `Permissions-Policy`, frame protection через CSP, корректный COOP/CORP без поломки Keycloak redirect/S3.
|
||
|
||
Bitrix placement может требовать embedding: для exact `/bitrix/placement` CSP `frame-ancestors` задаётся отдельным allow-list Bitrix24, а не ослабляет SPA.
|
||
|
||
## 11. Callback i-Digital Direct
|
||
|
||
- Только exact `location = /callbacks/idgtl/sms`; разрешён только `POST`, остальные методы отклоняются.
|
||
- Source IP allowlist — `185.203.96.7`, но значение обязательно повторно сверяется с актуальной документацией Direct перед production. При WAF/LB используется только нормализованный trusted client IP.
|
||
- TLS обязателен; cache выключен; body size ограничен под массив callback items.
|
||
- Basic `Authorization` передаётся `sms-service`, но никогда не записывается в access/error logs. URL с credentials также редактируется.
|
||
- Nginx не проверяет provider payload и не преобразует статусы; это делает `sms-service`. Ошибку upstream/DB нельзя маскировать `2xx`, иначе Direct не повторит callback.
|
||
- `/internal/sms/*` и порт sms-service наружу не публиковать.
|
||
|
||
## 12. Health и synthetic ВМ1
|
||
|
||
Внутренний `/nginx-health/live` — arch-08 §8. Внешняя synthetic проверка отдельно проверяет TLS, redirect, public API, auth discovery и SMS callback route. Upstream `/health/ready` local app не публикуется без решения ops.
|
||
|
||
## 13. Layout и Compose ВМ1
|
||
|
||
Каркас arch-08 §10 плюс snippets `websocket.conf`. Volume `frontend-static` только на ВМ1.
|
||
|
||
`nginx` публикует `${NGINX_HTTP_PORT}:80`, `${NGINX_HTTPS_PORT}:443`. Private `8443` на ВМ1 нет.
|
||
|
||
## 14. Failure behavior ВМ1
|
||
|
||
Дополнительно к arch-08 §12:
|
||
|
||
- 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.
|
||
|
||
Остановленный nginx ВМ1 не должен ломать приём CRM webhook на ВМ2.
|
||
|
||
## 15. Валидация и тесты ВМ1
|
||
|
||
Команды acceptance (подставить `<PUBLIC_HOST>` ВМ1):
|
||
|
||
```text
|
||
docker compose config
|
||
docker compose exec nginx nginx -t
|
||
curl -I http://<PUBLIC_HOST>/
|
||
curl -vk https://<PUBLIC_HOST>/api/v1/public/app-config
|
||
openssl s_client -connect <PUBLIC_HOST>:443 -servername <PUBLIC_HOST>
|
||
curl -i https://<PUBLIC_HOST>/internal/safety/v2/messages/check
|
||
```
|
||
|
||
Ожидания: HTTP → 308; public config 200; internal Safety снаружи `404`; valid cert.
|
||
|
||
Автоматические тесты:
|
||
|
||
- 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, включая notification read/action/upload/public;
|
||
- public cache HIT/MISS/bypass/no private cache;
|
||
- CSP/CORS preflight и Bitrix placement exception;
|
||
- CSP содержит `frame-src 'none'`; инструкция проверяется как новая вкладка без embedded content;
|
||
- `/internal/notifications/*` снаружи всегда `404`;
|
||
- allowed Direct callback проходит; wrong IP/method и любой `/internal/sms/*` отклоняются; Authorization отсутствует в логах;
|
||
- CRM webhook paths на ВМ1 не проксируются в `bitrix-sync`;
|
||
- upstream down/timeout, failed reload, renewal rehearsal;
|
||
- logs не содержат secrets/query tokens.
|
||
|
||
## 16. Definition of Done ВМ1
|
||
|
||
Дополнительно к arch-08 §13:
|
||
|
||
- routing matrix §2 и route precedence покрыты;
|
||
- WS работает на `/api/v1/realtime`;
|
||
- message timeout равен safety max + минимум 30 секунд;
|
||
- limits, public cache, CSP/CORS/security headers проверены;
|
||
- static production и dev proxy guard работают;
|
||
- SMS callback allow-list/method/redaction проверены;
|
||
- internal Safety/sync снаружи `404`.
|
||
|
||
## 17. TBD ВМ1
|
||
|
||
- N1: доверенные WAF CIDR перед ВМ1.
|
||
- N3: нужен ли публичный health.
|
||
- N4: точный CSP Expo build.
|
||
- N5: Bitrix frame ancestor domains.
|
||
- N6: финальные burst/connection limits зон ВМ1.
|
||
|
||
## 18. Ссылки
|
||
|
||
- Контракт: [`arch-08-nginx.md`](../../architectory/arch-08-nginx.md).
|
||
- ВМ2: [`module-03-nginx-vm2.md`](../../VM2_services/documentation/module-03-nginx-vm2.md).
|
||
- Указатель: [`module-03-nginx.md`](module-03-nginx.md).
|
||
- API / local app / SMS / Keycloak: [`module-01-api-backend.md`](module-01-api-backend.md), [`module-06-bitrix-local-app.md`](module-06-bitrix-local-app.md), [`module-11-idgtl-sms.md`](module-11-idgtl-sms.md), [`module-08-keycloak.md`](module-08-keycloak.md).
|