В этой статье я расскажу о том, как мы управляем секретами в Kubernetes-инфраструктуре BaseCRM.


Наша цель — использование Helm Charts в Kubernetes-кластерах BaseCRM с минимальными усилиями, подразумевающими управление лишь значениями параметров и секретами. В Helm или Kubernetes нет официального инструмента управления секретами, и мы решили это исправить.


Введение


Kubernetes — это разработанная в Google при существенной поддержке сообщества платформа для автоматизации развертывания и масштабирования контейнеризованных приложений в кластере. Это переносимая, надежная и способная к самовосстановлению система, предназначенная для построения PaaS-инфраструктур следующего поколения.


Поверх Kubernetes мы используем Helm — официальный менеджер пакетов Kubernetes. Располагая системой Charts с поддержкой шаблонов, он помогает управлять различными типами выполняющихся в Kubernetes приложений.


Helm Charts нужен для определения, установки и обновления многократно используемых шаблонов приложений в кластере Kubernetes, включая самые сложные, c множественными зависимыми субчартами (sub-charts). С Helm Charts можно легко управлять множественными версиями чартов, а также останавливать их с помощью прямых манифестов (direct manifests), которые в стоковом Kubernetes управляются только копипастом, что совершенно неприемлемо для сложных окружений.


С помощью Helm мы можем использовать части шаблонов чартов с чартами приложений любой сложности, которые пригодны для многократного использования в развертываниях Kubernetes PaaS в нескольких кластерах. С CI/CD мы можем с легкостью развернуть стек приложений в кластере Kubernetes или просто запустить Helm вручную и задеплоить целый мир с нуля.


Какую проблему мы пытаемся решить?


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


Одни хранят секреты в Git за VPN, при этом получая проблемы с множественными копиями, незашифрованными дисками и ротацией секретов. Другие прописывают их прямо в кластере и теряют эти данные вместе с кластером, когда с ним что-то случается.


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


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


Как мы это делаем?


С самого основания компании Base мы используем Ansible. Также мы решили, что для развертывания кластеров Kubernetes будем использовать Helm (эта тема, скорее всего, заслуживает отдельной статьи). Столкнувшись с проблемой управления секретами, мы придумали плагин helm-secrets, который во многом напоминал Ansible vault. В то время мы использовали PGP и шифровали файл с секретами целиком.


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


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


Все, что оставалось сделать, — это написать простую прослойку между SOPS-бэкендом и Helm-фронтендом с использованием первой версии helm-secrets.


Именно это мы и сделали, попутно решив поделиться результатами работы с общественностью. Встречайте релиз плагина helm-secrets.


Вот список его возможностей (их даже больше, чем перечислено здесь):


  • Простой уровень интеграции и установки совместно с плагином helm.
  • Поддержка шифрования YAML-структур Helm.
  • Шифрование отдельных значений – это позволяет использовать diff даже с зашифрованными файлами.
  • Для git diff обеспечивается расшифровка «на лету».
  • Для команд helm install/upgrade/rollback с помощью команды helm-wrapper обеспечивается расшифровка «на лету» и очистка.
  • Поддерживается одновременное использование в одном файле секретов решений управления множественными ключами, таких как PGP и AWS KMS.
  • Для зашифрованных файлов поддерживается простое добавление/удаление ключей.
  • Поддерживается работа с системой управления разрешениями для ключей AWS KMS без необходимости перешифрования.
  • Разделение дерева каталогов для хранения файлов секретов с использованием рекурсивного поиска по файлам .sops.yaml.
  • Извлечение отдельных элементов из зашифрованной структуры файлов.
  • Шифрование части файла.

Система способна работать в рамках CI/CD, обслуживая несколько команд разработчиков, имеющих изолированный доступ к определенным поддиректориям репозитория Git, который содержит секреты в разрезе проектов/окружений/регионов для определенных кластеров Kubernetes. Мы шифруем секреты с помощью разных ключей KMS и мастер-ключа PGP в качестве резерва.


Для автоматизации и ускорения выполнения задач в нашем внутреннем чарт-репозитории используется Makefile.




Вот как это работает


Установка Helm


Следуйте инструкциям на странице проекта helm.


Установка плагина helm-secrets


helm plugin install https://github.com/futuresimple/helm-secrets

