Безопасное распространение закрытых ключей в Docker на CoreOS

Мы начинаем использовать Docker/CoreOS в качестве нашей инфраструктуры. Мы развертываем на EC2. Кластер CoreOS управляется группой автоматического масштабирования, поэтому новые хосты приходят и уходят. К тому же их очень много. Я пытаюсь найти способ распространять секрет (закрытый ключ RSA или общий секрет для симметричного шифра) на все хосты, чтобы я мог использовать его для безопасного распространения таких вещей, как учетные данные базы данных, ключи доступа AWS для определенных сервисов и т. д.

Я хотел бы подчиняться "принципу наименьших привилегий". В частности, если у меня есть 2 приложения в 2 разных контейнерах, работающих на одном хосте, каждое из них должно иметь доступ только к тем секретам, которые нужны приложению. Например, приложение A может иметь доступ к учетным данным MySQL, а приложение B может иметь доступ к ключам AWS Access для Dynamo, но приложение A не может получить доступ к Dynamo, а приложение B не может получить доступ к MySQL.

Если бы у меня был секрет на каждом сервере, это было бы несложно. Я мог бы использовать такой инструмент, как Crypt, чтобы считывать зашифрованные данные конфигурации из etcd, а затем использовать карты томов для выборочного сделать учетные данные доступными для отдельных контейнеров.

Вопрос в том, как, черт возьми, я могу безопасно получить ключи на хосте.

Вот некоторые вещи, которые я рассмотрел и почему они не работают:

  • Используйте роли AWS, чтобы предоставить каждому хосту доступ к зашифрованной корзине S3. Затем хосты могут прочитать оттуда общий секрет. Но это не работает, потому что у S3 есть REST API, Docker не ограничивает доступ к сети у контейнеров, а роль распространяется на весь хост. Таким образом, любой контейнер на этом хосте может прочитать ключ из S3, затем прочитать все значения из etcd (который также имеет неограниченный REST API) и расшифровать их.
  • В моем шаблоне CloudFormation у меня может быть параметр для секретного ключа. Затем это встраивается в UserData и распространяется на все хосты. К сожалению, любой контейнер может получить ключ через REST API службы метаданных.
  • Используйте флот, чтобы отправить глобальное устройство всем хостам, и это устройство скопирует ключи. Однако контейнеры могут получить доступ к флоту через REST API и выполнить команду «fleetctl cat», чтобы увидеть ключ.
  • Поместите секретный ключ в контейнер в частном репозитории. Затем его можно распространить на все хосты как глобальную единицу, и приложение в этом контейнере может скопировать ключ на монтирование тома. Однако я предполагаю, что, имея учетные данные для частного репозитория, кто-то может загрузить контейнер стандартными сетевыми инструментами и извлечь ключ (хотя и с некоторыми усилиями). Затем возникает проблема, как безопасно распространять .dockercfg с учетными данными для частного репо, что, я думаю, возвращает нас к тому, с чего мы начали.

По сути, кажется, что основная проблема заключается в том, что у всего есть REST API, и я не знаю, как предотвратить доступ контейнеров к определенным сетевым ресурсам.

Идеи?


person Oliver Dain    schedule 24.02.2015    source источник


Ответы (1)


Если вы хотите сохранить секрет в AMI, вы можете использовать упомянутое вами решение Crypt. Я реализовал нечто подобное следующим образом:

  1. Генерация пары открытый/закрытый ключ
  2. Вставьте закрытый ключ в AMI, используемый для групп автомасштабирования.
  3. Используйте открытый ключ для шифрования сценария начальной загрузки, включая секреты.
  4. Base64 кодирует зашифрованный скрипт начальной загрузки
  5. Вставьте закодированный текст в скрипт-оболочку, который расшифровывает с помощью закрытого ключа, и используйте его в качестве пользовательских данных для конфигурации запуска AWS.

Например, скрипт начальной загрузки может выглядеть так:

db="mysql://username:password@somehost:3306/somedb"
apikey="some_api_secret_key"
docker run --name "first container" -e db=$db -d MyImage MyCommand
docker run --name "second container" -e apikey=$apikey -d MyOtherImage MyOtherCommand

Для шифрования используйте openssl со smime, чтобы обойти низкие ограничения rsautl. Предполагая, что скрипт начальной загрузки находится в /tmp/bootstrap.txt, его можно зашифровать и закодировать следующим образом:

$ openssl smime --encrypt -aes256 -binary -outform D -in /tmp/bootstrap.txt /tmp/public.key | openssl base64 -e > /tmp/encrypted.b64

Сценарий-оболочка, который становится пользовательскими данными, может выглядеть следующим образом:

#!/usr/bin/env bash -x 
exec >> /tmp/userdata.log 2>&1

cat << END > /tmp/bootstrap.dat
<contents of /tmp/encrypted.b64>
END
decrypted_blob=$(cat /tmp/bootstrap.dat | openssl base64 -d | openssl smime -decrypt -inform -D binary -inkey /path/to/secret.key
eval "${decrypted_blob}"
rm /tmp/bootstrap.dat

Теперь, если контейнеры получат доступ к метаданным EC2, они увидят сценарий пользовательских данных, но он будет иметь только зашифрованный большой двоичный объект. Закрытый ключ находится на хосте, к которому контейнеры не имеют доступа (теоретически).

Также обратите внимание, что предельный размер пользовательских данных составляет 16 КБ, поэтому сценарий и его зашифрованные данные должны быть меньше этого размера.

person Ben Whaley    schedule 25.02.2015