From 45326281f973c475e9683679c7a1a1035ebe8c60 Mon Sep 17 00:00:00 2001 From: nns Date: Wed, 27 May 2026 02:51:13 +0500 Subject: [PATCH] =?UTF-8?q?docs(deploy):=20.env.example=20+=20secrets.md,?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B1=D1=80=D0=BE=D1=81=20OpenIddict=20en?= =?UTF-8?q?v=20=D0=B2=20compose=20(P0-8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deploy/.env.example — все required/опц. переменные (POSTGRES_PASSWORD, REGISTRY, *_TAG, OPENIDDICT_ISSUER/CERT_PASSWORD, FM_* бэкапа, Cors/RateLimiting/MoySklad). docs/secrets.md — таблица переменных, где живут секреты (SMTP в БД, сертификаты в volume), ротация, гигиена. compose: api получает OpenIddict__Issuer (за прокси обязателен) и OpenIddict__CertPassword из .env. compose config валиден. Co-Authored-By: Claude Opus 4.7 --- deploy/.env.example | 43 ++++++++++++++++++++++++++++++++ deploy/docker-compose.yml | 5 ++++ docs/secrets.md | 52 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 deploy/.env.example create mode 100644 docs/secrets.md diff --git a/deploy/.env.example b/deploy/.env.example new file mode 100644 index 0000000..04caed2 --- /dev/null +++ b/deploy/.env.example @@ -0,0 +1,43 @@ +# food-market — пример переменных окружения для деплоя. +# +# Скопировать в deploy/.env и заполнить значениями (.env в .gitignore — НЕ коммитить). +# cp deploy/.env.example deploy/.env && $EDITOR deploy/.env +# +# docker-compose читает deploy/.env автоматически. Описание секретов и ротация — +# docs/secrets.md. Ключи OpenIddict — docs/openiddict-keys.md. + +# ─── Реестр образов и теги (docker-compose) ────────────────────────────────── +# Откуда тянуть образы. Локальный registry на stage — 127.0.0.1:5001 (см. CLAUDE/memory). +REGISTRY=127.0.0.1:5001 +API_TAG=latest +WEB_TAG=latest +PUBLIC_TAG=latest + +# ─── База данных (ОБЯЗАТЕЛЬНО) ─────────────────────────────────────────────── +# Пароль пользователя food_market в Postgres-контейнере. Подставляется и в +# POSTGRES_PASSWORD контейнера БД, и в ConnectionStrings__Default API. +# Сгенерировать: openssl rand -base64 24 +POSTGRES_PASSWORD=CHANGE_ME_strong_db_password + +# ─── OpenIddict / выдача токенов ───────────────────────────────────────────── +# Публичный URL админки = issuer токенов (обязателен за nginx-прокси). +OPENIDDICT_ISSUER=https://admin.food-market.kz/ +# Пароль PFX-сертификатов подписи/шифрования. Пусто = без пароля (self-signed +# генерируется автоматически в App_Data, если файлов нет). Подробности — docs/openiddict-keys.md. +OPENIDDICT_CERT_PASSWORD= + +# ─── Бэкап (systemd food-market-backup.*) ──────────────────────────────────── +# Переопределения для скрипта бэкапа. По умолчанию совпадают с compose — можно не задавать. +# FM_BACKUP_DIR=/opt/food-market-data/backups +# FM_UPLOADS_DIR=/opt/food-market-data/uploads +# FM_BACKUP_RETENTION_DAYS=30 + +# ─── Прочее (опционально, переопределяет appsettings.json) ─────────────────── +# CORS-origins фронта (если отличается от зашитых в appsettings). Индексируется с 0: +# Cors__AllowedOrigins__0=https://admin.food-market.kz +# Антибрутфорс на /connect/token и /api/auth/signup (дефолты 5/мин, 20/час): +# RateLimiting__Enabled=true +# RateLimiting__PerMinute=5 +# RateLimiting__PerHour=20 +# Интеграция МойСклад (по умолчанию боевой api.moysklad.ru): +# MoySklad__BaseUrl=https://api.moysklad.ru/api/remap/1.2/ diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 58d0d68..e0d4730 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -29,6 +29,11 @@ services: environment: ASPNETCORE_ENVIRONMENT: Production ConnectionStrings__Default: Host=postgres;Port=5432;Database=food_market;Username=food_market;Password=${POSTGRES_PASSWORD:-food_market_dev} + # Публичный issuer токенов — обязателен за прокси, иначе берётся из запроса + # (или дефолт localhost из appsettings, что неверно для прод). + OpenIddict__Issuer: ${OPENIDDICT_ISSUER:-https://admin.food-market.kz/} + # Пароль PFX-сертификатов OpenIddict (пусто = сертификаты без пароля). + OpenIddict__CertPassword: ${OPENIDDICT_CERT_PASSWORD:-} # Host port mapping: pick free ports on existing stage server (80/443 taken by # legacy nginx, 5000/5002/5005 taken by legacy .NET apps). ports: diff --git a/docs/secrets.md b/docs/secrets.md new file mode 100644 index 0000000..890634c --- /dev/null +++ b/docs/secrets.md @@ -0,0 +1,52 @@ +# Секреты и переменные окружения + +Все секреты задаются через `deploy/.env` (в `.gitignore`, **не коммитится**). +Шаблон со всеми переменными — `deploy/.env.example`. docker-compose читает `.env` +автоматически из каталога запуска (`deploy/`). + +```bash +cp deploy/.env.example deploy/.env +$EDITOR deploy/.env # заполнить значения +chmod 600 deploy/.env # ограничить доступ +``` + +## Перечень + +| Переменная | Обяз. | Назначение | Где используется | Как получить | +|---|:---:|---|---|---| +| `POSTGRES_PASSWORD` | ✅ | пароль БД `food_market` | контейнер postgres + `ConnectionStrings__Default` API | `openssl rand -base64 24` | +| `REGISTRY` | ✅ | реестр образов | image-ссылки в compose | стейдж: `127.0.0.1:5001` | +| `API_TAG` / `WEB_TAG` / `PUBLIC_TAG` | ✅ | теги образов | image-ссылки | тег из CI / `latest` | +| `OPENIDDICT_ISSUER` | ✅(прод) | публичный issuer токенов | API `OpenIddict__Issuer` | публичный URL админки, напр. `https://admin.food-market.kz/` | +| `OPENIDDICT_CERT_PASSWORD` | — | пароль PFX-сертификатов | API `OpenIddict__CertPassword` | свой пароль или пусто (self-signed без пароля) | +| `FM_BACKUP_DIR` / `FM_UPLOADS_DIR` / `FM_BACKUP_RETENTION_DAYS` | — | параметры бэкапа | `food-market-backup.sh` | дефолты совпадают с compose | +| `Cors__AllowedOrigins__N` | — | CORS-origins | API | переопределяет `appsettings.json` | +| `RateLimiting__*` | — | антибрутфорс лимиты | API | дефолты 5/мин, 20/час | +| `MoySklad__BaseUrl` | — | база API МойСклад | API | дефолт боевой `api.moysklad.ru` | + +> `__` (двойное подчёркивание) — разделитель секций конфигурации .NET +> (`OpenIddict__Issuer` ≡ `OpenIddict:Issuer`). + +## Где ещё живут секреты + +- **SMTP (отправка писем)** — НЕ в env. Хранятся в БД (`platform_settings`), + правятся из SuperAdmin-консоли (раздел «Платформа → SMTP»). Перечитываются на + каждой отправке без рестарта (см. `MailKitEmailSender`). +- **Сертификаты OpenIddict** — PFX в volume `api-data` (`/app/App_Data`). Генерируются + self-signed при отсутствии. Можно принести свои — см. [openiddict-keys.md](openiddict-keys.md). +- **Учётки БД/Forgejo на сервере** — вне репозитория (см. приватные заметки оператора). + +## Ротация + +| Секрет | Как ротировать | Влияние | +|---|---|---| +| `POSTGRES_PASSWORD` | `ALTER USER food_market PASSWORD '…'`, обновить `.env`, `docker compose up -d` | рестарт API | +| OpenIddict-сертификаты | заменить/удалить PFX, рестарт API | все токены инвалидируются — повторный логин | +| SMTP-пароль | через SuperAdmin-консоль | без рестарта | + +## Гигиена + +- `deploy/.env` — права `600`, владелец — пользователь деплоя. +- Не логировать значения секретов. Serilog настроен без дампа окружения. +- При утечке — ротировать затронутый секрет (таблица выше) и пересоздать токены. +- Проверка, что секреты не утекли в git: `git ls-files | grep -E '\.env$'` должен быть пуст.