diff --git a/Caddyfile b/Caddyfile index 1dda17ff..4902a5b1 100644 --- a/Caddyfile +++ b/Caddyfile @@ -10,56 +10,67 @@ # Auth gate: basic_auth for pilot phase (2026-05-23). # Users managed via caddy/users.caddy.snippet (git history = audit trail). # Public exclusions: /health (liveness probe), /preview/* (static mockups). +# +# IMPORTANT: route { } block is required to preserve directive order. +# Without route { }, Caddy executes directives in hard-coded default order +# (basic_auth runs before handle), making /health and /preview/* exclusions +# ineffective. With route { }, handlers are matched top-to-bottom as written. gendsgn.ru { encode zstd gzip log { - output file /var/log/caddy/gendsgn.ru.log + output file /var/log/caddy/gendsgn.ru.log { + roll_size 50MiB + roll_keep 5 + roll_keep_for 720h + } format json } - # Liveness probe — public, before auth (GHA deploy smoke check). - handle /health { - reverse_proxy backend:8000 - } + route { + # /health и /preview/* — public, без auth, short-circuit. + handle /health { + reverse_proxy backend:8000 + } - # Static HTML mockups для review (audit alternatives). - # Public access — без auth (по запросу 2026-05-17). - handle_path /preview/* { - root * /srv/preview - file_server browse - } + # Static HTML mockups для review (audit alternatives). + # Public access — без auth (по запросу 2026-05-17). + handle_path /preview/* { + root * /srv/preview + file_server browse + } - # Auth gate (applies to all routes not matched above). - import caddy/users.caddy.snippet + # Auth gate (applies to all routes below within this route block). + import caddy/users.caddy.snippet - # Trade-In MVP subproject (tradein-mvp/) — gendesign-tradein docker stack, - # подключен через gendesign_shared network. Routes ДО универсального handle - # потому что Caddy матчит handle-блоки сверху вниз. - handle /trade-in/api/* { - # `handle_path /trade-in/api/*` стрипал бы целиком /trade-in/api; - # FastAPI router замаунтен на /api/v1/trade-in/* — нужен strip только - # префикса basePath /trade-in (Next.js basePath leak). - uri strip_prefix /trade-in - reverse_proxy tradein-backend:8000 - } + # Trade-In MVP subproject (tradein-mvp/) — gendesign-tradein docker stack, + # подключен через gendesign_shared network. Routes ДО универсального handle + # потому что Caddy матчит handle-блоки сверху вниз. + handle /trade-in/api/* { + # `handle_path /trade-in/api/*` стрипал бы целиком /trade-in/api; + # FastAPI router замаунтен на /api/v1/trade-in/* — нужен strip только + # префикса basePath /trade-in (Next.js basePath leak). + uri strip_prefix /trade-in + reverse_proxy tradein-backend:8000 + } - # Matcher `path /trade-in /trade-in/*` ловит И /trade-in (без слеша), - # И /trade-in/ + /trade-in/anything. Без обоих случаев `handle /trade-in/*` - # пропускал /trade-in без слеша → попадал в общий frontend → пустой ответ. - @tradein path /trade-in /trade-in/* - handle @tradein { - # Next.js basePath=/trade-in — фронт сам ждёт префикса в URL - reverse_proxy tradein-frontend:3000 - } + # Matcher `path /trade-in /trade-in/*` ловит И /trade-in (без слеша), + # И /trade-in/ + /trade-in/anything. Без обоих случаев `handle /trade-in/*` + # пропускал /trade-in без слеша → попадал в общий frontend → пустой ответ. + @tradein path /trade-in /trade-in/* + handle @tradein { + # Next.js basePath=/trade-in — фронт сам ждёт префикса в URL + reverse_proxy tradein-frontend:3000 + } - handle /api/* { - reverse_proxy backend:8000 - } + handle /api/* { + reverse_proxy backend:8000 + } - handle { - reverse_proxy frontend:3000 + handle { + reverse_proxy frontend:3000 + } } } @@ -117,19 +128,22 @@ git.gendsgn.ru { :80 { encode zstd gzip - handle /health { - reverse_proxy backend:8000 - } + route { + # /health — public, без auth (GHA deploy smoke check, liveness probe). + handle /health { + reverse_proxy backend:8000 + } - # Auth gate (same snippet as gendsgn.ru). - import caddy/users.caddy.snippet + # Auth gate (same snippet as gendsgn.ru). + import caddy/users.caddy.snippet - handle /api/* { - reverse_proxy backend:8000 - } + handle /api/* { + reverse_proxy backend:8000 + } - handle { - reverse_proxy frontend:3000 + handle { + reverse_proxy frontend:3000 + } } } # Test deploy flow 2026-05-15T21:43:32Z diff --git a/docs/runbooks/basic_auth_management.md b/docs/runbooks/basic_auth_management.md index 718a1de2..9b863dfa 100644 --- a/docs/runbooks/basic_auth_management.md +++ b/docs/runbooks/basic_auth_management.md @@ -39,16 +39,13 @@ Caddy начнёт отклонять запросы этого юзера ср ## Аудит логи -- Access log: `/var/log/caddy/gendsgn.ru.log` (все запросы, JSON) -- Auth audit: `/var/log/caddy/auth_audit.log` (authentication events, JSON) - -```bash -# Просмотр auth событий в реальном времени -docker compose -f docker-compose.prod.yml exec caddy tail -f /var/log/caddy/auth_audit.log | jq - -# Посмотреть кто логинился сегодня -docker compose -f docker-compose.prod.yml exec caddy grep "$(date +%Y-%m-%d)" /var/log/caddy/auth_audit.log | jq '.user_id' -``` +- Access log: `/var/log/caddy/gendsgn.ru.log` (все запросы JSON, включая auth events). +- Failed auth последние 100: + ```bash + docker compose -f docker-compose.prod.yml exec caddy \ + grep -P '"status":401' /var/log/caddy/gendsgn.ru.log | tail -100 | jq + ``` +- Phase 2 TODO: отдельный auth_audit.log + GlitchTip forwarder sidecar. ## Caddy reload после ручной правки на VPS diff --git a/scripts/auth/add_user.sh b/scripts/auth/add_user.sh index b0584a5f..e8afa6b6 100644 --- a/scripts/auth/add_user.sh +++ b/scripts/auth/add_user.sh @@ -55,7 +55,11 @@ fi # Caddy 2.11+ requires base64-encoded bcrypt in Caddyfile basic_auth. # Password passed via stdin to avoid exposure in process list. -HASH="$(printf '%s' "$PLAIN_PASS" | docker run --rm -i caddy:2 sh -c 'read -r p; caddy hash-password --plaintext "$p" | tr -d "\n" | base64 -w 0')" +HASH="$(printf '%s' "$PLAIN_PASS" | docker run --rm -i caddy:2 sh -c ' + pass=$(cat) + raw=$(caddy hash-password --algorithm bcrypt --plaintext "$pass") + printf "%s" "$raw" | base64 | tr -d "\n" +')" if [[ -z "$HASH" ]]; then echo "ERROR: caddy hash-password returned empty hash" >&2