feat(tradein/caddy): снять basic_auth с /trade-in — своя авторизация (#2558)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Оба trade-in блока (`handle /trade-in/api/*`, `@tradein`) + связанный
`@saleshare` редирект перенесены ВЫШЕ `import caddy/users.caddy.snippet`
в route{} — Caddy матчит handle-блоки сверху вниз, поэтому они теперь
short-circuit'ят до basic_auth-гейта (тот же механизм, что уже применён
к /health, /preview/*, @uipreview). Site Finder (/, /api/*, /analytics)
остаётся под basic_auth без изменений.
Trade-in уже имеет собственную авторизацию (форма входа + opaque
session-cookie, #2552) поверх RBAC внутри tradein-backend — Caddy
basic_auth для него стал избыточным легаси-слоем.
Security: после переноса плейсхолдер {http.auth.user.id} для этих
блоков никогда не резолвится (basic_auth больше не выполняется на
пути к ним). Вместо `header_up X-Authenticated-User {http.auth.user.id}`
(Set-операция с пустым значением — недокументированно надёжна как
удаление заголовка) используется явное `header_up -X-Authenticated-User`
(документированная delete-семантика `-<field>`). Это исключает проход
клиентского X-Authenticated-User насквозь к tradein-backend, который
в auth_mode="dual" всё ещё принимает legacy trusted-header путь —
без явного удаления внешний запрос с подделанным заголовком мог бы
выдать себя за другого пользователя. X-Internal-Auth-Secret не тронут
(#2213, всегда перезаписывается из env).
Провалидировано: `caddy validate --config Caddyfile --adapter caddyfile`
(caddy:2 образ, локальный docker) → "Valid configuration".
This commit is contained in:
parent
80d54cb5cd
commit
01bf4a91e4
1 changed files with 47 additions and 14 deletions
61
Caddyfile
61
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
|
||||||
|
|
@ -73,23 +80,41 @@ gendsgn.ru {
|
||||||
reverse_proxy tradein-frontend:3000
|
reverse_proxy tradein-frontend:3000
|
||||||
}
|
}
|
||||||
|
|
||||||
# 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}`
|
||||||
|
# никогда не резолвится Caddy'ем (плейсхолдер пуст) — а поведение
|
||||||
|
# `header_up <field> <value>` при пустом value НЕ задокументировано как
|
||||||
|
# надёжное удаление заголовка (это Set-операция; нет гарантии, что Caddy
|
||||||
|
# трактует пустую строку как "не отправлять поле" во всех версиях).
|
||||||
|
# Полагаться на это было бы небезопасно: если бы Caddy просто НЕ
|
||||||
|
# устанавливал заголовок (leave as-is) при пустом value, то ЛЮБОЙ
|
||||||
|
# клиентский `X-Authenticated-User: someone` прошёл бы НАСКВОЗЬ до
|
||||||
|
# tradein-backend — а backend в auth_mode="dual" (`app/core/config.py`)
|
||||||
|
# всё ещё принимает legacy trusted-header путь (`rbac.py:186`) → внешняя
|
||||||
|
# подмена личности без единого валидного пароля. `-Field` — единственная
|
||||||
|
# ФОРМА С ЯВНО ЗАДОКУМЕНТИРОВАННОЙ семантикой "удалить заголовок"
|
||||||
|
# (Caddyfile reverse_proxy directive: `-<field>` = delete), поэтому
|
||||||
|
# используем её вместо 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 +123,10 @@ 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, который теперь публичен без basic_auth; оставлять
|
||||||
|
# короткий /sale-share под basic_auth, а канонический адрес назначения без
|
||||||
|
# него — несогласованно (короткий URL был бы строже целевой страницы).
|
||||||
@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 +139,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