Привет! Меня зовут Лев, я руководитель интеграционной разработки в финансовом маркетплейсе Банки.ру. Больше года назад мы начали переход на микросервисную архитектуру. Секретов становилось всё больше: пароли, токены, сертификаты, ключи. А управлять было ими всё сложнее. Количество команд давно перевалило за несколько десятков, а интеграций с партнерами, которые также защищены секретами, — за сотню.

Перед нами появилась задача выстроить удобную и безопасную структуру хранения и управления секретами. В этой статье поделюсь нашим опытом и расскажу:

  • В чем заключалась проблема в управлении секретами, как мы её решили и как могли бы улучшить этот процесс.

  • Какие способы хранения секретов существуют и как их можно интегрировать.

  • Как можно реализовать хранение через Vault.

  • Что такое политики доступа и как они применяются к структурам секретов.

  • Варианты реализации, которые наилучшим образом подходят для нашей конкретной ситуации.

Как выглядел процесс управления секретами

Совсем недавно мы хранили секреты в зашифрованном виде в Git. Процесс смены выглядел следующим образом:

  1. Чтобы добавить новые секреты, менеджер (ПМ) или поддержка передавали информацию тимлиду, который дальше передавал данные девопсу.

  2. Девопс вносил изменения в зашифрованный файл секретов в Git.

  3. После этого запускался процесс сборки нового билда или образа, который в некоторых случаях мог потребовать тестирования.

  4. После тестирования девопс деплоил изменения на продакшн.

  5. Команда по сопровождению продакшна включала сервис в мониторинг и следила за его работой, готовая в любой момент откатить изменения.

Прошлый процесс смены секретов/сертов

Уверен, что многим знаком этот процесс на личном опыте. Ключевая проблема здесь в том, что приходится задействовать весь конвейер разработки. Это занимает много времени и требует координации между различными командами.

Целевой процесс смены секретов/сертов

В один прекрасный момент (после очередного инцидента на проде из-за не вовремя обновленного секрета) пришло окончательное осознание того, что так дальше жить нельзя! Хотелось проще и надежнее: чтобы секреты менялись нажатием одной кнопки.
У нас родилась концепция будущего решения:

  • Обработка секретов должна происходить через удобный интерфейс.

  • При поступлении нового секрета ребята из сопровождения вносят изменения и сами сохраняют их той самой одной кнопкой.

  • Приложение обновляется автоматически, не требуя ручного деплоя или перезапуска.

Выглядит, как отличная цель, к которой стоит стремиться.

Где хранить общие секреты

Рассмотрим варианты хранения и управления секретами, которые чаще всего применяются на практике.

  1. Самый простой способ — использовать общее хранилище, такое как страница Wiki или таблица Excel. Однако здесь нет ни полноценного контроля доступа, ни аудита действий пользователей, что делает подход непригодным для серьезных задач с большим количеством команд.

  2. Вариант посложнее — менеджер паролей по типу KeyPass. Они защищают пароли шифрованием и позволяют ограничивать доступ, но все равно для командной работы плохо подходят, так как соответствующих инструментов там нет.

  3. Продвинутый уровень — хранить секреты в зашифрованном виде в Git. Такой подход обеспечивает безопасность, однако процесс управления секретами достаточно медленный: нужна сборка, тестирование, перезапуск приложений. Как было в нашем случае.

  4. Самый правильный и зрелый подход — использовать специализированные системы управления секретами, такие как Vault. Эти системы интегрируются с Kubernetes, их можно встроить в пайплайн развертывания, благодаря чему управлять доступом к секретам в командах, где применяется полноценный CI/CD, можно централизованно, безопасно и эффективно.

Что дает Vault и что для нас ценно

Сначала чуть подробнее про сам Vault.

Vault — это система безопасного хранения секретов. Все пароли в нем зашифрованы, и даже если нехороший человек получит физический доступ к диску с хранилищем, сделать с ним ничего не сможет.

  • Структура секретов представлена в виде ключ-значение и поддерживает формат JSON.

  • API Vault обеспечивает доступ к секретам, а проверка прав доступа выполняется через внешние сервисы аутентификации, в роли которого может выступать Kubernetes. Механизм ServiceAccount в Kubernetes генерирует короткоживущие токены для безопасного доступа к секретам. Так решается проблема пароля от всех паролей для технических учеток.

  • У Vault есть механизм аудита, который записывает все операции для последующего анализа службой безопасности.

  • Интеграции с Kubernetes облегчают внедрение Vault в существующую инфраструктуру.

  • Vault поддерживает отраслевые протоколы OAuth2.0, OpenID для доступа к секретам.

  • В Vault встроен механизм передачи секретов третьим сторонам с использованием одноразовых токенов. Этот механизм обеспечивает контроль над доступом и обнаруживает компрометации.

  • Важным преимуществом является версионирование секретов: в случае косяков изменения можно откатить.

