Привет, Хабр! Меня зовут Натиг Нагиев, я Devops-инженер в МТС Диджитал.
Проект, над которым я сейчас работаю, занимается обеспечением авторизации внешних клиентов. Это Mission Critical система, поэтому нам нужно было ускорить и оптимизировать доставку секретов в контейнеры с микросервисом, избежать дополнительных рабочих нагрузок в kubernetes, гарантировать безопасность при доставке секретов и исключить внешние зависимости.
Сейчас де-факто стандартом для таких задач стало хранилище HashiCorp Vault, поэтому мы рассматривали и пробовали разные инструменты: Vault Agent Injector, Vault CSI Provider, External Secrets Operator и OpenBao — но в итоге остановились на связке Bank-Vault и Vault Secrets Operator.
В этом посте я расскажу, чем они интересны и какие у них есть плюсы и минусы, а в продолжении — как мы реализовали итоговую систему.
Vault Agent Injector: удобный и безопасный, но потребляет слишком много ресурсов
По сути, это Kubernetes-контроллер, который перехватывает события типа Create и Update для под. Он анализирует наличие аннотации «vault.hashicorp.com/agent-inject: true» и при его наличии изменяет спецификацию пода с помощью других аннотаций. Это удобный и простой инструмент: мы добавляем лишь аннотации, а все действия по извлечению секретов из Vault и их шаринг в контейнер c микросервисом выполняются агентом
Инит-контейнер при запуске пода забирает секреты из Vault и предварительно загружает их в общий объем памяти, так называемый Volume Shared Memory перед запуском контейнера с микросервисом, это важно с точки зрения безопасности. Таким образом секреты не сохраняются в Kubernetes Secrets ни в каком виде. Кроме того, с помощью аннотаций можно как включить, так и отключить Sidecar-контейнер, который периодически проверяет актуальность наших секретов.
К сожалению, у Vault Agent Injector есть свои минусы:
Дополнительные рабочие нагрузки в виде init и sidecar containers, которые на тот момент мы не могли себе позволить, потому что наши кластеры не обладали значительными ресурсами. У нас много сервисов и гора инфраструктурных решений, связанных с мониторингом, логированием, трейсингом, ингресами, раннерами и так далее.
Не совсем тривиальный синтаксис темплейтинга в аннотациях, что также увеличивает сложность.
В целом Vault Agent Injector обеспечивает высокий уровень безопасности при доставке и обновлении секретов в микросервисах, минимизируя риски утечки данных. Однако он требует значительных ресурсов и его темплейтинг в аннотациях не сразу понятен и не прост. Поэтому мы стали рассматривать другие варианты.
External Secrets Operator: годный и хороший, но все-таки сторонний инструмент
External Secrets Operator автоматически синхронизирует секреты из внешних источников и добавляет их в Kubernetes в виде результирующих Opaque Secrets. Соответственно, у него есть провайдер с Vault, но нативно работает только KV Secret Engine. Для остальных типов Secret Engine необходимо использовать абстракцию в виде Vault Generator.
Один из ресурсов External Secrets Operator — SecretStore. По сути, это бэкенд, который определяет, какое хранилище секретов использовать и как к нему подключаться В нем указывается адрес кластера Vault и дополнительные опции, например, связанные с валидацией по TLS.
Второй - это External Secret, на нём остановимся чуть подробнее. Здесь стоит отметить две необходимые опции:
refreshInterval: показывает, как часто нужно обращаться за секретами в Vault, что влияет на скорость и оптимизацию доставки наших секретов;
secretStoreRef: это ссылка на SecretStore при обращении к Vault, благодаря которой External Secrets понимает, откуда забирать секреты.
External Secrets Operator покрывает основные кейсы работы с Vault и ее интеграции с kubernetes. Но мы его не используем, потому что это стороннее, а не вендорское решение. Плюс оператор External Secrets работает с внешним API хранилища секретов: облачного решения для хранения секретов, внешнего Vault за пределами кластера Kubernetes, или какого-либо другого решения для хранения секретов. Мы же для каждого кластера разворачиваем свой Vault, чтобы избежать единой точки отказа — в случае падения одного из кластеров.
Vault CSI Provider: хорошо решает кейсы, но есть внешние зависимости
Это обвязка поверх Hashicorp Vault. Мы взяли его в работу, потому что на первый взгляд он показался нам самым удачным:
Поставщик Vault CSI Provider легко развертывается включением одной директивы Helm чарте «csi.enabled=true»;
Можно монтировать секреты в файловую систему pod. Это и плюс, и минус, к чему я еще вернусь;
Он поддерживает преобразование секрета Vault в ключ Kubernetes Opaque Secret и, соответственно, в переменные среды pod;
Отсутствуют дополнительные рабочие нагрузки в виде init и sidecar контейнеров.
Однако у этого решения есть и минусы, которые в нашем случае оказались некритичными:
Имеется точка отказа в виде Storage, предоставляемыми внешними CSI-хранилищами;
Рутинное преобразование секретов из Vault в Opaque Secrets. От нас требуется прописать в манифесте Secret Provider Class маппинг каждого секрета из Vault в ключ Opaque Secret. Если у нас очень много секретов для микросервиса, то их нужно будет расписать в манифесте Secret Provider Class;
Для монтирования секретов в файловую систему pod используется Pod Security Policies. Он был объявлен как deprecated в Kubernetes v1.21, и окончательно удален в 1.25.
Работа с Vault CSI Provider
Мы сначала установили драйвер CSI Secret Store, используя обычный Helm чарт. Из интересных опций здесь:
syncSecret.enabled=true — включаем синхронизацию наших секретов.
enableSecretRotation=true — активируем опцию enableSecretRotation, которая запускает ротацию секретов.
rotationPollInterval=30s — настраиваемый интервал ротации, то есть период, с которым CSI-драйвер впоследствии проверяет и актуализирует наши секреты.
Затем мы задеплоили Vault CSI Provider: он ставится из того же чарта, что и сам Vault, и активируется одной опцией — csI.enabled=true. Включив ее, мы задеплоили на все наши worker ноды Vault CSI Provider в качестве Daemonset, что позволяет нам монтировать секреты в файловую систему pod.
Ключевой сущностью в связке csi-secret-store/vault-csi-provider является CustomResource «SecretProviderClass», который поставляется вместе с драйвером csi-secret-store. Основные его задачи — монтирование секретов в файловую систему pods (Volume/VolumeMounts) и преобразование секретов Vault в секреты типа Opaque в k8s.
Стоит отметить важный момент в манифесте SecretProviderClass. Параметр «objectName» — это имя объекта для маппинга секретов из Vault в ключ Opaque Secret в k8s. Он указывается как параметр для сбора секретов из Vault, так и в secretObjects для преобразования полученных секретов из Vault в секреты в k8s.
Чтобы наши секреты попали в pod из Vault, SecretProviderClass монтируется через volumeMounts. Тут стоит обратить внимание на директиву volumeAttributes.secretProviderClass — с ее помощью мы передаем имя описанного выше secretProviderClass для того, чтобы csi-driver смог корректно смонтировать volume c секретами в pod.
Также отмечу время аренды TTL. Если есть много микросервисов и нет возможности выделить кластеру Vault дополнительный объем хранилища, то возникает проблема с переполнением кластера незавершенными арендами, то есть c leases. По умолчанию аренда выдается на 60 минут, но вопрос решается изменением одной опции командой «vault auth tune -default-lease-tt|=30s kubernetes/» (так как метод авторизации у нас kubernetes). Тем самым мы закрываем проблему с незавершенными арендами.
Что мы получили в итоге
Спустя год работы с Vault CSI Provider мы поняли, что он не решает задач безопасности в kubernetes и у него есть проблемы:
Автоматизация рутинных ручных действий после развертывания кластера Vault средствами IaC, таких, как включение метода авторизации, подключение Secret Engine и изменение тех же параметров TTL, о которых уже упоминалось. Рассмотрим это подробнее далее.
Периодические отвалы CSI-хранилищ, что связано с присутствием внешних зависимостей. При этом мы не собирались изобретать велосипеды, писать скрипты или использовать какие-то внешние решения для авторизации, либо ставить отдельный инстанс Vault для распечатывания. Выйти на дополнительные внешние решения нам также не хотелось, так как у нас уже был опыт работы с Consul, и это создало еще одну внешнюю зависимость в виде стороннего бэкенда.
Пришлось искать другие варианты.
Мы натолкнулись на OpenBao, появившийся как ответ на изменение лицензии HashiCorp Vault с MPL v2.0 на BSS v1.1. Но на тот момент он показался нам сырым для интеграций с kubernetes, хотя, по сути, это форк версии HashiCorp Vault 1.14.8.
Итоговое решение: Bank-Vaults и Vault Secrets Operator
Вернувшись к решениям от HashiCorp Vault, мы взяли в работу такой инструмент, как Vault Secrets Operator. С помощью него мы смогли закрыть проблемы, связанные с использованием Vault CSI Provider:
Перестали описывать каждый секрет в values файлах микросервисов.
Ушли от монтирования секретов в файловую систему в качестве volume/volumeMounts, так как все наши сервисы идентичны по функциональности и могут забирать секреты из переменных окружения;
Отказались от использования CSI-хранилищ тем самым ушли от внешних зависимостей
Кроме этого, мы параллельно уходили от ванильного Vault и пришли к Bank-Vaults. Это решение развертывает кластер, в котором уже есть инструменты для автоматизации рутинных действий.
С его помощью мы средствами laC реализовали:
Распечатывание кластера Vault. Мы автоматизируем процесс создания и доставки Opaque Secret с Unseal токенами.
Изменение опций Vault: начальные параметры, которые необходимо задать в дефолтной конфигурации сразу после развёртывания кластера Vault, теперь доставляем вместе с процессом развёртывания кластера Vault.
Связка Vault Secrets Operator и Bank-Vaults хорошо показала себя в производственной среде. И сейчас мы остановились на ней.
В следующем посте я расскажу об использовании данного решения, о его плюсах и минусах, а также о задачах, которые остались открытыми. На этом у меня все, готов ответить на ваши вопросы.
Комментарии (15)
trublast
11.12.2024 14:19Как я это прочитал:
Разработчики кубернетес придумали хранить конфигурации в объектах ConfigMaps, а секретные/не публичные значения в объектах Secrets, для доступа к которым можно настроить отдельные rbaс
Это выглядит не безопасным, ведь секреты могут утекать, если неправильно настроены rbac, или у вас выполняются, а потом утекают, дампы etcd
Поэтому нужно использовать Vault, там можно гранулярнее задавать доступы к секретам и вести аудит доступа
А чтобы было удобно, используем оператор Vault Secrets Operator, который поместит секретные значения в кубернетес объекты Secrets
После чего в аудите Vault не будет никакой информации о доступе приложений к секретам (потому что они получают доступ к секретам через Secrets Kubernetes), а сами секреты продолжат утекать аналогично п. 2
Итого: мы не хотели хранить секреты в secrets kubernetes, и вместо этого использовали 2 других инструмента, чтобы ... в итоге хранить секреты в secrets kubernetes
Все время вспоминаю мультфильм Золотые ворота
Nat0892 Автор
11.12.2024 14:19Здравствуйте.
Да, с вашими тезисами соглашусь отчасти. Но конечная цель была в удобстве, отсутствии лишних рабочих нагрузок (в отличие от того же Vault Agent Injector, где у нас есть sidecar и init контейнеры) и в быстрой доставке:
"поэтому нам нужно было ускорить и оптимизировать доставку секретов в контейнеры с микросервисом, избежать дополнительных рабочих нагрузок в kubernetes, гарантировать безопасность при доставке секретов и исключить внешние зависимости" - вот главные причины, по которым мы пришли к инструментам, о которым говориться в самом конце. И тут сделан акцент на "гарантировать безопасность при доставке секретов" - доставка секретов безопасная, а вот ее итоговое хранение в Opaque Secrets почти в открытом виде (так как все мы знаем, на сколько безопасен base64 энкодинг =) ) - действительно нет.
Что же касается безопасности хранения секретов:
1) Vault здесь используется как единый источник хранения секретов, таким образом нам не нужны секреты хранить в репозиториях (пусть даже с шифрованием в виде инструментов SOPS, helm secrets, etc...) - ведь если мы говорим о подходе IaC, что для нас немаловажно, то хранить даже секреты где-то нужно и чтобы это "где-то" - было единственным источником правды.
2) Что касается secrets и rbac - тут вы правы про гранулярность и аудит доступов к Opaque Secrets. Поэтому вторая часть статьи будет именно про то, какие решения есть для шифрования этих Opaque Secrets уже непосредственно в etcd или обхода Opaque Secrets в процессе доставки секретов в микросервисы3) Ну и третье (из разряда "паранойи" =)) - если у злоумышленника есть доступ к Kubernetes кластеру, где он может даже с учетом настроенных rbac просмотреть Opaque Secrets, то он уже может "веселиться" с кластером, как он хочет. И говорить тут о чем-то более важном не имеет уже особого смысла.
Безусловно с точки зрения ИБ стоит вопрос о том, что есть также проблема, что в конечном счете секреты хранятся в переменных окружения, а не где-нибудь в RAM, shared memory и т.д. Собственно во второй части статьи будут предложены решения в том числе и по данной проблеме.
dersoverflow
11.12.2024 14:19Разработчики кубернетес придумали хранить конфигурации в объектах ConfigMaps, а секретные/не публичные значения в объектах Secrets
я вам даже больше скажу, разработчики кубернетес такие забавники:
Kubernetes 1.7 introduced encryption of Secrets but doesn’t enable it by default. Even when this becomes default, the data encryption key (DEK) is stored on the same node as the Secret! This means gaining access to a node lets you to bypass encryption. This is especially worrying on nodes that host the cluster store (etcd nodes).
Nat0892 Автор
11.12.2024 14:19Да, увы. Складывается ощущение, что безопасность из коробки до сегодняшних версий Kubernetes (вплоть до 1.30/1.31) совсем НЕ является приоритетом ни в каком виде.
dersoverflow
Наконец-то отличная тема! Вы уверены, что правильно понимаете зачем нужен Vault?
Допустим, мы используем Базу Данных.
Для соединения с БД нам нужен пароль. Т.к. без пароля злоумышленник получит все наши данные.
Мы не кладем пароль в конфиги, т.к. злоумышленник прочитает конфиги и получит пароль БД...
Мы храним пароль в Vault.
Отлично! Теперь вопрос: у вас есть пароль для самого Vault?
Если нет, то злоумышленник прочитает Vault и получит пароль БД...
Если да, то вам нужен Vault2 чтобы хранить пароль Vault...
Итак: вы уверены, что правильно понимаете зачем нужен Vault?
Nat0892 Автор
Здравствуйте!
Отвечая на ваши вопросы:
Что значит пароль для самого Vault? И каким образом злоумышленник прочитает Vault, если:
а) root token глубоко спрятан и доступен только лицам, которые отвечают за Vault кластер
б) Те, кто имеют доступ к Vault авторизуются в нем исключительно по LDAP (если мы говорим про UI)
в) Для того, чтобы микросервисы могли авторизоваться и забирать нужные секреты Vault настроена kubernetes авторизация на основе SA токенов по определенным политикам исключительно на чтение нужных секретов сервиса. (Собственно во второй части об этом будет информация)
г) Root-токен вообще не используется для авторизации пользователями
"то вам нужен Vault2 чтобы хранить пароль Vault" - мне очень интересно, как вы пришли к такому выводу о паролях Vault? Если изначальная из коробки аутентификация и авторизация происходит по root токену, если не настроено иное. Использование которого, как уже оговаривалось выше, является совсем не безопасным, так как есть риск утечки.
Что же касается хранения тогда уж НЕ пароля а Root-токена, то об этом как раз будет вторая часть статьи)
На основе двух вышеописанных пунктов, смею задать вам встречный вопрос : а вы уверены, что правильно понимаете зачем нужен Vault и как его использовать? =)
dersoverflow
это называется Security through obscurity. а мы смотрим по сути! и видим два утверждения:
с БД небезопасно соединяться без пароля.
но с Vault безопасно соединяться без пароля.
гмм... тогда:
если в Vault реализован "волшебный способ" соединения, то тот же самый способ мы можем СРАЗУ же использовать и для БД => Vault не нужен!
а если сказок не существует, то Vault мы используем небезопасно => Vault нам не нужен тем более!!
как-то так :)
Nat0892 Автор
У меня встречный вопрос к вам: вы я так понимаю кроме метода авторизации по паролю, других не пробовали?
1) по JWT
2) по SSL
3) по access токенам
Ну и тогда лее (то что сразу в голову пришло)
Пароль, точно также, как и токен, точно также, как сертификаты для авторизации, может быть скомпрометирован. Мы с вами говорим о совершенно разных вещах, вы скажем так, прицепились к тому, что в Vault авторизация без парольная, это действительно из коробки так. Но там авторизация идёт по токену, который точно также может быть утерян, скомпрометирован. Но в отличие от пароля никаким брутфорсом или иным методом НЕ подобран.
Как итог: мнение в том числе и разработчиков Vault( раз уж они из коробки авторизацию по root токену вшили) что аутентификация и авторизация по паролю менее безопасная, чем по токену, который, вновь повторюсь, в отличие от пароля НЕ может быть подобран простым перебором, брутфорсом и т.д.
P.S: почитайте про методы авторизации в Vault, про JWT токены, про access и refresh токены. А то у меня создалось ощущение, что вы даже не стали вчитываться в мой ответ =)
dersoverflow
вообще-то я Lead Solutions Architect и задаю этот вопрос на собеседованиях :) и как правило, 80% "архитектров" в ответ сыпят buzzword-ами не понимая СУТИ.
так давайте мы здесь сейчас разберемся и тем самым им всем поможем. итак, еще раз.
дано:
нам не дают соединиться с БД без пароля, т.к. это небезопасно.
но! нам дают без пароля соединиться с Vault и взять там пароль БД.
вопрос: нет ли здесь противоречия? :)
Nat0892 Автор
Я не могу ответить на поставленный вами вопрос, так как природа возникновения вашего утверждения "но! нам дают без пароля соединиться с Vault и взять там пароль БД" - мне не ясна.
И я в очередной раз буду утверждать, что беспарольная авторизация != отсутствие авторизации. Так как в Vault есть авторизация (из коробки по токену) из типов и методов в том числе, можно создать локальную УЗ с паролем.
Позтому, если это возможно, изложите свой тезис немного иначе. Так как со вторым пунктом я в корне не согласен, так как это не корелируется с Vault по ранее указанным мною пунктам :)
Nat0892 Автор
"нам дают без пароля соединиться с Vault и взять там пароль БД" - вам не дают соединиться с Vault без какого-либо типа авторизации, она все равно есть. Будь то UI для обычного пользователя. Будь то токены, пароли, LDAP и т.д для админов, либо сервисов =)
dersoverflow
именно это и хотелось бы уточнить. давайте рассмотрим сферический Vault в вакууме:
мы ставим на Linux сервер нативное Приложение.
у Приложения есть конфиг и ему нужно работать с Базой.
а дальше уже интересно:
все дети знают, что пароль к Базе нельзя хранить в конфиге!
все дети знают, что пароль к Базе нужно хранить в Vault.
вы, как представитель частного капитала, не можете остаться глухи к стонам родины! одни лишь маленькие дети, беспризорные, находятся без призора. и мы, господа присяжные заседатели, им поможем :)
объясните pls детям как В ЭТОМ СЛУЧАЕ безопасно добыть пароль из Vault.
а Kubernetes мы разберем потом, т.к. детям не нужно buzzwords и obscurity, окай? :)
Nat0892 Автор
Окей :)
6. Раз все дети знают, что пароль к Базе нужно хранить в Vault, то делаем вывод, что все дети знают что такое Vault, хотя бы просто знают =), таким образом:
все дети, зная что такое Vault, могут пойти и узнать или мы им можем поведать, что у чудного, неведомого Vault, есть инструмент (Database Secret Engine - не вдаваясь в тех. подробности сейчас).
7. все дети ТЕПЕРЬ знают, что есть инструмент для безопасной добычи пароля из Vault (абстрагируясь от абстракций Kubernetes, VM и т.д. будь то железка)
8. все дети, ТЕПЕРЬ знают, что в принципе есть различные инструменты безопасного извлечения секретов из Vault.
P.S: дети же когда-то, от кого-то узнали, что такое База и почему нельзя хранить пароль в конфиге, дети же когда-то узнали, от кого-то узнали, что пароль к Базе нужно хранить в Vault - соответственно именно дети задались вопросом: "А как соединить эти две сущности, чтобы безопасно получить пароль?" и пришли с ним ко взрослым =)
Nat0892 Автор
"но с Vault безопасно соединяться без пароля" - данным своим утверждением, вы полностью подтверждаете, что даже не вчитывались в весь мой ответ вам, в котором я не раз подчеркнул про root-токен и другие типы авторизаций в Vault
Nat0892 Автор
Кстати, что касается вашего конкретного примера с БД, отвечу и на это, для авторизации микросервисами в БД есть специальный тип для этого "Database Secret Engine" https://developer.hashicorp.com/vault/docs/secrets/databases