Пример использования


В репозитории helm-secrets приведен пример работы с этим плагином, а также соответствующая структура каталогов, предназначенная для хранения значений чартов helm. Для чартов kubernetes применяется аналогичная структура.


Для этого примера используется файл test.sh из репозитория helm-secrets.


example/helm_vars/
+-- .sops.yaml
+-- projectX
¦   +-- .sops.yaml
¦   +-- production
¦   ¦   L-- us-east-1
¦   ¦       L-- java-app
¦   ¦           +-- secrets.yaml
¦   ¦           L-- value.yaml
¦   L-- sandbox
¦       L-- us-east-1
¦           L-- java-app
¦               +-- secrets.yaml
¦               L-- value.yaml
+-- projectY
¦   +-- .sops.yaml
¦   +-- production
¦   ¦   L-- us-east-1
¦   ¦       L-- java-app
¦   ¦           +-- secrets.yaml
¦   ¦           L-- value.yaml
¦   L-- sandbox
¦       L-- us-east-1
¦           L-- java-app
¦               +-- secrets.yaml
¦               L-- value.yaml
+-- secrets.yaml
L-- values.yaml

Давайте разберем этот пример.


  • У нас есть два PGP-ключа (также можно использовать KMS-ключи):
    • один для projectx;
    • другой для projecty.
  • Все остальные секреты управляются в рамках проекта, и для каждого проекта используется один ключ.
  • Ключи изолированы друг от друга, и только глобальный файл secrets.yaml, находящийся в корне helm_vars, может быть расшифрован любым из этих ключей.
  • Все правила глубины рекурсии для шифрования/расшифровки находятся в файлах .sops.yaml.

Шифрование, расшифровка и даже больше


Перед шифрованием example/helm_vars/secrets.yaml


global_secret: global_bar

Теперь шифруем:


helm-wrapper secrets enc example/helm_vars/secrets.yaml

В результате мы получим файл с незашифрованными ключами и зашифрованными значениями.


После раздела с ключами идут sops-данные, необходимые для шифрования или расшифровки структур секретов.


Также есть метаданные типа sops version, lastmodified или unencrypted_suffix. Почитать о формате файла sops можно на сайте проекта Mozilla SOPS.


global_secret: ENC[AES256_GCM,data:pTyPdC6YA+z84Q==,iv:aF5hb9CS8Au0B3RWADPtP8fXYzYakU7JJ8ZxzJgHRF0=,tag:c3pCyOf0NpQU7VPL/72XPg==,type:str]
sops:
.…

….

….

    unencrypted_suffix: _unencrypted
    version: 2.0.9

Теперь расшифровка:


helm-wrapper secrets dec example/helm_vars/secrets.yaml

И результат:


global_secret: global_bar

Теперь мы можем работать с такими файлами без расшифровки, например, при поиске ключей в нескольких файлах секретов. Мы даже можем расшифровывать их «на лету» с git diff config, что делает работу еще более комфортной, особенно по сравнению с приложениями, которые не поддерживают подобную функциональность.


Мы также можем более гибко управлять разрешениями секретов, не пересоздавая их благодаря более продвинутому AWS KMS.


Более того, просмотр и редактирование секретов с помощью простых команд плагина облегчают ежедневную работу.


helm-wrapper secrets view example/helm_vars/secrets.yaml

После расшифровки файл секретов будет выведен в stdout:


helm-wrapper secrets edit example/helm_vars/secrets.yaml

По команде edit расшифрованный файл откроется в редакторе, а изменения будут автоматически сохранены в зашифрованном виде.


Эти функции значительно облегчают работу с зашифрованными данными практически при любом сценарии.


Пример использования Helm


Давайте развернем что-нибудь в кластере Kubernetes.


Чтобы упростить процесс и сделать его более прозрачным, мы создали helm-wrapper. Эта написанная на bash обвязка для helm автоматически расшифровывает все заданные секреты и использует расшифрованные данные для выполнения процедуры развертывания с помощью helm. При возникновении ошибки или в случае успешного завершения процедуры все временные данные с секретами удаляются.


Реальный пример для приложения типа helloworld:


