Задайте себе вопрос — как правильно хранить пароль от базы данных, которая используется вашим сервисом? В отдельном репозитории с секретами? В репозитории приложения? В системе деплоя (Jenkins, Teamcity, etc)? В системе управления конфигурациями? Только на личном компьютере? Только на серверах, на которых работает ваш сервис? В некоем хранилище секретов?
Зачем об этом думать? Чтобы минимизировать риски безопасности вашей инфраструктуры.
Начнём исследование вопроса с определения требований к хранению секретов.
Требования к способу хранения секретов инфраструктуры:
- Тонкая настройка правил доступа к секретам. Даём доступы только к тем секретам, которые необходимы и достаточны для выполнения работ.
- Управление жизненным циклом доступов. Возможность выдавать, отзывать, устанавливать срок жизни и перевыпускать или продлять доступы.
- Аудит доступа к секретам. Каждый акт доступа записан и будет проверен третьей стороной.
- Минимальный периметр атаки. Чем меньше "размазанность" по системе, тем лучше.
- Отказоустойчивость. Отсутствие единой точки отказа.
- Удобная работа с секретами для людей и автоматических систем. Минимум времени на обучение и внедрение новых инструментов.
Примерим наши требования к возможным решениям:
- Хранение в репозитории (любом):
- Нет гибкой настройки доступов к секретам. Доступ бинарен — либо есть, либо нет. Есть решения этой проблемы: для файлов используют gpg. Для систем управления конфигурациями используют Ansible vault, Puppet gpg-hiera, Chef encrypted data bags и так далее. Проблемы начинаются там, где появляется автоматизация. Злоумышленник, получивший доступ на сервер, где хранятся ключи для расшифровки (мастер-сервер для PuppetChef, хост для автоматического запуска плейбуков Ansible, хост для автоматических запусков задач, которым нужны шифрованные данные из репозитория), получает доступ к секретам.
- Нет поддержки полного жизненного цикла доступа. Вы не можете дать временный доступ. Вы не можете отозвать доступ к секретам (можете только сменить доступы к репозиторию и сами секреты в нём). Отсутствие регулярного процесса обновления доступов уменьшает прозрачность.
- Нет аудита.
- Непредсказуемая "размазанность" по системе. Каждое появление этого репозитория в инфраструктуре добавляет вам точек атаки. Будь то Jenkins, разные chefpuppet-сервера для prod/dev/test/uat окружений или необходимость запускать по крону с одного сервера маленький скриптик, который хранится в этом репозитории — вы получаете риски в обмен на 'удобство' и возможность не думать о безопасности.
- Хранение в системе деплоя:
- Нет гибкой настройки доступов к секретам. Человек, обладающий доступом к системе деплоя, может использовать секреты в своей задаче — и таким образом получить к ним доступ. Если вы используете Jenkins, то секреты можно вытащить на уровне сервера.
- Нет поддержки полного жизненного цикла доступа.
- Ручное подкладывание секретов на сервера:
- Нет поддержки полного жизненного цикла доступа.
- Нет аудита. Вы не знаете, кто и когда получил доступ к секретам.
- Нет отказоустойчивости. Если секрет хранится только на одном сервере — потеря сервера грозит потерей секрета.
- Нет удобства работы. Но этот способ можно использовать, когда у вас немного серверов и сервисов. С ростом инфраструктуры вас ждёт удорожание обслуживания процесса управления секретами.
Почему Vault?
Vault - хранилище секретов от признанных "решателей" проблем современной инфраструктуры — Hashicorp, авторов Vagrant, Consul, Terraform, Otto, etc. Секреты хранятся в key-value виде. Доступ к хранилищу осуществляется исключительно через API.
Основные фичи Vault:
- Все данные хранятся в зашифрованном контейнере. Получение самого контейнера не раскрывает данные.
- Гибкие политики доступа. Вы можете создать столько токенов для доступа и управления секретами, сколько вам нужно. И дать им те разрешения, которые необходимы и достаточны для выполнения работ.
- Возможность аудирования доступа к секретам. Каждый запрос к Vault будет записан в лог для последующего аудита.
- Поддерживается автоматическая генерация секретов для нескольких популярных баз данных (postgresql, mysql, mssql, cassandra), для rabbitmq, ssh и для aws.
- Поддержка шифрования-дешифрования данных без их сохранения. Это может быть удобно для передачи данных в зашифрованном виде по незащищённым каналам связи.
- Поддержка полного жизненного цикла секрета: создание/отзыв/завершение срока хранения/продление.
- Уберфича, значимость которой сложно переоценить, это возможность создания собственного CA (Certificate Authority) для управления самоподписанными сертификатами внутри своей инфраструктуры.
- Бэкенд /cubbyhole, который позволяет создать собственное хранилище секретов, не доступное даже другим root-токенам.
- Готовые модули и плагины для популярных систем управления конфигурацией.
Для нас Vault решает проблемы передачи секретов по незащищённым каналам, проблемы отказоустойчивого хранения секретов, а также проблемы гибкого разделения и аудита доступов. В планах использовать Vault как собственный CA.
Начало работы
Не буду переводить официальную документацию, поэтому вот несколько ссылок:
Итак, вы запустили Vault. Первым делом положим наш ключ в хранилище. Официальный процесс работы выглядит так:
- Для каждого сервиса создаём отдельный мастер-токен. Этот токен умеет управлять секретами в контейнере /secret/service_name/ и умеет создавать другие токены.
- Переключаемся на этот токен и создаём секрет.
- Выпускаем новый токен для чтения этого секрета.
- Скармливаем этот токен нашей системе автоматизации
- …
- PROFIT!!!
Дальше начинаются нюансы. Разберу их подробнее.
О мастер-токене для каждого сервиса
Оказалось неудобным постоянно переключать токены, если ты отвечаешь за несколько сервисов.
Из-за особенности управления политиками доступа (подробнее ниже) выросли накладные расходы на первичное внедрение Vault в рабочий процесс сервиса.
Про особенности управления политиками доступа
Они описываются в формате JSON или HCL и записываются в Vault с помощью запроса к API или через cli. Например, так:
$ vault policy-write policy_name policy_file.hcl
Чтобы созданный вами мастер-токен мог создавать новые токены, он должен иметь политику:
path "auth/token/create" {
policy = "write"
}
Мастер-токен может создавать токены только с теми политиками, которыми он обладает.
Это означает, что вам недостаточно выдать токену политику service_name_prod_root:
path "secret/service_name/prod/*" {
policy = "write"
}
которая обладает полным доступом к secret/service_name/prod/*, но также нужно озаботиться, политикой service_name_prod_read:
path "secret/service_name/prod/*" {
policy = "read"
}
и тогда вы сможете создавать токены для read-only доступа с помощью команды:
$ vault token-create -policy=service_name_prod_read
И тут есть нюанс: политики применяются по степени детализации. Это означает, что если вы сделаете root политику вида:
path "secret/service_name/prod/*" {
policy = "write"
}
и захотите дать доступ на чтение политике service_name_prod_database_read:
path "secret/service_name/prod/database/*" {
policy = "read"
}
то вам нужно будет назначить токену, управляющему этим сервисом обе этих политики. Когда вы это сделаете, вы не сможете писать в secret/service_name/prod/database/*:
$ vault write secret/service_name/prod/database/replicas
secret_key=sha1blabla
Error writing data to secret/service_name/prod/database/replicas: Error making API request.
URL: PUT https://vault.service.consul:8200/v1/secret/service_name/prod/database/replicas
Code: 400. Errors:
* permission denied
Вам придётся уравновесить каждую детализированную политику на чтение, чтобы этого не случилось. Например, видоизменить политику service_name_prod_root:
path "secret/service_name/prod/*" {
policy = "write"
}
path "secret/service_name/prod/database/*" {
policy = "write"
}
Это не делает жизнь операторов легче, поэтому мы отказались от этого подхода и работаем с секретами только из под root-ключей.
Об учёте и обновлении токенов
В документации к Vault зря не сказано о важности учёта выпущенных токенов.
Нет простого способа узнать, какие ключи присутствуют в системе. Если вы создали токен и забыли о нём, не записав никакой информации, то вам придётся дожидаться, пока у него закончится срок жизни.
У root-токенов нет срока жизни, поэтому они будут оставаться в вашей системе бесконечно. Чтобы избежать этой ситуации, для создания нового токена в целях тестирования и проверки, укажите прекрасный флаг -ttl="1h", который устанавливает время жизни.
Это позволит спокойно работать и не бояться бесконтрольно увеличить количество токенов.
Также есть риск не уследить за истечением срока жизни токена и узнать об этом, например, во время деплоя.
Эту проблему можно решить, записывая токены и мониторя срок жизни. Но записывать токены куда-то в одно место — небезопасно. Поэтому с версии Vault 0.5.2 каждый токен после создания возвращает параметр accessor, зная который, можно взаимодействовать с токеном, не зная его самого (отзывать, обновлять, добавлять политики), например
$ vault token-revoke --accessor b30ee2a3-ea4b-9da0-3e5c-4189d375cad9
Подробности тут.
Ведение данных о выданных токенах позволяет проводить быстрый аудит активных доступов в системе и настроить мониторинг на истечение срока жизни токена.
О параметрах токена
$ vault token-create -policy=service_name_prod_root -policy=service_name_prod_read
Key Value
--- -----
token 82c5fb97-da1b-1d2c-cfd5-23fa1dca7c85
token_accessor dd256e17-b9d9-172d-981b-a70422e12cb8
token_duration 2592000
token_renewable true
token_policies [default, service_name_prod_root, service_name_prod_read ]
Пройдём по возвращённым параметрам:
token - тот самый ключ доступа к Vault.
token_accessor - ключ, по которому можно производить действия с ключом, не имея самого ключа. Разрешены следующие действия: посмотреть на метаданные токена, отозвать токен.
token_duration - время жизни токена в секундах.
token_renewable - если true, то время жизни токена может быть обновлено, но не более чем на срок от времени создания до параметра max-lease-ttl, который по умолчанию также 30d. Это означает, что если вы создали токен со сроком жизни в 30 дней и максимальный срок жизни тоже 30 дней, то обновить вы его не сможете.
token_policies - политики, которые назначены токену. Список политик изменить невозможно, возможно только отозвать токен и пересоздать заново.
Неочевидности и полезности
Если токен был родительским для других токенов, то по умолчанию при отзыве этого токена все токены и секреты, созданные отзываемым токеном, отзываются рекурсивно. Этого можно избежать, указав флаг -mode. Более подробно
vault token-revoke -h
Если вы будете использовать consul-template для автоматической перегенерации конфигов при изменении секретов, имейте в виду (и этого вы не найдёте в документации), что consul-template опрашивает изменение секрета в двух случаях:
- Старт или рестарт самого consul-template.
- Каждые (TimeToLive секрета/2) секунд после старта consul-template.
Чтобы не вводить свой рабочий токен постоянно, его можно положить в $HOME/.vault-token или в переменную окружения VAULT_TOKEN. Тут надо оговориться, что я по умолчанию считаю рабочую станцию админа защищённой (зашифрованные диски, отсутствие гостевого входа, автоблокирование через минуту неактивности). Если это не так — то стоит отказаться от этой идеи.
Наш процесс работы:
Нам не подошёл официальный процесс работы с Vault по причине излишней трудоёмкости внедрения Vault в эксплуатацию сервиса. Поделюсь нашими решениями и ответами на возникающие вопросы в процессе внедрения Vault.
- Отказоустойчивость:
Мы используем Consul в инфраструктуре, поэтому выбрали его как отказоустойчивый бэкенд для Vault. - Количество ключей для распечатки хранилища:
Распечатывание - исключительно ручной процесс, во время которого зашифрованный контейнер с секретами помещается в память и восстанавливается мастер-ключ для расшифровки. Подробности тут. Дать возможность распечатать хранилище одному человеку — риск, потому что при компрометации этого ключа или ключей злоумышленники получат доступ к хранилищу (но пока не к секретам). Надо понимать, что если вы не наберёте необходимый минимум ключей для распечатывания хранилища, доступ к данным будет утерян. Мы решили, что нам достаточно 4 владельцев ключей и 2 ключей для расшифровки. - Root токены и их роль в системе:
Официальная позиция Hashicorp — использовать эти токены только для управления политиками, ролями, настройками системы и для выдачи токенов для сервисов. Чем меньше root ключей в системе, тем меньше вероятности компрометации ключей. Но чем меньше таких ключей в системе, тем больше всё завязывается на определённых людей, которые болеют, ходят в отпуск, в общем — бывают недоступны. Мы начали с 4 root-ключей на группу из 7 человек. Это было неудобно, поэтому мы в первый раз отступили от официального процесса — выдали root-токены всем инженерам эксплуатации и перестали создавать мастер-токены для управления секретами сервиса. Все операции производятся под root-токенами. Создаются только read-only токены. - Схема хранения секретов:
Для нас я выбрал следующую схему — храним секреты сервисов в /secret/service_name/env. А обще-инфраструктурные ключи (например, api-token для jenkins или реквизиты для доступа к пакетному репозиторию) храним в /secret/infra/*. -
Именование секретов:
$ vault write secret/service_name/prod/database base=appname login=appname password=difficult_password
или, например,
$ vault write secret/service_name/prod/database/base value=appname $ vault write secret/service_name/prod/database/login value=appname $ vault write secret/service_name/prod/database/password value=difficult_password
Решите этот вопрос до того, как ваш список секретов начнёт быстро расти и вы начнёте прикручивать различные средства автоматизации. Мы используем первую схему.
- Учёт ключей и мониторинг их времени жизни:
По-умолчанию максимальный срок жизни НЕ root токена равен 30 дням. Мы ведём учёт accessor-ключей для токенов с помощью репозитория — и это неудобно. Зато позволило настроить мониторинг, который сообщает об истечении срока жизни ключей за 5 дней.
В планах завести небольшой сервис для учёта ключей и написать небольшую обёртку для автоматизации некоторых действий с токенами (например, автоматическую запись вновь созданного токена в сервис).
Вернёмся к хранению секрета от базы данных
Мы установили и запустили Vault, получили root-токен. Что дальше?
По последней версии процесса, принятого в моей компании, это будет выглядеть так:
Установим vault.
Укажем расположение Vault:
$ export VAULT_ADDR='https://vault.service.consul:8200'
Авторизуемся под root токеном:
$ vault auth 82c5fb97-da1b-1d2c-cfd5-23fa1dca7c85
Запишем наш секрет:
$ vault write secret/service_name/prod/database base=appname login=appname password=difficult_password
Запишем политику для чтения в файл service_name_prod_read.hcl:
path "secret/service_name/prod/database*" {
policy = "read"
}
Создадим политику в Vault:
$ vault policy-write service_name_prod_read service_prod_read.hcl
Сгенерируем токен для чтения:
$ vault token-create -policy=service_name_prod_read
Key Value
--- -----
token cb347ae0-9eb4-85d1-c556-df43e82be4b0
token_accessor c8996492-17e3-16a7-2af1-d58598ae10d8
token_duration 2592000
token_renewable true
token_policies [default, service_name_prod_read ]
Запишем token_accessor для последующего аудита и мониторинга.
Проверим, что есть доступ на чтение:
$ vault auth cb347ae0-9eb4-85d1-c556-df43e82be4b0
$ vault read secret/service_name/prod/database
Key Value
lease_duration 2592000
base appname
login appname
password difficult_password
Всё работает, мы готовы использовать наш токен в системах автоматизации. Приведу примеры для популярных систем.
Просто curl:
$ curl
-H "X-Vault-Token:cb347ae0-9eb4-85d1-c556-df43e82be4b0"
https://vault.service.consul:8200/v1/secret/service_name/prod/database
Ansible (мы используем https://github.com/jhaals/ansible-vault):
Настраиваем окружение:
$ export VAULT_ADDR=https://vault.service.consul:8200
$ export VAULT_TOKEN=cb347ae0-9eb4-85d1-c556-df43e82be4b0
Используем переменные из Vault в роли:
database_password: "{{ lookup('vault', 'secret/service_name/prod/database', 'password') }}"
Мы делаем запросы к vault только на уровне group_vars/group_name. Это удобно и позволяет не искать переменные по роли.
Chef:
Hashicorp в своём блоге описали несколько путей использования секретов из Vault в своих chef-кукбуках — https://www.hashicorp.com/blog/using-hashicorp-vault-with-chef.html
Puppet:
Для puppet существует прекрасный модуль https://github.com/jsok/hiera-vault, из документации к которому понятен процесс использования секретов из Vault.
Consul-template:
Необходимо либо иметь токен и адрес Vault в переменных окружения:
$ export VAULT_ADDR=https://vault.service.consul:8200
$ export VAULT_TOKEN=cb347ae0-9eb4-85d1-c556-df43e82be4b0
или добавить в конфиг строчки:
vault {
address = "https://vault.service.consul:8200"
token = "cb347ae0-9eb4-85d1-c556-df43e82be4b0"
renew = true
}
и использовать секреты в своих шаблонах:
{{with secret "secret/service_name/prod/database"}}{{.Data.password}}{{end}}
Более подробно — в readme
Спасибо за потраченное время, надеюсь, это было полезно.
Готов ответить на ваши вопросы.
Автор: bhavenger