feat(security): GlitchTip forwarder для basic_auth 401 events (Phase 2 PR #426) #430
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#430
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/glitchtip-auth-forwarder"
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?
Цель
Phase 2 follow-up для PR #426 (basic_auth gate). Sidecar tail'ит Caddy access log, шлёт failed auth (HTTP 401) в GlitchTip как Sentry warning events.
Без этого — bruteforce attempts на pilot невидимы.
Архитектура
gendesign-auth-forwarderв основном stacksentry-sdk==2.18.0caddy_logsvolume read-only + persistent state volumeauth_forwarder_state(/state/offset.json)USER nobody,restart: unless-stopped,depends_on: caddyGLITCHTIP_DSNчто и backend (из.env.runtime, уже есть на VPS)Логика forwarder.py
tempfile + os.replace)status==401→ emit Sentry eventevent_type=basic_auth_failed,http_status=401,http_method, path/IP/UA в contextevent_type=basic_auth_stormwarning, отдельные events не шлём пока stormsentry_sdk.flush(timeout=5)Изменения
ops/glitchtip-auth-forwarder/forwarder.py(новый)ops/glitchtip-auth-forwarder/Dockerfile(новый)ops/glitchtip-auth-forwarder/pyproject.toml(новый)docker-compose.prod.ymlauth_forwarder_statevolume +caddy_logsmount к caddydocs/runbooks/basic_auth_management.md.env.exampleGLITCHTIP_DSNуже был, обновлён комментарийVault
domains/infra/Basic_Auth_Gate_May23.md— Phase 2 секция appended.Ключевые уточнения от worker'а (после inspect реального лога на VPS)
request.client_ip(неremote_ip) для реального IP за docker networkrequest.headers["User-Agent"]— list, берём[0]with sentry_sdk.new_scope()— не загрязняем global statedefault_integrations=False— minimum overheadPre-commit verification
⚠️ После merge — ручной шаг на VPS
deploy.ymlНЕ собирает локально — нужно вручную (один раз):GLITCHTIP_DSNуже в/opt/gendesign/.env— дополнительных настроек не нужно.Можно потом добавить в
deploy.ymlbuild glitchtip-auth-forwarderследующим PR (если ручной запуск напрягает).Verification после первого 401
После запуска sidecar:
curl https://gendsgn.ru/(без auth) → 401docker compose logs auth-forwarder→ entry с capture_messageevent_type:basic_auth_failedVault references
domains/infra/Basic_Auth_Gate_May23.mdPhase 2 секцияOpen follow-ups (low priority)
build auth-forwarderвdeploy.ymlчтобы автоматизировать deployDeep Code Review — verdict
Summary
Conflict analysis (mergeable=false)
merge_base = ae0fce35...отстаёт отmain = cc2d1489...на один merged PR (#426 от 2026-05-23). PR #426 уже внёс точно такие же изменения в общие участки:1.
docker-compose.prod.yml— duplicate volume + неактуальный contextЧто добавил PR #426 (уже на main):
PR #430 пытается добавить точно те же строки (
caddy_logs:/var/log/caddyиcaddy_logs:). При rebase получишь либо merge conflict, либо (если git auto-resolve) — duplicate entries. Плюс PR #430 адресует caddy service на строке 131, а на main он уже на 185 (после PR #426 reorg) — hunk context потерян.Fix: после rebase убрать из diff обе строки про
caddy_logs(они уже есть в main) — оставить только добавление нового сервисаglitchtip-auth-forwarder+auth_forwarder_state:volume.2.
docs/runbooks/basic_auth_management.md— add/add conflictPR #430 создаёт файл (
status: added), но он уже существует на main (61 строка из PR #426) с другим содержимым (audit log section, утечка credentials, Caddy reload). После rebase нужно смерджить вручную, не перезаписать целиком — иначе потеряешь PR #426 секции (Audit logs, утечка credentials, Caddy reload черезcompose -f).Конкретно: сохранить весь PR #426 контент + append твою «GlitchTip auth event forwarder» секцию и Phase 2/3 footer.
3. Recommendation: rebase локально с
git rebase main, ручной conflict resolution, force push в свою branch (нормально для feature branch, не main). Я НЕ буду делатьupdate_pr_branchrebase сам — auto-resolve duplicate volume + add/add file конфликта потеряет содержимое.Code review (после resolve conflicts код OK)
Correctness — ОК:
sentry_sdk.new_scope()подтверждён в Sentry SDK 2.x docs как корректный per-event scope.push_scope()deprecated в 1.x → 2.x.tempfile.mkstemp(dir=STATE_FILE.parent) + os.replace()— правильный pattern, tempfile на том же volume.deque+time.monotonic()— иммунен к wall-clock jump, cutoff корректно убирает старые timestamps до измеренияwindow_count.current_size < offsetловит truncate. SIGTERM/SIGINT graceful shutdown +sentry_sdk.flush(timeout=5).default_integrations=False+traces_sample_rate=0.0+send_default_pii=False+attach_stacktrace=False— все правильные настройки для headless sidecar.DSN = os.environ["GLITCHTIP_DSN"]— fail-fast (KeyError при отсутствии) — это OK для sidecar;restart: unless-stoppedостановит после первого crash без restart loop.Security — ОК:
USER nobodyв Dockerfile.caddy_logs:/var/log/caddy:ro— read-only mount.send_default_pii=Falseкритично — Authorization header в Caddy log будетREDACTED(Caddy сам redact'ит), но даже если просочится — не пойдёт в Sentry breadcrumbs.Minor (non-blocking, можно отдельным PR):
forwarder.py:269— race condition на rotation reset:offset = 0; save_offset(0); recent_timestamps.clear()происходит до того, как мы прочитаем новый file (с начала). Если log rotation случается внутри прочтения batch'а (мы уже вfor raw_line in f),current_size < offsetсработает только на следующей итерацииwhile not _shutdown. До этогоoffset = f.tell()может перетереть offset большим значением. Edge case, в реальном Caddy rotation редкая, но возможно. Mitigation: проверятьcurrent_size < offsetпослеf.tell()тоже, или сразуcontinueпосле reset безsave_offset.forwarder.py:75—is_monitor_uaиспользуетstartswithна префиксы"GlitchTip/","SentryUptimeBot/". SentryUptimeBot реальный UA видаMozilla/5.0 (compatible; SentryUptimeBot/...)—startswith("SentryUptimeBot/")не сматчит, потому что UA начинается сMozilla/. Нужноinsubstring match либоre.search. Если ты валидировал реальные UAs в логе VPS — игнорь, но в коде это потенциальный false-positive bypass throttle для GlitchTip-monitor traffic.docker-compose.prod.yml:166—depends_on: caddyбез condition: sidecar запустится сразу после старта caddy container, но log file ещё может не существовать. Worker handle'ит этот case черезwhile not LOG_FILE.exists()loop — OK, не блокирующее. Опциональноcondition: service_startedдля явности.Volume perms для
nobody:auth_forwarder_statenamed volume создаётся docker'ом сroot:root.nobody(uid 65534) не сможет писать в/stateпри первом старте —save_offsetупадёт сPermissionError. Проверить вручную после deploy (первый запуск либо как root сделатьchown -R 65534:65534 /var/lib/docker/volumes/gendesign_auth_forwarder_state/_data, либо в Dockerfile добавитьRUN mkdir -p /state && chown nobody:nogroup /state— но volume mount перекроет). Самый чистый fix: первый запуск без USER, потом chown'нуть, потом restart с USER. Или entrypoint script сchown(но нужен root для этого).pyproject.toml— действительно стуб без реальной пользы (Dockerfile ставит sentry-sdk напрямую через pip). Можно удалить либо оставить ради ruff/IDE — твой выбор, не блокирующее.emit_event:118—entry.get("ts")проходит как float (unix epoch) в context. Sentry/GlitchTip ожидает ISO-8601 для readability. Минор — context arbitrary structure, оба работают.Manual deploy step упомянут в PR body. Acceptable для Phase 2 — можно добавить
glitchtip-auth-forwarderвdeploy.ymlbuild/up отдельным PR (упомянуто в TODO). Не блокирующее.Cross-file impact analysis
caddy_logsvolume теперь shared междуcaddy(RW) иglitchtip-auth-forwarder(RO). Caddy уже пишет туда (PR #426). Sidecar не помешает.GLITCHTIP_DSNуже используется backend (sentry_sdk.initвbackend/app/...). Тот же DSN на двух источниках — оба пойдут в один project в GlitchTip с разнымиrelease(auth-forwarder-1 vs backend release). Совместимо..env.exampleобновлён грамотно — комментарий что DSN используется и backend и sidecar.Vault cross-check
domains/infra/Basic_Auth_Gate_May23.md— Phase 2 секция уже appended worker'ом, content matches diff. Phase 2 TODO marked выполненным. ✅meta/00_credentials.md—GLITCHTIP_DSNуже задокументирован для backend/frontend. Sidecar использует тот же DSN — consistent. ✅project_glitchtip_status.mdopen thread "Logs, Dockerfile fix" — не конфликтует, это про GlitchTip server side (Django logs), а не про auth-forwarder sidecar.Performance
Positive observations
digest_sent = Falseкогда window < threshold).Verdict
APPROVE на код. НЕ merge из-за conflicts — нужен ручной rebase + resolve:
git fetch forgejo main && git checkout feat/glitchtip-auth-forwarder && git rebase forgejo/maindocker-compose.prod.yml: оставить толькоglitchtip-auth-forwarderservice +auth_forwarder_state:volume. Убратьcaddy_logsиз caddy mounts и volumes (уже в main).docs/runbooks/basic_auth_management.md: оставить целиком main версию + append свою «GlitchTip auth event forwarding» секцию + Phase 2/3 footer. Не overwrite.git push --force-with-leaseв feature branch (это OK, не main).Minor items 1-7 можно адресовать отдельными PR'ами после merge — особенно #2 (UA substring) и #4 (volume perms) — стоит проверить на VPS после первого запуска.
Complexity / blast radius score
docker compose stop glitchtip-auth-forwarder + rmf08c02b6c9to71cbdbe238Merged via deep-code-reviewer — verdict APPROVE.
Re-review post-rebase summary:
docker-compose.prod.ymlresolved cleanly: только новыйglitchtip-auth-forwarderservice +auth_forwarder_statevolume добавлены, нет дублированияcaddy_logsmount/volume (PR #426 content сохранён на месте).docs/runbooks/basic_auth_management.md— Phase 1 содержимое reshape'нуто (более компактно), Phase 2 секция appended корректно.add_user.shinvocation не соответствует реальному скрипту (требует 2 args, не 1).Все minor вынесены в follow-up issue для отдельного PR.
Manual deploy step остался на user'е (PR description его описывает):