Files

17 KiB
Raw Permalink Blame History

arch-08. Контракт корневого nginx

Канонический контракт nginx для всех application VM.
Compose-топология, сети и published ports — arch-03-docker-compose-blueprint.md.
Реализация на конкретной VM — module-03-nginx-vm1.md и module-03-nginx-vm2.md.
Telemetry access log — arch-07-observability.md. Env — arch-04-settings-and-content.md. Host security — arch-06-service-hosting-security.md.

Назначение

Документ фиксирует то, что должно совпасть между ВМ1 и ВМ2: независимый nginx на каждой машине, TLS/ACME, request id, forwarded headers, запрет internal paths, JSON access log, hardening Compose и failure/reload policy.

Routing matrix, upstreams, SPA, WebSocket, CSP и allow-list конкретной машины здесь не детализируются и не кастомизируются так, чтобы сломать этот контракт.

1. Обязательная топология

В production-like контуре каждая VM имеет собственный nginx в своём root Compose и собственный deployment lifecycle.

  • публичный трафик одной VM не проходит через nginx другой;
  • отказ или deploy одной VM не обязан прерывать ingress другой;
  • контейнеры приложений не публикуют host ports; на каждой VM наружу смотрит только её nginx;
  • если перед конкретной VM есть WAF/LB, trusted proxy CIDR задаются отдельно (N1).

Между public route ВМ1 и ВМ2 нет reverse-proxy chain и нет SPA/API fallback на другую VM.

2. Общие правила routing

Порядок location критичен. Exact locations объявляются до prefix.

На обоих public hosts:

  • /internal/, /_internal/, Redis/OTLP/admin/status/config files возвращают 404;
  • fallback на другую VM или чужой SPA запрещён;
  • query не участвует в exact location matching;
  • адрес из недоверенного X-Forwarded-For не используется для allow-list;
  • при внешнем LB сначала настраиваются его trusted CIDR и нормализация real IP.

Upstream failures не перенаправляются на другой сервис и не попадают в SPA. API-подобные locations возвращают 502/504 с безопасным nginx body и X-Request-ID; custom JSON error допустим только там, где профильная спецификация это разрешает, и не имитирует backend domain code.

Проверка prompt injection / malware не выполняется в nginx: это message-safety через api-backend (arch-02).

3. HTTP/HTTPS и TLS

  • public host каждой VM на :80 обслуживает только ACME challenge и 308 https://$host$request_uri;
  • единственное route-specific исключение: exact CRM webhook ВМ2 по HTTP возвращает 404/426 без redirect, чтобы query token не отражался в Location;
  • выделенный 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;
  • Server tokens скрыты; upstream X-Powered-By удаляется.

ВМ2 дополнительно слушает private 8443 с сертификатом internal CA — детали в спецификации ВМ2.

4. ACME lifecycle

Выбран webroot Certbot/ACME client с host-каталогами:

/etc/letsencrypt                 root-only ACME state; rw только у root/ACME client
/var/lib/han-chat/public-tls     staged fullchain.pem/privkey.pem; ro bind в nginx
/var/lib/han-chat/acme           webroot; bind в nginx и ACME client

Named volume для public certificate/key запрещён. Non-root nginx никогда не монтирует /etc/letsencrypt: после успешного issuance/renewal root hook проверяет certificate/key и атомарно копирует только нужные PEM в /var/lib/han-chat/public-tls с root:<dedicated-tls-group>, directory 0750 и files 0640. Каждая VM выпускает свой сертификат на свой public host; host-каталоги двух VM не общие.

Bootstrap:

  1. DNS указывает на VM; 80/443 разрешены.
  2. Подготовить root-owned webroot /var/lib/han-chat/acme и временный HTTP config с /.well-known/acme-challenge/.
  3. Выпустить certificate в root-only /etc/letsencrypt без остановки nginx.
  4. Проверить certificate/key, атомарно обновить /var/lib/han-chat/public-tls, выполнить nginx -t, активировать TLS config и reload.

Root-owned host timer выполняет certbot renew минимум дважды в сутки; ACME client может быть контейнеризован, но только он получает rw bind /etc/letsencrypt, а public certificate остаётся host bind, не named volume. После фактического renewal hook проверяет пару certificate/key, атомарно обновляет staging и проверяет рабочую конфигурацию командой docker compose exec -T nginx nginx -t -c /tmp/nginx.conf и отправляет master-процессу docker compose kill -s HUP nginx. Bare-команды nginx -t и nginx -s reload запрещены: контейнер read-only, а рабочие config/PID находятся в /tmp. При ошибке остаётся старый worker/config/cert и срабатывает alert. Успешный deploy/renew hook возвращает 0 с пустым stderr: вывод успешного nginx -t и progress signal command перехватывается или подавляется; при ошибке сохранённая диагностика полностью печатается в stderr. Любой stderr на success path считается дефектом интеграции с Certbot. Контролируются expiry days и последняя успешная попытка. Staging CA используется в rehearsal, чтобы не исчерпать лимиты.

Non-root nginx получает только read-only staging /var/lib/han-chat/public-tls; root-only /etc/letsencrypt ему недоступен.

5. Request ID и forwarded headers

На edge формируется trusted request id. Базовый nginx не генерирует UUID штатной переменной, поэтому используется njs/Lua либо модуль request-id, включённый в закреплённый image. Входящий X-Request-ID принимается только если соответствует UUID/ULID и длине; иначе генерируется новый.

