11 KiB
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
- Создайте отдельный проект
han-chat-secrets-prod. - Создайте сервисного пользователя
han-chat-secrets-reader. - Назначьте ему
memberтолько в этом проекте. Не выдавайте account scope,iam.adminи доступ к другим production-ресурсам. Если Selectel добавит отдельную read-only роль Secrets Manager, заменитеmemberна неё. - Ограничьте обращения к
api.selectel.ruисходящим IP ВМ, если функция доступна в аккаунте. - Включите экспорт audit logs. Контролируйте события
secrets.secret*иsecrets.secret_version*; alert на delete, смену current version вне окна, массовые чтения и обращения не от штатного пользователя/IP. - Выполните 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
- Добавьте новую версию секрета, не меняя имя.
- Для canary при необходимости временно pin числовой
versionв JSON-карте. - Выполните sync, validation и smoke без вывода конфигурации.
- Сделайте версию current, удалите pin, снова sync и пересоздайте только потребителей.
- Для rollback активируйте предыдущую provider version; не храните snapshot
старого
/etc/han/vm1.env.
Selectel не позволяет удалить отдельную версию — только секрет целиком. Старые
значения должны быть отозваны в PostgreSQL/S3/Bitrix/i-Digital после окна
rollback. Все значения из прежнего .env считайте раскрытыми и ротируйте после
перехода.