
Привет, Хабр! Меня зовут Натиг Нагиев, я Devops-инженер в МТС Диджитал.
На нашем проекте мы обеспечиваем авторизацию внешних клиентов в продуктах МТС. Это Mission Critical система, где мы оптимизировали и гарантировали доставку секретов в контейнеры с микросервисом, избавлялись от дополнительных рабочих нагрузок и исключали внешние зависимости. В прошлом материале я сравнил разные инструменты, которые мы перебрали, а в этом расскажу про наше итоговое решение — связку Bank-Vaults и Vault Secrets Operator.
Что мы используем
Bank-Vaults
В самой документации этот инструмент назван «швейцарским армейским ножом» для Vault. Он позволяет автоматизировать рутинные задачи: распечатывать кластера, автоматически обновлять токены, задавать такие первоначальные настройки, как методы аутентификации, политики, аренды и так далее.
С помощью этого инструмента мы:
Решаем проблему с автоматизацией распечатывания кластера Vault (средствами laC). Он устраняет проблему ручного создания Opaque Secret в Kubernetes, который хранит в себе Unseal токены для быстрого и автоматического распечатывания кластера
Используем протокол Raft в качестве бэкенд хранилища. Bank-Vault позволяет быстро восстанавливать кластера в случае падения Raft. Конфигурация с Raft описана манифестами в helm чарте и раскатывается дополнительно с помощью ArgoCD. Это позволяет быстро передеплоить Vault-кластер с Raft без использования внешних зависимостей в качестве storage backend, как у нас это было в связке с Consul.
Автоматизируем изменения опций Vault (средствами laC). Доставляем вместе с процессом развертывания кластера Vault начальные параметры, которые сразу необходимо задать в дефолтной конфигурации
Vault Secrets Operator
Как понятно из названия, это kubernetes-оператор, который позволяет синхронизировать в Kubernetes Opaque Secrets секреты из Vault. C ним не нужно подключать внешние зависимости, как это было с Vault CSI Provider, где используются CSI хранилища.
С его помощью мы:
Больше не описываем каждый секрет в values файлах микросервисов. А вот в CSI Provider маппинг каждого секрета нужно описывать вручную.
Ушли от монтирования секретов в файловую систему в качестве volume/volumeMounts, так как все наши сервисы идентичны по функциональности и могут забирать секреты из переменных окружения.
Отказались от использования CSI. Vault Secrets Operator решил нам проблему, связанную с внешними зависимости в качестве CSI хранилищ.
Настройка Bank-Vaults
С помощью Bank-Vaults мы автоматизировали доставку токенов в Opaque Secrets средствами IaC. Если с кластером Vault что-то произойдет, то быстрее и проще будет переразвернуть кластер, так как уже есть Opaque Secret с Unseal токенами и сам Vault кластер деплоится чартом Bank-Vaults с помощью ArgoCD.

Видно, что в нем хранятся все unseal-токены для распечатывания кластера, а также root-токен, который изначально нам доступен при первом развертывании Vault.
Bank-Vaults позволяет включить или изменить некоторые дефолтные конфигурации сразу при развертывании Vault-кластера. В разделе External Config указываем метод авторизации, в нашем случае Kubernetes Auth, настраиваем KV Secret Engine версии 2, а также тут есть важный момент: если ранее мы вручную настраивали команду vault auth tune для изменения времени аренды секретов, то теперь директива auth.type.roles.ttl настраивает эту опцию на 30 секунд сразу при развертывании.

Хочу подробнее остановиться на конфигурации Unseal. В ней указывается общее количество Unseal-токенов, secretShares — в нашем случае их 5 и количество токенов для распечатывания кластера, secretThreshold — у нас их 3. Важным элементом является зарезервированный kubernetes.secretNamespace, который формирует как раз Opaque Secret vault-unseal-keys, хранящий в себе root- и unseal-токены при условии хранения этих токенов в Kubernetes.

Команда сайдкарного контейнера, отвечающего за начальное распечатывание кластера Vault, запускается с одной из опций --k8s-secret-name. Этой опции в качестве аргумента передается vault-unseal-keys - имя Opaque Secret. Соответственно, эта команда парсит секрет на наличие unseal-токенов для распечатывания кластера.
Выглядит команда следующим образом:

Итак, с помощью новой конфигурации мы настроили автоматизацию средствами IaC и смогли минимизировать ручные операции сразу после развертывания кластера. Тем не менее оставалась нерешенной проблема периодической недоступности CSI-хранилищ, и мы стали искать новые варианты оптимизации и ускорения процесса доставки секретов в контейнеры с микросервисами.
Vault Secrets Operator
Этот инструмент мы взяли в работу сразу после выхода его стабильной версии v0.1.0 от 12 июня 2023 года. У него есть свой helm chart, с помощью которого он устанавливается поверх уже развернутого средствами Bank-Vaults кластера Vault:
helm upgrade --install vault-secrets-operator ./vault-secrets-operator -f values.yaml --create-namespace -n vault-secrets-operator
После установки создаются новые CRD (Custom Resource Definitions), среди них для нашего кейса используются VaultConnection, VaultAuth, VaultStaticSecret. Разберем их подробно:
VaultConnection отвечает за подключение. В нем указывается адрес кластера Vault. В данном случае это внутренний адрес, так как кластер разворачивается в Kubernetes. Также в нем можно дополнительно указать разные опции, например skipTLSVerify для пропуска валидации TLS:

VaultAuth описывает методы авторизации и их опции. Здесь в директиве VaultConnectionRef указываем название используемого подключения:

