Phase 1). Phase 2 — custom client domains через cloudflare-custom-hostnames.

Контекст

Multi-tenant pacient portal (см. multi-tenant-fhir-storage). Каждой Organization нужен свой адрес для доступа. Параллельно решается вопрос как клиент потом сможет подцепить собственный домен (patient.pzalab.com). Phase 1 — наш subdomain без участия клиента; Phase 2 — custom client-owned domains.

Рассматривали

  • Slug-based {slug}.portal.bloodgpt.tech (читаемое имя организации). Плюс — узнаваемо, легко произносить. Минус — slug можно угадать (zalab, demo, bgpt-eu и т.д.), что даёт атакующему target-список. Особенно плохо если URL раскрывает имя клиента, который сам это раскрывать не хочет.
  • Hash-based {hash}.portal.bloodgpt.tech, где hash = sha256(orgId + HOSTING_SECRET).slice(0, 12). Hash непредсказуем без секрета, поэтому работает как proof-of-ownership: сам факт что клиент знает hash означает что мы ему его выдали. Wildcard DNS *.portal.bloodgpt.tech + wildcard cert уже настроены — новые tenants работают без terraform apply.
  • Dash-delimited {hash}-portal.bloodgpt.tech — рассмотрено как формат, но конфликтует с wildcard *.bloodgpt.tech (ловит api, www и прочие сервисы), отброшено.

Выбрали: hash-based на scope .portal.bloodgpt.tech

hash = sha256(orgId + HOSTING_SECRET).slice(0, 12)
// → "a1b2c3d4e5f6" → a1b2c3d4e5f6.portal.bloodgpt.tech

Wildcard инфраструктура:

  • *.portal DNS record в bloodgpt-stage-eu/project.hcl + bloodgpt-prod-eu/project.hcl
  • *.portal.bloodgpt.tech в cert hostnames в common-infra/cloudflare-ssl/terragrunt.hcl
  • Cookies шарятся через Domain=.portal.bloodgpt.tech (wildcard cookie domain) — никакого token-redirect не нужно для Phase 1.

Почему

  • Hash непредсказуем → нельзя enumerate чужие tenants по URL.
  • Hash-домен сам по себе подтверждает ownership — отдельная TXT-верификация не нужна.
  • Wildcard инфра уже на месте, новые клиенты не требуют infra-изменений.
  • .portal.bloodgpt.tech scope не пересекается с другими bloodgpt-сервисами (api, www, app), поэтому wildcard cert и cookies работают без коллизий.

Ильдар: «слаг не работает сейчас. черех хеш лучше думаю да. может быть типа a1b2c3d4e5f6-portal.bloodgpt.tech?» (после обсуждения wildcard conflict выбрана dot-separated форма)

DDoS-защита

Замечание Евгения — hash-проверка должна происходить дёшево, иначе невалидные host’ы становятся вектором. Решение — 3 уровня защиты:

  1. CF edge WAF — формат hostname валидируется на edge (regex-check), невалидные отбрасываются до origin.
  2. Next.js middleware — in-memory Set<string> допустимых хэшей, обновляется раз в минуту из БД. Невалидный host отбивается за микросекунды без обращения к Postgres.
  3. GKE rate limit — традиционный ingress rate limit как последний слой.

Ильдар: «ЖЕня говорит что это будет опсаность ддоса» (ответ — 3-level defense, primary = CF edge + hash format validation)

Следствия

  • Новый tenant видит свой URL мгновенно после создания Organization (без infra-pipeline).
  • Phase 2 (custom client domains типа patient.pzalab.com) — отдельный путь через Cloudflare Custom Hostnames + Transform Rule, см. cloudflare-custom-hostnames и custom-domains-saas. В Phase 2 потребуется token-redirect для cross-domain cookies (см. [Н5] в digest).
  • WorkOS auth: Phase 1 использует один redirect_uri + state-parameter с return_to. Phase 2 потребует доп. шаг token-exchange.
  • Документация для клиента в Phase 2 — “добавьте 1 CNAME patient.yourdomain.com → {hash}.portal.bloodgpt.tech”, остальное автоматически.

Связано

Источники

Источники: 1.

Сноски

  1. Cloudflare Custom Hostnames docs, accessed 2026-05-17, https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/start/getting-started/.