Распечатывание Vault

Напомню, что в Vault все секреты зашифрованы — чтобы с ними работать, их нужно расшифровать. Происходит это при старте Vault и называется распечатыванием хранилища.

Процесс распечатывания Vault — задача непростая. По умолчанию генерируются пять секретных ключей, которые затем распределяются среди инженеров. Для распечатывания необходимо ввести одновременно несколько ключей, например, три из пяти. Если распечатывание падает, то Vault не поднимется. Очень напоминает клюквенные боевики с пусками ядерных ракет и армагеддоном :-).

Такой алгоритм гарантирует безопасность хранилища, но вместе с тем создает сложности для администраторов. Особенно при перезапуске Vault, когда требуется повторное распечатывание хранилища. Обращайте на это внимание, когда строите высокодоступную систему.

Хранилища Vault могут быть настроены на использование внешних систем хранения данных, таких как Postgres или Consul. Недавно они перешли к использованию внутренних хранилищ.

Кластер Vault работает по протоколу распределенных экземпляров, что обеспечивает надежность и устойчивость. Рекомендуется использовать нечетное количество экземпляров Vault в кластере, например, не менее трёх. Идеально распределить их по разным ЦОДам для повышения отказоустойчивости.

Текущая реализация Vault через Ansible

Чтобы внедрить Vault на десятки команд, нужны значительные административные усилия для синхронизации такого перехода и существенные изменения базовых пайплайнов.
Поэтому мы пошли по пути промежуточного решения. У нас есть кластер Vault, кластер Kubernetes, Ansible для создания манифестов, и поды с приложениями. Также есть Git-репозиторий, где хранятся шаблоны, переменные для Ansible и исходный код. Рядом развернут AD для управления корпоративными учетными записями пользователей Vault.

Мы определили три базовые роли:

  • администраторы с полным доступом к секретам;

  • специалисты по информационной безопасности (ИБ), которые контролируют аудит;

  • пользователи, разделенные по группам или продуктам.

Простое решение заключается в том, что Ansible, генерируя манифесты для развертывания наших приложений в Kubernetes, обращается к Vault, чтобы получить секреты и подставить их в манифесты. Это не самый безопасный подход, т.к. есть мастер пароль для доступа Ansible в Vault.
Кроме того, секреты сохраняются в манифестах и, как следствие, в конфигурации Kubernetes в etcd. В целевом решении мы уберем этот недостаток.

С использованием Vault у нас появляются средства управления секретами. С их помощью группы пользователей меняют секреты и сообщают об этом девопсам для развертывания приложений. Так команда сопровождения может менять пароли и управлять ими, а ИБ получает больше контроля над процессом.

Целевая реализация Vault через инъекции vault-k8s

Наша целевая архитектура выглядит более сложной и включает в себя интеграцию Vault с Kubernetes и автоматическое обновление секретов без участия девопсов.

В этой схеме у нас есть кластер, включающий Vault, Git, Ansible и Kubernetes (k8s), изображенный слева, а также наше приложение, которое запускается в Kubernetes.
При запуске пода запускается Init container. Этот контейнер обращается к Vault для получения секретов, которые затем передаются в контейнер с нашим приложением. Vault взаимодействует с Kubernetes как с сервисом аутентификации.

Init container запускается от имени учетной записи внутри кластера Kubernetes через механизм SeviceAccounts. Vault доверяет Kubernetes генерацию и проверку токенов для Init container. В итоге в Kubernetes не хранится пароль от всех паролей, а есть только короткоживущие сервисные токены.

Далее Init container извлекает секреты и передает их в контейнеры «на лету» при запуске нашего приложения. Как следствие, в манифестах нет секретов приложения, как нет их в etcd конфигурации самого Kubernetes.

В схеме есть сложность: если мы изменяем секреты в Vault и хотим, чтобы они появились в приложении, его нужно перезапустить. Только тогда сработает процесс с Init container. Для решения этой проблемы используем инструмент sidecar. Это дополнительный контейнер, который работает в поде нашего приложения и отслеживает изменение секретов. Когда секрет меняется, sidecar обновляет его в приложении или перезапускает приложение.

Чтобы приложение считывало секреты, которые ему передает Init container или sidecar, по умолчанию используется следующая схема:

  • для приложения контейнер разделяет диск в оперативной памяти, файл с секретами записывается туда как подмонтированный диск.

  • приложение читает эти секреты как из обычного конфигурационного файла.

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

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

