# 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. Ожидаемые артефакты: ```sh 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. Штатный режим: ```dotenv SECRETS_SOURCE=selectel APP_ENV=production ``` ## 4. Bootstrap credential Не храните пароль service user в `.env` или JSON. На целевой ВМ: ```sh 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: ```sh 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`, добавьте: ```json "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`: ```dotenv 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` считайте раскрытыми и ротируйте после перехода.