fix(security): применить ревью PR #426 — route wrap + log rotation + base64 busybox + runbook log fix

- Caddyfile: обернуть gendsgn.ru и :80 в route { } — сохраняет порядок директив,
  /health и /preview/* теперь действительно short-circuit до basic_auth
- Caddyfile: добавить roll_size 50MiB / roll_keep 5 / roll_keep_for 720h в gendsgn.ru.log
- scripts/auth/add_user.sh: заменить base64 -w 0 (GNU-only) на base64 | tr -d "\n"
  (busybox-compatible, caddy:2 Alpine base image)
- docs/runbooks/basic_auth_management.md: убрать несуществующий auth_audit.log,
  заменить на реальную команду grep по статусу 401 в gendsgn.ru.log

caddy validate: Valid configuration (confirmed locally via caddy:2 container).
Ref: #426 (comment)
This commit is contained in:
lekss361 2026-05-23 11:11:50 +03:00
parent b89b34c300
commit cdce8e84a1
3 changed files with 72 additions and 57 deletions

View file

@ -10,16 +10,26 @@
# 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).
route {
# /health и /preview/* — public, без auth, short-circuit.
handle /health {
reverse_proxy backend:8000
}
@ -31,7 +41,7 @@ gendsgn.ru {
file_server browse
}
# Auth gate (applies to all routes not matched above).
# 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,
@ -62,6 +72,7 @@ gendsgn.ru {
reverse_proxy frontend:3000
}
}
}
www.gendsgn.ru {
redir https://gendsgn.ru{uri} permanent
@ -117,6 +128,8 @@ git.gendsgn.ru {
:80 {
encode zstd gzip
route {
# /health — public, без auth (GHA deploy smoke check, liveness probe).
handle /health {
reverse_proxy backend:8000
}
@ -132,4 +145,5 @@ git.gendsgn.ru {
reverse_proxy frontend:3000
}
}
}
# Test deploy flow 2026-05-15T21:43:32Z

View file

@ -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)
- Access log: `/var/log/caddy/gendsgn.ru.log` (все запросы JSON, включая auth events).
- Failed auth последние 100:
```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'
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

View file

@ -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