fix(security): log_credentials + auth_audit.log → username из 401 #436
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#436
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/caddy-log-credentials-for-username"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Цель
Закрыть последний gap: чтобы
attempted_usernameреально появлялся в GlitchTip events.Root cause (diagnosed предыдущим worker)
Caddy 2.x hardcode-редактирует
Authorizationheader в access log → пишет"REDACTED". Поведение зашито вcaddy/modules/caddyhttp/access_log.go(sensitiveHeaderslist).PR #434
_extract_attempted_username()написан корректно, но получал["REDACTED"]→ неBasic ...→ возвращал None →attempted_username: "(none)".Fix (user-approved Option A)
1.
Caddyfile—log_credentialsglobal + отдельныйauth_audit.logGlobal option в
{ servers { log_credentials } }отключает REDACT для Authorization/Cookie во всех access logs.Отдельный
log auth_auditdirective в блокеgendsgn.ruпишет в/var/log/caddy/auth_audit.logс короткой retention (10MiB × 3, 168h = 7 дней) — специально для forwarder'а.2.
docker-compose.prod.ymlForwarder теперь читает
auth_audit.logвместоgendsgn.ru.log— меньше volume, фокусированный на auth events.3.
ops/glitchtip-auth-forwarder/forwarder.pyDocstring обновлён — явно указывает что требует
log_credentialsglobal и читаетauth_audit.log.4.
docs/runbooks/basic_auth_management.mdНовая секция «Аудит логи» + security note про trade-off:
Security trade-off (user одобрил)
Authorization: "REDACTED"Basic <base64>plainattempted_username:kopylovPractical security impact ≈ 0 — root на VPS уже мог восстановить session любого юзера. Plain Base64 в логе не делает доступ "более доступным".
⚠️ Side effect
log_credentials— global Caddy option, нельзя per-host. Применяется ко всем virtual hosts:obsidian.gendsgn.ru— CouchDB basic_auth идёт через прокси → его credentials тоже plain в Caddy loggit.gendsgn.ru— Forgejo не использует basic_auth (cookie session), не affectederrors.gendsgn.ru— GlitchTip cookie session, не affectedВ этих логах credentials accessible тем же root'у — same security tier. Acceptable.
Verification
caddy validate→ Valid configuration (caddy:2.11.3)curl -u testbruteforce:wrong https://gendsgn.ru/→ 401 → GlitchTip event с tagattempted_username:testbruteforceChanges
4 файла, +70 / -13.
Related
- Caddyfile: global servers { log_credentials } — Caddy перестаёт редактировать Authorization header в "REDACTED" - Caddyfile: отдельный log auth_audit { } → /var/log/caddy/auth_audit.log (retention 7d, roll 10MiB×3) для изоляции auth-событий - docker-compose.prod.yml: CADDY_LOG_FILE → auth_audit.log (forwarder читает меньший файл, снижает false reads от non-auth requests) - forwarder.py: обновлены docstrings — требования к log_credentials явные - docs/runbooks: секция «Аудит логи» + security note про log_credentials Результат: _extract_attempted_username получает raw Basic auth header (не "REDACTED") → attempted_username tag в GlitchTip event заполнен. Caddy validate: Valid configuration (caddy:2 v2.11.3).Deep Code Review — verdict APPROVE (with MINOR recommendations)
Files reviewed: 4 (Caddyfile P1, docker-compose.prod.yml P1, forwarder.py P1, runbook P3)
Lines: +70 / -13
Branch:
fix/caddy-log-credentials-for-username@6f36f29Base:
main@5e3ba35— up-to-date, mergeableAcknowledgement (closing my own missed finding)
This PR fixes a gap I missed in my PR #434 review. I claimed "Caddy access log writes Authorization as REDACTED per default" without verifying that
_extract_attempted_username()would actually receive a usable value. Caddy 2.x hardcode-redacts viasensitiveHeaderslist incaddy/modules/caddyhttp/access_log.go— extraction returned None despite correct code. This is my second missed Caddy-related finding (aftercaddy/**paths in #433). Lesson recorded for future Caddy-touching PRs: verify Caddy source forsensitiveHeaderslist when relying onrequest.headers.Xfields in JSON access log.Verification of PR claims
log_credentialssyntax — verified via Caddy docs (https://caddyserver.com/docs/caddyfile/options): correct placement in{ servers { ... } }global block, server-wide only (cannot be per-host). PR Caddyfile lines 19-26 — correct.auth_audit.logseparation —Caddyfile:43-51. Caddy'slog <name>directive within a site block writes to both the default log AND the named log when both are defined. So gendsgn.ru access events go to bothgendsgn.ru.logANDauth_audit.log(acknowledged in runbook). Forwarder only readsauth_audit.log— focused dataset, OK.auth_audit.log: 10MiB × 3 × 168h (~30MB cap, 7d) ✅;gendsgn.ru.log: 50MiB × 5 × 720h (~250MB cap, 30d). Total ≤ 280MB caddy_logs volume — acceptable.forwarder.py:262-269already has rotation detection (if offset > file_size: reset to 0). SwitchingCADDY_LOG_FILEto a new file path that may not exist yet handled byforwarder.py:240-247(waits for file to appear). State offset will self-correct on first iteration since new file size < old offset → reset. No manual state wipe needed on deploy.caddy_logs:/var/log/caddy:romount unchanged (same volume),CADDY_LOG_FILEenv updated toauth_audit.log. ✅Side-effect impact — full matrix (corrects PR body)
logdirectivegendsgn.rugendsgn.ru.log+auth_audit.logobsidian.gendsgn.rudocker logs caddyshows plain Basic headererrors.gendsgn.ruerrors.gendsgn.ru.loggit.gendsgn.rugit.gendsgn.ru.log:80Correction to PR body: claim "Forgejo не использует basic_auth (cookie session)" is partially incorrect. Forgejo docs (
forgejo.org/docs/latest/user/api-usage/) confirm HTTP Basic auth is supported for API including token creation endpoint. Git CLIgit push https://lekss361:PASSWORD@git.gendsgn.ru/...puts admin password plain ingit.gendsgn.ru.logfor 30 days. Risk is bounded (VPS root-only, no log export), but for admin account (lekss361) this is a meaningful exposure vector.User explicitly pre-approved Option A with full trade-off context ("practical security impact ≈ 0 — root уже мог восстановить session"), so this is acceptable but should be documented.
MINOR recommendations (post-merge follow-up, non-blocking)
docs/runbooks/basic_auth_management.mdsecurity note: "При HTTPS git push/pull через Forgejo рекомендуется использовать SSH-ключ (ssh://git@...) или Personal Access Token черезgh-credential-helper, чтобы пароль admin не попадал вgit.gendsgn.ru.log."log auth_audit { format json }пишет ВСЕ requests на gendsgn.ru (включая успешные с credentials). Optimisation:log auth_audit { include http.handlers.authentication; output ... }или matcher на 401. Не блокирует — forwarder фильтрует на стороне consumer'а, лишний disk I/O минимален. Можно отдельным follow-up PR.log { output file ... }блока чтобы credentials не уходили вdocker logs caddy(stdout по умолчанию). Альтернатива:log { output discard }для обсидиана (он сам пишет CouchDB request log).Positive observations
_extract_attempted_usernamehas good defensive checks (> 128 chars, base64validate=True,errors="replace")Complexity / blast radius
caddy reload, вauth_audit.logпосле ротации credentials исчезнут за ≤7 днейVerdict
✅ APPROVE — user pre-approved security trade-off, side-effects acceptable given VPS root-only access tier, retention bounded. Mergeable, CI/syntax validated. Merging via squash.
Post-merge verify (manual, user)
deploy.ymlauto-triggered (Caddyfile changed — paths fixed в #434)curl -u testbruteforce:wrong https://gendsgn.ru/→ 401 → GlitchTip event с tagattempted_username:testbruteforcessh gendesign 'docker compose -p gendesign exec caddy ls -la /var/log/caddy/'— verify auth_audit.log создан + retention rotation работаетssh gendesign 'docker compose -p gendesign exec caddy tail /var/log/caddy/auth_audit.log | grep "Basic " | wc -l'— должно показать ненулевое количество (был хотя бы один запрос с auth)