AWS_PROFILE=production helm-secrets upgrade --install --timeout 600 --wait helloworld stable/java-app --kube-context=production --namespace=projectx --set global.app_version=bff8fc4 -f helm_vars/projectx/sandbox/us-east-1/java-app/helloworld/secrets.yaml -f helm_vars/projectx/sandbox/us-east-1/java-app/helloworld/values.yaml -f helm_vars/secrets.yaml -f helm_vars/values.yaml
>>>>>> Decrypt
Decrypting helm_vars/projectx/sandbox/us-east-1/java-app/helloworld/secrets.yaml
>>>>>> Decrypt
Decrypting helm_vars/secrets.yaml

Release "helloworld" has been upgraded. Happy Helming!
LAST DEPLOYED: Fri May  5 13:27:01 2017
NAMESPACE: projectx
STATUS: DEPLOYED

RESOURCES:
==> extensions/v1beta1/Deployment
NAME        DESIRED  CURRENT  UP-TO-DATE  AVAILABLE  AGE
helloworld  3        3        3           2          1h

==> v1/Secret
NAME        TYPE    DATA  AGE
helloworld  Opaque  10    1h

==> v1/ConfigMap
NAME        DATA  AGE
helloworld  2     1h

==> v1/Service
NAME        CLUSTER-IP      EXTERNAL-IP  PORT(S)   AGE
helloworld  100.65.221.245         8080/TCP  1h

NOTES:
Deploy success helloworld-bff8fc4 in namespace projectx

>>>>>> Cleanup
helm_vars/projectx/sandbox/us-east-1/java-app/helloworld/secrets.yaml.dec
helm_vars/secrets.yaml.dec

Как можно увидеть из листинга, там есть специальная команда, которая выполняется на стороне CI для развертывания приложения. В этом примере используется наш внутренний универсальный чарт для приложений на java, который содержит шаблон для config map, секретов, сервисов и развертывания.


Все это генерируется из значений в файлах, указанных с помощью опции -f команды helm. Если в таком файле встретится зашифрованный секрет, скрипт расшифрует его «на лету», и helm ничего не заметит.


При использовании AWS KMS необходимо указать имя AWS_PROFILE.


При возникновении ошибки будет выполнена очистка.


AWS_PROFILE=production helm-wrapper upgrade --install --timeout 600 --wait helloworld stable/java-app --kube-context=wrongcontext --namespace=projectx --set global.app_version=bff8fc4 -f helm_vars/projectx/sandbox/us-east-1/java-app/helloworld/secrets.yaml -f helm_vars/projectx/sandbox/us-east-1/java-app/helloworld/values.yaml -f helm_vars/secrets.yaml -f helm_vars/values.yaml
>>>>>> Decrypt
Decrypting helm_vars/projectx/sandbox/us-east-1/java-app/helloworld/secrets.yaml
>>>>>> Decrypt
Decrypting helm_vars/secrets.yaml

Error: could not get kubernetes config for context 'wrongcontext': context "wrongcontext" does not exist

>>>>>> Cleanup
helm_vars/projectx/sandbox/us-east-1/java-app/helloworld/secrets.yaml.dec
helm_vars/secrets.yaml.dec

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


Безопасное использование с git


При наличии расшифрованных секретов (расшифрованных вручную) у нас остается еще два уровня обеспечения безопасности: исключение расшифрованных файлов с помощью .gitignore и добавление хуков, которые проверяют, не зашифрованы ли файлы коммита с помощью SOPS.


Все это также есть в документации helm-secret и проверено нашим процессом CI/CD.


Заключение


Helm-secrets — это лишь тонкая обертка для sops-бэкенда, и любая команда этого плагина может быть заменена на другие инструменты.


Мы также используем этот плагин для распространения секретов через публичный git-репозиторий и планируем добавить в процесс работы с секретами интегрированную в Kubernetes службу Vaulting, что придаст нашему решению законченность.


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


При желании добавить в проект какие-либо функции или просто его обсудить заходите на https://github.com/futuresimple/helm-secrets.


Ссылки:


  1. Оригинал: HELM SECRETS – A MISSING PIECE IN KUBERNETES.

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


  1. ayurtaykin
    30.08.2017 11:04

    Hm, и ни одного комментария за сутки? В чем храните секреты? хочу уйти от публичного репо за VPN.


  1. n0madic
    01.09.2017 19:05

    А почему бы не использовать встроенный Kubernetes Secrets?