Как правильно хранить секреты? В репозитории, в системе деплоя или в системе управления конфигурациями? На личном компьютере, на серверах, а может в коробке под кроватью? А как управлять секретами, чтобы не допускать утечек?
Сергей Носков (Albibek) — руководитель группы информационной безопасности платформы из Авито, знает ответ на эти вопросы и поделится с нами. В Авито два года активно используют HashiCorp Vault, за это время набили шишки, и прокачали опыт до уровня «Мастер».
В статье всесторонне поговорим про Vault: что это такое, где и как используется в компании, как в Авито управляют секретами с помощью HashiCorp Vault, как используют Puppet и Kubernetes, варианты использования с Puppet и другими SCM, какие возникают проблемы, что болит у безопасников и разработчиков, и, конечно, поделимся идеями, как все исправить.
Любая конфиденциальная информация:
Всю информацию, которую мы хотим сохранить в тайне, мы называем секретом. Это создает проблему с хранением: в репозитории хранить плохо, в зашифрованном виде — нужно где-то держать ключи шифрования.
HashiCorp Vault — одно из неплохих решений проблемы.
На мой взгляд, это достаточно удобный инструмент.
Что нового в HashiCorp Vault
Инструмент развивается и за последнее время в нем появилось много интересных фич: CORS-заголовки для GUI без посредников; встроенный GUI; нативная интеграция с Kubernetes; плагины для logical- и auth-бэкендов и фреймворк.
Большинство изменений, которые понравились лично мне, — это возможность не писать расширения и дополнения, которые будут стоять снаружи относительно инструмента.
Например, есть Vault, вы хотите его расширить — написать дополнительную логику или свой UI для автоматизации, который будет что-то автоматизировать. До изменений приходилось поднимать дополнительный сервис, который стоит перед Vault, и проксирует все запросы: сначала запросы идут в сервис, потом — в Vault. Это плохо тем, что в промежуточном сервисе может быть пониженный уровень безопасности, а через него идут все секреты. Риски безопасности намного выше, когда секрет проходит через несколько точек сразу!
Проблема курицы и яйца
Когда вы поднимаете вопрос хранения конфиденциальной информации и решаете шифровать, то, как только вы что-то зашифровали, ваш секрет смещается из места шифровки туда, где хранится ключ. Это происходит постоянно: как только вы сохранили куда-то секрет или изменили существующий, у вас появляется еще один и начинается заколдованный круг — где хранить секрет для доступа к секрету.
Секрет доступа к секрету — это та часть безопасности, которая называется аутентификацией. У безопасности есть еще одна часть — авторизация. В процессе авторизации проверяется, можно ли пользователю получить доступ именно туда, куда он запрашивает. В случае с Vault есть доверенная третья сторона, которая решает, выдать секрет или нет. Авторизация лишь частично решает проблему.
HashiCorp Vault в Авито
В Авито HashiCorp установлен в единственной большой инсталляции на всю сеть. У HashiCorp Vault много разных бэкендов. Мы используем бэкенд на базе Consul тоже от HashiCorp, потому что Vault умеет поддерживать собственную отказоустойчивость только через Consul.ь.
Unseal — это способ не держать в одном месте мастер-ключ. Когда запускается Vault, он все шифрует на каком-то ключе, и снова появляется проблема курицы и яйца: где хранить секрет, которым будут шифроваться все остальные секреты. Чтобы избежать этой проблемы, в Vault предусмотрен составной ключ, для получения которого требуется несколько частей ключа, которые мы раздаём нескольким сотрудникам. В Авито мы настроили в опциях Unseal на 3 человека из 7. Если мы запускаем Vault, то чтобы он начал работать, обязательно должны прийти как минимум 3 человека и ввести свою часть ключа. Ключ поделен на 7 частей и можно принести любые из них.
Мы собрали маленький тестовый Vault — песочницу для разработчиков, где они могут играть. Он есть в виде Docker-контейнера и создает простые секретики, чтобы люди могли потрогать инструмент руками, освоиться. В песочнице нет Consul и кластеризации, это просто файловая система, на которой Vault держит зашифрованные секреты, и маленький скрипт для инициализации.
Вот что мы сейчас храним в Vault:
Внутри себя Vault хранит всё только в JSON, это не всегда удобно и требует дополнительных действий от разработчика, поэтому в основном мы выкладываем секреты в виде файла.
Мы не говорим разработчику: «Иди в Vault, бери секрет!», а выкладываем на диск файл и сообщаем: «Разработчик, у тебя на диске появится файл, забирай секрет из него, а мы уже разберемся, как достать его из Vault и принести тебе».
![](https://habrastorage.org/r/w1560/webt/rg/9o/mj/rg9omjizdszcnw0lzrr_iogfosc.png)
Мы приняли простое соглашение для полей JSON, в котором указываем, с какими правами выкладывать файл. Это метаданные для файловой системы, а поле data — это закодированная строка c самим секретом, которая станет содержимым файла.
Puppet + Hiera + Vault
Практически во всей инфраструктуре Авито используется Puppet, им раскатываются все серверы.
У Puppet есть удобный инструмент организации иерархии — Hiera. Vault очень хорошо интегрируется с Hiera через дополнительный модуль, потому что в эту библиотеку идёт запрос ключ-значение, а Vault сам база ключ-значение, но со всеми функциями безопасности — с прозрачным шифрованием и возможностью выбора доступа к ключам.
Поэтому первое, что мы внедрили — это Vault в Puppet, но с одним дополнением — у нас есть промежуточный слой, который называется Router backend. Router backend — отдельный модуль Hiera, просто файлы на диске, в которых написано, куда Hiera должна идти за ключом — в Vault или в другое место.
Он нужен, чтобы Hiera не ходила в Vault постоянно, потому что она идет всегда по всей иерархии. Это не проблема Vault или нагрузки на него, а особенность работы самой Hiera. Поэтому если оставить только модуль для Vault без Router backend, Puppet-мастер будет очень долго собирать конфигурацию для Puppet-агента, так как будет проверять каждый ключ в Vault.
![](https://habrastorage.org/r/w1560/webt/dv/di/f6/dvdif6z09jpflg98zykbwf7l7cq.png)
Для Puppet проблема курицы и яйца решается за счёт того, что авторизующая сторона у нас — Puppet-мастер. Именно он выдает секрет для доступа к секрету. Puppet-мастер имеет доступ ко всем секретам сразу, но каждому хосту разрешено получать только тот, который ему предназначен. Хост на Puppet-мастере уже авторизован своим сертификатом, который генерируется локально и пределов хоста не покидает. В принципе, секрет для доступа к секрету остается, но это уже не так критично.
Наш процесс выкладки нового секрета в Puppet состоит из следующих этапов.
Puppet-агенты прогоняются у нас каждые 30 мин, поэтому приходится немного подождать, пока выкатится секрет. Проблем это не вызывает — мы выкладываем секреты не каждый день. Пока в дело не включается Kubernetes, накладных расходов немного и мы готовы выкладывать секреты в Vault руками с минимальной автоматизацией.
Дополнительным плюсом мы получаем «фишку» Hiera — секрет можно выкладывать сразу для группы хостов или в зависимости от роли хоста, которую мы выставляем в переменной role.
Единственная опасность: если у вас Puppet, и вы используете Hiera, не подставляйте в пути в шаблоны переменных что попало, потому что многие факты и переменные собираются на клиентской стороне. Если злоумышленник подменит факт на клиенте, Puppet-мастер отдаст ему чужие секреты. Обязательно проверяйте переменные: используйте только те, что Puppet-мастер запрещает определять на клиентской стороне.
Как быть с SCM без мастера?
Если вдруг у вас не Puppet, то, скорее всего, Ansible. Для Chef и других централизованных SCM свои решения — это плагин, который умеет обращаться к Vault. Предлагаю несколько вариантов, которые можно реализовать с Ansible.
Локальный агент
Локально для сервера генерируете токен, который фактически является паролем для доступа к Vault. Токен действует постоянно. Вы можете обновлять его или автоматизировать. С этим токеном в обращаетесь в Vault и забираете свои секреты.
Идея в том, что у вас на сервере, куда надо доставить секреты, крутится агент, который приходит в Vault, смотрит все секреты и выкладывает их в виде файлов. Мы используем агента на нескольких отдельных серверах, где нет Puppet.
Минусы:
Транзитное шифрование
Vault имеет функцию transit encryption, суть которой в том, что Vault выступает сервером шифрования. Вы просто приносите ему открытый текст, а он на своем закрытом ключе, который есть только у него, шифрует и выдает закрытый текст. Дальше вы выбираете, кто этот закрытый текст сможет расшифровать.
У Ansible есть сущность, которая тоже называется Vault. Это — не HashiCorp Vault, а Ansible Vault. Здесь нужно не перепутать, а секреты можно хранить как в первом, так и во втором. В Ansible есть уже готовый плагин для доcтавки секретов из Hashicorp Vault. Если вам дать личный доступ в Vault, то вы сможете расшифровывать секреты. Когда вы накатываете Ansible, он от вашего имени идёт в Vault, расшифровывает секреты, которые в зашифрованном виде находятся в репозитории, и выкатывает в продакшен.
Здесь тоже есть недостаток — каждый администратор получает доступ к секретам. Но есть аудит: Vault умеет вести журнал активности о том, какой пользователь приходил, какой секрет читал, какой получил доступ. Вы всегда знаете, кто, когда и что делал с секретом. Этот вариант мне кажется неплохим.
Большой Недостаток № 1
Самый большой недостаток, который вызывает самую большую боль у нас — то, что в Vault нельзя никому делегировать полное управление какой-то частью данных. В Vault доступ к секрету осуществляется по путям, похожим на пути в UNIX — имена принято разделять слешами, и в результате получается «директория». Когда у вас есть такой путь, иногда вы хотите взять часть пути и отдать её на управление кому-то еще.
![](https://habrastorage.org/r/w1560/webt/w-/hq/gf/w-hqgfwmxgok3_dimlgnn8baxag.png)
Например, вы завели сертификаты, назвали /certs, и хотите отдать отдельным безопасникам, которые занимаются PKI. В Vault не получится этого сделать. Вы не можете дать право выдавать права внутри этого префикса — чтобы безопасники сами могли раздавать права на сертификаты еще кому-то.
В Vault нет возможности выборочно выдавать права на выдачу прав. Как только вы дали право на выдачу прав, то выдали и возможность получить полный доступ ко всем секретам. Другими словами, вы не можете дать доступ на часть Vault.
Это одна из самых больших проблем. У меня есть идея, как ее решить, позже про нее расскажу.
Kubernetes
На РИТ++ я рассказывал про отдельную систему, которую мы реализовали для Kubernetes: она служит третьей стороной, ходит в API, проверяет доступ и потом запрашивает секрет в Vault.
Сейчас наша система потеряла актуальность, потому что в Vault 0.9 появилась нативная поддержка Kubernetes. Теперь Vault сам умеет ходить в Kubernetes и убеждаться, что доступ к секрету разрешён. Делает он это при помощи Service Account Token. Например, когда у вас выкатился pod, там лежит специальный, подписанный и авторизованный для него JWT, предназначенный для запросов к API Kubernetes. С токеном можно также авторизоваться в Vault и получить секреты именно для вашего namespace.
Все делается на уровне самого Vault. Правда, на каждый namespace надо будет заводить роль, то есть сообщать Vault, что есть такой namespace, в нём будет авторизация, и прописывать, куда ходить в Kubernetes. Это делается один раз, а дальше Vault будет сам ходить в API, подтверждать валидность JWT и выдавать свой собственный токен для доступа.
Правила Kubernetes
В плане имени сервисов и дополнительных метаданных мы доверяем разработчикам. Есть маленькая вероятность, что разработчики могут случайно или намеренно получить секреты других сервисов, которые крутятся в одном namespace, поэтому мы ввели правило: один сервис — один namespace.
Новый микросервис? Заводите новый namespace со своими секретами. Перейти через границу в соседний нельзя — там свои Service Account Token. Граница безопасности в Kubernetes на данный момент — это namespace. Если в двух разных namespace нужен один секрет — копируем.
В Kubernetes есть kubernetes secrets. Они хранятся в etcd в Kubernetes в незашифрованном виде и могут «засветиться» в dashboard или при запуске kubectl get pods. Если в вашем кластере отключена аутентификация в etcd, или если вы дали кому-то полный read-only-доступ, то ему видны все секреты. Именно поэтому мы ввели два правила: запрещено использовать kubernetes secrets и запрещено указывать секреты в переменные окружения в манифестах. Если вы в deployment.yaml прописываете секрет в environment — это плохо, потому что сам манифест могут посмотреть все кому не лень.
Доставка Kubernetes
Как я уже сказал, мы должны как-то положить файл в Kubernetes. У нас есть какой-то секрет: сущность, пароль, который в JSON записан в Vault. Как его теперь превратить в файл внутри контейнера в Kubernetes?
![](https://habrastorage.org/r/w1560/webt/gm/ki/wb/gmkiwblvlm7ca4hiwmvhhx14wxu.png)
Первый вариант доставки.
Разработчику всего лишь надо запомнить путь, в котором лежит его секрет.
Мы используем примерно такой префикс:
В имя префикса заложены имя кластера, namespace и имя сервиса. У каждого сервиса свой секрет, в каждом namespace свой секрет.
Второй вариант — это свой собственный entrypoint. К нему мы сейчас переходим в Авито, потому что с init-container у разработчиков естьпроблемы. На схеме этот вариант справа.
Собственный entrypoint не все могут себе позволить. Мы — можем, поэтому в каждом контейнере мы форсим наш специальный entrypoint.
Наш entrypoint делает то же самое, что init-container: идет в Vault с Service Account Token, берет секреты и выкладывает их. Кроме как в файлы, он кладет их еще в environment. Вы получаете возможность запускать приложение как это рекомендуется концепцией Twelve-Factor App: приложение берет все настройки, в том числе, секреты, из переменных окружения.
Переменные окружения не видно в манифестах и дашборде, поскольку их устанавливает PID 1(основной процесс контейнера) при запуске. Это не переменные окружения из deployment.yaml, а переменные окружения, которые выставил entrypoint в процессе работы. Они не видны в dashboard, их не видно, даже если сделать kubectl exec в контейнер, потому что в этом случае запускается другой процесс, параллельный PID1.
Workflow
С организационной точки зрения, процесс идет следующим образом. Разработчик узнаёт от security champion или из документации, что ему нельзя хранить секреты в репозитории, а только в Vault. Тогда он приходит к нам и спрашивает, куда класть секреты — подаёт заявку в security на заведение префикса. В будущем можно создавать префикс без заявки, сразу при создании сервиса.
Разработчик ждет, и это плохо, т.к. для него главное — time-to-market. Дальше он читает инструкции, разбирается с длинными файлами — «вставь туда ту строку, вставь сюда эту строку». Никогда раньше разработчик не заводил init-container, но он вынужден разобраться и прописывать его в deployment.yaml (helm chart).
Коммитит, ждет, пока TeamCity выкатит, видит ошибки в TeamCity, начинает испытывать боль, пытается что-то поправить, снова испытывает боль. Дополнительно накладывается то, что каждая выкатка в TeamCity ещё может встать в очередь. Иногда разработчик не может разобраться сам, приходит к нам, и мы разбираемся вместе.
В основном разработчик страдает из-за собственных ошибок: неправильно задал init-container или не дочитал документацию.
Со стороны Security тоже есть проблемы. Безопасник получает заявку, в которой всегда мало информации, и мы все равно выясняем недостающие вопросы: выясняем имена кластеров, namespace сервиса, так как разработчик не указывает их в заявке и даже не всегда знает, что это такое. Когда всё выясним, создаём политики и роли в Vault, прописываем политики группам и вместе с разработчиком начинаем выяснять, где и почему он ошибся, и вместе читаем логи.
Проблему помогает решить юнит «Архитектура» сокрытием от разработчика deployment.yaml. Они разрабатывают штуку, которая все генерирует за разработчика, в том числе entrypoint. Благодаря тому, что мы подставляем свой entrypoint, мы можем использовать его не только для доставки секретов, но и для других вещей, которые может понадобиться делать при старте.
Очевидные проблемы с секретами Kubernetes.
Мы говорим: «Зачем вам в dev-кластере секреты production? Заведите тестовый секрет, ходите с ним!» В итоге появляются копи и секретов, которыми сложно управлять. Если секрет поменялся, надо про это не забыть, пойти и поменять везде, и пока нет возможности определить, что это один и тот же секрет, кроме как по имени сервиса.
Идея: Kubernetes KMS
В новых версиях Kubernetes появилась подсистема KMS — Key Management Service — новая возможность Kubernetes по шифрованию секретов. В v1.11 она была с состоянии alpha, в v1.12 её перевели в beta.
![](https://habrastorage.org/r/w1560/webt/yq/nw/i9/yqnwi9a_t6atxpxdu_qrtu0qz80.png)
Картинка с сайта проекта провайдера KMS для Vault, и на ней есть ошибка. Если найдете — напишите в комментариях.
Смысл KMS в том, чтобы устранить один-единственный недостаток — нешифрованное хранение данных в etcd.
KMS, как Ansible, умеет вот что.
Разработчики написали специальный сервис, который это делает, используя transit encryption. Идея выглядит рабочей, но важно помнить, что секреты перестают находиться только под контролем Vault и уходят куда-то ещё, в зону ответственности администраторов Kubernetes.
Минусы KMS.
Плюсы KMS.
CI/CD: TeamCity
В TeamCity все просто, потому что JetBrains написали плагин, который сам умеет прописать у себя секреты для доступа к секрету, зашифровать их средствами TeamCity, а потом там же в качестве параметра где-нибудь в шаблоне подставить через проценты. В этот момент агент TeamCity сам сходит в Vault, заберёт секрет и принесёт в билд в виде параметра.
Некоторые секреты нужны при деплое, например, миграции БД или оповещения в Slack. AppRole заводится на каждый проект — настройки тоже содержат секрет (данные для AppRole), но он вводится в режиме write-only — прочитать его потом TeamCity не позволяет.
TeamCity сам заботится о том, чтобы при попадании секрета в логи билда, он автоматически маскировался. В результате секрет либо не «проезжает» через диск совсем, либо вычищается с диска средствами TeamCity. В результате вся безопасность секрета хорошо обеспечена самим TeamCity и плагином, и дополнительных танцев с бубном не требуется
CI/CD не TeamCity?
Вот основные вопросы, над которыми надо подумать, если в качестве CI вы используете другую систему (не TeamCity).
В результате, скорее всего, вы напишете для своей CI/CD что-то очень похожее на плагин TeamCity. Авторизующей стороной здесь будет выступать, скорее всего, CI/CD, и именно она будет решать, можно ли этому билду иметь доступ к этому секрету, и по результатам отдавать или не отдавать сам секрет.
Важно не забывать чистить результаты билда по окончании сборки, если они выложились на диск, либо обеспечить, чтобы они находились только в памяти.
Сертификаты
С сертификатами ничего особенного — мы используем Vault в основном для их хранения.
![](https://habrastorage.org/r/w1560/webt/ix/ye/4j/ixye4jru2blznsbq2jxmljomqi8.png)
Vault имеет специальный PKI backend для выпуска сертификатов, в котором можно заводить Certificate Authority и подписывать новые сертификаты. У нас единый внутренний PKI… Корневой CA и CA второго уровня существуют отдельно, а CA третьего уровня мы уже управляем через Vault. Для хранения выпущенных сертификатов любого уровня, включая сертификаты подписанные внешними CA, мы используем отдельный префикс, и кладём туда почти все действующие сертификаты в целях учёта и мониторинга. Формат хранения сертификатов собственный, подходящий для хранения отдельно закрытого ключа и собственно сертификата.
Резюме
Слишком много ручной работы для безопасника, слишком большой порог вхождения для разработчика и нет встроенных средств для делегирования, хотя очень хочется…
Как быть? Дальше начинаются мечты.
Идеи: как сделать лучше
Как можно избавиться от кучи копий секрета?
Доставка master-slave
У нас есть master-секрет и специальный демон, который ходит, смотрит секрет и его метаданные, выкладывает куда надо, получается slave-секрет. По пути, куда демон выложил slave, ничего нельзя менять руками, потому что демон придет и заново выложит master-секрет поверх slave.
Сначала мы хотели сделать механизм симлинков, чтобы просто указать: «Вот этот секрет ищи там!», как в Linux. Оказалось, что возникают проблемы с правами доступа: неизвестно, как права доступа проверять — как в Linux или нет, с родительскими путями, с переходами между точками монтирования. Слишком много неоднозначных моментов и шансов ошибиться, поэтому от симлинков мы отказались.
Авторизация на основе владения
Второе, что мы хотим сделать — определить владельца для каждого секрета. По умолчанию секрет принадлежит тому, кто его создал. При необходимости можно расширить зону ответственности до юнита, выдав группу-владельца.
Когда мы научимся делегировать, мы выдадим владельцу права на секрет, и он сможет делать с секретом, что хочет.
Сейчас за все секреты и безопасность отвечаем мы, а хотим переложить ответственность на создателя. Безопасность не пострадает, потому что человек, который пришел к нам с запросом хранения секрета, понимает, что ему нужно безопасно хранить секрет и осознает свою ответственность.
Поскольку это владелец секрета, то для варианта доставки master-slave он мог бы выбирать, куда и в каком формате ему доставлять секрет. Получается, что владелец сам всем управляет, не надо подавать заявки, нужный префикс можно занять самостоятельно, секреты тоже можно создавать и удалять самому.
Делегирование через шаблоны ACL
Политика доступа Access Control List в Vault поделена на две части:
Сейчас только администратор Vault может менять ACL. Получив доступ на такую ACL, можно внутрь прописать все, что хочется, например,
Мы хотим задавать ACL готовым шаблоном, который содержит путь и плейсхолдеры, на которых разрешено генерировать новые ACL по этому шаблону.
Пример
Ниже желтым шрифтом записан путь готовой стандартной ACL от Vault и дальше разрешённые действия на этот путь. Мы ее рассматриваем, как ACL на разрешение менять другую ACL внизу, которая задана в виде шаблона.
![](https://habrastorage.org/r/w1560/webt/ew/xe/jc/ewxejc20z7xa7dqg-o8yxwaesxk.png)
Мы хотим делегировать доступ на /k8s, разрешаем генерировать только такие шаблоны. Например, давать доступ только на чтение в конкретный кластер, namespace, сервис, но не менять поле capabilities.
![](https://habrastorage.org/r/w1560/webt/ew/xe/jc/ewxejc20z7xa7dqg-o8yxwaesxk.png)
Дополнительно мы хотим давать разрешение привязывать эти ACL и выдавать разные права.
Мы применили шаблон для выдачи прав разработчику. При шаблонизации он запустил команду
![](https://habrastorage.org/r/w1560/webt/ro/ih/ur/roihurfk7gxhvrv-hjpik6lctxc.png)
В результате разработчик по своему усмотрению через наш плагин присваивает эту ACL кому захочет, а сам становится владельцем, может ей управлять, как секретом: удалять, отдавать и отнимать у пользователей и групп. Теперь человек сам ответственен за свой префикс: он управляет всеми секретами, генерирует по шаблону ACL, может присваивать ACL тем, кому захочет. Естественно, мы тоже можем его ограничить.
Вся магия работает при помощи новой сущности Vault — плагинов. Они представляют собой отдельный сервис, очень похожий на тот, о котором я говорил в начале, и работают почти точно также. Единственное важное отличие — они не прокси. Плагины запускаются «сбоку» от Vault, причём запускает их основной процесс Vault. За счёт этого все запросы идут не через сервис, а в Vault, который уже сам взаимодействует с плагином, отправляя ему проверенный и очищенный запрос.
Про плагины, как они устроены и как их писать, можно почитать на сайте Vault. Писать их лучше всего на Go, что достаточно просто, т.к. для Go есть фреймворк. Vault общается с плагином по grpc, запускает его как сервис, но не надо пугаться, вы его не касаетесь — во фреймворке уже все заложено. Вы просто пишете, более-менее стандартное REST приложение, в котором указываете endpoints, к ним даете готовые функции, хэндлеры, на которых будет логика.
Не бойтесь, что вы что-то сломаете в основном Vault. Плагин — это отдельный сервис. Даже если плагин у вас запаниковал и упал, работу Vault это никак не нарушит. Vault просто перезапустит плагин и будет работать дальше.
Кроме того, есть дополнительные настройки для самого плагина: он обязательно проверяет хэш-суммы, чтобы никто не подменил бинарник. Безопасность запуска плагинов обеспечена.
Сергей Носков (Albibek) — руководитель группы информационной безопасности платформы из Авито, знает ответ на эти вопросы и поделится с нами. В Авито два года активно используют HashiCorp Vault, за это время набили шишки, и прокачали опыт до уровня «Мастер».
В статье всесторонне поговорим про Vault: что это такое, где и как используется в компании, как в Авито управляют секретами с помощью HashiCorp Vault, как используют Puppet и Kubernetes, варианты использования с Puppet и другими SCM, какие возникают проблемы, что болит у безопасников и разработчиков, и, конечно, поделимся идеями, как все исправить.
Что такое секрет
Любая конфиденциальная информация:
- логин и пароль, например, к БД;
- API-ключи;
- ключ сертификата сервера (*.google.com);
- ключ клиентского сертификата (партнёры, Yandex money, QIWI);
- ключ для подписи мобильных приложений.
Всю информацию, которую мы хотим сохранить в тайне, мы называем секретом. Это создает проблему с хранением: в репозитории хранить плохо, в зашифрованном виде — нужно где-то держать ключи шифрования.
HashiCorp Vault — одно из неплохих решений проблемы.
- Безопасно хранит и управляет ключами.
- Заточен на мир микросервисов, так как сам по себе микросервис.
- В HashiCorp Vault много сделано для аутентификации и авторизации доступа к секретам, например, ACL и принцип минимальных привилегий.
- REST интерфейс с JSON.
- Безопасность не идеальна, но на достаточно высоком уровне.
На мой взгляд, это достаточно удобный инструмент.
Что нового в HashiCorp Vault
Инструмент развивается и за последнее время в нем появилось много интересных фич: CORS-заголовки для GUI без посредников; встроенный GUI; нативная интеграция с Kubernetes; плагины для logical- и auth-бэкендов и фреймворк.
Большинство изменений, которые понравились лично мне, — это возможность не писать расширения и дополнения, которые будут стоять снаружи относительно инструмента.
Например, есть Vault, вы хотите его расширить — написать дополнительную логику или свой UI для автоматизации, который будет что-то автоматизировать. До изменений приходилось поднимать дополнительный сервис, который стоит перед Vault, и проксирует все запросы: сначала запросы идут в сервис, потом — в Vault. Это плохо тем, что в промежуточном сервисе может быть пониженный уровень безопасности, а через него идут все секреты. Риски безопасности намного выше, когда секрет проходит через несколько точек сразу!
Проблема курицы и яйца
Когда вы поднимаете вопрос хранения конфиденциальной информации и решаете шифровать, то, как только вы что-то зашифровали, ваш секрет смещается из места шифровки туда, где хранится ключ. Это происходит постоянно: как только вы сохранили куда-то секрет или изменили существующий, у вас появляется еще один и начинается заколдованный круг — где хранить секрет для доступа к секрету.
Секрет доступа к секрету — это та часть безопасности, которая называется аутентификацией. У безопасности есть еще одна часть — авторизация. В процессе авторизации проверяется, можно ли пользователю получить доступ именно туда, куда он запрашивает. В случае с Vault есть доверенная третья сторона, которая решает, выдать секрет или нет. Авторизация лишь частично решает проблему.
HashiCorp Vault в Авито
В Авито HashiCorp установлен в единственной большой инсталляции на всю сеть. У HashiCorp Vault много разных бэкендов. Мы используем бэкенд на базе Consul тоже от HashiCorp, потому что Vault умеет поддерживать собственную отказоустойчивость только через Consul.ь.
Unseal — это способ не держать в одном месте мастер-ключ. Когда запускается Vault, он все шифрует на каком-то ключе, и снова появляется проблема курицы и яйца: где хранить секрет, которым будут шифроваться все остальные секреты. Чтобы избежать этой проблемы, в Vault предусмотрен составной ключ, для получения которого требуется несколько частей ключа, которые мы раздаём нескольким сотрудникам. В Авито мы настроили в опциях Unseal на 3 человека из 7. Если мы запускаем Vault, то чтобы он начал работать, обязательно должны прийти как минимум 3 человека и ввести свою часть ключа. Ключ поделен на 7 частей и можно принести любые из них.
Мы собрали маленький тестовый Vault — песочницу для разработчиков, где они могут играть. Он есть в виде Docker-контейнера и создает простые секретики, чтобы люди могли потрогать инструмент руками, освоиться. В песочнице нет Consul и кластеризации, это просто файловая система, на которой Vault держит зашифрованные секреты, и маленький скрипт для инициализации.
Вот что мы сейчас храним в Vault:
- Практически все секреты для микросервисов Kubernetes: пароли от баз, API-ключи, всё, что перечислено выше.
- Секреты для выкладки на «железные» серверы и LXC.
- Секреты для билдов CI/CD в TeamCity мы тоже кладем в Vault. Покрытие не 100%, но вполне приемлемое.
- Ключи всех сертификатов: внутренний PKI, внешние CA, например, GeoTrust и подобных.
- Общие секреты для команд.
Внутри себя Vault хранит всё только в JSON, это не всегда удобно и требует дополнительных действий от разработчика, поэтому в основном мы выкладываем секреты в виде файла.
Мы стараемся доставлять секреты в виде файлов.
Мы не говорим разработчику: «Иди в Vault, бери секрет!», а выкладываем на диск файл и сообщаем: «Разработчик, у тебя на диске появится файл, забирай секрет из него, а мы уже разберемся, как достать его из Vault и принести тебе».
![](https://habrastorage.org/webt/rg/9o/mj/rg9omjizdszcnw0lzrr_iogfosc.png)
Мы приняли простое соглашение для полей JSON, в котором указываем, с какими правами выкладывать файл. Это метаданные для файловой системы, а поле data — это закодированная строка c самим секретом, которая станет содержимым файла.
Puppet + Hiera + Vault
Практически во всей инфраструктуре Авито используется Puppet, им раскатываются все серверы.
У Puppet есть удобный инструмент организации иерархии — Hiera. Vault очень хорошо интегрируется с Hiera через дополнительный модуль, потому что в эту библиотеку идёт запрос ключ-значение, а Vault сам база ключ-значение, но со всеми функциями безопасности — с прозрачным шифрованием и возможностью выбора доступа к ключам.
Поэтому первое, что мы внедрили — это Vault в Puppet, но с одним дополнением — у нас есть промежуточный слой, который называется Router backend. Router backend — отдельный модуль Hiera, просто файлы на диске, в которых написано, куда Hiera должна идти за ключом — в Vault или в другое место.
Он нужен, чтобы Hiera не ходила в Vault постоянно, потому что она идет всегда по всей иерархии. Это не проблема Vault или нагрузки на него, а особенность работы самой Hiera. Поэтому если оставить только модуль для Vault без Router backend, Puppet-мастер будет очень долго собирать конфигурацию для Puppet-агента, так как будет проверять каждый ключ в Vault.
![](https://habrastorage.org/webt/dv/di/f6/dvdif6z09jpflg98zykbwf7l7cq.png)
Для Puppet проблема курицы и яйца решается за счёт того, что авторизующая сторона у нас — Puppet-мастер. Именно он выдает секрет для доступа к секрету. Puppet-мастер имеет доступ ко всем секретам сразу, но каждому хосту разрешено получать только тот, который ему предназначен. Хост на Puppet-мастере уже авторизован своим сертификатом, который генерируется локально и пределов хоста не покидает. В принципе, секрет для доступа к секрету остается, но это уже не так критично.
Наш процесс выкладки нового секрета в Puppet состоит из следующих этапов.
- Мы где-то берём секрет — нам его кто-то отдает или выкладывает.
- Кладем секрет в Vault, с иерархией как в Hiera: /puppet/role/www/site.ssl.key.
- Прописываем в манифест Puppet префикс, с указанием, что файл лежит в Vault и где его брать.
- Прописываем в YAML для Hiera router путь в Vault и указание на backend, чтобы Hiera могла его найти.
- Pull request через GIT в репозиторий манифестов.
- Прогоняем или дожидаемся прогона Puppet-агента.
Puppet-агенты прогоняются у нас каждые 30 мин, поэтому приходится немного подождать, пока выкатится секрет. Проблем это не вызывает — мы выкладываем секреты не каждый день. Пока в дело не включается Kubernetes, накладных расходов немного и мы готовы выкладывать секреты в Vault руками с минимальной автоматизацией.
Дополнительным плюсом мы получаем «фишку» Hiera — секрет можно выкладывать сразу для группы хостов или в зависимости от роли хоста, которую мы выставляем в переменной role.
Единственная опасность: если у вас Puppet, и вы используете Hiera, не подставляйте в пути в шаблоны переменных что попало, потому что многие факты и переменные собираются на клиентской стороне. Если злоумышленник подменит факт на клиенте, Puppet-мастер отдаст ему чужие секреты. Обязательно проверяйте переменные: используйте только те, что Puppet-мастер запрещает определять на клиентской стороне.
Как быть с SCM без мастера?
Если вдруг у вас не Puppet, то, скорее всего, Ansible. Для Chef и других централизованных SCM свои решения — это плагин, который умеет обращаться к Vault. Предлагаю несколько вариантов, которые можно реализовать с Ansible.
Локальный агент
Локально для сервера генерируете токен, который фактически является паролем для доступа к Vault. Токен действует постоянно. Вы можете обновлять его или автоматизировать. С этим токеном в обращаетесь в Vault и забираете свои секреты.
Идея в том, что у вас на сервере, куда надо доставить секреты, крутится агент, который приходит в Vault, смотрит все секреты и выкладывает их в виде файлов. Мы используем агента на нескольких отдельных серверах, где нет Puppet.
Минусы:
- Токен легко вписать в небольшой сегмент, но если у вас деплоятся несколько десятков серверов в день, придется для каждого сервера генерировать токен и прописывать политику. Это неудобно.
- Токен надо обновлять.
- Группировка серверов по роли, назначению или фактам, затруднена, её надо синхронизировать с Vault.
Транзитное шифрование
Vault имеет функцию transit encryption, суть которой в том, что Vault выступает сервером шифрования. Вы просто приносите ему открытый текст, а он на своем закрытом ключе, который есть только у него, шифрует и выдает закрытый текст. Дальше вы выбираете, кто этот закрытый текст сможет расшифровать.
У Ansible есть сущность, которая тоже называется Vault. Это — не HashiCorp Vault, а Ansible Vault. Здесь нужно не перепутать, а секреты можно хранить как в первом, так и во втором. В Ansible есть уже готовый плагин для доcтавки секретов из Hashicorp Vault. Если вам дать личный доступ в Vault, то вы сможете расшифровывать секреты. Когда вы накатываете Ansible, он от вашего имени идёт в Vault, расшифровывает секреты, которые в зашифрованном виде находятся в репозитории, и выкатывает в продакшен.
Здесь тоже есть недостаток — каждый администратор получает доступ к секретам. Но есть аудит: Vault умеет вести журнал активности о том, какой пользователь приходил, какой секрет читал, какой получил доступ. Вы всегда знаете, кто, когда и что делал с секретом. Этот вариант мне кажется неплохим.
Большой Недостаток № 1
Самый большой недостаток, который вызывает самую большую боль у нас — то, что в Vault нельзя никому делегировать полное управление какой-то частью данных. В Vault доступ к секрету осуществляется по путям, похожим на пути в UNIX — имена принято разделять слешами, и в результате получается «директория». Когда у вас есть такой путь, иногда вы хотите взять часть пути и отдать её на управление кому-то еще.
![](https://habrastorage.org/webt/w-/hq/gf/w-hqgfwmxgok3_dimlgnn8baxag.png)
Например, вы завели сертификаты, назвали /certs, и хотите отдать отдельным безопасникам, которые занимаются PKI. В Vault не получится этого сделать. Вы не можете дать право выдавать права внутри этого префикса — чтобы безопасники сами могли раздавать права на сертификаты еще кому-то.
В Vault нет возможности выборочно выдавать права на выдачу прав. Как только вы дали право на выдачу прав, то выдали и возможность получить полный доступ ко всем секретам. Другими словами, вы не можете дать доступ на часть Vault.
Это одна из самых больших проблем. У меня есть идея, как ее решить, позже про нее расскажу.
Kubernetes
На РИТ++ я рассказывал про отдельную систему, которую мы реализовали для Kubernetes: она служит третьей стороной, ходит в API, проверяет доступ и потом запрашивает секрет в Vault.
Сейчас наша система потеряла актуальность, потому что в Vault 0.9 появилась нативная поддержка Kubernetes. Теперь Vault сам умеет ходить в Kubernetes и убеждаться, что доступ к секрету разрешён. Делает он это при помощи Service Account Token. Например, когда у вас выкатился pod, там лежит специальный, подписанный и авторизованный для него JWT, предназначенный для запросов к API Kubernetes. С токеном можно также авторизоваться в Vault и получить секреты именно для вашего namespace.
Все делается на уровне самого Vault. Правда, на каждый namespace надо будет заводить роль, то есть сообщать Vault, что есть такой namespace, в нём будет авторизация, и прописывать, куда ходить в Kubernetes. Это делается один раз, а дальше Vault будет сам ходить в API, подтверждать валидность JWT и выдавать свой собственный токен для доступа.
Правила Kubernetes
В плане имени сервисов и дополнительных метаданных мы доверяем разработчикам. Есть маленькая вероятность, что разработчики могут случайно или намеренно получить секреты других сервисов, которые крутятся в одном namespace, поэтому мы ввели правило: один сервис — один namespace.
Новый микросервис? Заводите новый namespace со своими секретами. Перейти через границу в соседний нельзя — там свои Service Account Token. Граница безопасности в Kubernetes на данный момент — это namespace. Если в двух разных namespace нужен один секрет — копируем.
В Kubernetes есть kubernetes secrets. Они хранятся в etcd в Kubernetes в незашифрованном виде и могут «засветиться» в dashboard или при запуске kubectl get pods. Если в вашем кластере отключена аутентификация в etcd, или если вы дали кому-то полный read-only-доступ, то ему видны все секреты. Именно поэтому мы ввели два правила: запрещено использовать kubernetes secrets и запрещено указывать секреты в переменные окружения в манифестах. Если вы в deployment.yaml прописываете секрет в environment — это плохо, потому что сам манифест могут посмотреть все кому не лень.
Доставка Kubernetes
Как я уже сказал, мы должны как-то положить файл в Kubernetes. У нас есть какой-то секрет: сущность, пароль, который в JSON записан в Vault. Как его теперь превратить в файл внутри контейнера в Kubernetes?
![](https://habrastorage.org/webt/gm/ki/wb/gmkiwblvlm7ca4hiwmvhhx14wxu.png)
Первый вариант доставки.
- Мы заводим специальный init-container.
- Он запускается с нашего образа.
- В образе лежит маленькая утилита, которая с Service Account Token идет в Vault, забирает секрет и выкладывает его в Shared volume.
- Для утилиты монтируется специальный Shared volume только в памяти TMPFS, чтобы секреты не проходили через диск.
- Init-container идет в Vault, выкладывает в этот volume в виде файлов все секреты, которые найдёт по указанному пути.
- Дальше Shared volume монтируется в основной контейнер, в котором он требуется.
- Когда основной контейнер запустился, он сразу получает то, что надо разработчику — секреты в виде файла на диске.
Разработчику всего лишь надо запомнить путь, в котором лежит его секрет.
Мы используем примерно такой префикс:
/k8s/<cluster>/<namespace>/<service>/some_secret
В имя префикса заложены имя кластера, namespace и имя сервиса. У каждого сервиса свой секрет, в каждом namespace свой секрет.
Второй вариант — это свой собственный entrypoint. К нему мы сейчас переходим в Авито, потому что с init-container у разработчиков естьпроблемы. На схеме этот вариант справа.
Собственный entrypoint не все могут себе позволить. Мы — можем, поэтому в каждом контейнере мы форсим наш специальный entrypoint.
Наш entrypoint делает то же самое, что init-container: идет в Vault с Service Account Token, берет секреты и выкладывает их. Кроме как в файлы, он кладет их еще в environment. Вы получаете возможность запускать приложение как это рекомендуется концепцией Twelve-Factor App: приложение берет все настройки, в том числе, секреты, из переменных окружения.
Переменные окружения не видно в манифестах и дашборде, поскольку их устанавливает PID 1(основной процесс контейнера) при запуске. Это не переменные окружения из deployment.yaml, а переменные окружения, которые выставил entrypoint в процессе работы. Они не видны в dashboard, их не видно, даже если сделать kubectl exec в контейнер, потому что в этом случае запускается другой процесс, параллельный PID1.
Workflow
С организационной точки зрения, процесс идет следующим образом. Разработчик узнаёт от security champion или из документации, что ему нельзя хранить секреты в репозитории, а только в Vault. Тогда он приходит к нам и спрашивает, куда класть секреты — подаёт заявку в security на заведение префикса. В будущем можно создавать префикс без заявки, сразу при создании сервиса.
Разработчик ждет, и это плохо, т.к. для него главное — time-to-market. Дальше он читает инструкции, разбирается с длинными файлами — «вставь туда ту строку, вставь сюда эту строку». Никогда раньше разработчик не заводил init-container, но он вынужден разобраться и прописывать его в deployment.yaml (helm chart).
Commit -> deploy -> feel pain -> fix -> repeat
Коммитит, ждет, пока TeamCity выкатит, видит ошибки в TeamCity, начинает испытывать боль, пытается что-то поправить, снова испытывает боль. Дополнительно накладывается то, что каждая выкатка в TeamCity ещё может встать в очередь. Иногда разработчик не может разобраться сам, приходит к нам, и мы разбираемся вместе.
В основном разработчик страдает из-за собственных ошибок: неправильно задал init-container или не дочитал документацию.
Со стороны Security тоже есть проблемы. Безопасник получает заявку, в которой всегда мало информации, и мы все равно выясняем недостающие вопросы: выясняем имена кластеров, namespace сервиса, так как разработчик не указывает их в заявке и даже не всегда знает, что это такое. Когда всё выясним, создаём политики и роли в Vault, прописываем политики группам и вместе с разработчиком начинаем выяснять, где и почему он ошибся, и вместе читаем логи.
Проблему помогает решить юнит «Архитектура» сокрытием от разработчика deployment.yaml. Они разрабатывают штуку, которая все генерирует за разработчика, в том числе entrypoint. Благодаря тому, что мы подставляем свой entrypoint, мы можем использовать его не только для доставки секретов, но и для других вещей, которые может понадобиться делать при старте.
Очевидные проблемы с секретами Kubernetes.
- Очень сложный workflow и для разработчика, и для безопасника.
- Нельзя никому ничего делегировать. У безопасника есть полный доступ в Vault, а дать частичный доступ невозможно (см. Большой недостаток № 1).
- Возникают трудности при переезде разработчиков из кластера в кластер, из namespace в namespace, когда нужны расшаренные секреты, потому что изначально предполагается, что в разных кластерах разные секреты.
Мы говорим: «Зачем вам в dev-кластере секреты production? Заведите тестовый секрет, ходите с ним!» В итоге появляются копи и секретов, которыми сложно управлять. Если секрет поменялся, надо про это не забыть, пойти и поменять везде, и пока нет возможности определить, что это один и тот же секрет, кроме как по имени сервиса.
Идея: Kubernetes KMS
В новых версиях Kubernetes появилась подсистема KMS — Key Management Service — новая возможность Kubernetes по шифрованию секретов. В v1.11 она была с состоянии alpha, в v1.12 её перевели в beta.
![](https://habrastorage.org/webt/yq/nw/i9/yqnwi9a_t6atxpxdu_qrtu0qz80.png)
Картинка с сайта проекта провайдера KMS для Vault, и на ней есть ошибка. Если найдете — напишите в комментариях.
Смысл KMS в том, чтобы устранить один-единственный недостаток — нешифрованное хранение данных в etcd.
KMS, как Ansible, умеет вот что.
- Сходить куда-нибудь, зашифровать родной нативный секрет Kubernetes и положить его в зашифрованном виде.
- По необходимости доставить в pod, расшифровать и выложить в расшифрованном виде.
Разработчики написали специальный сервис, который это делает, используя transit encryption. Идея выглядит рабочей, но важно помнить, что секреты перестают находиться только под контролем Vault и уходят куда-то ещё, в зону ответственности администраторов Kubernetes.
Минусы KMS.
- Децентрализация хранения — вынос из Vault в Kubernetes (etcd). Секреты становятся неподконтрольными Vault, а он хорош как централизованное хранилище секретов. Получается, что половина секретов в Vault, а половина где-то ещё.
- Kubernetes-only решение. Если у вас Kubernetes-only инфраструктура, вы поднимаете Vault, и почти не думаете, что там хранится, т.к. в нём лежат только ключи шифрования, которыми вы правильно управляете — регулярно ротируете и т.п… Сами секреты при этом находятся в Kubernetes, и это удобно.
- Сложно делить секреты между кластерами. Для каждого нового кластера нужно заводить всё по новой, копировать секреты как в случае с единым Vault может не получиться.
Плюсы KMS.
- Нативная поддержка в Kubernetes, включая скрытие при показе environment.
- Авторизация в зоне ответственности Kubernetes.
- Практически не надо поддерживать Vault.
- Ротация ключей из коробки.
CI/CD: TeamCity
В TeamCity все просто, потому что JetBrains написали плагин, который сам умеет прописать у себя секреты для доступа к секрету, зашифровать их средствами TeamCity, а потом там же в качестве параметра где-нибудь в шаблоне подставить через проценты. В этот момент агент TeamCity сам сходит в Vault, заберёт секрет и принесёт в билд в виде параметра.
Некоторые секреты нужны при деплое, например, миграции БД или оповещения в Slack. AppRole заводится на каждый проект — настройки тоже содержат секрет (данные для AppRole), но он вводится в режиме write-only — прочитать его потом TeamCity не позволяет.
TeamCity сам заботится о том, чтобы при попадании секрета в логи билда, он автоматически маскировался. В результате секрет либо не «проезжает» через диск совсем, либо вычищается с диска средствами TeamCity. В результате вся безопасность секрета хорошо обеспечена самим TeamCity и плагином, и дополнительных танцев с бубном не требуется
CI/CD не TeamCity?
Вот основные вопросы, над которыми надо подумать, если в качестве CI вы используете другую систему (не TeamCity).
- Изоляция: ограничить область видимости секрета проектом, командой и т.д.
- Кто авторизует доступ к секрету.
- Исключить возможность просматривать секрет авторизующей стороны.
- Отдельным этапом билда импортировать секрет в файлы.
- Почистить за собой.
В результате, скорее всего, вы напишете для своей CI/CD что-то очень похожее на плагин TeamCity. Авторизующей стороной здесь будет выступать, скорее всего, CI/CD, и именно она будет решать, можно ли этому билду иметь доступ к этому секрету, и по результатам отдавать или не отдавать сам секрет.
Важно не забывать чистить результаты билда по окончании сборки, если они выложились на диск, либо обеспечить, чтобы они находились только в памяти.
Сертификаты
С сертификатами ничего особенного — мы используем Vault в основном для их хранения.
![](https://habrastorage.org/webt/ix/ye/4j/ixye4jru2blznsbq2jxmljomqi8.png)
Vault имеет специальный PKI backend для выпуска сертификатов, в котором можно заводить Certificate Authority и подписывать новые сертификаты. У нас единый внутренний PKI… Корневой CA и CA второго уровня существуют отдельно, а CA третьего уровня мы уже управляем через Vault. Для хранения выпущенных сертификатов любого уровня, включая сертификаты подписанные внешними CA, мы используем отдельный префикс, и кладём туда почти все действующие сертификаты в целях учёта и мониторинга. Формат хранения сертификатов собственный, подходящий для хранения отдельно закрытого ключа и собственно сертификата.
Резюме
Слишком много ручной работы для безопасника, слишком большой порог вхождения для разработчика и нет встроенных средств для делегирования, хотя очень хочется…
Как быть? Дальше начинаются мечты.
Идеи: как сделать лучше
Как можно избавиться от кучи копий секрета?
Доставка master-slave
У нас есть master-секрет и специальный демон, который ходит, смотрит секрет и его метаданные, выкладывает куда надо, получается slave-секрет. По пути, куда демон выложил slave, ничего нельзя менять руками, потому что демон придет и заново выложит master-секрет поверх slave.
Сначала мы хотели сделать механизм симлинков, чтобы просто указать: «Вот этот секрет ищи там!», как в Linux. Оказалось, что возникают проблемы с правами доступа: неизвестно, как права доступа проверять — как в Linux или нет, с родительскими путями, с переходами между точками монтирования. Слишком много неоднозначных моментов и шансов ошибиться, поэтому от симлинков мы отказались.
Авторизация на основе владения
Второе, что мы хотим сделать — определить владельца для каждого секрета. По умолчанию секрет принадлежит тому, кто его создал. При необходимости можно расширить зону ответственности до юнита, выдав группу-владельца.
Когда мы научимся делегировать, мы выдадим владельцу права на секрет, и он сможет делать с секретом, что хочет.
- Выкладывать в k8s — генерируется политика, создаётся slave-копия.
- Выкладывать на сервер — генерируется политика, создаётся slave-копия.
- Выкладывать в CI/CD — ...
- Передавать другому владельцу.
- Давать новый доступ, генерировать новые ACL.
Сейчас за все секреты и безопасность отвечаем мы, а хотим переложить ответственность на создателя. Безопасность не пострадает, потому что человек, который пришел к нам с запросом хранения секрета, понимает, что ему нужно безопасно хранить секрет и осознает свою ответственность.
Поскольку это владелец секрета, то для варианта доставки master-slave он мог бы выбирать, куда и в каком формате ему доставлять секрет. Получается, что владелец сам всем управляет, не надо подавать заявки, нужный префикс можно занять самостоятельно, секреты тоже можно создавать и удалять самому.
Делегирование через шаблоны ACL
Политика доступа Access Control List в Vault поделена на две части:
- Access Control List в классическом представлении, который описывает доступ к префиксу, по какому пути можно читать и писать, по какому только читать и т.д.
- При создании ACL внутри можно прописывать звездочку на конце, что означает «этот префикс, и все, что ниже него». Префикс можно присваивать отдельной операцией, отдавать пользователю или группе, то есть привязать на несколько разных сущностей.
Сейчас только администратор Vault может менять ACL. Получив доступ на такую ACL, можно внутрь прописать все, что хочется, например,
path “*” { capabilities = [sudo, ...] }
, и получить полный доступ. Это суть Большого недостатка № 1 — невозможно запретить менять со держимое ACL.Мы хотим задавать ACL готовым шаблоном, который содержит путь и плейсхолдеры, на которых разрешено генерировать новые ACL по этому шаблону.
Пример
Ниже желтым шрифтом записан путь готовой стандартной ACL от Vault и дальше разрешённые действия на этот путь. Мы ее рассматриваем, как ACL на разрешение менять другую ACL внизу, которая задана в виде шаблона.
![](https://habrastorage.org/webt/ew/xe/jc/ewxejc20z7xa7dqg-o8yxwaesxk.png)
Мы хотим делегировать доступ на /k8s, разрешаем генерировать только такие шаблоны. Например, давать доступ только на чтение в конкретный кластер, namespace, сервис, но не менять поле capabilities.
![](https://habrastorage.org/webt/ew/xe/jc/ewxejc20z7xa7dqg-o8yxwaesxk.png)
Дополнительно мы хотим давать разрешение привязывать эти ACL и выдавать разные права.
Мы применили шаблон для выдачи прав разработчику. При шаблонизации он запустил команду
$ vault write policy-mgr/create/k8s-microservice ...
. И в результате получили ACL, в которой указано cluster=prod, namespace=..., service=… и т.д. Права проставились автоматически, создалась политика с именем /k8s/some-srv
— это просто имя ACL, которое можно генерировать по шаблону.![](https://habrastorage.org/webt/ro/ih/ur/roihurfk7gxhvrv-hjpik6lctxc.png)
В результате разработчик по своему усмотрению через наш плагин присваивает эту ACL кому захочет, а сам становится владельцем, может ей управлять, как секретом: удалять, отдавать и отнимать у пользователей и групп. Теперь человек сам ответственен за свой префикс: он управляет всеми секретами, генерирует по шаблону ACL, может присваивать ACL тем, кому захочет. Естественно, мы тоже можем его ограничить.
Вся магия работает при помощи новой сущности Vault — плагинов. Они представляют собой отдельный сервис, очень похожий на тот, о котором я говорил в начале, и работают почти точно также. Единственное важное отличие — они не прокси. Плагины запускаются «сбоку» от Vault, причём запускает их основной процесс Vault. За счёт этого все запросы идут не через сервис, а в Vault, который уже сам взаимодействует с плагином, отправляя ему проверенный и очищенный запрос.
Про плагины, как они устроены и как их писать, можно почитать на сайте Vault. Писать их лучше всего на Go, что достаточно просто, т.к. для Go есть фреймворк. Vault общается с плагином по grpc, запускает его как сервис, но не надо пугаться, вы его не касаетесь — во фреймворке уже все заложено. Вы просто пишете, более-менее стандартное REST приложение, в котором указываете endpoints, к ним даете готовые функции, хэндлеры, на которых будет логика.
Не бойтесь, что вы что-то сломаете в основном Vault. Плагин — это отдельный сервис. Даже если плагин у вас запаниковал и упал, работу Vault это никак не нарушит. Vault просто перезапустит плагин и будет работать дальше.
Кроме того, есть дополнительные настройки для самого плагина: он обязательно проверяет хэш-суммы, чтобы никто не подменил бинарник. Безопасность запуска плагинов обеспечена.
Полезные ссылки:
- www.vaultproject.io
- github.com/jovandeginste/hiera-router
- github.com/jsok/hiera-vault
- www.owasp.org/index.php/Security_Champions
- blog.jetbrains.com/teamcity/2017/09/vault
- github.com/oracle/kubernetes-vault-kms-plugin
Про DevOps и безопасность, CI/CD, k8s, Puppet и всё в таком духе будем говорить на HighLoad++ (ближайший в Питере в апреле) и на DevOpsConf. Приходите поделиться своим опытом или посмотреть на других. Чтобы не забыть, подпишитесь на блог и рассылку, в которой мы будем напоминать о дедлайнах и собирать полезные материалы.