Vault 1.6 и динамические секреты: production БД без статических паролей
Переводим production базы на динамические credentials через HashiCorp Vault 1.6: приложение получает временный логин на час, Kubernetes интегрируется через vault-agent-injector.
HashiCorp Vault 1.6 выходит с улучшенным Database Secrets Engine и автоматической ротацией root credentials
HashiCorp на прошлой неделе выкатил Vault 1.6. Главное в релизе для нас - улучшенный Database Secrets Engine с автоматической ротацией root credentials. Мы как раз в середине проекта по переводу клиентского production на динамические секреты, и появление этой фичи - хорошее подкрепление для разговора с теми, кто ещё сомневается, нужно ли это вообще.
Почему статические пароли к БД - это проблема
Разберём ситуацию, которую видим регулярно. Приложение в Kubernetes читает пароль к PostgreSQL из Secret. Этот Secret был создан однажды при деплое и с тех пор не менялся. Тот же пароль знает DevOps-инженер, который его создавал. Возможно, он лежит в .env в репозитории или в каком-нибудь корпоративном wiki «для удобства». В CI/CD он упоминается как переменная окружения, которую когда-то добавили в GitLab.
В итоге у одного статического пароля - неизвестное число точек утечки и нет истории ротации. Если нужно отозвать доступ скомпрометированного сервиса - придётся поменять пароль и обновить все места, где он прописан. А если точно не знаешь, сколько этих мест - ситуация неприятная.
Динамические credentials решают это структурно: Vault сам создаёт временную пару логин/пароль для каждого запроса, устанавливает TTL (у нас обычно час), и по истечении TTL автоматически отзывает этот конкретный аккаунт в СУБД. Приложение никогда не знает «постоянного» пароля - только тот, который выдан ему лично и живёт не дольше одного сеанса.
Что добавил Vault 1.6
До 1.6 Database Secrets Engine умел выдавать динамические credentials, но root credentials (тот аккаунт, от имени которого Vault создаёт временных пользователей) надо было ротировать вручную или скриптами. Теперь это делается встроенным механизмом: Vault сам периодически меняет пароль root-аккаунта в СУБД и сохраняет новый у себя. Это закрывает один из неудобных вопросов при аудите: «а кто знает root-пароль к базе?» - теперь ответ «никто из людей, только Vault».
Как это устроено в PostgreSQL
Настройка в целом несложная. Vault нужен служебный пользователь в PostgreSQL с правами создавать и удалять роли:
CREATE ROLE vault_admin WITH LOGIN PASSWORD 'initial_password' CREATEROLE;
GRANT CONNECT ON DATABASE appdb TO vault_admin;
Подключаем database backend в Vault:
vault secrets enable database
vault write database/config/appdb \
plugin_name=postgresql-database-plugin \
allowed_roles="app-role" \
connection_url="postgresql://{{username}}:{{password}}@postgres:5432/appdb" \
username="vault_admin" \
password="initial_password"
Сразу после этого имеет смысл выполнить ротацию root credentials - Vault сменит пароль vault_admin в PostgreSQL и запомнит новый, исходный initial_password перестанет работать:
vault write -force database/rotate-root/appdb
Создаём роль с шаблоном SQL для временных пользователей:
vault write database/roles/app-role \
db_name=appdb \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
revocation_statements="DROP ROLE IF EXISTS \"{{name}}\";" \
default_ttl="1h" \
max_ttl="4h"
После этого vault read database/creds/app-role выдаёт новую пару логин/пароль, и в PostgreSQL появляется соответствующая роль с expiration.
Интеграция с Kubernetes через vault-agent-injector
Это самое интересное. Приложение в Kubernetes не должно само вызывать Vault API - это усложняет код и создаёт зависимость. Vault Agent Injector решает это через sidecar: агент получает credentials от Vault и кладёт их в файл внутри pod-а. Приложение читает файл с учётными данными как раньше - просто он теперь обновляется автоматически.
Аннотации к pod-у выглядят так:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "app-role"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/app-role"
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "database/creds/app-role" -}}
PGUSER={{ .Data.username }}
PGPASSWORD={{ .Data.password }}
{{- end }}
Vault Agent Injector (устанавливается через Helm-чарт vault) добавляет init-контейнер, который получает credentials до старта основного контейнера, и sidecar, который следит за TTL и обновляет файл перед истечением. Приложению нужно уметь перечитывать конфиг при изменении файла - либо перезапускаться. Второй вариант грубее, но во многих случаях достаточен: TTL час, приложение перестартует раз в час с новыми credentials.
Для аутентификации Kubernetes-workload в Vault используется Kubernetes auth method: Vault проверяет ServiceAccount token pod-а и сопоставляет его с заранее настроенной политикой.
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
vault write auth/kubernetes/role/app-role \
bound_service_account_names=app-sa \
bound_service_account_namespaces=production \
policies=db-app-policy \
ttl=1h
Где неудобно
Приложение должно уметь работать с ротацией. Если база данных закрыла старое соединение по истечении срока действия роли, а приложение держит connection pool и не переоткрывает соединения - получим ошибки. Проверить это в staging перед переводом в production - обязательно.
Миграции. Временный пользователь с правами SELECT/INSERT/UPDATE/DELETE не может запустить DDL-миграции. Для миграций нужна отдельная роль Vault с более широкими правами, и запускать их нужно явно в рамках деплоя, а не из приложения.
Мониторинг TTL. Если Vault недоступен и агент не смог обновить credentials до истечения TTL - приложение теряет доступ к базе. Vault должен быть в HA-конфигурации, и это нужно отслеживать.
Состояние проекта
Перевели два сервиса из семи. На остальных пока статические пароли с планом миграции до конца квартала. Основная работа - не в настройке Vault, это занимает день, а в проверке того, как каждое приложение ведёт себя при ротации credentials.
Если нужен взгляд на то, насколько ваша инфраструктура готова к динамическим секретам и где сейчас реальные риски - аудит как раз подходит для такой инвентаризации.