Прим. перев.: Оригинал этой статьи был опубликован в официальном блоге Kubernetes и написан Andrew Martin — одним из основателей молодой британской компании Control Plane, специализирующейся на безопасности для cloud native-приложений, запускаемых в K8s.
Безопасность в Kubernetes прошла длинный путь с момента появления проекта, однако в ней по-прежнему встречаются подводные камни. Предлагаем список полезных рекомендаций по тому, как защитить кластеры и повысить их устойчивость в случае взлома: начнём с control plane, продолжим с рабочими нагрузками и сетевой безопасностью, а закончим оценкой будущего безопасности.
Часть 1: Control Plane
Control plane — это
1. Повсеместный TLS
Для каждого компонента, поддерживающего TLS, он должен быть включён — для предотвращения сниффинга трафика, удостоверения идентичности сервера и (в случае Mutual TLS) удостоверения идентичности клиента.
«Обратите внимание, что некоторые компоненты и методы инсталляции могут активировать локальные порты для HTTP, поэтому администраторам необходимо ознакомиться с настройками каждого компонента, чтобы выявить возможные пути для небезопасного трафика».
Приведённая ниже сетевая диаграмма от Lucas Käldström демонстрирует, где в идеале необходимо применение TLS: между каждым компонентом на мастере, а также между Kubelet и сервером API. Ставший классикой «Kubernetes The Hard Way» от Kelsey Hightower и документация по безопасности в etcd предлагают подробные инструкции для достижения этих целей.
Исторически автоматическое масштабирование узлов Kubernetes складывалось непросто, поскольку каждый узел требовал TLS-ключа для подключения к мастеру, а хранение секретов в базовых образах — плохая практика. Kubelet TLS bootstrapping обеспечивает возможность новому Kubelet'у создавать запрос на подпись сертификата, чтобы сертификаты генерировались во время загрузки:
2. Минимальные привилегии в RBAC, отключение ABAC, мониторинг логов
Управление доступом на основе ролей (RBAC) обеспечивает тонкое управление политиками, по которым пользователи получают доступ к таким ресурсам, как пространства имён.
Система управления доступом на основе атрибутов (ABAC, Attribute Based Access Control) в Kubernetes была заменена на RBAC с релиза K8s 1.6 и не должна быть включена на стороне сервера API. Используйте RBAC вместо неё: --authorization-mode=RBAC
(или этот флаг для GKE: --no-enable-legacy-authorization
).
Есть множество хороших примеров политик RBAC для различных сервисов в кластере, а также документация. Но не стоит этим ограничиваться: грамотные настройки для RBAC-политик можно получить с помощью audit2rbac из логов audit
.
Неправильные или излишне разрешающие политики RBAC — угроза безопасности в случае компрометации пода. Поддержка правил RBAC с минимальными привилегиями, их постоянный аудит и улучшение должно стать частью «гигиены технического долга», которую команды применяют в жизненном цикле разработки.
Audit logging (бета-версия в Kubernetes 1.10) предлагает настраиваемый API для логирования для рабочих нагрузок (например, запроса и ответа на него) и на уровне метаданных. Уровень логирования может быть настроен в соответствии с принятой в организации политикой безопасности — GKE применяет разумные значения по умолчанию для тех, кто только начинает с ним работать.
Для запросов на чтение, таких как get, list и watch, в audit-логах сохраняется только запрашиваемый объект, без ответного объекта. Для запросов, затрагивающих конфиденциальные данные вроде Secret или ConfigMap, экспортируются только метаданные. Для всех остальных запросов в audit-логи записываются оба объекта: и запрос, и ответ.
Не забывайте: хранение этих логов внутри кластера — угроза безопасности в случае компрометации. Эти логи, как и любые другие чувствительные к безопасности, должны помещаться вне кластера во избежание негативных последствий в случае уязвимости.
3. Используйте стороннюю аутентификацию для API Server
Централизация аутентификации и авторизации для всей организации (т.е. Single Sign On) помогает в процессах принятия и ухода новых сотрудников, обеспечении консистентных прав доступа.
Интеграция Kubernetes со сторонними поставщиками аутентификации (вроде Google или GitHub) обеспечивает гарантии идентичности от удалённой платформы (с защитой вроде двухфакторной аутентификации) и избавляет администраторов от необходимости перенастраивать сервер API в Kubernetes для добавления/удаления пользователей.
Dex — провайдер OpenID Connect Identity (OIDC) и OAuth 2.0 с подключаемыми «разъёмами» (connectors). Компания Pusher пошла ещё дальше, предоставив настраиваемый инструментарий, в дополнение к которому есть и некоторые другие помощники, ориентированные на иные случаи применения.
4. Отделите и поместите за firewall свой кластер etcd
etcd хранит информацию о состоянии Kubernetes и секретах, является критичным компонентом K8s — он должен быть защищён отдельно от остального кластера.
Доступ на запись в etcd у сервера API равнозначен выдаче root-прав на весь кластер, да и даже доступ на чтение может быть легко использован для эскалации привилегий.
Планировщик Kubernetes именно в etcd ищет определения пода, у которых нет узла. Затем он отправляет все найденные поды на доступный Kubelet для планирования. Валидация этих подов производится сервером API перед тем, как записывать их в etcd, поэтому злоумышленники, напрямую пишущие в etcd, могут обойти множество механизмов безопасности — например, PodSecurityPolicies
.
etcd должен быть настроен с обоими TLS-сертификатами (client и peer) и деплоиться на выделенные узлы. Для снижения риска кражи и использования приватных ключей с рабочих узлов можно также ограничить firewall'ом API Server кластера.
5. Ротация ключей шифрования
Регулярная ротация ключей безопасности и сертификатов — лучшая практика в безопасности, позволяющая ограничить «радиус поражения» при компрометации ключа.
Kubernetes будет автоматически ротировать некоторые сертификаты (в частности, клиентский и серверный сертификаты Kubelet) созданием новых CSR по истечении времени действия текущих.
Однако симметричные ключи, используемые сервером API для шифрования значений etcd, не ротируются автоматически — это нужно делать вручную. Для данной операции требуется доступ уровня master, поэтому управляемые сервисы (вроде GKE и AKS) прячут проблему от пользователя.
Часть 2: рабочие нагрузки
При обеспечении минимальной безопасности на уровне control plane кластер уже может функционировать безопасно. Однако, как и для корабля с потенциально опасным грузом, контейнеры такого судна должны защитить груз на случай непредвиденной аварии или утечки. То же самое справедливо для рабочих нагрузок в Kubernetes (Pods, Deployments, Jobs, Sets и т.п.) — им можно доверять на момент деплоя, но, если они доступны извне, всегда есть риск, что впоследствии ими воспользуются [злоумышленники]. Снижению этого риска может способствовать запуск рабочих нагрузок с минимальными привилегиями и их безопасной конфигурацией.
6. Используйте фичи безопасности в Linux и PodSecurityPolicies
У ядра Linux есть множество частично перекрывающих друг друга расширений безопасности (capabilities, SELinux, AppArmor, seccomp-bpf), которые можно настроить так, чтобы предоставлять приложениям минимальные привилегии.
Утилиты вроде bane помогут сгенерировать профили для AppArmor, а docker-slim — профили seccomp, но будьте осторожны: для выявления всех побочных эффектов применения этих политик требуется всесторонний набор тестов, проверяющий весь код приложения.
PodSecurityPolicies регулируют использование этих расширений безопасности, а также других директив безопасности Kubernetes. Они отвечают за минимальные требования, которым должен соответствовать под, чтобы попасть на сервер API, включая профили безопасности, флаг привилегированности, совместное использование сети хоста, процессов или пространств имён для IPC.
Эти директивы важны, потому что помогают предотвратить выход контейнеризированных процессов из их изолированных границ. Пример PodSecurityPolicy от Tim Allclair весьма универсален — его можно взять за основу и поднастроить под свой случай.
7. Проводите статический анализ YAML
Если PodSecurityPolicies ограничивают доступ к серверу API, то статический анализ можно также использовать в процессе разработки для моделирования нормативных требований организации и склонности к риску.
Чувствительная информация не должна храниться в YAML-ресурсах вроде подов (Pods, Deployments, Sets и т.п.), а чувствительные ConfigMaps и Secrets необходимо шифровать утилитами вроде Vault (с оператором от CoreOS), git-crypt, sealed secrets или KMS облачного провайдера.
Статический анализ YAML-конфигурации может использоваться как основа для обеспечения безопасности во время запуска. kubesec генерирует оценки риска для ресурсов:
{
"score": -30,
"scoring": {
"critical": [{
"selector": "containers[] .securityContext .privileged == true",
"reason": "Privileged containers can allow almost completely unrestricted host access"
}],
"advise": [{
"selector": "containers[] .securityContext .runAsNonRoot == true",
"reason": "Force the running image to run as a non-root user to ensure least privilege"
}, {
"selector": "containers[] .securityContext .capabilities .drop",
"reason": "Reducing kernel capabilities available to a container limits its attack surface",
"href": "https://kubernetes.io/docs/tasks/configure-pod-container/security-context/"
}]
}
}
А kubetest — фреймворк для модульного тестирования конфигураций Kubernetes:
#// vim: set ft=python:
def test_for_team_label():
if spec["kind"] == "Deployment":
labels = spec["spec"]["template"]["metadata"]["labels"]
assert_contains(labels, "team", "should indicate which team owns the deployment")
test_for_team_label()
Эти утилиты реализуют подход «shift left» (т.е. перемещают проверки и верификацию на ранние этапы цикла разработки). Тестирование безопасности на этапе разработки даёт пользователям быструю обратную связь по коду и конфигурации, которые могут быть в дальнейшем отклонены ручной или автоматизированной проверкой, а также может облегчить введение дополнительных практик по безопасности.
8. Запускайте контейнеры не под root
У контейнеров, которые запускают с правами root, зачастую намного больше прав, чем того требуют их рабочие нагрузки, а в случае компрометации они помогают атакующим добиться больших возможностей.
Контейнеры по-прежнему опираются на традиционную модель безопасности UNIX (называемую DAC, discretionary access control) — всё есть файл, а права выдаются пользователям и группам.
Пространства имён для пользователей не работают в Kubernetes. Это означает, что таблица пользовательских ID в контейнере отображается на таблицу пользователей хоста, и запуск процесса с правами root внутри контейнера приводит к его запуску с правами root на хосте. Хоть ко всему этому и добавляются механизмы, предотвращающие выход из пределов контейнера, запуск с правами root внутри контейнера не рекомендуется.
Многие образы контейнеров используют пользователя root для запуска PID 1: если этот процесс скомпрометирован, злоумышленник получает root в контейнере и при любой ошибке в конфигурации эксплуатация проблемы становится значительно проще.
В Bitnami проделали большую работу по переводу своих образов контейнеров на обычных (не root) пользователей (к тому же, это требование OpenShift по умолчанию), что может упростить и вашу миграцию на образы, не использующие права root.
Этот фрагмент PodSecurityPolicy предотвращает запуск процессов с правами root в контейнере и эскалацию до root'а:
# Required to prevent escalations to root.
allowPrivilegeEscalation: false
runAsUser:
# Require the container to run without root privileges.
rule: 'MustRunAsNonRoot'
Контейнеры, не использующие root, не могут занимать привилегированные порты, т.е. до 1024 (за это отвечает соответствующая capability в ядре Linux — CAP_NET_BIND_SERVICE
), однако использование Services помогает обойти это ограничение. Вот пример для приложения MyApp, которое занимает порт 8443 в контейнере, но Service делает его доступным по порту 443, проксируя запросы на targetPort
:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 443
targetPort: 8443
Необходимость запуска рабочих нагрузок без использования root будет сохраняться до тех пор, пока в ходу пространства имён для пользователей или же наработки по запуску контейнеров без root не войдут в состав исполняемых сред для контейнеров.
9. Используйте сетевые политики
По умолчанию сеть в Kubernetes разрешает весь трафик между подами. Эту настройку можно ограничить сетевой политикой — NetworkPolicy.
Традиционные сервисы ограничены firewall'ами, использующими статические IP-адреса и диапазоны портов для каждого сервиса. Поскольку эти IP-адреса очень редко меняются, они исторически использовались как вид идентификации. У контейнеров редко бывают статические IP — их природа подразумевает возможность быстрого падения и повторного создания, для них вместо статических IP-адресов используется service discovery. Эти особенности значительно усложняют настройку и проверку работы firewall'ов.
Поскольку Kubernetes хранит все данные о состоянии системы в etcd, возможна настройка динамического firewall'а — если есть необходимая поддержка в сетевом плагине CNI. Calico, Cilium, kube-router, Romana и Weave Net — все эти плагины поддерживают сетевые политики.
Важно заметить, что политики работают по принципу fail-closed, то есть отсутствие podSelector
здесь по умолчанию приравнивается ко всем возможным значениям (wildcard):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector:
Далее приведён пример NetworkPolicy, которая запрещает всё от egress кроме UDP 53 (DNS), что также предотвращает входящие подключения к приложению. Политики NetworkPolicies являются stateful, поэтому приложение всё-таки получит ответы на исходящие подключения.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: myapp-deny-external-egress
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Egress
egress:
- ports:
- port: 53
protocol: UDP
- to:
- namespaceSelector: {}
Сетевые политики Kubernetes нельзя применять к DNS-именам. Причина в том, что DNS поддерживает round-robin на множество IP-адресов и динамические ответы, зависимые от обращающегося IP, так что сетевые политики применяются только к фиксированным IP-адресам или podSelector
'ам (для динамических IP-адресов Kubernetes).
Лучшая практика — начать с запрета всего трафика для пространства имён и пошагово добавлять маршруты, требуемые приложению для прохождения приёмочного тестирования. Процесс может быть непростым, поэтому в ControlPlane разработали netassert — утилиту для тестирования сетевой безопасности в сценариях DevSecOps с сильно распараллеленным nmap:
k8s: # used for Kubernetes pods
deployment: # only deployments currently supported
test-frontend: # pod name, defaults to `default` namespace
test-microservice: 80 # `test-microservice` is the DNS name of the target service
test-database: -80 # `test-frontend` should not be able to access test-database’s port 80
169.254.169.254: -80, -443 # AWS metadata API
metadata.google.internal: -80, -443 # GCP metadata API
new-namespace:test-microservice: # `new-namespace` is the namespace name
test-database.new-namespace: 80 # longer DNS names can be used for other namespaces
test-frontend.default: 80
169.254.169.254: -80, -443 # AWS metadata API
metadata.google.internal: -80, -443 # GCP metadata API
API с метаданными от облачного провайдера — постоянный источник потенциальной эскалации (демонстрируется недавним bug bounty от Shopify), так что специальные тесты, подтверждающие блокировку API в сети контейнеров, помогут защититься от ошибок в конфигурации.
10. Сканируйте образы и запустите IDS
Веб-серверы — плацдарм для атаки сетей, к которым они прикреплены. Сканирование файлов, установленных в образы, позволяет убедиться в отсутствии известных уязвимостей, которыми злоумышленник может воспользоваться для получения удалённого доступа к контейнеру. Системы обнаружения вторжений (IDS) фиксируют эти события, если они происходят.
Kubernetes допускает поды в кластер через набор контролирующих проверок (в admission controller), применяемых не только к подам, но и другим ресурсам вроде Deployments. В них каждый под может валидироваться на допуск или меняться его содержимое, в дополнение к чему теперь также поддерживаются webhooks на стороне backend'а.
Эти webhooks могут использоваться инструментами сканирования образов контейнеров для проверки образов до их деплоя в кластер. Образы, не прошедшие проверку, не получат допуск контроллера.
Сканирование образов контейнеров на известные уязвимости помогает снизить время, за которое атакующий сможет воспользоваться открытой CVE. Для предотвращения выкатывания образов с критическими уязвимости в pipeline'е деплоя можно использовать бесплатные утилиты вроде Clair от CoreOS и Micro Scanner от Aqua.
Такие инструменты, как Grafeas, позволяют хранить метаданные образа для постоянных проверок на соответствие и уязвимости с помощью уникальной подписи контейнера (или специального контентно-адресуемого хэша). Сканирование образа контейнера с помощью этого хэша равносильно сканированию образов, разворачиваемых в production, и может осуществляться постоянно без необходимости иметь доступ к production-окружениям.
Неизвестные 0day-уязвимости всегда будут существовать, поэтому в Kubernetes следует развернуть систему обнаружения вторжений вроде Twistlock, Aqua или Sysdig Secure. IDS обнаруживает необычное поведение в контейнере и останавливает или убивает его. Falco от Sysdig — Open Source-движок правил и отправная точка к этой экосистеме.
Часть 3: Будущее
Следующим этапом безопасности в «эволюции cloud native», похоже, будет service mesh, хотя его принятие случится не сразу: эта миграция требует смещения сложности приложений в mesh-инфраструктуру, и организациям предстоит осознать эту лучшую практику.
11. Запустите service mesh
Service mesh — это сеть постоянных зашифрованных соединений, сделанных между «подключёнными сбоку» (по аналогии с «sidecar»), высокопроизводительными прокси-серверами вроде Envoy и Linkerd. Она приносит управление трафиком, мониторинг и политики — и всё это без изменений в микросервисах.
Перенос из микросервисов кода, связанного с безопасностью и сетями, в разделяемый и проверенный в бою набор библиотек был возможен уже с Linkerd, а появление Istio от Google, IBM и Lyft, принесли альтернативу в это пространство. С дополнением в виде SPIFFE для криптографической идентичности каждого пода и множества других возможностей, Istio может упростить развёртывание сетевой безопасности следующего поколения.
В сетях «нулевого доверия» (zero trust) может и не быть потребности в традиционном firewall'е или сетевых политиках Kubernetes, поскольку каждое взаимодействие происходит с использованием mTLS (mutual TLS), что не только гарантирует безопасность взаимодействия, но и подтверждает идентичность обоих сервисов.
Этот переход от традиционных сетевых подходов к принципам безопасности мира Cloud Native не будет простым для тех, у кого устоялось традиционное
На данный момент доступен Istio 0.8 LTS, и проект быстро приближается к своему релизу 1.0. Версионирование проекта в вопросах стабильности ведётся аналогично модели Kubernetes: стабильное ядро с отдельными API, для которых с помощью пространств имён указывается их статус alpha или beta. Ожидайте увидеть рост распространения Istio в ближайшие месяцы.
Заключение
У приложений категории Cloud Native более детализированный набор легковесных примитивов безопасности, позволяющих защитить рабочие нагрузки и инфраструктуру. Мощность и гибкость таких инструментов — одновременно и благословение, и проклятие: не имея достаточной автоматизации [для их применения], выставить наружу небезопасные приложения, позволяющие выходить за рамки контейнера или их изоляционной модели, стало ещё проще.
Утилит для защиты доступно больше, чем когда-либо, однако для снижения возможностей для атаки и потенциала некорректных конфигураций необходимо пользоваться ими с осторожностью.
Если безопасность замедляет скорость организации в доставке изменений, она никогда не станет первоочерёдной. Использование принципов Continuous Delivery применительно к цепочке поставки программного обеспечения позволяет организации добиться соблюдения нормативных требований, непрерывного аудита и усиленного управления, не влияя на бизнес-показатели.
Быстрое поступательное улучшение безопасности — самый простой путь при наличии всеобъемлющего набора тестов. Оно достигается с Continuous Security — альтернативой заданным определённым временем пентестам с постоянной валидацией в pipeline'е, гарантирующей, что площадь атаки известна, риск всегда понятен и управляем.
P.S. от переводчика
Читайте также в нашем блоге:
- «OPA и SPIFFE — два новых проекта в CNCF для безопасности облачных приложений»;
- «Vulnerable Docker VM — виртуалка-головоломка по Docker и pentesting »;
- «Что такое service mesh и почему он мне нужен [для облачного приложения с микросервисами]?»;
- «Conduit — легковесный service mesh для Kubernetes»;
- «Иллюстрированное руководство по устройству сети в Kubernetes»;
- «Инфраструктура с Kubernetes как доступная услуга».
Автор: shurup