From 95072586b93f62f9d3c18f77cfc8aa044a653671 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 12 May 2026 12:49:58 +0300 Subject: [PATCH] SECURITY - PLATFORM: internal token and staging plan --- docs/SECURITY_CHECKLIST.md | 19 +++- docs/STAGING_SECURITY_PLAN.md | 125 +++++++++++++++++++++++ infra/.env.example | 1 + infra/scripts/bootstrap-authentik-dev.sh | 1 + infra/scripts/init-dev-env.sh | 1 + 5 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 docs/STAGING_SECURITY_PLAN.md diff --git a/docs/SECURITY_CHECKLIST.md b/docs/SECURITY_CHECKLIST.md index 5f6d0a9..75d5239 100644 --- a/docs/SECURITY_CHECKLIST.md +++ b/docs/SECURITY_CHECKLIST.md @@ -1,5 +1,7 @@ # Security Checklist +Staging path and runbook: `docs/STAGING_SECURITY_PLAN.md`. + ## Network - [x] Local dev compose публикует только reverse proxy port. @@ -26,6 +28,8 @@ - [ ] Admin endpoints требуют `nodedc:superadmin` или `nodedc:launcher:admin`. - [ ] Все admin actions пишутся в audit log. - [ ] Удаление пользователя реализовано как deactivate/disable, не hard delete. +- [x] Local runtime не отдает `storage/launcher-data.json` напрямую через public static route. +- [x] Local runtime требует user session для `/api/storage/data`. ## Plane @@ -43,13 +47,20 @@ - [ ] Access/refresh tokens не логируются. - [ ] Session cookies имеют `secure=true` в staging/production. - [ ] В production включены HTTPS и HSTS. +- [x] Local internal API token отделен от `PLANE_OIDC_CLIENT_SECRET`. +- [x] Launcher internal access API отклоняет запросы без token, с неверным token и со старым OIDC client secret. +- [x] Tasker internal logout API отклоняет запросы без token и со старым OIDC client secret. ## Acceptance scenarios -- [ ] Без логина Launcher отправляет в Authentik. -- [ ] Пользователь без `nodedc:taskmanager:access` не видит Task Manager в Launcher. -- [ ] Пользователь без `nodedc:taskmanager:access` получает deny на прямой `task.local.nodedc`. +- [x] Без логина Launcher отправляет в Authentik. +- [x] Пользователь без Task Manager app access не видит Task Manager в Launcher. +- [x] Пользователь без Task Manager app access получает deny в Tasker access middleware при прямом доступе. - [ ] Пользователь с доступом открывает Task Manager. - [ ] Старый Plane admin после OIDC видит старые workspace/tasks/comments. - [ ] Деактивированный пользователь теряет доступ. -- [ ] Admin action появляется в audit log. +- [x] Hard-deleted/annulled user теряет stale Tasker identity link и старая unlinked-сессия отзывается middleware. +- [x] Hard-deleted/annulled user удаляется из active Tasker workspace/project members и issue assignees. +- [x] Self-host Tasker workspace invite создает pending request в Launcher, а launcher-managed workspace не принимает self-service invite request. +- [x] Admin action появляется в audit log. +- [x] Повторный accept уже принятого invite отклоняется. diff --git a/docs/STAGING_SECURITY_PLAN.md b/docs/STAGING_SECURITY_PLAN.md new file mode 100644 index 0000000..048517a --- /dev/null +++ b/docs/STAGING_SECURITY_PLAN.md @@ -0,0 +1,125 @@ +# Staging Security Plan + +Актуализировано: 2026-05-12. + +Этот документ фиксирует минимальный staging path для закрытого demo release NODE.DC platform. Billing, тарифы и email automation не входят в текущий scope: доступы, approve и передача ссылок остаются ручными через Launcher/root-admin. + +## Целевые домены + +Для staging нужны отдельные домены от локального `.local.nodedc`: + +```text +auth.staging.nodedc.example -> Authentik +launcher.staging.nodedc.example -> Launcher BFF/web +task.staging.nodedc.example -> Task Manager / Operational Core +``` + +Финальные DNS-имена заменяются перед deploy. Все redirect URI в Authentik должны совпадать с этими доменами и использовать `https://`. + +## Публичная поверхность + +Снаружи публикуется только edge reverse proxy: + +- `80/tcp` — только redirect на HTTPS; +- `443/tcp` — HTTPS termination, HSTS, proxy headers; +- Postgres, Redis/Valkey, MinIO, Authentik server/worker, Launcher BFF и Tasker backend не публикуются напрямую наружу. + +Reverse proxy обязан прокидывать: + +- `Host`; +- `X-Forwarded-Proto`; +- `X-Forwarded-For`; +- WebSocket/HTTP upgrade для Tasker realtime/live. + +## Runtime topology + +Staging можно собрать compose-файлом или эквивалентным deployment unit, но правила одинаковые: + +- Authentik `server` и `worker` используют отдельный Postgres volume/network; +- Authentik worker не монтирует `/var/run/docker.sock`; +- Launcher хранит control-plane snapshot только в server-side storage; +- Tasker остаётся отдельным приложением со своей БД, storage и доменной моделью; +- Launcher не читает и не пишет Plane DB напрямую; +- интеграция Launcher -> Tasker идёт через internal API с отдельным `NODEDC_INTERNAL_ACCESS_TOKEN`. + +## Обязательные env + +Значения с `change-me`, local token и dev secrets запрещены в staging. + +```text +COOKIE_SECURE=true +COOKIE_DOMAIN=.staging.nodedc.example + +NODEDC_INTERNAL_ACCESS_TOKEN= +SESSION_SECRET= +AUTHENTIK_SECRET_KEY= +PG_PASS= + +LAUNCHER_OIDC_ISSUER=https://auth.staging.nodedc.example/application/o/launcher/ +LAUNCHER_OIDC_REDIRECT_URI=https://launcher.staging.nodedc.example/auth/callback +LAUNCHER_OIDC_LOGGED_OUT_REDIRECT_URI=https://launcher.staging.nodedc.example/auth/logged-out + +PLANE_OIDC_ISSUER=https://auth.staging.nodedc.example/application/o/task-manager/ +PLANE_OIDC_REDIRECT_URI=https://task.staging.nodedc.example/auth/oidc/callback +PLANE_NODEDC_ACCESS_ENFORCEMENT=1 +PLANE_NODEDC_ACCESS_ENFORCE_UNLINKED=1 +PLANE_NODEDC_ACCESS_TOKEN= +``` + +OIDC client secrets для Launcher и Tasker должны быть разными. Internal API token не должен совпадать ни с одним OIDC client secret. + +## Trusted proxies + +Локальный default `127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,::1/128` допустим только для dev. + +В staging `AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS` и аналогичные параметры Tasker должны указывать только на фактический reverse-proxy subnet или ingress CIDR. Широкие private ranges без необходимости запрещены. + +## Cookies и HTTPS + +Для staging: + +- TLS обязателен на всех публичных доменах; +- HTTP должен делать permanent redirect на HTTPS; +- `COOKIE_SECURE=true`; +- session cookies остаются `HttpOnly`; +- `SameSite=Lax` допустим для текущего top-level OIDC flow; +- включить HSTS после проверки сертификатов и redirect URI. + +## Backup перед staging + +Перед первым staging deploy и перед destructive security smoke: + +- dump Authentik Postgres; +- dump Tasker/Plane Postgres; +- архив Launcher `server/storage`; +- архив Tasker uploads/MinIO data; +- архив `.env`/secret references без публикации в git; +- зафиксировать image tags Launcher/Tasker/Authentik. + +Минимальный restore check: поднять копию DB/storage на isolated host или namespace и проверить login + открытие Tasker. + +## Secrets rotation + +Rotation policy до допуска внешних пользователей: + +- `NODEDC_INTERNAL_ACCESS_TOKEN` — rotate при любом подозрении на утечку и перед production; +- OIDC client secrets — rotate отдельно для Launcher и Tasker; +- `SESSION_SECRET` — rotate с принудительным logout всех пользователей; +- DB passwords — rotate через maintenance window; +- старые secrets удаляются из `.env`, shell history и runtime logs. + +## Acceptance перед людьми + +Минимальный staging smoke: + +1. Super sudo входит в Launcher через `https://launcher...`. +2. Обычный active user видит только разрешённые сервисы. +3. User без Task Manager app access не видит Task Manager в витрине и получает deny по прямому `https://task...`. +4. Blocked/annulled user теряет Launcher и Tasker session после hard refresh. +5. Self-host workspace invite создаёт pending request в Launcher. +6. Launcher-managed workspace не принимает self-service invite request из Tasker. +7. Hard delete удаляет active WorkspaceMember/ProjectMember/IssueAssignee в Tasker. +8. Audit сохраняет admin actions, approve/reject и hard delete. +9. Повторный accept уже принятого invite отклоняется. +10. Прямые backend/internal endpoints без token возвращают `401`. + diff --git a/infra/.env.example b/infra/.env.example index 264e16b..828459c 100644 --- a/infra/.env.example +++ b/infra/.env.example @@ -42,5 +42,6 @@ PLANE_OIDC_REDIRECT_URI=http://task.local.nodedc/auth/oidc/callback # security SESSION_SECRET=change-me-generate-with-infra-scripts-init-dev-env +NODEDC_INTERNAL_ACCESS_TOKEN=change-me-generate-with-infra-scripts-init-dev-env COOKIE_DOMAIN=.local.nodedc COOKIE_SECURE=false diff --git a/infra/scripts/bootstrap-authentik-dev.sh b/infra/scripts/bootstrap-authentik-dev.sh index c72e8e3..eaae3c0 100755 --- a/infra/scripts/bootstrap-authentik-dev.sh +++ b/infra/scripts/bootstrap-authentik-dev.sh @@ -58,6 +58,7 @@ ensure_env_value LAUNCHER_OIDC_CLIENT_ID nodedc-launcher ensure_env_value PLANE_OIDC_CLIENT_ID nodedc-task-manager ensure_env_value LAUNCHER_OIDC_CLIENT_SECRET "$(rand_hex 48)" ensure_env_value PLANE_OIDC_CLIENT_SECRET "$(rand_hex 48)" +ensure_env_value NODEDC_INTERNAL_ACCESS_TOKEN "$(rand_hex 48)" ensure_env_value LAUNCHER_OIDC_REDIRECT_URI http://launcher.local.nodedc/auth/callback ensure_env_value PLANE_OIDC_REDIRECT_URI http://task.local.nodedc/auth/oidc/callback diff --git a/infra/scripts/init-dev-env.sh b/infra/scripts/init-dev-env.sh index b2a073b..97ade7c 100755 --- a/infra/scripts/init-dev-env.sh +++ b/infra/scripts/init-dev-env.sh @@ -56,6 +56,7 @@ PLANE_OIDC_REDIRECT_URI=http://task.local.nodedc/auth/oidc/callback # security SESSION_SECRET=$(rand 48) +NODEDC_INTERNAL_ACCESS_TOKEN=$(openssl rand -hex 48 | tr -d '\n') COOKIE_DOMAIN=.local.nodedc COOKIE_SECURE=false EOF