Перейти к содержанию

nginx — Public Ingress

nginx — публичный шлюз OneWallet: терминирует TLS, маршрутизирует трафик из Flutter-приложения, делегирует аутентификацию (auth_request) Auth Center и проксирует read-only запросы напрямую в Payment Manager (баланс, история).

Назначение

nginx — единственная публично доступная точка входа в систему OneWallet. Все внешние клиенты (Flutter App, веб-кошелёк, мини-аппы) обращаются исключительно к нему; внутренние сервисы (Auth Center, Payment Manager, TigerBeetle, Redis) находятся в приватной кластерной сети и недоступны напрямую снаружи.

Ключевые функции:

  • TLS-терминация и HSTS на публичном домене.
  • Аутентификация запросов через директиву auth_request к Auth Center (валидация JWT).
  • Маршрутизация: read-only PM-эндпоинты (/api/pm/*) — напрямую в PM; всё остальное (/api/*) — в Auth Center.
  • Inject HMAC-заголовков (X-Service-Id, X-Timestamp, X-Signature) для запросов в PM от имени nginx-gateway.
  • Inject X-User-Id из ответа auth_request для пробрасывания идентификатора пользователя в downstream.

Общая диаграмма с местом nginx в архитектуре — см. корневой /CLAUDE.md.

Routing

/api/pm/* → PM direct (с auth_request)

Маршруты read-only, ориентированные на быстрый ответ без round-trip через Auth Center как бэкенд:

Путь Цель Назначение
GET /api/pm/accounts/balance PM Текущий баланс пользователя
GET /api/pm/intents/history PM История платёжных операций

Поток обработки запроса:

  1. nginx принимает запрос от Flutter App с заголовком Authorization: Bearer <JWT>.
  2. Внутренний sub-request auth_request к Auth Center валидирует JWT.
  3. При успехе Auth Center возвращает X-User-Id в response-заголовке auth_request.
  4. nginx подписывает запрос HMAC-ключом сервиса nginx-gateway и проксирует его в PM, передавая X-User-Id.
  5. PM проверяет HMAC, авторизует операцию по permissions ключа nginx-gateway (read-only) и отвечает.

Остальные /api/* → Auth Center

Все прочие запросы (login, KYC, профили, mini-app каталог, создание интентов и т. д.) проксируются в Auth Center как в самостоятельный backend. Дальнейшие вызовы в PM (например, createIntent → POST /intents) Auth Center делает уже сам — со своим service key auth-center и собственной HMAC-подписью.

Trust Boundary

nginx живёт в той же приватной кластерной сети, что и PM, но это не даёт ему привилегий:

  • NO-GO: «доверенные» вызовы без подписи. PM обязан проверять HMAC на каждом входящем запросе независимо от источника. Network-level доверие (cluster-internal IP, mTLS между подами) — дополнительный слой защиты, а не замена HMAC.
  • Service key nginx-gateway имеет минимальные права (allowedOperationTypes: []) — он не может создавать интенты, только дёргать read-only эндпоинты.
  • HMAC-секрет для nginx-gateway хранится в SERVICE_SECRETS (env), не в БД. PM сверяет подпись runtime через src/auth/hmacPlugin.ts.
  • Любой компромис nginx (RCE, утечка конфига) → атакующий получает только read-only-доступ к балансам/истории; write-операции по-прежнему требуют ключа auth-center.

Это прямо соответствует кросс-сервисному правилу из корневого CLAUDE.md:

Единый HMAC auth для всех вызовов в PM — никаких «доверенных» вызовов без подписи (X-Service-Id, X-Timestamp, X-Signature).

Service Key: nginx-gateway

В drizzle/seed.ts ключ зарегистрирован как:

{
  serviceId:   'nginx-gateway',
  permissions: {
    allowedOperationTypes: [], // read-only: balance + history only
  },
  active: true,
}

Поведенческие последствия:

  • Пустой allowedOperationTypes → любой POST /intents от имени nginx-gateway отклоняется на этапе авторизации в PM. nginx физически не может породить интент даже при ошибке конфигурации location-блока.
  • Нет fromAccountOverride / toAccountOverride — переопределять счета этому ключу запрещено.
  • forceResolve не выставлен — X-User-Id, переданный nginx, используется как есть; PM сам резолвит дефолтный кошелёк пользователя для read-операций.

Секрет ключа (SERVICE_SECRETS['nginx-gateway']) обязателен на старте PM — seed-скрипт делает fail-fast, если он отсутствует:

if (!secrets['auth-center'] || !secrets['nginx-gateway'] || ...) {
  console.error('SERVICE_SECRETS must include auth-center, nginx-gateway, ...')
  process.exit(1)
}

Заголовки между слоями

Заголовок Откуда Куда Назначение
Authorization: Bearer <JWT> Flutter App nginx → Auth Center (sub-request) Валидация пользователя
X-User-Id Auth Center response nginx → PM Идентификатор пользователя для read-операций
X-Service-Id: nginx-gateway nginx PM Идентификация сервиса
X-Timestamp nginx PM Защита от replay (PM требует ±N сек)
X-Signature nginx PM HMAC-SHA256 над канонической строкой запроса

Пример конфигурации (упрощённо)

Схема location-блоков (без TLS, rate-limit и логирования — только маршрутизация):

# Внутренний sub-request к Auth Center для валидации JWT
location = /_auth_verify {
    internal;
    proxy_pass         http://auth-center:8080/_verify;
    proxy_pass_request_body off;
    proxy_set_header   Content-Length "";
    proxy_set_header   Authorization $http_authorization;
}

# Read-only PM endpoints
location /api/pm/ {
    auth_request        /_auth_verify;
    auth_request_set    $user_id $upstream_http_x_user_id;

    # HMAC-заголовки рассчитывает Lua/sidecar и кладёт в переменные
    proxy_set_header    X-User-Id     $user_id;
    proxy_set_header    X-Service-Id  nginx-gateway;
    proxy_set_header    X-Timestamp   $hmac_ts;
    proxy_set_header    X-Signature   $hmac_sig;

    proxy_pass          http://payment-manager:3000/;
}

# Всё остальное — в Auth Center
location /api/ {
    proxy_pass          http://auth-center:8080/;
}

Расчёт HMAC внутри nginx делается через ngx_http_lua_module (или sidecar-процесс); ключ nginx-gateway инжектится в окружение nginx-пода так же, как SERVICE_SECRETS в PM.

FAQ

Почему PM не доверяет cluster-internal IP nginx и всё равно проверяет HMAC? Defence-in-depth: компромис любого пода в кластере (sidecar, неправильно настроенный NetworkPolicy, утечка через service mesh) не должен автоматически давать доступ к API PM. HMAC привязывает запрос к конкретному ключу + времени, не к сетевому пути.

Может ли nginx создавать интенты от имени пользователя? Нет. allowedOperationTypes: [] в seed → PM отклонит любой POST /intents с X-Service-Id: nginx-gateway. Создание интентов идёт строго через Auth Center (X-Service-Id: auth-center).

Что произойдёт, если HMAC-подпись неверна? PM возвращает 401 Unauthorized ещё до маршрутизации в бизнес-логику — это поведение src/auth/hmacPlugin.ts и оно одинаково для всех сервисных ключей, включая nginx-gateway.

Связанные документы

  • /CLAUDE.md — общая диаграмма OneWallet и кросс-сервисные правила.
  • docs/AUTH-POLICIES.md — политики step-up auth (если read-операция требует повышенного уровня).
  • docs/dev/integrations/auth-center.md — как Auth Center сам ходит в PM (через тот же HMAC, но со своим ключом).