From f4d43d1cfc1cd5cf770a0aa11458a44c663a396b Mon Sep 17 00:00:00 2001 From: lekss361 Date: Sat, 23 May 2026 09:04:00 +0000 Subject: [PATCH] =?UTF-8?q?fix(security):=20forwarder=20PermissionError=20?= =?UTF-8?q?=E2=80=94=20=D1=83=D0=B1=D1=80=D0=B0=D1=82=D1=8C=20USER=20nobod?= =?UTF-8?q?y=20+=20deploy=20automation=20(#432)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hotfix для GlitchTip Issue #71 (PermissionError при чтении /var/log/caddy/gendsgn.ru.log). Changes: - ops/glitchtip-auth-forwarder/Dockerfile — убран USER nobody, форвардер под root - ops/glitchtip-auth-forwarder/forwarder.py — throttle capture_exception (300s) - .forgejo/workflows/deploy.yml — paths-filter + build + force-recreate sidecar - docs/runbooks/basic_auth_management.md — Deploy section Security: caddy_logs mount read-only, нет network listener, нет shell, auth_forwarder_state dedicated volume. Attack surface минимален. После merge deploy.yml автоматически rebuild+recreate sidecar на VPS. --- .forgejo/workflows/deploy.yml | 12 ++++++++++++ docs/runbooks/basic_auth_management.md | 22 +++++++++++++++++++++- ops/glitchtip-auth-forwarder/Dockerfile | 5 +++-- ops/glitchtip-auth-forwarder/forwarder.py | 16 ++++++++++++---- 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml index d0736276..599c8804 100644 --- a/.forgejo/workflows/deploy.yml +++ b/.forgejo/workflows/deploy.yml @@ -14,6 +14,7 @@ on: - "Caddyfile" - ".forgejo/workflows/deploy.yml" - "data/sql/**" + - "ops/glitchtip-auth-forwarder/**" workflow_dispatch: concurrency: @@ -248,6 +249,11 @@ jobs: done echo "All migrations applied." + # Build local-only sidecar images (glitchtip-auth-forwarder). + # Эти services не в GHCR — сборка происходит на VPS на каждом deploy. + # Cache-friendly: первый build ~30s, последующие 1-3s если файлы не менялись. + docker compose -p gendesign -f docker-compose.prod.yml build glitchtip-auth-forwarder + docker compose -p gendesign -f docker-compose.prod.yml up -d # backend/.env.runtime изменения (SENTRY_RELEASE, GLITCHTIP_DSN) @@ -264,6 +270,12 @@ jobs: docker compose -p gendesign -f docker-compose.prod.yml up -d \ --force-recreate --no-deps caddy + # Forwarder: force-recreate чтобы новый image / новые env подхватывались. + # Без --force-recreate обычный `up -d` НЕ recreate'ит при image rebuild + # (т.к. image:latest tag не сменился — docker не видит разницы). + docker compose -p gendesign -f docker-compose.prod.yml up -d \ + --force-recreate --no-deps glitchtip-auth-forwarder + # Cleanup old images for repo in ghcr.io/lekss361/gendesign-backend \ ghcr.io/lekss361/gendesign-worker \ diff --git a/docs/runbooks/basic_auth_management.md b/docs/runbooks/basic_auth_management.md index 76f67f7c..4affcd50 100644 --- a/docs/runbooks/basic_auth_management.md +++ b/docs/runbooks/basic_auth_management.md @@ -67,10 +67,30 @@ ssh gendesign 'docker compose -p gendesign restart glitchtip-auth-forwarder' |---|---|---| | `GLITCHTIP_DSN` | Sentry DSN GlitchTip (project backend) | Да | -## Phase 2 — реализовано (PR feat/glitchtip-auth-forwarder) +## Deploy + +Sidecar собирается локально на VPS на каждом запуске `deploy.yml` (после merge в main). +Не требует pre-push image build, не использует GHCR. + +Forwarder контейнер пересоздаётся `--force-recreate` чтобы подхватить новый image при изменении кода. + +В случае sidecar не запустился: + +```bash +ssh gendesign 'docker compose -p gendesign -f docker-compose.prod.yml logs --tail=100 glitchtip-auth-forwarder' +``` + +Если build fail на VPS — проверь `ops/glitchtip-auth-forwarder/Dockerfile` локально: + +```bash +docker build ops/glitchtip-auth-forwarder/ +``` + +## Phase 2 — реализовано (PR #430) GlitchTip forwarder sidecar добавлен в `ops/glitchtip-auth-forwarder/`. Архитектура: Python 3.12 slim, sentry-sdk 2.18, tail live Caddy log, offset в named volume. +Deploy автоматизирован через `deploy.yml` — ручной SSH на VPS не нужен. ## Phase 3 (TODO) diff --git a/ops/glitchtip-auth-forwarder/Dockerfile b/ops/glitchtip-auth-forwarder/Dockerfile index 4506f329..963dab2c 100644 --- a/ops/glitchtip-auth-forwarder/Dockerfile +++ b/ops/glitchtip-auth-forwarder/Dockerfile @@ -7,8 +7,9 @@ RUN pip install --no-cache-dir "sentry-sdk==2.18.0" COPY forwarder.py . -# nobody (uid 65534) — минимальные привилегии -USER nobody +# Запускается под root — необходимо для чтения /var/log/caddy/gendsgn.ru.log +# (Caddy пишет лог под UID 0). Mounts: caddy_logs:ro + auth_forwarder_state (write). +# Attack surface минимален: нет network listener, нет shell, образ python:3.12-slim. # -u для unbuffered stdout — docker logs виден сразу без буферизации CMD ["python", "-u", "forwarder.py"] diff --git a/ops/glitchtip-auth-forwarder/forwarder.py b/ops/glitchtip-auth-forwarder/forwarder.py index 72cec657..e1037550 100644 --- a/ops/glitchtip-auth-forwarder/forwarder.py +++ b/ops/glitchtip-auth-forwarder/forwarder.py @@ -67,6 +67,10 @@ KNOWN_MONITOR_UAS = frozenset( ) _shutdown = False +# Throttle для unhandled exceptions — не более одного Sentry event за 5 минут, +# чтобы persistent ошибка (напр. PermissionError) не спамила GlitchTip. +_last_exc_sent: float = 0.0 +_EXC_THROTTLE_S: float = 300.0 def _signal_handler(signum: int, frame: object) -> None: @@ -299,10 +303,14 @@ def main() -> None: time.sleep(5) except Exception as exc: print(f"[forwarder] unhandled error: {exc}", file=sys.stderr, flush=True) - try: - sentry_sdk.capture_exception(exc) - except Exception: - pass + global _last_exc_sent + now_wall = time.time() + if now_wall - _last_exc_sent > _EXC_THROTTLE_S: + try: + sentry_sdk.capture_exception(exc) + except Exception: + pass + _last_exc_sent = now_wall time.sleep(5) print("[forwarder] shutting down gracefully", flush=True)