Upstream получает:

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.

Private listener доверяет forwarded headers только от allow-listed private caller; public listener применяет собственную trusted proxy policy.

6. Timeouts и body limits — общие ориентиры

Группа connect/send/read Владелец
обычный API 3s / 30s / 30s ВМ1
auth 3s / 30s / 60s ВМ1
Bitrix local callback 3s / 30s / 60s ВМ1
Direct SMS callback 3s / 30s / 60s ВМ1
WS 3s / 30s / 75s+ ВМ1
message POST 3s / 30s / MESSAGE_SAFETY_TASK_POLL_MAX_SEC + 30s минимум ВМ1
CRM webhook 3s / 30s / 60s ВМ2
private Safety по контракту Safety, не короче caller budget ВМ2

Значение timeout генерируется из env template до startup; nginx не выполняет арифметику env runtime.

client_max_body_size global 8m по arch-04, но JSON locations получают более строгие limits, где возможно. Байты вложения не проходят через nginx/API: клиент PUT напрямую в S3. Buffering request допустим для малого JSON; для callback устанавливается bounded temp storage. Header count/size ограничены.

7. Edge rate limits — общие правила

limit_req_zone использует binary remote address. Ответ превышения — 429, Retry-After (статический/вычисляемый для зоны) и request id. Edge не реализует user-level бизнес-лимит; это делает API/Redis.

Карта зон принадлежит спецификации VM. Новые зоны добавляются только там, затем при необходимости сюда как реестр имён.

8. Health

  • внутренний GET /nginx-health/live возвращает static 200 и доступен Docker healthcheck;
  • внешний health публикуется только если нужен мониторингу, с allow-list (N3);
  • nginx health не утверждает готовность upstream;
  • Docker healthcheck использует только binary, гарантированно присутствующий и проверенный внутри exact pinned nginx digest; wget/curl запрещены, если их наличие не подтверждено image inventory;
  • upstream /health/ready не агрегируется публично без решения ops.

9. Логи и OTEL correlation

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.

Nginx передаёт W3C trace context; native OTEL module допустим при закреплённой версии (arch-07 O-TBD5). Если edge создаёт span, request id остаётся отдельным correlation key. Логи идут stdout/stderr; Docker/collector отвечает за доставку и rotation.

Parser и service.name=nginx — arch-07 §8.4.

10. Layout конфигурации

Общий каркас репозитория nginx:

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
  njs/request_id.js
  scripts/{render,validate,reload-after-renew}.sh
  tests/

Профильная спецификация добавляет только нужные snippets (websocket.conf, private server block, webhook allow-list). Image и modules pin по digest/version. Render использует allow-list env и fail-fast для пустых host/cert/upstream/timeouts. Секреты в rendered config не требуются.

11. Docker Compose

nginx подключён к public и backend (или эквивалентным сетям своей VM). Публикация host ports разрешена только nginx. Filesystem read-only, tmpfs для cache/run/temp//etc/nginx/conf.d по arch-03, non-root где позволяет bind ports/capabilities. Host staging public TLS и ACME webroot монтируются с минимальными правами; named volume сертификатов запрещён. ACME client имеет только необходимые bind mounts/network.

Non-root nginx получает writable tmpfs только для /etc/nginx/conf.d, /var/cache/nginx, /var/run и /tmp; tmpfs задаёт явные UID/GID/mode. Основной nginx.conf подключает конкретный rendered include, не неограниченный wildcard, который позволил бы обойти nginx -t.

depends_on health не заменяет retry/readiness. После healthy upstream обязателен config test с production service DNS names и reload/recreate nginx. После recreate upstream повторяется reload policy либо используется явно протестированный dynamic resolver. Nginx может временно отдавать bounded 502, но не считается ready до этой post-ready проверки.

Published Docker ports сопоставляются original host destination через conntrack/DOCKER-USER по arch-06.

12. Failure behavior

  • upstream down: bounded 502/504, без SPA fallback и без переноса на другую VM;
  • 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.

13. Общий Definition of Done

  • на каждой VM ровно один nginx; независимые public 80/443 и собственные сертификаты;
  • TLS/ACME bootstrap, renewal и safe reload испытаны;
  • internal endpoints/ports извне недоступны;
  • request id и trusted forwarded headers корректны;
  • JSON logs коррелируют request/trace и не содержат секретов;
  • health/synthetic checks и failure tests проходят;
  • image non-root/read-only насколько возможно, versions pinned;
  • runbooks для cert, reload, upstream outage и rollback готовы.

Профильный DoD VM дополняет routing matrix, allow-list и listener этой машины.

14. Решения, допущения и TBD

Решения: независимые public nginx ВМ1/ВМ2; njs/module для UUID; public internal paths → 404; webroot ACME; CRM webhook приходит прямо на ВМ2; private 8443 ВМ2 только server-to-server.

Допущения: ВМ1 и ВМ2 используют разные public hosts и сертификаты; upstream service names стабильны внутри каждого Compose; S3 CORS настраивается отдельно.

TBD:

  • N1: доверенные WAF CIDR — по VM.
  • N2: production cipher suite/OCSP.
  • N3: нужен ли публичный health.
  • N6: финальные burst/connection limits — по зонам VM.
  • N7: certbot vs другой ACME client после ops review.

N4 (CSP Expo) и N5 (Bitrix frame ancestors) — спецификация ВМ1.

15. Ссылки