VaultStaticSecret отвечает за то, какие именно секреты мы забираем из Vault. В части spec.destination указывается, нужно ли создавать соответствующий результирующий Opaque Secret и, если да, то под каким именем. Также указываем точку монтирования секрета, его тип и по какому пути забирать секрет из Vault. И прописываем ссылку на VaultAuth, чтобы VaultStaticSecret смог забрать нужные секреты, использовав правильный метод авторизации:

Далее в values-файлах единого helm chart для всех микросервисов мы указываем, создавать ли эти три CRD-ресурса и, если да, то с какими опциями. Данные 10 строк вместе с темплейтами генерируют VaultConnection, VaultAuth, VaultStaticSecret для каждого микросервиса:

Если кратко и схематично, то выглядит это следующим образом:

С приведенными манифестами и конфигурациями можно ознакомиться в репозитории на Github.
Чем же хорош и плох Vault Secrets Operator
Плюсы этого инструмента:
Автоматизация доставки секретов: оператор автоматически извлекает секреты из Vault и преобразует их в Kubernetes Secrets, что упрощает процесс доставки и обновления секретов.
Интеграция с Kubernetes: применение стандартных примитивов Kubernetes, то есть Opaque Secrets, облегчает работу с секретами и снижает порог входа для использования Vault.
Автоматическое обновление секретов: он самостоятельно отслеживает изменения в Vault и обновляет Kubernetes Secrets, актуализируя секреты без ручного вмешательства разработчиков.
Масштабируемость: Vault Secrets Operator может управлять секретами для множества приложений и пространств имен, что делает его удобным для масштабируемых кластеров.
Из недостатков мы выделили для себя пока один, но он достаточно серьезный. Несмотря на то, что со стороны Vault обеспечивается высокая степень безопасности хранения секретов, чувствительные данные в итоге сохраняются в Kubernetes почти в открытом виде в качестве Opaque Secrets (base64 encoding).
Решаем проблему открытого хранения секретов в Kubernetes
Мы сейчас рассматриваем решения для шифрования секретов или доставки их в микросервисы, минуя Opaque Secrets, но пока они не внедрены из-за недостаточной зрелости или неудобства в наших кейсах.
Vault Response Wrapping Tokens
Он позволяет доставлять чувствительные данные, минуя такие абстракции Kubernetes, как Secrets, ConfigMaps:

Из его минусов можно отметить:
необходимость поддерживать функциональность в приложениях;
сложность масштабирования при большом количестве реплик;
необходимость автоматизации доставки wrapping token в микросервис.
Vault KMS Plugin

Это сравнительно новый инструмент, у которого есть несколько важных плюсов:
Не требуются внешние интеграции, так как используется нативный Transit Secret Engine от Vault.
Объекты вроде Secrets и ConfigMap шифруются прямо в etcd.
Так как он развертывается как static pod на control plane узлах или на тех узлах, где запущен kube-apiserver, то во взаимодействии между vault-kms-plugin и kube-apiserver отсутствуют лишние абстракции. Плагин по сути создает обычный Unix сокет и получает запросы шифрования через этот сокет от kube-apiserver.
Недостатки:
Настройка требует вмешательства в control plane кластера Kubernetes, а также не стоит забывать про настройки со стороны Vault c использованием Transit Key.
Если потерять или пересоздать KMS-ключ, Vault KMS Plugin не сможет дальше расшифровать и получить секреты.
Bank-Vaults Mutating Admission Webhook

Сейчас мы считаем этот инструмент самым подходящим для решения проблемы безопасного хранения секретов в Kubernetes. Он обходит механизм Kubernetes Secrets, внедряя секреты, полученные из Vault, прямо в pod. Mutating Webhook внедряет исполняемый файл vault-env в поды в качестве Init Container и может запрашивать секреты из Vault через аннотации и специальные переменные среды, считывая значения этих переменных непосредственно из Vault.
Работа инструмента с аннотациями напоминает Vault Agent Injector. Но Mutating Webhook удобнее и проще, так как отсутствует темплейтинг и своеобразный синтаксис, а также режим работы sidecar-less. По другим пунктам для сравнения можно пройтись в официальной документации.

Для наших задач он подходит по нескольким причинам:
Мы уже используем Bank-Vaults, и Mutating Webhook является частью их стека.
Это простое решение, которое применяет только аннотации и переменные окружения.
Данные не сохраняются на диске или в etcd — все секреты хранятся in-memory и доступны только процессу, который их запросил.
Отсутствует постоянное соединение с Vault, что влияет на нагрузку в Kubernetes, то есть sidecar-less режим.
Токен Vault, используемый для чтения секретов, удаляется из памяти перед запуском контейнера с микросервисом, минимизируя риск утечки.
Если вам интересен Mutating Webhook, то вот полезные ссылки про него:
С манифестами и командами для внедрения Bank-Vaults Mutating Webhook можно также ознакомиться на Github.
Нет предела совершенству

На текущем этапе связка Vault Secrets Operator и Bank-Vaults решает большинство наших проблем. Остается одна — хранение секретов почти в прозрачном виде, так как Opaque Secrets предусматривает из всей своей безопасности только энкодинг в base64, а все мы с вами знаем насколько "безопасна" эта безопасность =).
Мы решаем эту проблему защитой на уровне кластеров: они находятся в закрытом контуре и защищены на нескольких уровнях. При этом на рынке уже есть решения для шифрования или передачи секретов в обход Kubernetes Opaque Secrets: Vault Response Wrapping Tokens, Vault KMS Plugin, Bank-Vaults Mutating Admission Webhook. Возможно, для ваших задач они окажутся подходящим решением. Что же, на этом все. Желаю вам успехов и безопасных деплоев!