Compare commits
4 commits
fix/tradei
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f6b68b6eca | |||
|
|
2b24408c3f | ||
| 69fd0ba260 | |||
|
|
01bf4a91e4 |
1 changed files with 66 additions and 15 deletions
81
Caddyfile
81
Caddyfile
|
|
@ -11,6 +11,13 @@
|
||||||
# Users managed via caddy/users.caddy.snippet (git history = audit trail).
|
# Users managed via caddy/users.caddy.snippet (git history = audit trail).
|
||||||
# Public exclusions: /health (liveness probe), /preview/* (static mockups).
|
# Public exclusions: /health (liveness probe), /preview/* (static mockups).
|
||||||
#
|
#
|
||||||
|
# #2558: с 2026-07 basic_auth гейтит ТОЛЬКО Site Finder (`/`, `/api/*`,
|
||||||
|
# `/analytics` и т.д.). `/trade-in/*` (+ `/sale-share` redirect) вынесены ВЫШЕ
|
||||||
|
# import'а — у trade-in своя авторизация (форма входа + opaque session-cookie,
|
||||||
|
# см. #2552) поверх RBAC (`tradein-mvp/backend/app/core/rbac.py`). Site Finder
|
||||||
|
# всё ещё легаси-пилотный basic_auth (roles.yaml dual-mode остаётся живым для
|
||||||
|
# него — НЕ трогать caddy/users.caddy.snippet).
|
||||||
|
#
|
||||||
# IMPORTANT: route { } block is required to preserve directive order.
|
# IMPORTANT: route { } block is required to preserve directive order.
|
||||||
# Without route { }, Caddy executes directives in hard-coded default order
|
# Without route { }, Caddy executes directives in hard-coded default order
|
||||||
# (basic_auth runs before handle), making /health and /preview/* exclusions
|
# (basic_auth runs before handle), making /health and /preview/* exclusions
|
||||||
|
|
@ -70,26 +77,56 @@ gendsgn.ru {
|
||||||
# Оба ДО auth-import, иначе ассеты страницы уходят в @tradein (под auth) → 401 → без CSS.
|
# Оба ДО auth-import, иначе ассеты страницы уходят в @tradein (под auth) → 401 → без CSS.
|
||||||
@uipreview path /trade-in/ui-preview/* /trade-in/_next/static/*
|
@uipreview path /trade-in/ui-preview/* /trade-in/_next/static/*
|
||||||
handle @uipreview {
|
handle @uipreview {
|
||||||
reverse_proxy tradein-frontend:3000
|
reverse_proxy tradein-frontend:3000 {
|
||||||
|
# #2558 review: тот же периметр-scrub, что и у /trade-in/api/* и
|
||||||
|
# @tradein ниже — этот блок тоже теперь ДО basic_auth, клиент
|
||||||
|
# мог бы прислать свой X-Authenticated-User. Сейчас инертно
|
||||||
|
# (страница статична, у tradein-frontend нет секрета для
|
||||||
|
# X-Internal-Auth-Secret), но убираем ради единообразия периметра,
|
||||||
|
# а не полагаясь на то, что downstream ничего не делает с заголовком.
|
||||||
|
header_up -X-Authenticated-User
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Auth gate (applies to all routes below within this route block).
|
# #2558: Trade-In MVP subproject (tradein-mvp/) — gendesign-tradein docker
|
||||||
import caddy/users.caddy.snippet
|
# stack, подключен через gendesign_shared network. Секция ЦЕЛИКОМ ДО
|
||||||
|
# `import caddy/users.caddy.snippet` ниже — /trade-in имеет собственную
|
||||||
# Trade-In MVP subproject (tradein-mvp/) — gendesign-tradein docker stack,
|
# авторизацию (форма входа + opaque session-cookie, #2552; RBAC-проверка
|
||||||
# подключен через gendesign_shared network. Routes ДО универсального handle
|
# роли внутри tradein-backend, `app/core/rbac.py`), Site Finder basic_auth
|
||||||
# потому что Caddy матчит handle-блоки сверху вниз.
|
# ей больше не нужен и не должен применяться (short-circuit сверху вниз,
|
||||||
|
# как /health и /preview/* выше).
|
||||||
|
#
|
||||||
|
# X-Authenticated-User — ЯВНОЕ УДАЛЕНИЕ (`header_up -X-Authenticated-User`),
|
||||||
|
# НЕ `header_up X-Authenticated-User {http.auth.user.id}`. Причина: этот
|
||||||
|
# блок больше не идёт ПОСЛЕ basic_auth, поэтому `{http.auth.user.id}`
|
||||||
|
# никогда не резолвится авторизованным юзером на этом пути.
|
||||||
|
# Проверено эмпирически (echo-стенд на образе caddy:2, `caddy adapt`):
|
||||||
|
# старая Set-форма (`header_up X-Authenticated-User {http.auth.user.id}`)
|
||||||
|
# НЕ пропустила бы клиентский заголовок насквозь и НЕ оставила бы поле
|
||||||
|
# пустым — Caddy подставляет НЕРАЗРЕШЁННЫЙ плейсхолдер как ЛИТЕРАЛЬНУЮ
|
||||||
|
# строку (`ReplaceKnown`), т.е. upstream получил бы буквально
|
||||||
|
# `X-Authenticated-User: {http.auth.user.id}`. Для backend (auth_mode=
|
||||||
|
# "dual", `app/core/config.py`) это НЕ подмена личности — legacy path
|
||||||
|
# (`rbac.py:186`) сделал бы `get_role("{http.auth.user.id}")`, юзер не
|
||||||
|
# найден в roles.yaml → 403 для всех. Т.е. старая форма была бы не
|
||||||
|
# security-дырой, а fail-closed-but-сломанной (все trade-in запросы без
|
||||||
|
# session-cookie получали бы 403 вместо ожидаемого 401/редиректа на логин).
|
||||||
|
# `-Field` остаётся правильным выбором не потому что Set был бы дырой, а
|
||||||
|
# потому что это ЕДИНСТВЕННАЯ форма с явно задокументированной семантикой
|
||||||
|
# "удалить заголовок" (Caddyfile reverse_proxy directive: `-<field>` =
|
||||||
|
# delete) — корректное поведение не должно зависеть от того, как именно
|
||||||
|
# Caddy трактует нерезолвленный/пустой плейсхолдер в Set-операции.
|
||||||
|
# X-Internal-Auth-Secret НЕ трогаем — #2213-секрет всегда перезаписывается
|
||||||
|
# из env (Set-операция с непустым значением, никак не связана с auth-гейтом
|
||||||
|
# basic_auth), это единственное, что теперь отсекает подделку заголовков
|
||||||
|
# изнутри gendesign_shared network для legacy dual-mode пути.
|
||||||
handle /trade-in/api/* {
|
handle /trade-in/api/* {
|
||||||
# `handle_path /trade-in/api/*` стрипал бы целиком /trade-in/api;
|
# `handle_path /trade-in/api/*` стрипал бы целиком /trade-in/api;
|
||||||
# FastAPI router замаунтен на /api/v1/trade-in/* — нужен strip только
|
# FastAPI router замаунтен на /api/v1/trade-in/* — нужен strip только
|
||||||
# префикса basePath /trade-in (Next.js basePath leak).
|
# префикса basePath /trade-in (Next.js basePath leak).
|
||||||
uri strip_prefix /trade-in
|
uri strip_prefix /trade-in
|
||||||
reverse_proxy tradein-backend:8000 {
|
reverse_proxy tradein-backend:8000 {
|
||||||
header_up X-Authenticated-User {http.auth.user.id}
|
header_up -X-Authenticated-User
|
||||||
# #2213 defense-in-depth: общий секрет Caddy↔tradein-backend. header_up
|
|
||||||
# с value ПЕРЕЗАПИСЫВАЕТ (стирает) любой клиентский X-Internal-Auth-Secret —
|
|
||||||
# тот же механизм, что защищает X-Authenticated-User выше. Пусто пока
|
|
||||||
# TRADEIN_INTERNAL_AUTH_SECRET не задан в .env (fail-open, backend не проверяет).
|
|
||||||
header_up X-Internal-Auth-Secret {env.TRADEIN_INTERNAL_AUTH_SECRET}
|
header_up X-Internal-Auth-Secret {env.TRADEIN_INTERNAL_AUTH_SECRET}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -98,6 +135,16 @@ gendsgn.ru {
|
||||||
# Next basePath=/trade-in → редиректим на канонический /trade-in/sale-share
|
# Next basePath=/trade-in → редиректим на канонический /trade-in/sale-share
|
||||||
# (тот же tradein-frontend контейнер; query-string сохраняется). True vanity-URL
|
# (тот же tradein-frontend контейнер; query-string сохраняется). True vanity-URL
|
||||||
# в адресной строке требует отдельного Next-app с basePath=/sale-share.
|
# в адресной строке требует отдельного Next-app с basePath=/sale-share.
|
||||||
|
# #2558: перенесён ВЫШЕ auth-import вместе с trade-in — редирект ведёт на
|
||||||
|
# /trade-in/sale-share, для которого теперь нет Caddy basic_auth (как и
|
||||||
|
# для остального /trade-in). Это НЕ делает страницу публичной: она всё
|
||||||
|
# ещё за собственной авторизацией trade-in — `RouteGuard` во фронте
|
||||||
|
# (`app/layout.tsx`, «Доступ: pilot + admin») и сессия для
|
||||||
|
# `/api/v1/buildings/sale-share*` на бэке; без валидной сессии юзер
|
||||||
|
# получит редирект на /login, а не контент. Смысл переноса — не открыть
|
||||||
|
# страницу всем, а убрать несогласованность: короткий URL не должен быть
|
||||||
|
# строже (Caddy basic_auth) целевого адреса, к которому и так уже нет
|
||||||
|
# basic_auth-барьера (только собственный login trade-in).
|
||||||
@saleshare path /sale-share /sale-share/
|
@saleshare path /sale-share /sale-share/
|
||||||
handle @saleshare {
|
handle @saleshare {
|
||||||
redir /trade-in/sale-share permanent
|
redir /trade-in/sale-share permanent
|
||||||
|
|
@ -110,13 +157,17 @@ gendsgn.ru {
|
||||||
handle @tradein {
|
handle @tradein {
|
||||||
# Next.js basePath=/trade-in — фронт сам ждёт префикса в URL
|
# Next.js basePath=/trade-in — фронт сам ждёт префикса в URL
|
||||||
reverse_proxy tradein-frontend:3000 {
|
reverse_proxy tradein-frontend:3000 {
|
||||||
header_up X-Authenticated-User {http.auth.user.id}
|
# См. комментарий над /trade-in/api/* выше — та же логика (явное
|
||||||
# #2213: симметрично с /trade-in/api/* — перезаписываем секрет из env
|
# удаление вместо Set с пустым {http.auth.user.id}).
|
||||||
# (стирает клиентский), на случай SSR-forwardʼa фронтом в backend.
|
header_up -X-Authenticated-User
|
||||||
header_up X-Internal-Auth-Secret {env.TRADEIN_INTERNAL_AUTH_SECRET}
|
header_up X-Internal-Auth-Secret {env.TRADEIN_INTERNAL_AUTH_SECRET}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Auth gate — с #2558 применяется ТОЛЬКО к Site Finder (handle /api/* и
|
||||||
|
# handle {} ниже). Trade-In уже отработал и short-circuit'нул выше.
|
||||||
|
import caddy/users.caddy.snippet
|
||||||
|
|
||||||
handle /api/* {
|
handle /api/* {
|
||||||
reverse_proxy backend:8000 {
|
reverse_proxy backend:8000 {
|
||||||
header_up X-Authenticated-User {http.auth.user.id}
|
header_up X-Authenticated-User {http.auth.user.id}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue