Files
han-app/VM1_app/codebase/backend/deployment/secrets/SELECTEL_RUNBOOK.ru.md
T

11 KiB
Raw Blame History

Selectel Secrets Manager для HAN Chat

Модель

han-secrets читает только SECRETS_SOURCE=selectel|file из обычного .env, выбирает соответствующую root-only JSON-карту и вызывает secrets_loader.py. Загрузчик получает project-scoped IAM token, читает объявленные секреты и создаёт в /run/han-chat/secrets:

  • отдельные файлы с каноническими именами для Compose secrets;
  • узкие service dotenv-файлы для диагностики состава без вывода значений;
  • manifest вида NAME=/absolute/path, используемый валидатором.

Каталог /run находится в tmpfs и имеет режим 0700. Канонические файлы имеют 0444: локальные пользователи не могут пройти через root-only каталог, а не-root UID контейнера может прочитать только явно смонтированный Compose secret. Значения не передаются через Docker Config.Env, argv, общий .env или логи. После полного root/docker-компромисса runtime-значения извлекаемы — это ограничение модели, а не гарантия Secret Manager.

Сбой Selectel никогда автоматически не включает file fallback. Уже работающие контейнеры продолжают использовать текущие значения; новый sync завершается fail-closed.

1. Ресурсы Selectel

  1. Создайте отдельный проект han-chat-secrets-prod.
  2. Создайте сервисного пользователя han-chat-secrets-reader.
  3. Назначьте ему member только в этом проекте. Не выдавайте account scope, iam.admin и доступ к другим production-ресурсам. Если Selectel добавит отдельную read-only роль Secrets Manager, замените member на неё.
  4. Ограничьте обращения к api.selectel.ru исходящим IP ВМ, если функция доступна в аккаунте.
  5. Включите экспорт audit logs. Контролируйте события secrets.secret* и secrets.secret_version*; alert на delete, смену current version вне окна, массовые чтения и обращения не от штатного пользователя/IP.
  6. Выполните canary и убедитесь, что provider audit содержит metadata операции, но не value, Base64 payload, IAM token или response body.

Secrets Manager принимает project-scoped IAM token в X-Auth-Token. Token живёт до 24 часов, но загрузчик использует его только в памяти одного запуска. TLS и redirect policy отключать нельзя.

2. Каталог секретов

Скопируйте config.example.json в /etc/han/secrets/production.selectel.json и замените account, username, project, region и remote. Каноническое имя слева обязано совпадать с Compose/validator; remote — неизменяемый ключ в Selectel.

Используйте консервативные provider keys с дефисами, например:

  • han-chat-prod-pg-han-app-dsn, han-chat-prod-pg-bitrix-local-dsn, han-chat-prod-pg-sms-dsn, han-chat-prod-pg-keycloak-password, han-chat-prod-pg-backup-dsn;
  • han-chat-prod-redis-api-password, han-chat-prod-redis-health-password, а также credential-bearing URL только для Redis DB0/DB1;
  • han-chat-prod-message-safety-token, han-chat-prod-bitrix-internal-token, han-chat-prod-bitrix-forward-token, han-chat-prod-keycloak-settings-token, han-chat-prod-sms-service-token;
  • Keycloak bootstrap password, OTP HMAC и mock code только для среды, где mock действительно включён;
  • Bitrix client secret, application token и token encryption key;
  • пары S3 app access/secret key;
  • i-Digital API key и callback username/password.

Одинаковые пары env (BITRIX_LOCAL_APP_INTERNAL_TOKEN / BITRIX_INTERNAL_API_TOKEN, BITRIX_API_FORWARD_TOKEN / BITRIX_API_INBOX_TOKEN, SMS_SERVICE_TOKEN / KEYCLOAK_SMS_SERVICE_TOKEN) должны ссылаться на один remote.

literal: "" разрешён только для заведомо пустого optional-параметра, например OTLP auth при self-hosted SigNoz или выключенного CAPTCHA server key. Секреты не записывайте в JSON. Не заводите planned/unused Message Safety DB, quarantine S3 и Bitrix sync credentials до появления потребляющего кода.

Загружайте значения в Selectel через скрытый prompt/stdin. Не передавайте value позиционным аргументом CLI и не включайте set -x/curl -v.

3. Установка на ВМ

