Шхуна не тонет: security, infra и доки на русском.
Безопасность довёл до ума — Cursor-генерацию переписал руками. IDOR закрыл, CSRF задушил, refresh rotation теперь как надо. HSTS на staging, ENABLE_DOCS=false, install.env recovery протестил. Backend: - jwt_denylist + auth_epoch: мгновенный revoke access JWT (logout/block/reset) - auth/admin/users: bump epoch, logout с Bearer, forgot_password skip для blocked - install_secrets: путь всегда apps/api/data/secrets/ (bootstrap из корня не ломает Docker) - seed: SEED_DEMO_USERS=false на prod/staging - тесты: jwt revoke, integration, coverage gate 90% Frontend: - logout шлёт Bearer, обработка TOKEN_REVOKED - guards TypeScript fix - E2E: blocked user → 401 сразу после block Infra: - staging/prod compose, TLS nginx, deploy-скрипты - k6 §17.2, backup/health/smoke scripts Docs: - docs/ на русском: project, security, deploy, release (старые md слили) - README короткий + план ТЗ + стандартные логины dev Код готов к плаванию. Капитан может идти писать фронт.
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# Документация Compton
|
||||
|
||||
README в корне — только «как запустить за 30 секунд». Всё остальное — здесь.
|
||||
|
||||
Мы сознательно держим **мало файлов**, но каждый — **плотный**: таблицы, схемы, команды. Можно с лёгким юмором, но без простыней на 500 строк.
|
||||
|
||||
## Карта
|
||||
|
||||
| Документ | Когда открывать |
|
||||
|----------|-----------------|
|
||||
| [project.md](./project.md) | «Где что лежит?», архитектура, API, маршруты, env |
|
||||
| [deploy.md](./deploy.md) | Запуск, **стандартные логины dev**, staging/prod, troubleshooting |
|
||||
| [security.md](./security.md) | Auth, JWT revoke, секреты, nginx, чеклист prod |
|
||||
| [release.md](./release.md) | Перед выкладкой: E2E, k6, ZAP, Lighthouse, smoke |
|
||||
| [TZ.md](./TZ.md) | Полное ТЗ — источник правды по требованиям |
|
||||
|
||||
## Быстрые ссылки
|
||||
|
||||
```bash
|
||||
# dev
|
||||
python apps/api/scripts/bootstrap_install.py
|
||||
docker compose --profile docker-web up -d --build
|
||||
|
||||
# тесты
|
||||
pnpm --filter web test:ci
|
||||
cd apps/api && python -m pytest --cov=app --cov-fail-under=90
|
||||
|
||||
# prod smoke (после деплоя)
|
||||
./infra/scripts/smoke-prod.sh https://your-domain.com
|
||||
```
|
||||
|
||||
## Что куда не кладём
|
||||
|
||||
| Не в git | Почему |
|
||||
|----------|--------|
|
||||
| `apps/api/data/secrets/install.env` | пароли БД, JWT, MinIO |
|
||||
| `.env`, `apps/api/.env` | локальные секреты |
|
||||
| `apps/api/data/logs/` | runtime-логи |
|
||||
| `node_modules/`, `.venv/` | очевидно |
|
||||
|
||||
Если секрет утёк в git — считайте его скомпрометированным. Force-push не спасает совесть.
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
# Деплой и эксплуатация
|
||||
|
||||
От «запустил на ноуте» до «живёт на VPS и не стыдно показать security.md».
|
||||
|
||||
## Среды
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Dev[docker-compose.yml] --> St[staging]
|
||||
St --> QA[k6 + ZAP + E2E]
|
||||
QA --> Prod[production]
|
||||
```
|
||||
|
||||
| Среда | Compose | APP_ENV | Docs | Demo users |
|
||||
|-------|---------|---------|------|------------|
|
||||
| Dev | `docker-compose.yml` | development | ✅ | ✅ |
|
||||
| CI/E2E | `docker-compose.test.yml` | test | ✅ | ✅ |
|
||||
| Staging | `infra/docker/docker-compose.staging.yml` | staging | ❌ | ❌ |
|
||||
| Production | `infra/docker/docker-compose.prod.yml` | production | ❌ | ❌ |
|
||||
|
||||
## Локальная разработка
|
||||
|
||||
```bash
|
||||
# 1. venv + зависимости backend (один раз)
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
||||
pip install -r apps/api/requirements-dev.txt
|
||||
|
||||
# 2. Секреты установки (один раз, до первого docker compose up)
|
||||
python apps/api/scripts/bootstrap_install.py
|
||||
|
||||
# 3. Полный стек в Docker (API + БД + web)
|
||||
docker compose --profile docker-web up -d --build
|
||||
|
||||
# 4. Проверка
|
||||
curl http://localhost:8000/api/v1/health
|
||||
```
|
||||
|
||||
| Сервис | URL |
|
||||
|--------|-----|
|
||||
| Web | http://localhost:5173 |
|
||||
| API | http://localhost:8000 |
|
||||
| PG/Redis/MinIO на хост | `docker-compose.dev-ports.yml` → 5432, 6379, 9000/9001 |
|
||||
|
||||
> Bootstrap и локальные тесты (`pytest`, `mypy`) — через активированный `.venv`. Docker API использует свой образ; venv нужен для скриптов и разработки на хосте.
|
||||
|
||||
**Гибрид** (инфра в Docker, frontend локально):
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
docker compose up -d
|
||||
pnpm install
|
||||
pnpm --filter web dev
|
||||
```
|
||||
|
||||
**Install secrets на хост** (DBeaver): Admin → Security → Install Secrets → Reveal DB password.
|
||||
|
||||
### Стандартные логины (dev)
|
||||
|
||||
Создаются при seed на старте API. Пароли по умолчанию — из `apps/api/.env.example` (или дефолты в `config.py`).
|
||||
|
||||
| Email | Пароль | Env | Роль | Superuser | Куда заходит |
|
||||
|-------|--------|-----|------|:---------:|--------------|
|
||||
| `admin@compton.example` | `Admin1234` | `ADMIN_INITIAL_PASSWORD` | admin | да | `/admin` — Users, Content, Security, Diagnostics, secrets |
|
||||
| `ops@compton.example` | `OpsAdmin1234` | `DEMO_OPS_PASSWORD` | admin | нет | `/admin` — Users, Content, Activity (без Security) |
|
||||
| `user@compton.example` | `User1234` | `DEMO_USER_PASSWORD` | user | — | `/profile` |
|
||||
|
||||
**CMS-страницы (seed):** `about`, `privacy`, `terms` → `/pages/about` и т.д.
|
||||
|
||||
> **Staging/production:** `SEED_DEMO_USERS=false` — demo `user@` и `ops@` **не создаются**, только admin + CMS. Пароль admin задаётся через `ADMIN_INITIAL_PASSWORD` **до первого seed**, потом — сменить в UI.
|
||||
|
||||
## Staging
|
||||
|
||||
**Нужно:** VPS, DNS, TLS certs в `infra/docker/certs/`, SMTP.
|
||||
|
||||
```bash
|
||||
git clone https://git.groupkomton.ru/Matvey/site.git && cd site
|
||||
python3 apps/api/scripts/bootstrap_install.py
|
||||
cp infra/docker/.env.staging.example infra/docker/.env.staging
|
||||
# правим: домен, CORS, SMTP, ADMIN_INITIAL_PASSWORD
|
||||
./infra/docker/deploy-staging.sh
|
||||
```
|
||||
|
||||
Проверка:
|
||||
|
||||
```bash
|
||||
curl -fsS https://STAGING/api/v1/health
|
||||
curl -fsS -o /dev/null -w "%{http_code}" https://STAGING/api/v1/docs # 404
|
||||
./infra/scripts/health-check.sh https://STAGING
|
||||
```
|
||||
|
||||
Nginx: `default.tls.conf` — 80→443, HSTS.
|
||||
|
||||
## Production
|
||||
|
||||
```bash
|
||||
python3 apps/api/scripts/bootstrap_install.py
|
||||
cp infra/docker/.env.production.example infra/docker/.env.production
|
||||
./infra/docker/deploy-prod.sh infra/docker/.env.production
|
||||
```
|
||||
|
||||
**Сразу после bootstrap:**
|
||||
1. Бэкап `install.env` off-server (зашифровать)
|
||||
2. Сменить пароль admin
|
||||
|
||||
| Сервис | Host ports |
|
||||
|--------|------------|
|
||||
| nginx | 80, 443 |
|
||||
| api, web, pg, redis, minio | internal only |
|
||||
|
||||
```bash
|
||||
./infra/scripts/smoke-prod.sh https://YOUR_DOMAIN
|
||||
```
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
docker compose -f infra/docker/docker-compose.prod.yml down
|
||||
git checkout PREVIOUS_TAG
|
||||
./infra/docker/deploy-prod.sh infra/docker/.env.production
|
||||
```
|
||||
|
||||
## Бэкапы и мониторинг
|
||||
|
||||
| Что | Команда / как |
|
||||
|-----|---------------|
|
||||
| PostgreSQL | `./infra/scripts/backup-postgres.sh` → `backups/postgres-*.sql.gz` |
|
||||
| install.env | `cp …/install.env backups/install.env.$(date +%F).enc` + gpg |
|
||||
| Uptime | `./infra/scripts/health-check.sh URL` или UptimeRobot на `/api/v1/health` + `/` |
|
||||
| Логи | logrotate для `server.log`, `admin-audit.jsonl` |
|
||||
|
||||
## Восстановление секретов
|
||||
|
||||
`install.env` — единственный источник runtime-секретов. Потеряли — не генерируйте новый вслепую.
|
||||
|
||||
### Симптомы
|
||||
|
||||
- `password authentication failed for user "compton_app"`
|
||||
- bootstrap после того, как Postgres volume уже создан
|
||||
|
||||
### Fix (есть бэкап)
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
# восстановить apps/api/data/secrets/install.env
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
### Fix (нет бэкапа)
|
||||
|
||||
| Вариант | Данные |
|
||||
|---------|--------|
|
||||
| Reveal из другой среды | сохраняются |
|
||||
| `down -v` + bootstrap (**только dev**) | **удаляются все** |
|
||||
|
||||
```bash
|
||||
docker compose --profile docker-web down -v
|
||||
python apps/api/scripts/bootstrap_install.py
|
||||
docker compose --profile docker-web up -d --build
|
||||
```
|
||||
|
||||
## SMTP
|
||||
|
||||
Staging/prod: `EMAIL_DELIVERY_MODE=smtp`. Dev: `memory` (письма в RAM, SMTP не нужен).
|
||||
|
||||
| Env | Назначение |
|
||||
|-----|------------|
|
||||
| `SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM` | сервер |
|
||||
| `FRONTEND_URL` | ссылки в письмах |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Проблема | Решение |
|
||||
|----------|---------|
|
||||
| `install.env missing` | `python apps/api/scripts/bootstrap_install.py` |
|
||||
| `password authentication failed` | [восстановление секретов](#восстановление-секретов) |
|
||||
| Login failed | `curl …/health`, проверить `.env`, restart web |
|
||||
| 401 refresh в консоли (гость) | норма на публичных страницах |
|
||||
| Logout после F5 на `/admin` | rebuild web, перелогиниться |
|
||||
| «На сайт» ведёт на login | должно быть `href="/"`, rebuild |
|
||||
| Port 5173 busy | stop Docker web **или** local Vite |
|
||||
| CORS | `VITE_USE_API_PROXY=true`, не бить напрямую :8000 |
|
||||
| Нет Security/Diagnostics | логин `admin@`, не `ops@` |
|
||||
| Docker web: missing modules | `docker compose … up -d --build web` |
|
||||
|
||||
## Compose-справочник
|
||||
|
||||
| Файл | Назначение |
|
||||
|------|------------|
|
||||
| `docker-compose.yml` | dev |
|
||||
| `docker-compose.dev-ports.yml` | порты на хост |
|
||||
| `docker-compose.test.yml` | CI/E2E |
|
||||
| `infra/docker/docker-compose.staging.yml` | staging |
|
||||
| `infra/docker/docker-compose.prod.yml` | production |
|
||||
|
||||
Deploy: `infra/docker/deploy-staging.sh`, `deploy-prod.sh`.
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
# Структура и архитектура
|
||||
|
||||
Monorepo-lite: статический лендинг + React SPA + FastAPI + PostgreSQL + Redis + MinIO.
|
||||
Если вы искали микросервисы на Kubernetes — это другой коридор.
|
||||
|
||||
## Стек
|
||||
|
||||
| Слой | Технологии |
|
||||
|------|------------|
|
||||
| Frontend | React 19, TS, Vite, React Router, TanStack Query, Zustand, RHF+Zod, Ant Design |
|
||||
| Backend | FastAPI, SQLAlchemy 2, Alembic, Pydantic v2 |
|
||||
| Данные | PostgreSQL 16, Redis 7, MinIO |
|
||||
| Инфра | Docker Compose, Nginx, GitHub Actions |
|
||||
| Качество | Vitest, Playwright, pytest (≥90% / ≥85% cov) |
|
||||
|
||||
## Дерево репозитория
|
||||
|
||||
```
|
||||
site/
|
||||
├── apps/
|
||||
│ ├── api/ # Backend
|
||||
│ │ ├── app/
|
||||
│ │ │ ├── core/ # crypto, jwt_denylist, redis, install_secrets…
|
||||
│ │ │ ├── db/ # models, seed, migrations helpers
|
||||
│ │ │ └── modules/ # auth, users, content, admin, media, test
|
||||
│ │ ├── migrations/ # Alembic
|
||||
│ │ ├── scripts/ # bootstrap_install.py, docker_entrypoint.py
|
||||
│ │ ├── tests/
|
||||
│ │ └── data/
|
||||
│ │ ├── secrets/ # install.env (gitignore!)
|
||||
│ │ └── logs/ # server.log, admin-audit.jsonl (gitignore)
|
||||
│ └── web/
|
||||
│ ├── index.html # лендинг /
|
||||
│ ├── app.html # SPA entry
|
||||
│ ├── main/ # статика лендинга (CSS/JS/video)
|
||||
│ ├── src/
|
||||
│ │ ├── app/ # router, guards
|
||||
│ │ ├── modules/ # auth, profile, admin, content, landing
|
||||
│ │ ├── pages/
|
||||
│ │ └── shared/ # api client, ui
|
||||
│ └── e2e/ # Playwright
|
||||
├── packages/ # eslint-config, shared-types (target)
|
||||
├── infra/
|
||||
│ ├── docker/ # staging/prod compose, deploy.sh
|
||||
│ ├── nginx/ # default.conf, default.tls.conf
|
||||
│ ├── k6/ # load test §17.2
|
||||
│ └── scripts/ # backup, health, smoke
|
||||
├── docs/ # вы здесь
|
||||
├── docker-compose.yml # dev
|
||||
└── docker-compose.test.yml # CI / E2E
|
||||
```
|
||||
|
||||
## Runtime
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph browser [Браузер]
|
||||
L[index.html /]
|
||||
S[app.html SPA]
|
||||
end
|
||||
subgraph edge [Nginx :80/:443]
|
||||
N[TLS + headers]
|
||||
end
|
||||
subgraph internal [Docker internal]
|
||||
W[web]
|
||||
A[api]
|
||||
PG[(PostgreSQL)]
|
||||
R[(Redis)]
|
||||
M[(MinIO)]
|
||||
end
|
||||
L --> N
|
||||
S --> N
|
||||
N --> W
|
||||
N --> A
|
||||
A --> PG
|
||||
A --> R
|
||||
A --> M
|
||||
```
|
||||
|
||||
**Prod/staging:** наружу только nginx. Postgres, Redis, MinIO — без host-портов.
|
||||
|
||||
## Frontend
|
||||
|
||||
### Два входа (dual-entry)
|
||||
|
||||
| Entry | URL | Содержимое |
|
||||
|-------|-----|------------|
|
||||
| `index.html` | `/` | Маркетинговый лендинг (`main/`) |
|
||||
| `app.html` | `/login`, `/admin`, … | React SPA |
|
||||
|
||||
Vite переписывает SPA-пути на `app.html` (`vite.main-static.ts`).
|
||||
|
||||
### Маршруты
|
||||
|
||||
| Путь | Guard | Кто |
|
||||
|------|-------|-----|
|
||||
| `/` | — | все |
|
||||
| `/login`, `/register`, `/forgot-password`, `/reset-password` | GuestGuard | гости |
|
||||
| `/verify`, `/pages/:slug` | — | публично |
|
||||
| `/profile` | AuthGuard | user |
|
||||
| `/admin` | AdminGuard | admin |
|
||||
|
||||
**Auth UX:**
|
||||
- Access JWT — только в памяти (Zustand), не localStorage
|
||||
- Refresh — HttpOnly cookie, `Path=/api/v1/auth`
|
||||
- После login: admin → `/admin`, user → `/profile`
|
||||
- Кнопка «На сайт» в админке — **полный** переход на `/` (не React Router)
|
||||
|
||||
### Админка (WESP-style)
|
||||
|
||||
| Раздел | Кому | Что |
|
||||
|--------|------|-----|
|
||||
| Users | admin | CRUD пользователей |
|
||||
| Content | admin | CMS |
|
||||
| Security | superuser | runtime settings, Install Secrets |
|
||||
| Diagnostics | superuser | health checks |
|
||||
| Activity | admin | audit feed, server log |
|
||||
|
||||
Тема Light/Dark — `localStorage.wespAdminTheme`. Auth-страницы — zootech-карточки (`#48816d`).
|
||||
|
||||
## Backend API
|
||||
|
||||
База: `/api/v1`
|
||||
|
||||
| Модуль | Эндпоинты (основное) |
|
||||
|--------|----------------------|
|
||||
| health | `GET /health` |
|
||||
| auth | register, login, logout, refresh, verify, forgot/reset password |
|
||||
| users | `GET/PATCH /me`, password, avatar |
|
||||
| content | публичные pages + admin CRUD |
|
||||
| admin | users, stats, settings, diagnostics, secrets, activity |
|
||||
| media | подписанные URL файлов |
|
||||
| test | `/test/emails/latest-token` — **только** E2E |
|
||||
|
||||
### Core (`apps/api/app/core/`)
|
||||
|
||||
| Модуль | Зачем |
|
||||
|--------|-------|
|
||||
| `crypto.py` | bcrypt, JWT, HMAC, token hash — одна точка |
|
||||
| `jwt_denylist.py` | мгновенный revoke access JWT |
|
||||
| `install_secrets.py` | bootstrap + lock |
|
||||
| `dependencies.py` | `get_current_user` |
|
||||
| `redis.py` | rate limit + JWT revoke |
|
||||
| `storage.py` | MinIO / memory |
|
||||
|
||||
## База данных
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
users ||--o| user_profiles : has
|
||||
users ||--o{ refresh_tokens : owns
|
||||
users ||--o{ password_reset_tokens : owns
|
||||
users ||--o{ email_verification_tokens : owns
|
||||
```
|
||||
|
||||
Таблицы: `users`, `user_profiles`, `refresh_tokens`, `password_reset_tokens`, `email_verification_tokens`, `content_pages`.
|
||||
|
||||
```bash
|
||||
cd apps/api && alembic upgrade head
|
||||
```
|
||||
|
||||
CHECK constraints на `role`, `status`; superuser только при `role=admin`. Cleanup expired tokens при старте API.
|
||||
|
||||
### Seed: стандартные логины (dev)
|
||||
|
||||
| Email | Пароль | Env |
|
||||
|-------|--------|-----|
|
||||
| admin@compton.example | Admin1234 | `ADMIN_INITIAL_PASSWORD` |
|
||||
| ops@compton.example | OpsAdmin1234 | `DEMO_OPS_PASSWORD` |
|
||||
| user@compton.example | User1234 | `DEMO_USER_PASSWORD` |
|
||||
|
||||
CMS: `about`, `privacy`, `terms`. Подробнее — [deploy.md § логины](./deploy.md#стандартные-логины-dev).
|
||||
|
||||
## Переменные окружения (ключевые)
|
||||
|
||||
| Переменная | Где | Назначение |
|
||||
|------------|-----|------------|
|
||||
| `DATABASE_URL` | api | PostgreSQL |
|
||||
| `APP_ENV` | api | development / staging / production |
|
||||
| `JWT_ACCESS_SECRET`, `JWT_REFRESH_PEPPER` | api | токены |
|
||||
| `REDIS_URL` | api | rate limit + JWT revoke (prod обязателен) |
|
||||
| `SEED_DEMO_USERS` | api | `false` на staging/prod |
|
||||
| `ADMIN_INITIAL_PASSWORD` | api | пароль admin при seed (default `Admin1234`) |
|
||||
| `DEMO_USER_PASSWORD`, `DEMO_OPS_PASSWORD` | api | demo user/ops (только dev) |
|
||||
| `ENABLE_DOCS` | api | `false` на prod |
|
||||
| `ENABLE_TEST_ROUTES` | api | `true` только E2E |
|
||||
| `EMAIL_DELIVERY_MODE` | api | `memory` (dev) / `smtp` (prod) |
|
||||
| `STORAGE_MODE` | api | `s3` / `memory` |
|
||||
| `VITE_API_URL` | web | `http://api:8000` в Docker |
|
||||
| `VITE_USE_API_PROXY` | web | `true` в dev |
|
||||
|
||||
Полные примеры: `apps/api/.env.example`, `apps/api/.env.production.example`.
|
||||
|
||||
### Runtime settings
|
||||
|
||||
`apps/api/data/compton_settings.json` — toggles без секретов. Superuser: `GET/PATCH /admin/settings`. Env с тем же ключом = **lock** (нельзя менять из UI).
|
||||
|
||||
## Docker Compose
|
||||
|
||||
| Файл | Когда |
|
||||
|------|-------|
|
||||
| `docker-compose.yml` | локальная разработка |
|
||||
| `docker-compose.dev-ports.yml` | PG/Redis/MinIO на хост (DBeaver) |
|
||||
| `docker-compose.test.yml` | CI, Playwright |
|
||||
| `infra/docker/docker-compose.staging.yml` | staging VPS |
|
||||
| `infra/docker/docker-compose.prod.yml` | production VPS |
|
||||
|
||||
## CI
|
||||
|
||||
`.github/workflows/ci.yml`: lint → types → mypy → tests → audit → Bandit → gitleaks → E2E.
|
||||
|
||||
## Тесты
|
||||
|
||||
```bash
|
||||
pnpm --filter web test:ci
|
||||
cd apps/api && python -m pytest --cov=app --cov-fail-under=90
|
||||
pnpm --filter web e2e
|
||||
```
|
||||
|
||||
E2E поднимает API `:8001` + Vite `:5175`. Против staging: `E2E_BASE_URL=… E2E_START_API=false`.
|
||||
@@ -1,14 +0,0 @@
|
||||
# MVP Regression Checklist
|
||||
|
||||
This checklist mirrors the required release scenarios.
|
||||
|
||||
1. Landing hero/marquee and reduced-motion behavior.
|
||||
2. Register -> verify -> login -> profile edit -> logout.
|
||||
3. Forgot password -> reset -> login.
|
||||
4. Admin publish content -> public slug availability.
|
||||
5. Admin blocks user -> blocked user login denied.
|
||||
6. Pending user cannot access `/profile`.
|
||||
7. Refresh token rotation and old token rejection.
|
||||
8. IDOR check: user A cannot access user B.
|
||||
9. Admin cannot demote/block self; last admin protected.
|
||||
10. Avatar upload rejects invalid MIME/oversize/SVG.
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
# Релиз и QA gates
|
||||
|
||||
Перед production не «авось прокатит», а чеклист из ТЗ §17. Если что-то красное — сначала staging, потом prod. Живёт один раз.
|
||||
|
||||
## Pipeline
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
CI[CI green] --> ST[Staging + TLS]
|
||||
ST --> E2E[E2E 11/11]
|
||||
ST --> K6[k6 pass]
|
||||
ST --> ZAP[ZAP 0 High/Crit]
|
||||
ST --> LH[Lighthouse ≥ 85]
|
||||
E2E --> PROD[Production deploy]
|
||||
K6 --> PROD
|
||||
ZAP --> PROD
|
||||
LH --> PROD
|
||||
PROD --> SM[smoke-prod.sh]
|
||||
SM --> DNS[DNS cutover]
|
||||
DNS --> MON[24h мониторинг]
|
||||
```
|
||||
|
||||
## Регрессия E2E
|
||||
|
||||
**Последний локальный прогон:** 2026-07-14 — backend 137 / 90.43% cov, frontend 46.
|
||||
|
||||
### Staging
|
||||
|
||||
```bash
|
||||
E2E_BASE_URL=https://staging.example.com E2E_START_API=false pnpm --filter web e2e
|
||||
```
|
||||
|
||||
| # | Сценарий | Local | Staging |
|
||||
|---|----------|:-----:|:-------:|
|
||||
| 1 | Landing hero, reduced-motion | ☐ | ☐ |
|
||||
| 2 | Register → verify → login → profile → logout | ☐ | ☐ |
|
||||
| 3 | Forgot → reset → login | ☐ | ☐ |
|
||||
| 4 | Admin publish → public slug | ☐ | ☐ |
|
||||
| 5 | Block user → login denied | ☐ | ☐ |
|
||||
| 6 | Pending → нет `/profile` | ☐ | ☐ |
|
||||
| 7 | Refresh rotation | ☐ | ☐ |
|
||||
| 8 | IDOR user A ≠ user B | ☐ | ☐ |
|
||||
| 9 | Admin не блокирует себя / last admin | ☐ | ☐ |
|
||||
| 10 | Avatar: bad MIME / size / SVG | ☐ | ☐ |
|
||||
| 11 | Block → access JWT 401 TOKEN_REVOKED | ☐ | ☐ |
|
||||
|
||||
### Локально
|
||||
|
||||
```bash
|
||||
pnpm --filter web e2e # API :8001, Vite :5175
|
||||
```
|
||||
|
||||
## k6 (§17.2)
|
||||
|
||||
```bash
|
||||
k6 run infra/k6/mvp-load-test.js -e BASE_URL=https://staging.example.com
|
||||
```
|
||||
|
||||
| Параметр | Порог |
|
||||
|----------|-------|
|
||||
| VU / ramp | 50 / 5 min |
|
||||
| Mix | 35% list, 25% login, 20% me, 10% refresh, 10% slug |
|
||||
| p95 | < 300 ms |
|
||||
| Errors | < 1% |
|
||||
|
||||
## OWASP ZAP
|
||||
|
||||
```bash
|
||||
docker run --rm -v "$(pwd):/zap/wrk:rw" -t ghcr.io/zap/zaproxy:stable \
|
||||
zap-baseline.py -t https://staging.example.com -r zap-report.html
|
||||
```
|
||||
|
||||
| Severity | Pass |
|
||||
|----------|------|
|
||||
| High, Critical | **0** |
|
||||
|
||||
Medium/Low — review руками. `zap-report.html` — в архив релиза.
|
||||
|
||||
## Lighthouse
|
||||
|
||||
```bash
|
||||
npx lighthouse https://staging.example.com \
|
||||
--preset=mobile --only-categories=performance \
|
||||
--output=json --output-path=./lighthouse-report.json
|
||||
```
|
||||
|
||||
| Метрика | ≥ |
|
||||
|---------|---|
|
||||
| Performance (mobile, `/`) | 85 |
|
||||
|
||||
Не прошло — hero video `preload="none"`, font swap, меньше JS на лендинге.
|
||||
|
||||
## Release gates (сводка)
|
||||
|
||||
- [ ] CI green на `main`
|
||||
- [ ] E2E 11/11 на staging
|
||||
- [ ] k6 pass
|
||||
- [ ] ZAP 0 High/Critical
|
||||
- [ ] Lighthouse ≥ 85
|
||||
- [ ] [security.md](./security.md) staging-пункты
|
||||
- [ ] `./infra/scripts/smoke-prod.sh` на prod
|
||||
- [ ] DNS cutover + rollback plan (previous tag)
|
||||
|
||||
## CI локально
|
||||
|
||||
```bash
|
||||
pnpm lint && pnpm typecheck
|
||||
cd apps/api && python -m mypy app
|
||||
pnpm --filter web test:ci
|
||||
cd apps/api && python -m pytest --cov=app --cov-fail-under=90
|
||||
```
|
||||
|
||||
Полный pipeline: `.github/workflows/ci.yml`.
|
||||
@@ -1,30 +0,0 @@
|
||||
# Install Secrets Recovery
|
||||
|
||||
This project keeps runtime installation secrets in `apps/api/data/secrets/install.env`.
|
||||
|
||||
## Important
|
||||
|
||||
- Do not rotate `POSTGRES_PASSWORD`, `JWT_ACCESS_SECRET`, or `JWT_REFRESH_PEPPER` automatically after first bootstrap.
|
||||
- A mismatch between `install.env` and initialized Postgres volume can break database access.
|
||||
|
||||
## Safe recovery steps
|
||||
|
||||
1. Stop services:
|
||||
- `docker compose down`
|
||||
2. Restore `apps/api/data/secrets/install.env` from backup.
|
||||
3. Start services:
|
||||
- `docker compose up -d --build`
|
||||
|
||||
If backup is unavailable, you have two options:
|
||||
|
||||
- Preferred: recover credentials directly from running database/admin secret reveal in another environment.
|
||||
- Last resort: reset local volumes and lose local dev data:
|
||||
- `docker compose down -v`
|
||||
- `python apps/api/scripts/bootstrap_install.py`
|
||||
- `docker compose up -d --build`
|
||||
|
||||
## Dev access ports
|
||||
|
||||
To expose DB/Redis/MinIO to host tools:
|
||||
|
||||
- `docker compose -f docker-compose.yml -f docker-compose.dev-ports.yml up -d`
|
||||
@@ -1,20 +0,0 @@
|
||||
# Security Checklist (MVP Pre-Production)
|
||||
|
||||
- [x] Access JWT is memory-only in frontend state (no sessionStorage/localStorage persistence).
|
||||
- [x] Refresh token is HttpOnly/Secure/SameSite cookie on `/api/v1/auth` path.
|
||||
- [x] Auth endpoints implemented with neutral anti-enumeration messaging.
|
||||
- [x] Origin/Referer validation is enforced for auth endpoints.
|
||||
- [x] Authenticated/admin route guards and role checks are enforced, including `SUPERUSER_ONLY` checks for critical endpoints.
|
||||
- [x] Content sanitization is enabled for CMS HTML body.
|
||||
- [x] Security headers configured in `infra/nginx/default.conf`.
|
||||
- [x] CI includes dependency audit, Bandit, and gitleaks scans.
|
||||
- [x] Settings runtime supports `data/compton_settings.json` with env lock behavior.
|
||||
- [x] Admin audit feed is persisted to `data/logs/admin-audit.jsonl`.
|
||||
- [x] Install secrets bootstrap is enabled (`apps/api/data/secrets/install.env`) and locked after first run.
|
||||
- [x] Database, Redis and MinIO are internal by default in base docker compose.
|
||||
- [x] `refresh` validates user status and rate limit is checked before token rotation.
|
||||
- [x] CMS sanitization enforces allowed URL protocols (`http`, `https`, `mailto`).
|
||||
- [x] Admin password create/reset uses shared password policy validators.
|
||||
- [ ] Production docs endpoint switch (`ENABLE_DOCS=false`) validated in staging/prod env.
|
||||
- [ ] HSTS behavior validated behind TLS ingress in staging/prod.
|
||||
- [ ] Recovery runbook for lost `install.env` tested (`docs/secrets-recovery.md`).
|
||||
@@ -0,0 +1,173 @@
|
||||
# Безопасность
|
||||
|
||||
Compton MVP — не банк, но и не «admin/admin в prod». Ниже — как устроена защита и что проверить перед выкладкой.
|
||||
|
||||
## Auth: схема
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant B as Браузер
|
||||
participant API as FastAPI
|
||||
participant RD as Redis
|
||||
participant PG as PostgreSQL
|
||||
|
||||
B->>API: POST /auth/login
|
||||
API->>PG: bcrypt verify
|
||||
API->>RD: read auth_epoch
|
||||
API-->>B: access JWT (memory) + refresh cookie
|
||||
|
||||
B->>API: GET /users/me + Bearer
|
||||
API->>RD: jti denied? epoch ok?
|
||||
alt revoked
|
||||
API-->>B: 401 TOKEN_REVOKED
|
||||
else ok
|
||||
API-->>B: 200
|
||||
end
|
||||
|
||||
B->>API: POST /auth/logout
|
||||
API->>RD: deny_jti + revoke refresh
|
||||
API-->>B: cookie cleared
|
||||
```
|
||||
|
||||
## Токены
|
||||
|
||||
| Токен | Где живёт | Отзыв |
|
||||
|-------|-----------|-------|
|
||||
| Access JWT | память frontend | jti denylist + auth_epoch (Redis) |
|
||||
| Refresh | HttpOnly cookie | rotation + family reuse detection |
|
||||
| Email/reset | opaque → hash в БД | one-time, TTL 1ч |
|
||||
|
||||
JWT claims: `sub`, `role`, `jti`, `auth_epoch`, `exp`.
|
||||
|
||||
### Мгновенный revoke
|
||||
|
||||
`apps/api/app/core/jwt_denylist.py`:
|
||||
|
||||
| Redis key | Смысл |
|
||||
|-----------|-------|
|
||||
| `jwt:deny:{jti}` | конкретный access-токен |
|
||||
| `auth:epoch:{user_id}` | версия сессий пользователя |
|
||||
|
||||
| Событие | Действие |
|
||||
|---------|----------|
|
||||
| Logout | deny jti + revoke refresh |
|
||||
| Block | INCR epoch + revoke refresh |
|
||||
| Смена/reset пароля | INCR epoch + revoke refresh |
|
||||
|
||||
**Production:** без Redis API не стартует. Redis упал — fail-closed (401, не «ну ладно»).
|
||||
|
||||
**Dev:** in-memory fallback (не путать с prod).
|
||||
|
||||
## Криптография
|
||||
|
||||
Всё через `apps/api/app/core/crypto.py`:
|
||||
|
||||
| Данные | Метод |
|
||||
|--------|-------|
|
||||
| Пароли | bcrypt cost 12 |
|
||||
| Access JWT | HS256 |
|
||||
| Refresh/email tokens | SHA-256 + pepper |
|
||||
| Media URLs | HMAC-SHA256 + TTL |
|
||||
| Install secrets | `secrets.token_*`, generate-once + lock |
|
||||
|
||||
## Install secrets
|
||||
|
||||
```bash
|
||||
python apps/api/scripts/bootstrap_install.py # до первого docker compose up
|
||||
```
|
||||
|
||||
| Файл | Содержимое |
|
||||
|------|------------|
|
||||
| `data/secrets/install.env` | PG, JWT, S3, MinIO |
|
||||
| `install.meta.json` | install ID, lock time |
|
||||
|
||||
**Не ротировать** `POSTGRES_PASSWORD` / JWT после bootstrap без плана — иначе Postgres скажет фразу, которую вы уже видели, и будет прав.
|
||||
|
||||
Reveal: Admin → Security → Install Secrets (superuser, аудит в `admin-audit.jsonl`).
|
||||
|
||||
Восстановление: [deploy.md § восстановление](./deploy.md#восстановление-секретов).
|
||||
|
||||
## RBAC
|
||||
|
||||
| Правило | Enforcement |
|
||||
|---------|-------------|
|
||||
| `/admin` → role=admin | Guard + API |
|
||||
| Security/Diagnostics/secrets → superuser | API + UI tabs |
|
||||
| Нельзя block/demote себя | admin service |
|
||||
| Last admin protected | admin service |
|
||||
| blocked/pending → 403 refresh | auth service |
|
||||
| forgot_password skip для blocked | auth service |
|
||||
| IDOR на профиль | users router |
|
||||
|
||||
## HTTP / инфра
|
||||
|
||||
### Nginx headers
|
||||
|
||||
| Header | Значение |
|
||||
|--------|----------|
|
||||
| X-Frame-Options | DENY |
|
||||
| X-Content-Type-Options | nosniff |
|
||||
| Referrer-Policy | strict-origin-when-cross-origin |
|
||||
| HSTS | `default.tls.conf` (staging/prod) |
|
||||
|
||||
### Прочее
|
||||
|
||||
- Origin/Referer на cookie-auth endpoints
|
||||
- Rate limit (prod: обязателен)
|
||||
- CMS: bleach, протоколы http/https/mailto
|
||||
- Avatar: jpeg/png/webp, re-encode, **SVG — нет**
|
||||
- CI: Bandit, pip-audit, gitleaks, npm audit
|
||||
- Postgres/Redis/MinIO — internal network
|
||||
- Firewall VPS: 22, 80, 443
|
||||
|
||||
## Production guards
|
||||
|
||||
`APP_ENV=production` → API **не стартует**, если:
|
||||
|
||||
| Проблема | Env |
|
||||
|----------|-----|
|
||||
| Test routes | `ENABLE_TEST_ROUTES=true` |
|
||||
| OpenAPI | `ENABLE_DOCS=true` |
|
||||
| Rate limit off | `ENABLE_RATE_LIMIT=false` |
|
||||
| Cookie без Secure | `COOKIE_SECURE=false` |
|
||||
| Placeholder JWT | `change-me-*` |
|
||||
| Дефолтная БД | user:pass |
|
||||
| Без SSL mode | нет `sslmode=require` |
|
||||
| Без Redis | JWT revocation |
|
||||
|
||||
## Prod env (минимум)
|
||||
|
||||
| Переменная | Значение |
|
||||
|------------|----------|
|
||||
| `APP_ENV` | production |
|
||||
| `ENABLE_DOCS` | false |
|
||||
| `ENABLE_TEST_ROUTES` | false |
|
||||
| `COOKIE_SECURE` | true |
|
||||
| `ENABLE_RATE_LIMIT` | true |
|
||||
| `EMAIL_DELIVERY_MODE` | smtp |
|
||||
| `SEED_DEMO_USERS` | false |
|
||||
| `DATABASE_URL` | …?sslmode=require |
|
||||
|
||||
## Чеклист
|
||||
|
||||
### Сделано в коде
|
||||
|
||||
- [x] JWT в memory, refresh HttpOnly
|
||||
- [x] Anti-enumeration auth
|
||||
- [x] Origin/Referer validation
|
||||
- [x] RBAC + superuser
|
||||
- [x] CMS sanitization
|
||||
- [x] Nginx security headers
|
||||
- [x] CI security scans
|
||||
- [x] Install secrets bootstrap + lock
|
||||
- [x] JWT jti denylist + auth_epoch
|
||||
- [x] Admin audit log
|
||||
|
||||
### Проверить на staging
|
||||
|
||||
- [ ] `/api/v1/docs` → 404
|
||||
- [ ] HSTS за TLS
|
||||
- [ ] Dry-run recovery секретов
|
||||
- [ ] ZAP: 0 High/Critical → [release.md](./release.md)
|
||||
- [ ] k6 pass → [release.md](./release.md)
|
||||
- [ ] Lighthouse ≥ 85 на `/`
|
||||
Reference in New Issue
Block a user