Основной недостаток централизованного хранилища секретов заключается в том, что если не уделять ему должного внимания, Vault становится точкой отказа. Нужно внимательно следить за журналами логов хранилища и при необходимости производить ротацию ключей. Если журнал переполняется, то Vault переходит в режим отказа обслуживания. В результате перестают запускаться все приложения с секретами.

Методы перезапуска подов

Далеко не всегда есть ресурсы для запуска дополнительного sidecar, особенно если у вас несколько сотен сервисов. В этом случае можно рассмотреть перезапуск подов силами команды сопровождения без изменения манифестов Kubernetes. Простейший метод — использование команды kubectl rollout restart. Далее на рисунке приведены другие способы перезапуска приложений в Kubernetes, включая автоматические. Важно, чтобы сопровождение знало об этом процессе и было готово реагировать на возможные проблемы.

Особенности Vault

Особенности Vault несут в себе риски, которые могут привести к его неработоспособности инструмента.

  1. Распечатывание хранилища, дешифровка и считывание при запуске — критически важные операции. Их нужно контролировать, чтобы гарантировать корректную работу Vault.

  2. Необходимо следить за журналом аудита и не допускать его переполнения. Настройка политик по использованию аудита и его очистке также важна для обеспечения стабильной работы.

Как происходит привязка доступов к секретам

Секреты в Vault организованы в иерархическую структуру, напоминающую дерево. Это значит, что они разбиты на категории и подкатегории для более удобного управления.

Для каждой роли (пользователь, администратор, ИБ) определяются соответствующие политики. Политика в Vault подобна списку контроля доступа, который определяет, какие действия можно выполнять с определенными секретами.

Например, политика может разрешать чтение, запись или удаление секретов в определенном пути в зависимости от роли пользователя или группы.

