12 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-like.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-bitrix-sync-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-safety-password,han-chat-prod-redis-health-password, а также три credential-bearing URL;han-chat-prod-message-safety-token,han-chat-prod-bitrix-internal-token,han-chat-prod-bitrix-forward-token,han-chat-prod-bitrix-sync-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 после копирования проекта
устанавливает loader, launcher, systemd template и этот runbook. Вручную:
sudo install -d -m 0700 /etc/han/secrets /etc/han/credentials
sudo install -d -m 0755 /usr/local/lib/han-secrets
sudo install -m 0750 secrets_loader.py han-secrets /usr/local/lib/han-secrets/
sudo install -m 0750 han-compose /usr/local/bin/han-compose
sudo install -m 0644 han-secrets@.service /etc/systemd/system/
sudo install -m 0600 config.example.json \
/etc/han/secrets/production-like.selectel.json
Обычный /opt/han-chat/backend/.env содержит только несекретные параметры.
Штатный режим:
SECRETS_SOURCE=selectel
APP_ENV=production-like
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. Проверка и запуск
Все команды, которым нужны Compose secrets, запускайте от root через wrapper:
cd /opt/han-chat/backend
sudo ./scripts/validate-env .env
sudo systemctl daemon-reload
sudo systemctl enable han-secrets@production.service
sudo systemctl restart han-secrets@production.service
sudo ./scripts/validate-env .env \
--runtime-manifest /run/han-chat/secrets/manifest
sudo deployment/secrets/han-compose config --quiet
sudo deployment/secrets/han-compose up -d --wait
Selectel sync запускается именно unit-файлом: только он предоставляет
расшифрованный bootstrap credential через $CREDENTIALS_DIRECTORY.
han-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.
На выделенной только под HAN Chat ВМ после canary и проверки file fallback установите fail-closed ordering:
sudo install -d -m 0755 /etc/systemd/system/docker.service.d
sudo install -m 0644 \
deployment/secrets/docker-han-secrets.conf.example \
/etc/systemd/system/docker.service.d/han-secrets.conf
sudo systemctl daemon-reload
sudo systemctl restart docker
После этого проведите reboot rehearsal: materializer должен завершиться до
autorestart контейнеров. Ошибка Selectel намеренно блокирует Docker. На ВМ с
другими workloads такой глобальный Requires= запрещён: нужен отдельный Docker
daemon/VM, иначе fail-closed HAN остановит несвязанные системы.
6. Явный file fallback
Подготовьте отдельную карту
/etc/han/secrets/production-like.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-файл из защищённой офлайн-копии и только затем
явно измените .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
старого
.env.
Selectel не позволяет удалить отдельную версию — только секрет целиком. Старые
значения должны быть отозваны в PostgreSQL/S3/Bitrix/i-Digital после окна
rollback. Все значения из прежнего .env считайте раскрытыми и ротируйте после
перехода.