Реализованы сервисы ВМ2 - проверка сообщений и синхронизация с Б24 (деплой еще без перевода в боевой режим)
This commit is contained in:
+48
-14
@@ -1,39 +1,68 @@
|
||||
# module-03. Проектная спецификация корневого `nginx`
|
||||
|
||||
> Статус: целевая спецификация полностью рабочего edge-контура MVP.
|
||||
> Статус: целевая спецификация независимых nginx-контуров ВМ1 и ВМ2.
|
||||
> Источники: [`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.
|
||||
В production-like контуре каждая VM имеет собственный nginx в своём root Compose и собственный deployment lifecycle:
|
||||
|
||||
Если перед VM есть внешний WAF/LB, доверенные proxy CIDR задаются явно; nginx не доверяет произвольному `X-Forwarded-For`. Другой nginx на host не должен маршрутизировать сервисы по отдельности.
|
||||
- nginx ВМ1 обслуживает frontend/API/auth/Open Lines/SMS;
|
||||
- nginx ВМ2 напрямую обслуживает публичные CRM webhook `bitrix-sync` и private Message Safety API;
|
||||
- публичный трафик ВМ2 не проходит через ВМ1;
|
||||
- отказ или deploy ВМ1 не прерывает приём CRM webhook на ВМ2.
|
||||
|
||||
Контейнеры приложений не публикуют host ports. На каждой VM наружу смотрит только её nginx. Если перед конкретной VM есть WAF/LB, trusted proxy CIDR задаются отдельно.
|
||||
|
||||
## 2. Routing matrix
|
||||
|
||||
Порядок location критичен: exact/longest public routes до общего `/bitrix/`.
|
||||
Порядок location критичен. Публичные route разделены по host/VM.
|
||||
|
||||
### ВМ1
|
||||
|
||||
| Внешний путь | 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/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 |
|
||||
|
||||
Notification paths внутри `/api/` имеют отдельные edge-зоны: public catalog/campaigns, JWT read, actions, uploads и downloads. `/internal/`, `/_internal/`, Redis/OTLP/admin/status/config files запрещены exact prefix response `404` (допустим `403`, но единообразно выбран `404`). Никакого fallback internal path в SPA или общий proxy. `message-safety`, `/internal/sms/*` и `/internal/notifications/*` не имеют публичного route.
|
||||
### ВМ2
|
||||
|
||||
| Внешний путь | Upstream | Режим |
|
||||
|---|---|---|
|
||||
| exact `/bitrix/sync/webhook/contact` | `bitrix-sync:8080` | public HTTPS Contact event; source IP CIDR/method/body/rate limits, query-token auth в upstream |
|
||||
| exact `/bitrix/sync/webhook/alert` | `bitrix-sync:8080` | public HTTPS smart-process event; те же ограничения |
|
||||
|
||||
Notification paths внутри `/api/` ВМ1 имеют отдельные edge-зоны. На обоих public hosts `/internal/`, `/_internal/`, Redis/OTLP/admin/status/config files возвращают `404`; fallback на другую VM или SPA запрещён. До full sync cutover оба exact webhook route ВМ2 закрыты либо возвращают retryable `503`; успешный `2xx ignored` запрещён.
|
||||
|
||||
Query не участвует в exact location matching: URL штатного робота `/bitrix/sync/webhook/<type>?token=...&ID=...` попадает в соответствующий exact route. До proxy nginx проверяет непосредственный source IP по version-controlled `BITRIX_WEBHOOK_ALLOWED_CIDRS`; пустой/невалидный список при enabled receiver блокирует deployment. Адрес из недоверенного `X-Forwarded-For` не используется. При внешнем LB сначала настраиваются его trusted CIDR и нормализация real IP.
|
||||
|
||||
Запрос вне allow-list получает generic `403` без proxy. В безопасном журнале с ограниченным retention сохраняются только timestamp, source IP, route class и outcome; query/body не сохраняются. Telemetry pipeline экспортирует `webhook_rejected_total{receiver,reason="source_ip"}` без IP label. Allow-list не расширяется автоматически: всплеск Contact, восстановленных инкрементальной reconciliation, инициирует проверку rejected-IP журнала, подтверждение принадлежности адреса Битрикс24 и reviewed reload конфигурации.
|
||||
|
||||
## 3. Upstreams
|
||||
|
||||
Именованные upstream: `api_backend`, `keycloak`, `sms_service`, `bitrix_local`, `bitrix_sync`, опционально `frontend_dev`. Для одной replica допустим `server service:port`; `keepalive` включён. Docker DNS resolver задаётся с коротким `valid` и `resolve` там, где поддерживает выбранная nginx edition; иначе контейнер перезапускается при смене IP upstream.
|
||||
Именованные upstream ВМ1: `api_backend`, `keycloak`, `sms_service`, `bitrix_local`, опционально `frontend_dev`, а также private `processing_gateway` только для вызовов Message Safety из api-backend.
|
||||
|
||||
Nginx ВМ2 имеет независимые server blocks:
|
||||
|
||||
- public `80/443` на отдельном DNS host: ACME/redirect и два exact CRM webhook;
|
||||
- private `8443` с сертификатом internal CA: только server-to-server Message Safety и approved ops.
|
||||
|
||||
| Path | Local upstream | Caller |
|
||||
|---|---|---|
|
||||
| `/internal/safety/v2/*` | `message-safety-api:8080` | api-backend ВМ1 |
|
||||
| `/internal/sync/v1/*` | `bitrix-sync:8080` | ops/allow-listed service |
|
||||
|
||||
Public и private server blocks не имеют общего fallback. Все прочие paths/methods возвращают `404/405`. Private listener доверяет forwarded headers только от allow-listed private caller; public listener применяет собственную trusted proxy policy.
|
||||
|
||||
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`;
|
||||
- public host каждой VM на `:80` обслуживает только ACME challenge и `308 https://$host$request_uri`; исключение — `/bitrix/sync/webhook/contact|alert`, которые возвращают generic `404/426` без redirect и отражения query token;
|
||||
- выделенный API host, если появится, не имеет listener `:80`;
|
||||
- `:443 ssl http2`, TLS 1.2/1.3, современные cipher suites, session tickets по ops policy;
|
||||
- сертификат доверенного CA, private key read-only и недоступен приложению;
|
||||
@@ -134,7 +163,7 @@ traceparent: входной валидный либо новый согласн
|
||||
- `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 в сервисе.
|
||||
Ответ превышения — `429`, `Retry-After` (статический/вычисляемый для зоны) и request id. Edge не реализует user-level бизнес-лимит; это делает API/Redis. OPTIONS не должен расходовать auth budget чрезмерно. Bitrix webhook retries имеют отдельный достаточный burst, проходят source IP allow-list и проверяют query receiver token в сервисе.
|
||||
|
||||
## 10. Static SPA и dev mode
|
||||
|
||||
@@ -195,7 +224,7 @@ Bitrix placement может требовать embedding: для exact `/bitrix/
|
||||
|
||||
## 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.
|
||||
JSON access log: timestamp, request_id, trace_id (если извлечён), remote IP/hashed policy, host, method, route class, normalized URI на основе `$uri` без `$request_uri`/`$args`, 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.
|
||||
|
||||
@@ -253,7 +282,7 @@ 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
|
||||
curl -i https://tohin.ru/internal/safety/v2/messages/check
|
||||
```
|
||||
|
||||
Автоматические тесты:
|
||||
@@ -271,15 +300,20 @@ curl -i https://tohin.ru/internal/safety/v1/messages/check
|
||||
- CSP/CORS preflight и Bitrix placement exception;
|
||||
- upstream down/timeout, failed reload, renewal rehearsal;
|
||||
- logs не содержат secrets/query tokens.
|
||||
- CRM webhook exact routes принимают query без изменения location matching; allowed source IP проксируется, wrong IP получает `403` до upstream;
|
||||
- HTTP webhook URL с query token не перенаправляется на HTTPS и не отражает query в `Location`/error;
|
||||
- source-IP rejects попадают в безопасный bounded-retention журнал и low-cardinality telemetry без query/body/IP label;
|
||||
- allowed Direct callback проходит; wrong IP/method и любой `/internal/sms/*` отклоняются; Authorization отсутствует в логах.
|
||||
- `/internal/notifications/*` снаружи всегда `404`; notification read/action/upload/public routes используют свои зоны и возвращают `429`.
|
||||
- CSP содержит `frame-src 'none'`; инструкция проверяется как новая вкладка без embedded content.
|
||||
|
||||
## 19. Definition of Done
|
||||
|
||||
- единственный root nginx публикует только 80/443;
|
||||
- на каждой VM ровно один nginx; ВМ1 и ВМ2 независимо публикуют только свои утверждённые `80/443`, ВМ2 дополнительно слушает private `8443`;
|
||||
- TLS/ACME bootstrap, renewal и safe reload испытаны;
|
||||
- routing matrix и route precedence покрыты;
|
||||
- CRM webhook достигает ВМ2 напрямую и продолжает приниматься при остановленном nginx ВМ1;
|
||||
- CRM webhook ограничен version-controlled source IP CIDR allow-list; query token и form body отсутствуют в access/error logs и traces;
|
||||
- internal endpoints/ports извне недоступны;
|
||||
- WS работает на `/api/v1/realtime`;
|
||||
- message timeout равен safety max + минимум 30 секунд;
|
||||
@@ -293,8 +327,8 @@ curl -i https://tohin.ru/internal/safety/v1/messages/check
|
||||
|
||||
## 20. Решения, допущения и TBD
|
||||
|
||||
**Решения:** один nginx; njs/module для UUID; internal → 404; webroot ACME; отдельная CSP для Bitrix placement; public cache только allow-listed endpoints.
|
||||
**Решения:** независимые public nginx ВМ1/ВМ2; CRM webhook приходит прямо на ВМ2 через source IP CIDR allow-list; private `8443` ВМ2 используется только server-to-server; njs/module для UUID; public internal paths → 404; webroot ACME; отдельная CSP для Bitrix placement; public cache только allow-listed endpoints.
|
||||
|
||||
**Допущения:** MVP использует единый host `tohin.ru`; upstream service names стабильны в Compose; S3 CORS настраивается отдельно.
|
||||
**Допущения:** ВМ1 и ВМ2 используют разные public hosts и сертификаты; 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.
|
||||
|
||||
Reference in New Issue
Block a user