path “secret/*” {
capabilities = [“create”, “read”, “update”, “patch”, “delete”, “list”]
}

В политиках Vault можно использовать регулярные выражения. Более того, можно даже подставлять атрибуты идентификации пользователя, который обращается к Vault, прямо в эти политики.

Также можно выполнять поиск по иерархии ключей и применять к ним права доступа на основе установленной маски. Подробная информация о том, какие права можно настроить для доступа к секретам, доступна по этой ссылке.

В политиках мы указываем путь к секретам, набор конкретных секретов и определяем, какие права доступа хотим предоставить на этот набор. Затем эта политика привязывается к пользователям, группам AD и другим идентификационным сущностям.

Структура секретов

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

Самый простой, но и самый плохой вариант — плоский список по микросервисам. За простотой будет скрываться сложность сопровождения, т.к. администратору придется прописывать политику для каждого сервиса, а этих политик будет в несколько раз больше, чем сервисов. В этом случае надо учесть роли пользователей, среды разработки и т.д.

Далее напрашивается вариант группировки сервисов по продуктам. Это уже гораздо лучше, ведь мы можем с помощью регулярных выражений настроить пару политик и тем самым изолировать команды друг от друга. Решение и правда хорошее, но только если у вас всего одна среда развертывания :-). В жизни такого не бывает. Очередной антипаттерн — добавить среды на уровень ниже сервисов. Если у вас есть разделение доступа к различным средам, то опять придется делать кучу политик для разных ролей пользователей. Ведь сопровождение ходит, как правило, только в прод, а тестовые среды им не нужны. В то же время разработчикам нужен стенд разработки, а прод обычно от них закрыт.

Мы учили эти моменты и разработали оптимальную структуру:

  • Иерархия начинается с указания продукта, за которым следует указание среды с микросервисом — продакшн, тестовая и т.д.

  • Помимо этого, мы используем дополнительное разделение на подсреды. Например, интеграционные, функциональные или разбитые по командам. Каждая из них может иметь свои политики доступа и свою модель угроз.

  • Кроме разделения на подсреды, мы применяем подход выделения общих секретов в папку «Общие» (commons). Это позволяет избежать дублирования секретов в разных сервисах.

  • В самом конце иерархии мы указываем папку в репозитории исходного кода конкретного микросервиса.

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

  • Продукт: MPK

    • Среда: Production

      • Подсреда: Main

        • Commons. Общие секреты, используемые всеми микросервисами MPK (RabbitMQ, Kafka и т.д.)

        • akbarsbank-adapter (конкретный микросервис MPK из репозитория Git)

Такая структура позволяет нам эффективно организовать доступ к секретам и управлять ими в зависимости от среды и роли пользователей.

Роли

Для себя мы определили три основных типа ролей. На рисунке приведено их описание.

Благодаря ролям, иерархии секретов и богатому функционалу Vault по настройке политик, мы можем учитывать потребности всех команд и их специфику. Нам не нужно загонять всех в один процесс. К примеру, где-то QA нужно выдать дополнительные права на интеграционный стенд, а где-то — системному аналитику на конкретный сервис в проде. Все это гибко настраивается за счет добавления новых кастомных ролей.

Выводы

Сейчас мы находимся в промежуточной точке.

  • Внедрили Vault и процесс смены секретов.

  • Секреты подставляются в манифесты на этапе сборки через скрипты Ansible.

  • Убрали из процесса тимлидов и не тратим их дорогое время.

  • При смене секретов задействованы только поддержка и девопсы.

  • Время на смену секретов сократилось с одного-двух дней до одного часа.

  • Уже сейчас инциденты на проде из-за секретов практически ушли, все секреты мы вовремя обновляем.

Конечно, хочется большего и сделать ту самую одну супер безопасную кнопку, но это будет в следующей серии.

Комментарии (12)


  1. project_delta
    29.07.2024 08:15
    +5

    а были ещё какие то варианты, помимо Vault, с подобным функционалом?
    или сразу был выбран Vault в качестве замены Ansible-vault?


    1. LevPoz Автор
      29.07.2024 08:15
      +2

      Другие варианты подробно не смотрели, т.к. до этого был опыт с Vault у ребят в команде.


  1. Luchnik22
    29.07.2024 08:15

    А вы не думали перейти на нативные библиотеки для работы с vault? Тогда не нужен будет контейнер sidecar - можно сделать:
    1. Поллинг секретов раз в сутки (например) или ротация их по времени экспирации
    2. Если получен отказ в доступе во время использования секрета, то приложение должно выполнить проверку на наличие нового секрета и в случае чего его обновить, тут правда важно учесть много нюансов и что это занимает весьма внушительный объём работы


    1. LevPoz Автор
      29.07.2024 08:15

      Посмотрим в эту сторону, спасибо. У нас много сервисов на разных стеках.


  1. nspickiy
    29.07.2024 08:15
    +1

    1. Продвинутый уровень — хранить секреты в зашифрованном виде в Git. Такой подход обеспечивает безопасность, однако процесс управления секретами достаточно медленный: нужна сборка, тестирование, перезапуск приложений. Как было в нашем случае.

    Мы недавно столкнулись с вопросом как управлять секретами и нужно ли нам переходить из AWS Secret Manager куда-то. Один из вариантов был SOPS, так как мы используем FluxCD для управления Kubernetes пространством. Самые главные плюсы:

    1. Можно отслеживать историю, видно кто и когда менял секреты, какие секреты менялись и зачем (ссылка на задачи в JIRA в commit messages)

    2. Есть возможность создавать отложенные изменения через merge request, когда нужно внести изменения в секретах вместе с другими изменениями

    3. Возможность быстро сделать rollback если что-то пошло не так

    4. Для предоставления доступа мы используем AWS KMS и через IAM роли выдаём доступ к ключам шифрования

    5. Это условно бесплатно, так как используется Git для хранения, но можно использовать платные сервисы для предоставления ключей шифрования. Но это всё ещё дешевле Hashicorp :)

    Из минусов:

    1. Нужно иметь анализаторы в хранилище с кодом, так как отправить незашифрованный секрет не составляет труда и это легко сделать по невнимательности или неопытности

    2. Высокий порог вхождения, нужно понимать как менять секреты. Но в VS Code есть различные дополнения, которые облегчают работу.


    1. LevPoz Автор
      29.07.2024 08:15

      По моему мнению, самый большой недостаток хранения секретов в Git - это то, что секреты в итоге сохраняются локально у тех у кого есть доступ.


      1. nspickiy
        29.07.2024 08:15

        С одной стороны да, с другой ничего не мешает это делать не рассказывая об этом другим. Мы стараемся решить это помощью различных подходов, например ограничения доступа к сервисам на уровне сети.


  1. vadav13
    29.07.2024 08:15
    +1

    А csi vault provider не рассматривали? Если не ошибаюсь к него есть функция по автоматическому перезапуску пода если секрет поменялся, ну или использовать в связке со stakater reloader


    1. LevPoz Автор
      29.07.2024 08:15

      Спасибо за рекомендацию, будем смотреть, это как раз ко второму этапу.


  1. iliks84
    29.07.2024 08:15
    +1

    А почему не пользуетесь External Secrets Operator? https://external-secrets.io/

    Позволяет отказаться от инит контейнеров, внешние секреты автоматом перебрасываются во внутренние куберовские.


    1. LevPoz Автор
      29.07.2024 08:15

      Спасибо, проработаем для второго этапа


      1. Fetos
        29.07.2024 08:15
        +1

        stakater