Один из самых частых вопросов, которые нам задают по поводу использования Terraform для управления инфраструктурой как кодом, — это как обращаться с секретами, такими как пароли, ключи API и другие конфиденциальные данные. Например, вот фрагмент кода Terraform, который можно использовать для развертывания MySQL с помощью Amazon RDS:
Обратите внимание, что Terraform требует указать два секрета — имя пользователя (username) и пароль (password), которые являются учетными данными для главного пользователя базы данных. В этой статье я рассмотрю наиболее распространенные методы безопасного управления такими секретами:
- Предварительное условие №1: Не храните секреты в открытом виде
- Предварительное условие №2: Обеспечьте безопасность состояния Terraform
- Метод №1: Переменные окружения
- Метод №2: Зашифрованные файлы (например, KMS, PGP, SOPS)
- Метод №3: Хранилища секретов (например, Vault, AWS Secrets Manager)
Предварительное условие №1: Не храните секреты в открытом виде
Первое правило управления секретами гласит:
Не храните секреты в открытом виде.
Второе правило управления секретами гласит:
НЕ ХРАНИТЕ СЕКРЕТЫ В ОТКРЫТОМ ВИДЕ.
Серьезно, не делайте этого. Например, НЕ зашивайте учетные данные базы данных прямо в код Terraform и не добавляйте их в систему контроля версий:
Хранение секретов в открытом виде в системе контроля версий — ПЛОХАЯ ИДЕЯ. Вот лишь несколько причин почему:
- Любой, у кого есть доступ к системе контроля версий, имеет доступ к этому секрету. В примере выше каждый разработчик в вашей компании имеет доступ к главным учетным данным вашей базы данных.
- Каждый компьютер, имеющий доступ к системе контроля версий, хранит копию этого секрета. На каждом компьютере, когда-либо склонировавшем этот репозиторий, все еще может оставаться копия этого секрета на локальном жестком диске. Сюда относятся компьютеры всех разработчиков в вашей команде, все компьютеры, задействованные в CI (например, Jenkins, CircleCI, GitLab и т.д.), все компьютеры системы контроля версий (GitHub, GitLab, BitBucket), все компьютеры, задействованные в развертывании (все ваши среды pre-prod и prod), все компьютеры для резервного копирования (CrashPlan, Time Machine и т.д.) и так далее.
- Любое запущенное вами программное обеспечение имеет доступ к этому секрету. Поскольку секреты хранятся в открытом виде на таком количестве жестких дисков, любая программа, работающая на любом из этих компьютеров, потенциально может прочитать этот секрет.
- Отсутствует способ аудита или отзыва доступа к этому секрету. Когда секреты хранятся на сотнях жестких дисков в открытом виде, вы не можете узнать, кто к ним обращался (нет журнала аудита), и не можете отозвать доступ.
Короче говоря, если вы храните секреты в открытом виде, вы даете злоумышленникам (хакерам, конкурентам, недовольным бывшим сотрудникам) бесчисленные способы получить доступ к самым конфиденциальным данным вашей компании — например, путем компрометации системы контроля версий, любого из ваших компьютеров или любой программы на них, — и вы даже не узнаете о взломе и не сможете легко все исправить.
Поэтому я настоятельно рекомендую всегда хранить секреты в зашифрованном виде. Это относится ко всем секретам, а не только к тем, что используются с Terraform! Далее в этой статье я расскажу о нескольких различных методах шифрования и дешифрования таких секретов.
Предварительное условие №2: Обеспечьте безопасность состояния Terraform
Будем надеяться, что предыдущий раздел убедил вас не хранить секреты в открытом виде, а следующие разделы покажут вам несколько методов шифрования секретов. Однако независимо от выбранного метода шифрования с вашей стороны, есть одно место, где они все равно окажутся в открытом виде: состояние Terraform.
Каждый раз, когда вы развертываете инфраструктуру с помощью Terraform, она сохраняет множество данных об этой инфраструктуре, включая все переданные вами параметры, в файл состояния. По умолчанию это файл terraform.tfstate, который автоматически создается в папке, где вы выполнили команду terraform apply. Так что даже если вы используете один из упомянутых ниже безопасных способов передачи секретов (например, учетных данных базы данных):
Эти секреты все равно попадут в terraform.tfstate в открытом виде! Эта проблема остается открытой уже более 6 лет, и четких планов по ее решению на уровне самого инструмента пока нет. Существуют обходные пути, позволяющие удалять секреты из файлов состояния, но они ненадежны и могут сломаться при любом новом релизе Terraform, поэтому я их не рекомендую.
В настоящее время, независимо от того, какой из описанных ниже методов вы решите использовать для управления секретами, вы также должны:
- Хранить состояние Terraform в бэкенде, поддерживающем шифрование. Вместо хранения состояния в локальном файле terraform.tfstate, Terraform из коробки поддерживает различные бэкенды, такие как S3, GCS и Azure Blob Storage. Многие из этих бэкендов поддерживают шифрование, поэтому файлы состояния никогда не будут храниться в открытом виде: они всегда зашифрованы как при передаче (например, по протоколу TLS), так и на диске (например, с помощью AES-256). Большинство бэкендов также поддерживают функции совместной работы (например, автоматическую отправку и получение состояния, блокировку), поэтому использование бэкенда является обязательным с точки зрения безопасности и командной работы. Дополнительную информацию см. в разделе «Как управлять состоянием Terraform».
- Строго контролировать доступ к вашему бэкенду Terraform. Поскольку файлы состояния Terraform могут содержать секреты, вам нужно тщательно контролировать, у кого есть доступ к бэкенду, используемому для хранения этих файлов. Например, если вы используете S3 в качестве бэкенда, вам следует настроить политику IAM, которая разрешает доступ к S3-бакету для продакшна только небольшой группе доверенных разработчиков (или исключительно CI-серверу, используемому для деплоя в продакшн).
А теперь, без лишних слов, давайте обсудим различные методы управления секретами с помощью Terraform.
Метод №1: Переменные окружения
Этот первый метод защищает ваш код от секретов в открытом виде за счет использования встроенной поддержки чтения переменных окружения в Terraform.
Обзор
Чтобы использовать этот метод, объявите переменные для секретов, которые хотите передать:
Обновление от 3 декабря 2020 г.: В Terraform 0.14 появилась возможность помечать переменные как чувствительные (sensitive), что помогает скрывать их в логах, поэтому вам следует добавить sensitive = true для обеих переменных выше!
Затем передайте переменные ресурсам Terraform, которым нужны эти секреты:
Теперь вы можете передать значение для каждой переменной foo, установив переменную окружения с именем TF_VAR_foo. Например, вот как вы можете задать имя пользователя и пароль через окружение в Linux, Unix или Mac, а затем запустить terraform apply для развертывания базы данных:
(Полезный совет: если у вас правильно настроена переменная окружения HISTCONTROL в терминале Bash, любая команда, начинающаяся с пробела, не будет сохраняться в истории Bash. Используйте это при установке переменных окружения с секретами, чтобы избежать сохранения этих секретов на диске).
Эта техника помогает избежать хранения секретов в виде простого текста в коде, но оставляет открытым вопрос о том, как на самом деле безопасно хранить секреты и управлять ими. Так что в определенном смысле этот подход лишь откладывает проблему на потом, в то время как другие методы, описанные далее в этой статье, являются более конкретными.
Тем не менее, чтобы не оставлять вас в неведении: если вы все же решите использовать переменные окружения, самым распространенным решением для хранения секретов и управления ими является использование менеджера паролей, например:
- 1Password: SaaS-инструмент, который предлагает (а) настольные и мобильные приложения для всех платформ и (b) облачную синхронизацию, позволяющую получить доступ к вашим секретам на всех устройствах и работать в команде.
- LastPass: SaaS-инструмент, который предлагает (а) настольные и мобильные приложения для всех платформ и (b) облачную синхронизацию, позволяющую получить доступ к вашим секретам на всех устройствах и работать в команде.
- pass: Инструмент с открытым исходным кодом, следующий философии Unix: это инструмент командной строки, он выполняет ввод и вывод через текстовые потоки (т.е. stdin, stdout), а под капотом хранит все в виде файлов, причем каждый секрет находится в собственном файле, зашифрованном с помощью PGP (эти зашифрованные файлы можно добавлять в систему контроля версий для командной работы).
Эти инструменты решают проблему «откладывания на потом», полагаясь на человеческую память: то есть на вашу способность запомнить пароль, который дает вам доступ к менеджеру паролей.
Пример использования pass
Давайте рассмотрим быстрый пример использования pass. Сначала вам нужно сохранить секреты с помощью команды pass insert:
Вы можете вывести секрет в stdout, запустив pass <secret>:
Вы можете использовать эту функциональность в субшелле, чтобы установить секреты в качестве переменных окружения, а затем вызвать terraform apply:
Преимущества этого подхода
- Секреты в виде простого текста не попадают в ваш код и систему контроля версий.
- Простое решение для начала работы.
- Интеграция с большинством других решений для управления секретами: например, если у вашей компании уже есть способ управления секретами, вы обычно можете заставить его работать с переменными окружения.
- Удобно для тестирования: при написании тестов для вашего кода Terraform (например, с помощью Terratest) не нужно настраивать зависимости (например, хранилища секретов), так как вы можете легко установить переменные окружения в фиктивные значения.
Недостатки этого подхода
- Не все определено в самом коде Terraform. Это усложняет понимание и поддержку кода.
- Каждый, кто использует ваш код, должен знать о необходимости совершения дополнительных шагов: либо вручную задавать эти переменные окружения, либо запускать вспомогательный скрипт.
- Никаких гарантий или мнений относительно безопасности. Поскольку управление всеми секретами происходит за пределами Terraform, код не обеспечивает соблюдение каких-либо свойств безопасности, и вполне возможно, что кто-то все еще управляет секретами небезопасным способом (например, хранит их в виде простого текста).
Техника № 2: Зашифрованные файлы (например, KMS, PGP, SOPS)
Второй подход заключается в шифровании секретов, сохранении зашифрованного текста в файл и добавлении этого файла в систему контроля версий.
Обзор
Чтобы зашифровать некоторые данные, например секреты в файле, вам нужен ключ шифрования. Этот ключ сам по себе является секретом! Это создает определенную дилемму: как безопасно хранить этот ключ? Вы не можете добавить ключ в систему контроля версий в виде простого текста, иначе шифрование чего-либо с его помощью теряет смысл. Вы можете зашифровать ключ другим ключом, но тогда вам придется выяснить, где хранить этот второй ключ. Так что вы возвращаетесь к проблеме «откладывания на потом», поскольку вам все равно нужно найти безопасный способ хранения ключа шифрования.
Наиболее распространенным решением этой дилеммы является хранение ключа в службе ключей, предоставляемой вашим облачным провайдером, такой как:
- AWS KMS
- GCP KMS
- Azure Key Vault
Эти службы ключей решают проблему «откладывания на потом», полагаясь на человеческую память: в данном случае на вашу способность запомнить пароль, который дает вам доступ к вашему облачному провайдеру (или, возможно, вы храните этот пароль в менеджере паролей и вместо этого запоминаете пароль от него).
Пример использования AWS KMS
Вот пример того, как можно использовать ключ, управляемый AWS KMS, для шифрования секретов. Сначала создайте файл db-creds.yml с вашими секретами:
Примечание: НЕ добавляйте этот файл в систему контроля версий!
Затем зашифруйте этот файл с помощью команды aws kms encrypt и запишите полученный зашифрованный текст в файл db-creds.yml.encrypted:
Теперь вы можете безопасно добавить db-creds.yml.encrypted в систему контроля версий.
Чтобы расшифровать секреты из этого файла в вашем коде Terraform, вы можете использовать источник данных aws_kms_secrets (для GCP KMS или Azure Key Vault вместо этого используются источники данных google_kms_secret или azurerm_key_vault_secret соответственно):
Приведенный выше код прочитает db-creds.yml.encrypted с диска и, при условии наличия у вас прав на доступ к соответствующему ключу в KMS, расшифрует содержимое для получения исходного YAML. Вы можете разобрать YAML следующим образом:
И теперь вы можете прочитать имя пользователя и пароль из этого YAML и передать их в ресурс aws_db_instance:
Одна из проблем такого подхода заключается в том, что работать с зашифрованными файлами неудобно. Чтобы внести изменения, вам нужно локально расшифровать файл с помощью длинной команды aws kms decrypt, внести изменения, снова зашифровать файл с помощью другой длинной команды aws kms encrypt и все это время быть предельно осторожным, чтобы случайно не добавить незашифрованные данные в систему контроля версий и не оставить их навсегда на своем компьютере. Это утомительный и подверженный ошибкам процесс, если только вы не используете такой инструмент, как sops.
Пример использования AWS KMS с sops и Terragrunt
sops — это инструмент с открытым исходным кодом, разработанный для упрощения редактирования и работы с файлами, зашифрованными с помощью AWS KMS, GCP KMS, Azure Key Vault или PGP. sops может автоматически расшифровывать файл при его открытии в текстовом редакторе, поэтому вы можете редактировать файл в виде простого текста, а при сохранении этих файлов он автоматически снова шифрует содержимое. Это избавляет от необходимости запускать длинные команды aws kms для шифрования или расшифровки данных, а также от беспокойства о том, что секреты в виде простого текста будут случайно добавлены в систему контроля версий. Вот .gif, показывающий sops в действии:
Terraform пока не имеет встроенной поддержки расшифровки файлов в формате, используемом sops. Одним из решений является установка и использование пользовательского провайдера для sops — terraform-provider-sops. Другой вариант, который я продемонстрирую здесь, — использование Terragrunt, в который встроена нативная поддержка sops. Terragrunt — это тонкая обертка над Terraform, которая помогает поддерживать ваш код Terraform в DRY-состоянии и пригодным для поддержки (ознакомьтесь с руководством по быстрому старту для получения общего представления).
Допустим, вы использовали sops для создания зашифрованного файла YAML с именем db-creds.yml, как показано на .gif выше. Теперь в вашей конфигурации terragrunt.hcl вы можете использовать встроенную в Terragrunt функцию sops_decrypt_file для расшифровки этого файла и yamldecode для его разбора как YAML:
Затем вы можете передать имя пользователя и пароль в качестве входных данных в ваш код Terraform:
Ваш код Terraform, в свою очередь, может читать эти входные данные через переменные:
Обновление от 3 декабря 2020 г.: в Terraform 0.14 добавлена возможность помечать переменные как конфиденциальные (sensitive), что помогает не допускать их попадания в логи, поэтому вам следует добавить sensitive = true к обеим переменным выше!
И передайте эти переменные в aws_db_instance:
Преимущества этого метода
- Храните секреты в виде открытого текста вне вашего кода и системы контроля версий.
- Ваши секреты хранятся в зашифрованном виде в системе контроля версий, поэтому они версионируются, упаковываются и тестируются вместе с остальным вашим кодом. Это помогает уменьшить количество ошибок конфигурации, например, когда вы добавляете новый секрет в одном окружении (например, staging), но забываете добавить его в другом (например, production).
- Работает с различными вариантами шифрования: AWS KMS, GCP KMS, PGP и т. д.
- Все описано в коде. Не требуется никаких дополнительных ручных шагов или сценариев-оберток (хотя интеграция sops требует либо пользовательского провайдера, либо вспомогательного инструмента, такого как Terragrunt).
Недостатки этого метода
- Шифрование данных требует дополнительных усилий. Вам приходится либо выполнять множество команд (например, aws kms encrypt), либо использовать внешний инструмент, такой как sops. Существует кривая обучения для правильного и безопасного использования этих инструментов.
- Секреты теперь зашифрованы, но поскольку они по-прежнему хранятся в системе контроля версий, их ротация и отмена затруднены. Если кто-то когда-либо скомпрометирует ключ шифрования, он сможет вернуться назад и расшифровать все секреты, которые когда-либо были им зашифрованы.
- Возможность аудита того, кто получал доступ к секретам, минимальна. Если вы используете облачную систему управления ключами (например, AWS KMS), она, скорее всего, ведет журнал аудита того, кто использовал ключ для расшифровки чего-либо, но вы не сможете узнать, что именно было расшифровано.
- Менее удобно для тестирования: при написании тестов для вашего кода Terraform (например, с помощью Terratest) вам придется выполнять дополнительную работу по шифрованию данных для ваших тестовых окружений.
- Большинство управляемых сервисов ключей стоят небольших денег. Например, каждый ключ, который вы храните в AWS KMS, стоит $1 в месяц.
Метод № 3: Хранилища секретов (например, Vault, AWS Secrets Manager)
Третий метод заключается в хранении секретов в выделенном хранилище секретов: то есть в базе данных, которая специально разработана для безопасного хранения конфиденциальных данных и жесткого контроля доступа к ним.
Обзор
Вот несколько более популярных хранилищ секретов, которые вы можете рассмотреть:
- HashiCorp Vault: кроссплатформенное хранилище секретов с открытым исходным кодом.
- AWS Secrets Manager: управляемое AWS хранилище секретов.
- AWS Param Store: управляемое AWS хранилище данных с поддержкой шифрования.
- GCP Secret Manager: управляемое GCP хранилище ключей/значений.
Эти хранилища секретов решают проблему «откладывания на потом», полагаясь на человеческую память: в данном случае на вашу способность запомнить пароль, который дает вам доступ к вашему облачному провайдеру (или несколько паролей в случае Vault, так как он использует разделение секрета Шамира).
Пример использования AWS Secrets Manager
Сначала войдите в пользовательский интерфейс AWS Secrets Manager, нажмите «store a new secret» и введите секреты, которые вы хотите сохранить:
По умолчанию используется формат JSON, как вы можете видеть на скриншоте выше. Затем дайте секрету уникальное имя:
Нажмите «next» и «store», чтобы сохранить секрет.
Теперь в своем коде Terraform вы можете использовать источник данных aws_secretsmanager_secret_version для чтения этого секрета (для HashiCorp Vault, AWS SSM Param Store или GCP Secret Store вместо этого вы будете использовать источник данных vault_generic_secret, aws_ssm_parameter или google_secret_manager_secret_version):
Если вы сохранили секретные данные в формате JSON, вы можете использовать jsondecode для их синтаксического анализа:
И теперь вы можете использовать эти секреты в остальной части вашего кода Terraform:
Преимущества этого метода
- Храните секреты в виде открытого текста вне вашего кода и системы контроля версий.
- Ваши секреты хранятся в специальном хранилище секретов, которое обеспечивает шифрование и строгий контроль доступа.
- Все определено в самом коде. Не требуется никаких дополнительных ручных шагов или сценариев-оберток.
- Использование веб-интерфейса для хранения секретов обеспечивает удобство работы с минимальной кривой обучения.
- Хранилища секретов обычно поддерживают ротацию секретов, что полезно в случае компрометации секрета. Вы даже можете включить ротацию по расписанию (например, каждые 30 дней) в качестве превентивной меры.
- Хранилища секретов обычно поддерживают подробные журналы аудита, которые показывают вам, кто именно и к каким данным получил доступ.
- Хранилища секретов обычно предоставляют API, который можно легко использовать из всех ваших приложений, а не только из кода Terraform. AWS Secrets Manager даже генерирует фрагменты кода, которые показывают вам, как именно читать ваши секреты из приложений, написанных на Java, Python, JavaScript, Ruby, Go и т. д.:
Недостатки этого метода
- Поскольку секреты не версионируются, не упаковываются и не тестируются вместе с вашим кодом, более вероятны ошибки конфигурации, такие как добавление нового секрета в одном окружении (например, staging), но забывание добавить его в другом (например, production).
- Большинство управляемых хранилищ секретов стоят денег. Например, AWS Secrets Manager взимает 0,40 доллара в месяц за каждый хранимый секрет плюс 0,05 доллара за каждые 10 000 вызовов API, которые вы делаете для сохранения или извлечения данных.
- Если вы используете хранилище секретов, управляемое самостоятельно, например HashiCorp Vault, вы тратите как деньги на запуск хранилища (например, платите AWS за 3–5 инстансов EC2 для запуска Vault в режиме высокой доступности), так и время и деньги на то, чтобы ваша команда развертывала, настраивала, управляла, обновляла и мониторила хранилище.
- Менее удобно для тестирования: при написании тестов для вашего кода Terraform (например, с помощью Terratest) вам придется выполнять дополнительную работу по записи данных в хранилища секретов.
Заключение
Вот основные выводы из этой публикации в блоге:
- Не храните секреты в виде обычного текста.
- Используйте бэкенд Terraform, поддерживающий шифрование.
- Используйте переменные окружения, зашифрованные файлы или хранилище секретов для безопасной передачи секретов в ваш код Terraform. См. таблицу ниже для сравнения преимуществ и недостатков этих вариантов.
Вся ваша инфраструктура. Определенная как код. Примерно за день. Gruntwork.io.