Повторный запуск deployment/scripts/setup-vm.sh после root-активации release устанавливает loader, launcher, systemd units и этот runbook. Production установка вручную не поддерживается: точные owner/mode и пути задаёт setup. Ожидаемые артефакты:

test -x /usr/local/lib/han-secrets/han-secrets
test -x /usr/local/sbin/han-vm1-compose
test -f /etc/systemd/system/han-secrets@.service
test -f /etc/systemd/system/han-stack@.service

/etc/han/vm1.env принадлежит root (0600) и содержит только несекретные параметры. Config находится вне immutable release. Штатный режим:

SECRETS_SOURCE=selectel
APP_ENV=production

4. Bootstrap credential

Не храните пароль service user в .env или JSON. На целевой ВМ:

sudo systemd-creds encrypt --name=selectel-service-user-password - \
  /etc/han/credentials/production.selectel-password.cred
sudo chmod 0600 /etc/han/credentials/production.selectel-password.cred

Введите пароль через интерактивный stdin. Unit передаёт расшифрованный файл через приватный $CREDENTIALS_DIRECTORY. Root всё равно может его извлечь; после инцидента credential и все доступные ему секреты необходимо ротировать.

5. Проверка и запуск

Secret sync и preflight запускает root. deploy не вызывает Docker/launcher:

cd /opt/han-chat/current/backend
./scripts/validate-env /etc/han/vm1.env
systemctl restart han-secrets@production.service
./scripts/validate-env /etc/han/vm1.env \
  --runtime-manifest /run/han-chat/secrets/manifest
deployment/preflight.sh
/usr/local/sbin/han-vm1-compose config --quiet

Selectel sync запускается именно unit-файлом: только он предоставляет расшифрованный bootstrap credential через $CREDENTIALS_DIRECTORY. han-vm1-compose и ops-скрипты используют уже синхронизированный manifest и отказываются работать, если SECRETS_SOURCE/loader config не совпадают с runtime state. После смены source, provider version или JSON-карты сначала выполняйте systemctl restart han-secrets@production.service.

Не используйте docker compose config без --quiet, docker inspect для поиска конфигурации, env, strace, core dump или debug HTTP proxy. Проверка приёмки должна подтвердить отсутствие canary value в docker inspect, stdout, json logs, traces и shell history.

Fail-closed ordering задают han-stack@production.service и han-secrets@production.service. Глобальную зависимость Docker daemon от секретов не устанавливайте. После настройки проведите reboot rehearsal: materializer должен завершиться до root stack unit. На ВМ с другими workloads тем более запрещено связывать весь Docker daemon/VM, иначе fail-closed HAN остановит несвязанные системы.

6. Явный file fallback

Подготовьте отдельную карту /etc/han/secrets/production.file.json: скопируйте Selectel-карту, установите "mode": "file", удалите selectel и http, добавьте:

"file": {
  "path": "/etc/han/break-glass/secrets.env",
  "max_bytes": 1048576
}

secrets map остаётся тем же. Для записей с literal: "" строка в fallback не нужна; остальные канонические ключи обязательны. Fallback parser не исполняет shell: запрещены export, substitutions, multiline, неизвестные и дублирующиеся ключи. Файл — root:root 0600.

При инциденте доставьте recovery-файл из защищённой офлайн-копии и только затем явно измените /etc/han/vm1.env:

SECRETS_SOURCE=file

Перезапустите han-secrets@production.service, затем выполните validate/recreate через wrappers. После восстановления Selectel верните SECRETS_SOURCE=selectel, снова перезапустите unit, повторите проверки и удалите recovery-файл. Не храните его постоянно на ВМ: это вернуло бы исходный риск монолитного .env.

7. Ротация и rollback

  1. Добавьте новую версию секрета, не меняя имя.
  2. Для canary при необходимости временно pin числовой version в JSON-карте.
  3. Выполните sync, validation и smoke без вывода конфигурации.
  4. Сделайте версию current, удалите pin, снова sync и пересоздайте только потребителей.
  5. Для rollback активируйте предыдущую provider version; не храните snapshot старого /etc/han/vm1.env.

Selectel не позволяет удалить отдельную версию — только секрет целиком. Старые значения должны быть отозваны в PostgreSQL/S3/Bitrix/i-Digital после окна rollback. Все значения из прежнего .env считайте раскрытыми и ротируйте после перехода.