Pull to refresh

Как не хранить секреты где придётся, или зачем нам Hashicorp Vault

Reading time 11 min
Views 99K

Vault header 


Задайте себе вопрос — как правильно хранить пароль от базы данных, которая используется вашим сервисом? В отдельном репозитории с секретами? В репозитории приложения? В системе деплоя (Jenkins, Teamcity, etc)? В системе управления конфигурациями? Только на личном компьютере? Только на серверах, на которых работает ваш сервис? В некоем хранилище секретов?
Зачем об этом думать? Чтобы минимизировать риски безопасности вашей инфраструктуры.
Начнём исследование вопроса с определения требований к хранению секретов.



Требования к способу хранения секретов инфраструктуры:


  • Тонкая настройка правил доступа к секретам. Даём доступы только к тем секретам, которые необходимы и достаточны для выполнения работ.
  • Управление жизненным циклом доступов. Возможность выдавать, отзывать, устанавливать срок жизни и перевыпускать или продлять доступы.
  • Аудит доступа к секретам. Каждый акт доступа записан и будет проверен третьей стороной.
  • Минимальный периметр атаки. Чем меньше "размазанность" по системе, тем лучше.
  • Отказоустойчивость. Отсутствие единой точки отказа.
  • Удобная работа с секретами для людей и автоматических систем. Минимум времени на обучение и внедрение новых инструментов.

Примерим наши требования к возможным решениям:


  • Хранение в репозитории (любом):
    • Нет гибкой настройки доступов к секретам. Доступ бинарен — либо есть, либо нет. Есть решения этой проблемы: для файлов используют gpg. Для систем управления конфигурациями используют Ansible vault, Puppet gpg-hiera, Chef encrypted data bags и так далее. Проблемы начинаются там, где появляется автоматизация. Злоумышленник, получивший доступ на сервер, где хранятся ключи для расшифровки (мастер-сервер для Puppet\Chef, хост для автоматического запуска плейбуков Ansible, хост для автоматических запусков задач, которым нужны шифрованные данные из репозитория), получает доступ к секретам.
    • Нет поддержки полного жизненного цикла доступа. Вы не можете дать временный доступ. Вы не можете отозвать доступ к секретам (можете только сменить доступы к репозиторию и сами секреты в нём). Отсутствие регулярного процесса обновления доступов уменьшает прозрачность.
    • Нет аудита.
    • Непредсказуемая "размазанность" по системе. Каждое появление этого репозитория в инфраструктуре добавляет вам точек атаки. Будь то Jenkins, разные chef\puppet-сервера для 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.


Начало работы


Не буду переводить официальную документацию, поэтому вот несколько ссылок:


  • Getting started можно пройти вот тут.
  • Тут интерактивная обучалка.
  • Вот ещё одна прекрасная статья.

Итак, вы запустили Vault. Первым делом положим наш ключ в хранилище. Официальный процесс работы выглядит так:


  1. Для каждого сервиса создаём отдельный мастер-токен. Этот токен умеет управлять секретами в контейнере /secret/service_name/ и умеет создавать другие токены.
  2. Переключаемся на этот токен и создаём секрет.
  3. Выпускаем новый токен для чтения этого секрета.
  4. Скармливаем этот токен нашей системе автоматизации
  5. 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


Спасибо за потраченное время, надеюсь, это было полезно.
Готов ответить на ваши вопросы.

Tags:
Hubs:
+42
Comments 14
Comments Comments 14

Articles