ADG Оставить заявку
Блог Информационная безопасность 4 мин чтения

HashiCorp Vault 1.0 и динамические секреты: временные учётные данные для PostgreSQL и MySQL

Подключили Vault 1.0 к базам данных клиентов: каждое приложение получает временный логин с TTL, а утечка конфига перестала быть поводом для ночных звонков.

Контекст момента

HashiCorp Vault 1.0 GA - динамические секреты и управление арендой учётных данных для баз данных

HashiCorp Vault 1.0 вышел в октябре, и мы наконец перестали откладывать его в ящик «разберёмся потом». Формально Vault существует давно, но именно версия 1.0 дала достаточно уверенности, чтобы тащить его в клиентские продакшн-окружения - не как эксперимент, а как инфраструктурный компонент. Первое что потрогали вплотную - database secrets engine и динамические учётные данные для PostgreSQL и MySQL.

Поводом стал не абстрактный «лучший практис», а конкретный неприятный разговор с одним из клиентов: в процессе разбора CI/CD-пайплайна обнаружился файл .env в репозитории с реальными логинами к базам данных. Не в публичном, внутреннем - но тем не менее. Пароли там жили, по всей видимости, с момента первоначальной настройки. Сколько людей их видело - никто не знал.

Как работает database secrets engine

Идея простая, но от этого не менее красивая: Vault сам создаёт временного пользователя в базе данных на момент запроса, выдаёт логин и пароль приложению через API, и сам же отзывает этого пользователя по истечении TTL. Приложение никогда не знает «постоянного» пароля - только временный, который живёт ровно столько, сколько нужно.

Для PostgreSQL это выглядит так: Vault подключается к базе через суперпользователя (или пользователя с правом CREATEROLE), по запросу выполняет SQL-шаблон создания роли с нужными правами, фиксирует lease, и когда lease истекает - выполняет DROP ROLE. Можно попросить продление (renew), а можно явно отозвать (revoke). Всё это через HTTP API.

Настройка на практике выглядела примерно так:

  • Подключение движка. vault secrets enable database - одна команда. Дальше конфигурируем connection string к PostgreSQL с учётными данными root-пользователя Vault.
  • Шаблон роли. Пишем SQL: какие права получает временный пользователь. У нас для каждого сервиса свой шаблон - read-only для аналитического дашборда, write для сервиса транзакций. Никакого SUPERUSER.
  • Настройка TTL. Default TTL - час. Max TTL - восемь часов. Приложение может обновлять lease пока оно живо, при рестарте получает новые учётные данные.
  • Политики доступа. Через Vault Policy ограничиваем, какой AppRole может запрашивать какую роль в какой базе. CI-пайплайн и продакшн-сервис - разные AppRole с разными политиками.

Что получили на выходе

Первое и самое ощутимое: исчезли статичные пароли в конфигах. .env файлы теперь содержат либо Vault-токен (который сам по себе короткоживущий и scope-ограниченный), либо AppRole ID + Secret ID - последнее тоже можно делать одноразовым.

Второе: утечка конфига перестала быть катастрофой в старом смысле. Если кто-то утащил token или Secret ID - это неприятно, но не означает, что у него теперь навсегда есть доступ к базе с продакшн-данными. Leak-detection и отзыв токена отрезает доступ немедленно, до того как успеют что-то сделать с украденными данными.

Третье: стало видно, кто и когда запрашивает учётные данные. Vault пишет audit log - каждый запрос, каждый renew, каждый revoke. На нагруженном сервисе сразу видно паттерн: если что-то запрашивает credentials аномально часто или из нетипичного места - это повод разобраться.

Где было не гладко

MySQL потребовал отдельного внимания. Синтаксис шаблонов создания пользователей там несколько отличается от PostgreSQL, и имя создаваемого пользователя Vault генерирует по своему шаблону - длинное, с UUID-подобным хвостом. В MySQL 5.7 максимальная длина имени пользователя - 32 символа, и дефолтный шаблон Vault в этот лимит не влезает. Потратили время на выяснение почему пользователи создаются с ошибкой, потом прочитали документацию внимательнее. Обошли это через creation_statements с явно укороченным именем пользователя в шаблоне.

Ещё один момент: AppRole auth работает хорошо, но Secret ID нужно где-то хранить при первом старте. Это классический bootstrap problem - чем защищаешь первый секрет? Для Kubernetes мы закрыли это через Vault Agent и kubernetes auth method: pod получает токен Kubernetes serviceaccount, идёт с ним в Vault, Vault проверяет через kube-apiserver, выдаёт Vault-токен. Для виртуальных машин без Kubernetes пришлось строить отдельный процесс первичной инициализации - это пока самое некрасивое место в схеме.

Текущее состояние

Два клиента уже работают на динамических учётных данных для PostgreSQL в продакшне. Ещё два - в процессе миграции с статичных паролей. MySQL-кейс у одного клиента запустили в стейджинге, в продакшн пойдём после того как наберём уверенности со стейджинга.

Vault как инфраструктурный компонент требует к себе серьёзного отношения - он сам становится critical path: если Vault недоступен, приложения не могут получить учётные данные и не запустятся. Это значит, что его HA-режим, backup sealed state и процедура unseal - это не опциональные настройки. У нас на каждом проекте Vault идёт минимум в трёх нодах с Consul storage backend.

Если хотите понять, как это работает под капотом для managed-инфраструктуры - это одно из направлений, которое мы сейчас активно отрабатываем на реальных проектах.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.