Меня зовут Юрий Шуткин, я инфраструктурный инженер в Тинькофф. В этой статье расскажу, как мы запустили сервис по хранению важной информации и избавились от небезопасной передачи секретов.
Секретами мы называем важную информацию, которую нельзя хранить в открытом виде: пароли, токены, сертификаты.
Простого указания недостаточно, чтобы люди стали безопасно хранить и передавать секреты. Коллеги загружены, и вопросы безопасности слишком часто оставляются на потом, которое на практике редко наступает. Поэтому мы хотели сделать простой и удобный сервис, чтобы им было приятно пользоваться и вопросы безопасности не страдали.
Почему HashiCorp Vault
Мы выбрали HashiCorp Vault, потому что у него есть встроенный ACL, аудит логов, хранилище Key Value, интеграция с Active Directory, можно не дублировать пользователей, PKI — и все это работает из коробки. У Vault отличный задокументированный API, которым удобно пользоваться даже из командной строки.
Начиная с версии 1.0.0 в OSS версии Vault появился веб-интерфейс. До этого он был только в платной Enterprise-версии. Мы используем OSS — нам достаточно бесплатной функциональности.
Когда заходишь внутрь секрета в HashiCorp Vault, сама секретная информация скрыта. Чтобы ее посмотреть, нужно нажать кнопку «Копировать» или иконку «глаз». В варианте с версионированием можно копировать секрет или создать новую версию.
Как мы используем HashiCorp Vault
У нас несколько типов пользователей, которые используют Vault по-разному:
Сотрудники, которым нужно лишь хранилище секретов, доступное через веб-интерфейс, — это тестировщики, которые не деплоят, а просто забирают логин и пароль, авторизуются и проверяют данные в сервисе.
HR, финтех-преподаватели, которым нужно хранить общие секреты — логины и пароли.
Коллеги, которые создают сервисные учетные записи и передают заказчикам реквизиты. Передавать их в открытом виде небезопасно, поэтому создатель учетной записи заносит реквизиты в формате JSON, нажимает кнопку Wrap, получает токен и отправляет его пользователю. Тот проверяет, что токен валиден, и дальше его расшифровывает. Если кто-то до него расшифровал этот токен или время жизни токена истекло, программа выдаст ошибку. Так мы узнаем, что секрет ушел не туда, и можем вовремя его заменить. Описан ручной вариант, но создать обертку можно с помощью API-запроса в Vault и развернуть ее через API.
Роботы и автоматизация — это различные API-запросы к Vault: приложения заходят в него, забирают секреты и живут с ними.
Одно из требований к системе — отказоустойчивость. Покажу примерную схему, как мы ее обеспечивали:
Чтобы понимать, как все работает, добавили Prometheus — ПО для мониторинга приложений. Мы собирали статистику с Nginx, Vault и Consul, чтобы видеть, насколько все хорошо или плохо. Но этого тоже не хватало, и мы добавили бота, который ходит в Vault и симулирует жизнь пользователей. Он авторизуется под различными аутентификационными бэкендами, например LDAP, Userpass, Approle. Бот пишет, читает реквизиты и пытается отозвать токен. Так мы проверяем, что сервис — действительно сервис, а не 200 OK, белая страница и ничего не работает на самом деле.
Несколько раз при обновлении версии Vault этот бот помог обнаружить проблемы с авторизацией. По метрикам бота можно строить графики. На рисунке — гистограмма по времени авторизации:
Основная фича Vault — Barrier, его задача — зашифровать данные, которые уходят в Storage и расшифровать те, что приходят обратно. Storage всегда зашифрован, и это третья инсталляция Vault:
Шифрование использует определенный алгоритм ключей, и без них RAW-данные из хранилища сложно расшифровать. Однажды я потерял ключи от первых двух инсталляций и не смог получить доступ.
Vault использует мастер-ключ для расшифровки своего хранилища. Мастер-ключ никому не известен, он даже в Vault не хранится и существует лишь в памяти процесса. Когда в первый раз запускаешь Vault, появляется окно с просьбой ввести максимальное количество ключей и информацией, сколько нужно ключей, чтобы собрать мастер-ключ.
У нас семь инженеров, владельцев ключей. Как в фильме «Властелин колец»: все семь колец правят миром, а три из них могут объединить свои ключи, чтобы получить мастер-ключ, расшифровать Vault и выпустить рутокен.
Если проинициализировать Vault и перезапустить его, появляется окно с просьбой ввести части ключей. Мы настроили хитрые локейшены, чтобы в любой момент отправить запрос на распечатывание определенной ноды Vault: /unseal/node1/unseal/node2...
Мы зашифровали ключи индивидуальными GPG-ключами и сохранили в Git. Это нужно для того, чтобы их можно было легко найти в одном месте. После каждой операции ротации ключей Rekey
генерируется новый мастер-ключ. Старые ключи прекращают работать, но с их помощью можно распечатать старые бэкапы, что пару раз было очень кстати.
Однажды мне позвонили в два часа ночи, потому что две трети кластера не работали и появлялась картинка «Vault запечатан». Пользователи пришли на работающую ноду, которая автоматически перебросила их на зашифрованную. И вроде авторизовался, но Vault не работает.
Это произошло, потому что сама веб-страница через равные интервалы времени отправляет запрос в Vault /sys/seal-status. Ответ на такой запрос у любой запущенной ноды — «200», но в теле ответа от запечатанной ноды написано, что Vault запечатан и не работает. У нас был простой конфиг для Nginx с перечислением апстримов до нод Vault без проверки на запечатанность. API-запросы с ответом 5хх или по таймауту автоматически отправляются в другой бэкенд, пока не переберут все имеющиеся. Если хотя бы одна нода Vault доступна, API работает.
Сначала мы вывели запечатанные ноды из ротации, чтобы работа веб-интерфейса восстановилась. Vault должен быть доступен 24/7, на него полагается большая часть автоматизации. Приложения не смогут проинициализироваться при старте, если Vault недоступен и не отдает секреты.
Я ввел часть ключа и активировал его для расшифровки Vault, но нужно было еще два. Мы стали искать тех, с кем можно распечатать Vault. Один инженер был в отпуске, до второго не удалось дозвониться. Третий пытался подключиться к своей машине по RDP, но что-то пошло не так, и машина зависла. Четвертый оказался доступен и помог. Утром мы перезагрузили машину третьего инженера, и он помог нам распечатать ноды Vault.
Последняя пара инженеров так часто пользовалась PGP-ключом, что один не вспомнил пароль, а второй забыл перенести PGP-ключ на новое оборудование. Тогда в проекте участвовали только два инженера, остальные просто хранили ключи и не были готовы к звонку.
История с ночными звонками показала, как нужно строить отказоустойчивость. Vault уже давно разрешил использовать облачные системы Key Management, но наша служба безопасности не рекомендовала их.
Мы хотели поднять приложение, например на Flask, загрузить в его память ключи, чтобы приложение регулярно пыталось ходить на каждую ноду Vault и распечатывать ее. К счастью, HashiCorp релизнул функцию печати кластера с помощью другого кластера.
Мы подняли еще один кластер, настроили его, и теперь он распечатывает основной кластер Vault. Это позволяет работать на Unseal-кластере с полной его недоступностью в течение суток, прежде чем пойдут проблемы в проде. С новым кластером проблем почти не бывает, он не нагружен, там никого нет, кроме иногда приходящих инженеров и робота. Теперь схема усложнилась:
Нам все еще нужно хранить Unseal-ключи для распечатывания второго кластера, но в прод-окружении у нас нет ключей. За счет того, что в основном кластере настроено автораспечатывание, мы немного больше защитили инсталляцию: нет группы инженеров, которые могут создать Root-токен продового окружения.
Как мы конфигурируем Vault
Нам нужно было конфигурировать объекты внутри Vault, чтобы управлять аутентификационными бэкендами и учетными записями в них. Рассматривали Terraform — опенсорс от компании HashiCorp, но обнаружили, что он мог удалить бэкенд при изменении времени жизни токена или при изменении описания бэкенда. Это риски, с которыми не хотелось сталкиваться.
Terraform не подошел, но с Vault можно работать через REST API. В Ansible есть модуль, который умеет работать через REST API, и экспертность — рассмотрели его как инструмент для управления объектами внутри Vault. Мы хотели добиться единообразия и простоты в описании объектов.
В итоге у нас 150+ каталогов c проектами в прод-окружении, в каждом каталоге есть свой набор файлов, в которых описан проект, группы AD, сервисные УЗ, политики проекта, кому и куда можно.
Как применяли конфигурации
Я был первым инженером в проекте и мог все катить сам. Быстро появился поток обращений «Юра, когда применится?» и «Юра, давай применим вот это». За пару месяцев получилось больше 70 проектов, и число растет до сих пор.
Чтобы решить проблему с потоком, мы настроили интеграцию с AWX — это «запускатель» Ansible. Однажды из-за бага AWX не подгрузилась одна переменная и мы вайпнули целое окружение — к счастью, тестовое, на котором проверяем новые версии и конфиги Vault.
Мы сохраняли не только регулярные бэкапы, но и бэкапы, запускаемые перед применением изменений. После вливания Merge Request в мастер-ветку смогли восстановить окружение. Бонусом настроили уведомления о применении конфигов в канал корпоративного мессенджера, чтобы не отвечать на вопросы «применилось ли».
Как создаем бэкапы
Первые бэкапы запускались в Cron. Но вручную проверять, как все запустилось, — трата времени, поэтому сделали скрипт, который отдавал метрики в систему мониторинга.
Чтобы снимать бэкапы по требованию, запустили Webhook-сервис. Webhook — это приложение, написанное на Go. С его помощью можно зайти на специальный локейшен по порту 9000 (дефолтный), c нужным секретом запустить бэкап либо запустить восстановление бэкапа при обращении к другому локейшену.
Регулярные бэкапы по Cron тоже запускаются через Webhook. Мы получим уведомление, если с момента последнего бэкапа прошло слишком много времени, а также с помощью Blackbox-экспортера можем проверить, что Webhook доступен.
Однажды Vault стал тормозить. Беглый осмотр метрик показал, что бэкап разросся с 70 Мб до 2 Гб и продолжал расти каждый час. Проверили логи: никаких ошибок, стандартные операции чтения и записи. Построили выборки по аудит-логу, нашли приложение, которое регулярно логинится в Vault. Время жизни токена было 32 дня, а использовался он несколько минут.
Мы уменьшили время жизни токенов после этого случая и добавили в процесс бэкапирования анализатор бэкапа. Это утилита, которая считает объекты в бэкапе и отдает метрики. Так мы можем понимать, как утилизируется хранилище Vault, кто сколько потребляет.
Как Vault помогает нам
Убрали логины и пароли из публичных мест.
Создали дружелюбный сервис для пользователей, чтобы они не терялись, куда нажать, как заводить секреты.
Обеспечили возможность передавать секретные данные через публичный канал. Это враппинг: мы зашифровали наше сообщение, оно живет в течение заданного интервала времени, все можно сделать через API и автоматизировать.
Сделали полностью динамические окружения, которые завязаны на Vault. Машины генерируют все логины и пароли, сразу складывают это в Vault, и пользователи приходят туда, смотрят реквизиты, могут авторизоваться, проверить, что все работает. Мы защитились от захардкоженного значения, одного маленького и единственного, которое 1000 лет никто не проверял, либо оно всегда вроде работало, и всегда этот пароль был точно такой же в Вики.
Обновить реквизиты стало проще. Мы получили единый источник правды, и нам достаточно поменять секреты в одном месте, а не идти в несколько систем и обновлять значения в них. Если приложение умеет само подгружать все изменения, оно сходит, обновит у себя значение и будет пользоваться новым паролем. Если приложение этого не умеет, можем запустить деплой. У нас все передеплоилось, и нет вопросов, все ли мы поменяли секреты, должны ли словить ошибку о том, что где-то что-то не работает.
Vault — интересный ресурс, с его помощью можно много всего автоматизировать, и его API довольно прост. Нам помогает в жизни автоматизация и шаблонизация. Наши проекты стандартизированы, за счет этого проще добавлять новые.