В Kubernetes есть такая сущность, как secrets, или секреты. Из названия понятно, что эти данные не могут быть общедоступными, и их нужно как-то защищать и особым образом доставлять в кластеры Kubernetes. В статье разберём, что такое secrets и в чём особенности их хранения и доставки. А также рассмотрим два популярных инструмента для работы с секретами: Hashicorp Vault и Helm Secrets.
Статья подготовлена на основе лекции Александра Терехина, Senior DevSecOps в компании DefinedCrowd. Лекция с практикой доступна в рамках курса «Безопасность в Kubernetes» в Слёрме.
Что такое secrets
Секрет — это любые данные, которые мы бы не хотели показывать широкому кругу лиц: парольная фраза, ключевая пара, ключ шифрования, API-токен. Эти данные нужно использовать внутри кластера Kubernetes, но при этом требуется ограничить к ним доступ.
Kubernetes считает secret специальным ресурсом, который он предлагает использовать при работе с секретными данными внутри кластера. Вот как может выглядеть простой secret:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: c2x1cm0=
password: U29tZVN0cm9uZ1Bhc3M=
В целом тут всё так же, как с другими ресурсами Kubernetes. В kind указываем тип ресурса, «secret». В metadata задаем имя. Прописываем тип секрета — у нас это Opaque, он позволяет хранить любые данные в формате ключ-значение. И потом задаём их в поле data. Они указаны в Base64 — это не шифрование, а просто кодирование.
Такой манифест нельзя взять и запушить в репозиторий — любой человек с доступом сможет спокойно сделать декод Base64 и узнать наши username и password. А мы этого не хотим, и ниже разберём, что с этим делать.
Всего существует восемь типов secrets:
![Типы секретов в k8s Типы секретов в k8s](https://habrastorage.org/getpro/habr/upload_files/0cb/cc3/38f/0cbcc338fba62eed883087428deb14a2.png)
Opaque. Позволяет хранить внутри себя любые данные, универсальный тип.
Service account token. Связан с service account и хранит для него JWT-токен. Практически всегда при создании service account автоматически создаётся его секрет. Внутри такого секрета всегда есть указание на то, для какого service account был выдан JWT-токен.
Dockercfg и dockerconfigjson. Позволяет хранить внутри себя настройки для доступа к docker container registry. Dockercfg — просто config-формат, configjson — json-формат.
Basic-auth. Логин:пароль в Base64 формате для Basic-аутентификации.
Ssh-auth. Может использоваться для аутентификации при установке соединений с использованием протокола SSH. Внутри такого секрета находится приватный ключ от ключевой пары. В поле Data манифеста мы должны указать ssh-privatekey.
TLS. Хранит сертификат и приватный ключ от него. Можно использовать его в качестве серверного сертификата. Например, cert-manager оперирует именно таким секретом — выпускает TLS-сертификат с приватным ключом и кладет в secret с типом TLS. Особенность секрета в том, что в поле data
в манифесте мы должны указать ключи tls.crt
и tls.key
, где в Base64-кодировке поместить сертификат и его приватный ключ.
Bootstrap token. Позволяет обеспечить безопасное добавление новых k8s-нод в кластер.
Подходы к доставке secrets
Секреты нужно как-то доставлять в кластеры Kubernetes. Для этого есть два разных подхода: push и pull.
Push. Секреты доставляются в кластеры сразу во время деплоя. В манифестах мы указываем секреты и используем их в том или ином виде, чтобы в момент деплоя создать ресурсы с указанием значений секретов.
Чтобы не добавлять их в открытом виде, нужно их преобразовать. Самый простой способ — шифровать сразу в манифестах или в значениях, например, в Helm-чартах. В итоге секреты хранятся в репозитории в нечитаемом виде, что обеспечивает их безопасность. А расшифровываем мы их только в момент применения манифеста. Либо после применения, если используем промежуточный k8s ресурс.
Pull. Секреты загружаются уже после деплоя. Например, если мы используем Helm-чарты, мы можем указать метаинформацию вместо значения секрета.
Здесь мы значение секрета в манифесты не добавляем вообще, а указываем только метаинформацию. Секреты создаются на основе этой метаинформации и загружаются из внешнего хранилища — например, Hashicorp Vault.
Стандартное решение для хранения secrets: Hashicorp Vault
Hashicorp Vault — это инструмент для хранения секретов, который де-факто является стандартом для Kubernetes. У него есть ряд функциональных возможностей:
Безопасное хранение секретов. Они хранятся в виде ключ-значение, то есть мы можем создать некий секрет, положить туда любой набор этих пар — и всё будет безопасно храниться.
Хранение динамических секретов. Можно сделать интеграцию между Hashicorp Vault и достаточно большим количеством систем, например AWS, Azure. Или же использовать для аутентификации по SSH-протоколу на серверах. И Vault будет управлять динамическими секретами с определённым сроком жизни, а нам не придётся постоянно менять их вручную.
Например, в Azure можно использовать Service Principal, чтобы создавать аккаунты для доступа к ресурсам внутри Azure Cloud. И Vault может по запросу ходить в Azure, выпускать новые секретные значения для Service Principal на основе настроенной интеграции и назначать им срок жизни. И уже дальше открывать клиенту, который запросил доступ, конечные Credential для этого Service Principal. А когда время жизни Service Principal кончится, Vault сам отзовёт Credential.
Таким образом можно организовать доступ и для SSH. Когда администраторы будут запрашивать новый доступ по SSH, они получат от Vault ключ с определённым сроком действия. А когда срок закончится, Vault сам отзовёт ключ и зачистит информацию на серверах.
Шифрование данных. Мы можем сгенерировать так называемый Transit Key, хранить его внутри самого Hashicorp Vault, и сделать неэкспортируемым. То есть из Vault секретный ключ для шифрования получить будет нельзя.
Как это будет работать? Например, у нас может быть web-приложение с которым работают наши клиенты. Это web-приложение занимается приёмом оплаты по платёжным картам. Стандарт безопасности платёжных карт требует, чтобы данные карт нигде не хранились в открытом виде. Так что наше web-приложение может обратиться к Vault. Vault на своем секретном ключе все зашифрует и вернет сервису уже в зашифрованном виде — а дальше их можно сохранить в базу данных. Для расшифрования схема обратная — сервис читает зашифрованный номер, просит Vault его расшифровать — и всё готово.
С функционалом закончили — теперь разберём архитектуру Hashicorp Vault. Схематично она выглядит так:
![Архитектура Hashicorp Vault Архитектура Hashicorp Vault](https://habrastorage.org/getpro/habr/upload_files/78f/248/d29/78f248d29fa790ca6dcd7cf81df41e69.png)
Разберём ключевые элементы архитектуры:
Core. Умеет работать с различными запросами. Все они поступают по API, так что какой бы интерфейс мы ни использовали, всё взаимодействие будет преобразовано в формат запрос-ответ на HTTP/API. После поступления на Core запрос маршрутизируется с использованием Path Routing. В зависимости от пути path этот компонент перенаправит запрос на тот или иной бэкенд.
System Backend. Отвечает за системные функции самого Vault: работу с политиками, токенами и т.п.
Secret Engine. На его функциональности строится вся работа Vault по хранению секретов и шифрованию. Для активации используется path, и два разных Secret engine нельзя активировать по одному пути.
Auth Method. Предназначен для аутентификации пользователей Vault. Это может быть внутренняя аутентификация, либо интеграция с внешней системой. Например, если у нас есть OpenLDAP сервер или внутренняя Active Directory, мы можем сделать интеграцию между Vault и этим сервером по протоколу LDAP. И вместо того, чтобы создавать локальных пользователей, можно будет использовать аутентификацию через настроенный Backend. Мы не только проводим аутентификацию в этом Auth Method — Vault ещё и управляет identity, специальными сущностями, которые представляют этих пользователей.
При этом у нас всё равно заводится внутренний identity, и ему при прохождении аутентификации назначается политика доступа. Vault выдаёт свои токены даже несмотря на то,что есть внешнее хранилище, и аутентификация происходит во внешнем бэкенде.
Token Store. У Vault есть интеграции с методами аутентификации, но он все равно выдаёт свои токены доступа для взаимодействия пользователя с Vault.
Policy Store. Элемент для хранения политик, которые позволяют разграничивать доступ к различным частям внутри Vault: кому можно пользоваться определёнными методами аутентификации и иметь доступ к системным функциям, в том числе к редактированию политик.
Этот объект оперирует понятиями Path и Capabilities. Path — это путь, по нему назначаются права. Мы указываем либо путь до Secrets engine, либо к какому-то системному компоненту, либо ещё что-то, для чего будут назначаться права. Либо можем указать прямой путь к секрету.
Capability — это возможности, наборы прав на определённый путь. Вот как может выглядеть политика:
![Vault Policy Vault Policy](https://habrastorage.org/getpro/habr/upload_files/24f/0e7/00c/24f0e700cb005303e51591ef78306c11.png)
Здесь capabilities – назначаемые права на этот путь:
Create — создание секретов и метаинформации внутри Secret engine и не только.
Read — чтение.
Update — обновление.
Delete — удаление.
List — вывод списка тех же секретов или политик.
На иллюстрации не хватает только capability sudo — она используется для управления системными элементами, например, самими политиками.
Audit Broker. Его задача — писать Audit Log всех действий, которые происходят с Vault и внутри Vault. Это позволяет разбирать какие-то ситуации и понимать, что происходило в тот или иной момент, как использовался секрет, как он менялся и почему удалился.
По умолчанию Audit Broker не активирован — нужно выполнить специальную команду vault audit enable
и указать device куда писать информацию: локальный файл, Syslog или открытый сокет.
Vault Secrets Operator: решение для доставки secrets из Hashicorp Vault в кластеры Kubernetes
Есть Hashicorp Vault, в нём есть секреты, и теперь эти секреты нужно доставить, чтобы создать ресурсы внутри кластера Kubernetes. Для реализации этого подхода существует решение с открытым исходным кодом Vault Secrets Operator. Оно работает по модели pull, когда до деплоя секреты недоступны. В момент деплоя в манифестах у нас только метаинформация, и значение секретов там не фигурируют.
Принцип действия таков. Мы создаём промежуточный кастомный ресурс VaultSecret, в котором будем указывать метаинформацию. А уже потом, на основе этой метаинформации внутри кластера operator сам будет разбираться и создавать секреты.
Схема работы выглядит так:
![Vault Secrets Operator Vault Secrets Operator](https://habrastorage.org/getpro/habr/upload_files/95f/a4a/419/95fa4a4199fae9cdb963b463931d4101.png)
Vault Secrets Operator деплоится внутрь кластера Kubernetes. Когда мы создаём кастомные ресурсы, Vault Secret Operator их обрабатывает и получает из них метаинформацию — пути, по которым нужно обратиться и загрузить из Vault необходимые секреты.
Vault Secret Operator это выполняет — читает секреты из Hashicorp Vault. Один из вариантов интеграции Vault Secrets Operator и Hashicorp Vault — использование Kubernetes-аутентификации. Это работает практически бесшовно: Vault Secrets Operator может без дополнительных сложностей читать Secret из Hashicorp Vault.
После того, как operator прочитал значение секрета, он сможет обратиться на Kubernetes API и сказать, что необходимо создать конкретные секреты с такими-то значениями. В итоге secret появится как ресурс внутри кластера Kubernetes.
Теперь подробнее про Kubernetes-аутентификацию. Ниже её схема.
![Vault Kubernetes Auth Vault Kubernetes Auth](https://habrastorage.org/getpro/habr/upload_files/b02/d90/873/b02d9087372a58ff6a487210da21b8ea.png)
Разберём её по порядку.
Предварительная настройка. У нас есть администратор, который настраивает в Vault Kubernetes-аутентификацию. Он активирует новый Auth Method с типом Kubernetes. Администратор должен указать сертификат удостоверяющего центра, который сгенерирован внутри кластера Kubernetes. Это нужно, чтобы Vault доверял сертификату, размещённому на Kube API.
После того, как мы указали сертификат, мы должны сформировать роли. Для каждого метода аутентификации можно указать свой набор ролей. С помощью ролей мы можем назначить соответствующие политики. Роли применяются на основе имени service account из Kubernetes, под которым будет запущен operator, и namespace, в котором этот service account находится.
Мы указали сертификат, создали роли. И ролям назначили определённые политики доступа к secret engine, чтобы operator мог читать определённые секреты.
Деплой. Когда мы переходим к деплою, попутно создается service account, который привязывается к деплойменту и соответствующим подам. Создается специальный тип секрета, service account, про который мы ранее говорили. Внутри него хранится JWT-Token, который должен использоваться для Kubernetes-аутентификации.
Наш operator может обратиться на Vault и сказать: «Я хочу пройти аутентификацию с использованием метода Kubernetes». Посылает JWT-Token от service account. Vault обращается на Kube API и проверяет этот токен. В ответ Vault получает информацию — для какого service account выпущен JWT-Token, в каком неймспейсе этот service account находится. Используя эту информацию, Vault ориентируется, какую роль необходимо назначить оператору и по какой политике он получит права доступа.
После того, как Vault это выполнил, он возвращает результат. Если всё удачно, Vault генерирует специальный токен и возвращает оператору. А дальше уже operator, используя этот Vault Token, может пойти в Vault и прочитать все доступные ему секреты.
Альтернативное решение для хранения и доставки секретов: Helm Secrets
Vault Secrets Operator работает по методу pull. Есть альтернатива с методом push — Helm Secrets. Здесь секреты хранятся в манифестах, но в зашифрованном виде. По сути Helm Secrets — это плагин и wrapper над Helm — он просто перенаправляет команды либо на Helm для установки нашего деплоймента, либо на бэкенд, связанный с преобразованием, шифрованием или получением секретов.
Особенность Helm Secrets в том, что у него есть несколько интеграций. По умолчанию используется Mozilla/sops — Secrets Operations. Этот sops позволяет шифровать секреты в манифестах с помощью специально сгенерированных GPG-ключей. Дальше, когда мы будем применять эти манифесты соответствующим образом с использованием GPG-ключа, эта информация должна быть расшифрована и перенаправлена в Helm для применения.
Но Sops — не единственная интеграция. Есть решение vals — оно реализует иной подход, когда в манифестах публикуются не сами значения секретов, а плейсхолдеры. В момент применения, когда Helm Secrets выполняется, он ссылается на vals, и vals заменяет плейсхолдеры внутри манифестов на реальные значения. Vals имеет интеграции с Azure keyvault, c key management system AWS/GCP и другими.
Рассмотрим совместное использование Helm Secrets и sops. Сначала нужно подготовиться и сгенерировать GPG-ключи для шифрования. Затем сразу же зашифровать все наши секреты в манифестах с помощью sops. При выполнении helm secrets upgrade
с помощью ключа -f
обязательно нужно указать файл с зашифрованными секретами. Если этого мы не сделаем, Helm Secrets будет считать, что у него такого файла нет, и ничего не произойдёт.
Теперь посмотрим на схему работы с Helm Secrets:
![Helm Secrets + sops Helm Secrets + sops](https://habrastorage.org/getpro/habr/upload_files/a45/0e2/253/a450e225338258ec1fc004a5c2d1e92c.png)
Предварительный шаг — это обязательно шифрование секретов в манифесте с помощью sops. На вход Helm Secrets при выполнении деплоймента мы подаём helm-чарт и эти зашифрованные секреты. Helm Secrets видит, что мы ему передали файл — у него по умолчанию настроен какой-то backend, допустим, sops. И он передаёт файл secrets.yaml в sops.
Sops использует GPG-ключи, расшифровывает информацию и создаёт некий промежуточный файл secrets.yaml.dec. Этот файл вместе с helm-чартом передаётся непосредственно в helm для выполнения деплоймента. Деплоймент в Kubernetes cluster проходит, и временный файл удаляется. Но чтобы подстраховаться, лучше указать в Git ignore все файлы с расширением .dec, чтобы они никогда не пушились в repository.
Заключение и полезные ссылки
Мы разобрали основные моменты, связанные с секретами в Kubernetes, и описали ключевые инструменты для управления и доставки секретов. Разобраться в этом всем подробнее помогут полезные ссылки:
Vault kv-v2 ACL rules — полезно, если будете писать свои политики.
Vault Secrets Operator и Helm Secrets — ссылки на репозитории. Это опенсорные решения, так что можете использовать их свободно — по лицензии можно даже для коммерческого применения.
Курс «Безопасность в Kubernetes» от Слёрма.
Примечание: для обеспечения отказоустойчивости решений на базе k8s лучше всего создавать локальный Docker registry и Helm Chart repo. И уже в локальные репозитории загружать необходимые образы и чарты. Таким образом, даже если официальные репозитории станут недоступны, мы не потеряем доступ к образам и чартам.
Фото на обложке статьи: Joe Dudeck, Unsplash.com