С этим докладом я выступал на недавней конференции VK Kubernetes Conf 2023. В нем рассказывается, какие правила безопасности в Kubernetes действительно необходимы, и разбираются пять шагов, которые помогают улучшить безопасность.
Актуальность вопроса в Kubernetes
Про безопасность знают все, но далеко не всегда делают всё правильно. В итоге нам приходится разбираться с разными последствиями этого.
Почему сегодня вопрос безопасности в Kubernetes приобрел такую остроту?
Ответ прост — Kubernetes уже давно перестал быть хайповой технологией. Сейчас это стандарт, который используется в крупном, малом и среднем бизнесе.
Различные исследования показывают: кластеров под управлением Kubernetes скоро станет еще больше.
Кажется, что чем выше их число, тем лучше. Kubernetes призван решить все наши проблемы: упростить операции, ускорить доставку, увеличить количество деплоев в день и т.д. Что может пойти не так?
В первую очередь на этапе внедрения новой для нас технологии мы сталкиваемся с недостатком экспертизы.
Накапливая ее и перенося все новые и новые рабочие нагрузки в кластер, мы начинаем задумываться о других проблемах. Зачастую в организациях, в которых контейнеры используются практически для всех приложений, безопасность становится главным вызовом.
Как показал опрос Red Hat, лишь 7% респондентов не сталкивались с инцидентами в области безопасности.
Kubernetes должен был ускорить развертывание приложений, но в итоге более половины респондентов откладывали или задерживали деплой приложений из-за проблем с безопасностью в кластере.
Как же быть? Как перестать беспокоиться и начать получать удовольствие от Kubernetes? Как начать использовать его по-максимуму?
О стандартах безопасности
Есть множество стандартов безопасности (международные, отечественные), которые говорят нам, как правильно делать Kubernetes.
Одним из самых известных является Kubernetes CIS Benchmark — стандарт от Center for Internet Security.
В нем содержатся конкретные инструкции о том, как сделать кластер безопасным (вплоть до того, какие флаги указать у API-сервера). Описывается, как правильно настроить компоненты control plane, конфигурацию, worker-узлы, политики и так далее.
Еще есть стандарт от Payment Card Industry Security Standards Council (вышел в 2022 году).
В него входят такие крупные платежные системы, как Visa и MasterCard. От CIS Benchmark он отличается тем, что ограничивается рекомендациями о том, как правильно выстроить процессы относительно средств оркестрации в целом. В нем нет советов конкретно для Kubernetes.
Также в 2022 году ФСТЭК выпустила свой стандарт с требованиями по безопасности к средствам контейнеризации.
Кроме того, есть множество других стандартов как от коммерческих, так и от некоммерческих организаций. Их дополняют спецификации и всевозможные чек-листы от компаний, производящих софт, связанный с безопасностью Kubernetes.
Общая идея всех стандартов
Стандартов много, но все они пересекаются между собой и имеют единый смысл. Заключается он в следующем: рассматривать безопасность в Kubernetes следует послойно.
Модель 4C называется так по первым буквам каждого слоя: Code, Container, Cluster и Cloud. В ней для каждого слоя приводятся свои рекомендации по безопасности.
Начинаем со слоя Code и организуем защиту используемых приложений и библиотек. Далее контролируем среду исполнения, ограничиваем доступ к Kubernetes control plane и узлам кластера, и, наконец, настраиваем безопасный доступ к API облака и инфраструктуре.
Понятно, что когда у злоумышленников есть доступ к API облака, неуязвимость кода в контейнере теряет всякое значение: они просто удалят виртуальные машины, скопируют диски и так далее.
Далее мы поговорим о слоях Code, Container и Cluster.
Пять шагов к безопасности в Kubernetes
Изучив все эти стандарты, проанализировав спецификации, пройдя кучу опросников служб информационной безопасности (мы внедряем нашу Kubernetes-платформу у многих Enterprise-клиентов; для этого сначала приходится общаться со службами ИБ и выполнять их требования), мы поняли, что 5 шагов к безопасности достаточно, чтобы удовлетворить 90% требований.
Рассмотрим их подробнее.
Шаг первый: корректная конфигурация кластера Kubernetes
Прежде всего необходимо запустить пресловутый CIS-бенчмарк и проверить, насколько настройка узлов и control plane соответствует тому, что описано в стандарте. Самая распространенная реализация этого стандарта — kube-bench: запустили, прогнали, посмотрели файлы, предупреждения, исправили их, и можно идти дальше.
При возможности следует ограничить сетевой доступ к Kubernetes API, настроить white-листы, включить доступ только через VPN и так далее.
Каждый пользователь должен уникально идентифицироваться при доступе к Kubernetes API. Это нужно для последующего проведения аудита и понимания, кто и какие действия провел; также это пригодится для грануляции RBAC'ов.
Должны выдаваться только необходимые RBAC'и. Например, некий оператор в кластере получает список всех подов. В этом случае не нужно выдавать ему возможность видеть все секреты и тем более редактировать их.
Кажется, что все просто. Но давайте рассмотрим реальные примеры.
Как дела с managed Kubernetes обстоят у российских облачных провайдеров?
Я развернул кластеры у пары провайдеров и запустил CIS-бенчмарк. Ошибок и предупреждений практически не было (тестировались только worker’ы, т.к. не у всех провайдеров доступны control plane-узлы). Затем я посмотрел, как организован доступ в мой только что развернутый кластер — ведь взаимодействие со новым кластером мы всегда начинаем с получения доступа к Kubernetes API.
Скачав kubeconfig, я обнаружил, что внутри используется группа system:masters. При этом в первом пункте чек-листа по безопасности в Kubernetes четко указано: после развертывания кластера никто не должен в нем аутентифицироваться как system:masters.
Сертификат выписан на месяц: если кто-то украдет у меня этот kubeconfig, придется перевыпускать все CA в кластере, потому что отозвать доступ у группы system:masters без этого невозможно.
Как видим, даже облачные провайдеры нарушают базовые принципы, поэтому о них говорить всегда актуально.
Шаг второй: сканирование образов
Теперь настало время подумать о том, какой софт мы будет запускать в кластере, и сделать его хотя бы минимально безопасным. Самым простым решением кажется сканирование используемых образов.
Провести его можно на разных этапах: на этапе конвейера CI/CD, настроить сканирование registry по расписанию или сканировать все образы, которые используются в кластере. Для этого существует множество решений. Каждый выбирает сам, что ему больше подходит — self-hosted или SaaS. Самое главное — следовать базовым принципам.
После сканирования следует проанализировать его результаты.
Нужно смотреть критические проблемы, warning-инциденты, которые у нас возникают, разбирать найденные уязвимости. Самое главное здесь — подходить к вопросу с холодной головой: если мы нашли критическую уязвимость, это еще не значит, что ее можно эксплуатировать.
Также следует использовать базовые образы. Если у нас есть golden image, и мы нашли в нем CVE, то его достаточно просто заменить и перезапустить все CI/CD.
И самый мой любимый момент — не нужно тащить лишние зависимости. Например, всякие инструменты для дебага не должны оказываться в образах для production. Для компилируемых языков собираем артефакт, в нем компилируем бинарный файл, копируем его в golden image, и уже его запускаем в production. Чем больше лишних зависимостей, тем выше риск обнаружить в них CVE.
Шаг третий: сетевая безопасность
Итак, мы собрали приложение и подготовили его к работе. Теперь нужно подумать, как запускать его в нашем новом классном кластере Kubernetes.
И первым в голову приходит то, что нужно сделать его безопасным именно с точки зрения сети:
CNI в кластере должен поддерживать сетевые политики;
Ingress и egress-политики должны быть настроены для всех компонентов кластера;
все соединения по умолчанию должны быть явно разрешены.
На практике я сталкивался с тем, что клиенты обращаются к нам за аудитом. Говорят, что у них все хорошо, Kubernetes развернут, все ходят со своими сертификатами… В итоге мы и правда видим: у разработчиков есть доступ только в пространство имен stage.
При этом живут клиенты в «коммунальном» кластере — многие клиенты, в том числе Enterprise-уровня, разворачивают несколько больших кластеров, в которых stage и production существуют в рамках одного control plane. Разработчики спокойно хотят в stage, но оттуда могут попадать куда угодно, в том числе и в production-базы данных, потому что сегментация сети и сетевая изоляция отсутствуют.
Такое часто встречается на практике. Что с этим можно сделать?
У себя в платформе мы используем Cilium. В нем есть визуальный редактор, в котором буквально в три клика можно настроить подходящую политику. Она разрешит разработчику ходить только в рамках своего пространства имен и обращаться к Kubernetes DNS.
Вот так выглядит созданная egress-политика:
egress:
- toEndpoints:
- matchLabels:
io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
rules:
dns:
- matchPattern: "*"
- toEndpoints:
- {}
В данном случае она решает все проблемы.
Разработчик может ходить в БД только в рамках stage и не может попадать в production.
Бывают и еще более ужасные вещи, когда пароли и названия stage- и production-баз совпадают. Если у разработчиков или инженеров достаточно пытливый ум, то запросы из stage начинают лететь в production-базу.
Политики необходимо внедрять также в случае, если у каждого сервиса свой отдельный кластер.
Давно пора уйти от правила «доверяй, но проверяй» или считать, что все, что разворачивается в закрытый контур, по умолчанию безопасно. Нужно отслеживать и понимать, кто и куда обращается. Это очень важно.
Кроме того, необходимо внедрить подход «политики как код». Он идентичен подходу «инфраструктура как код» и означает, что политики безопасности деплоятся вместе с приложением из Git-репозитория.
Внедрять все это необходимо на самом старте — в этот момент соответствующие практики привить гораздо проще. При введении нового компонента сразу должны описываться его сетевые взаимодействия и разворачиваться политики. В уже работающую инфраструктуру внедрять политики гораздо сложнее.
Шаг четвертый: контроль над запускаемыми приложениями
Мы обсудили сетевую безопасность, но остается еще такое понятие, как runtime — хост, на котором будет запускаться наш контейнер.
Запрос в Kubernetes проходит достаточно длинную цепочку. Одним из ее элементов является Admission Controller, который валидирует создаваемые в кластере объекты.
В Kubernetes 1.23 есть Pod Security Admission, который закрывает базовые потребности.
Существуют Pod Security Standards, которые, среди прочего, запрещают запускать поды от root, использовать host network и так далее. Если этих политик недостаточно, можно воспользоваться следующими вариантами:
В каких случаях стандартов Pod Security Standards недостаточно? Мы у себя в платформе составили список этих случаев и создали CRD OperationPolicy, которое работает поверх ресурсов Gatekeer. Пример его конфигурации:
apiVersion: deckhouse.io/v1alpha1
kind: OperationPolicy
metadata:
name: common
spec:
policies:
allowedRepos:
- myregistry.example.com
requiredResources:
limits:
- memory
requests:
- cpu
- memory
disallowedImageTags:
- latest
requiredProbes:
- livenessProbe
- readinessProbe
maxRevisionHistoryLimit: 3
imagePullPolicy: Always
priorityClassNames:
- production-high
checkHostNetworkDNSPolicy: true
checkContainerDuplicates: true
match:
namespaceSelector:
labelSelector:
matchLabels:
operation-policy.deckhouse.io/enabled: "true"
Пользователям рекомендуем сразу настраивать этот инструмент. В него входят следующие базовые принципы:
Самые важные, по моему мнению, точки контроля:
не разрешаем разработчикам использовать образы напрямую с Docker Hub в определенных пространствах имен, требуя конфигурации доверенного registry.
заставляем использовать priorityClass'ы.
В своей практике я нередко сталкивался с ситуациями, когда, например, у системы логирования приоритет был выше, чем у бэкенда. Результат — не совсем корректная работа системы в целом.
Какие вещи в целом закрывает Admission Controller?
Самый яркий пример — довольно старая CVE от 2019 года, до сих пор сохраняющая актуальность: если запустить контейнер от root, можно получить доступ к хосту. Риски стать жертвой этой уязвимости многократно повышаются, если разработчикам разрешено запускать образы из любых недоверенных registry.
В завершение этого шага хочется подчеркнуть, что описывать политики безопасности приложений просто необходимо. Больше контроля — меньше вероятность возникновения ошибки, а значит и ниже вероятность стать жертвой уязвимости.
Шаг пятый: аудит и регистрация событий
После запуска приложения хочется понимать, что происходит внутри Kubernetes и всей системы.
Речь сейчас не про мониторинг и снятие метрик с подов. Речь про аудит событий.
В Kubernetes встроен классный аудит «из коробки» — аудит Kubernetes API, который очень легко настроить. Пишем логи в файл или в stdout, собираем и отгружаем в систему сбора логов, а уже в ней — анализируем.
Пример простой политики сборки запросов, которые отправляются к ресурсам типа pods:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- RequestReceived
rules:
- level: RequestResponse
resources:
- group: ""
resources: ["pods"]
В результате мы получаем вот такой лог:
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "RequestResponse",
"auditID": "28eff2fc-2e81-41c1-980b-35d446480e77",
"stage": "ResponseComplete",
"requestURI": "/api/v1/namespaces/default/pods/nginx-6799fc88d8-5p7x5",
"verb": "delete",
"user": {
"username": "system:serviceaccount:d8-service-accounts:gitlab-runner-deploy",
"uid": "fd5a6209-c893-4b77-adf7-90500ecd2723",
"groups": [
"system:serviceaccounts",
"system:serviceaccounts:d8-service-accounts",
"system:authenticated"
]
},
...
Здесь видно, что сервисный аккаунт удалил под NGINX.
Достаточно ли этого? На самом деле нет.
Помимо сборки таких событий, их необходимо и анализировать. Если сервисный аккаунт удалил под или создал какой-то ресурс в production, то, вероятно, это легитимная операция, потому что мы что-то деплоим. А вот если простой пользователь со своей учеткой пошел что-то править и создавать в production, то это уже проблема. Хотелось бы получить о ней алерт и узнать, что в кластере какая-то подозрительная активность.
Также необходимо регистрировать события непосредственно в хостовой ОС и в среде исполнения. Например, команда crictl exec не пройдет через аудит Kubernetes API, и мы не узнаем, что кто-то вошел в под, хотя знать это хотелось бы. Плюс хотелось бы иметь информацию о том, какие вообще процессы запускаются внутри контейнеров.
Для решения таких вопросов есть приложения, которые реализуют runtime security. В Deckhouse мы используем для этого Falco. Он выполняет следующие функции:
разбор системных вызовов на хосте;
получение аудит-лога от Kubernetes API через Webhook backend;
проверка потока событий с помощью сконфигурированных правил;
отправка алертов в случае нарушения правил.
Схема работы:
У нас есть ядро, в нем есть технология eBPF, которая собирает системные вызовы, есть вебхук, который собирает события с Kubernetes API, есть Falco с загруженными правилами. Он проверяет поступающие события на соответствие этим правилам и в случае чего передает данные в алертинг-систему.
Falco разворачивается в виде DaemonSet'а на все узлы кластера.
Как выглядит под с Falco?
В нем есть:
init-контейнер, в котором загружается eBPF-программа;
Falco, собирающий события и обогащающий их метаданными;
shell-operator, который собирает Falco audit rules и подготавливает правила для Falco;
метрики отдаем с помощью falcosidekick;
доступ к метрикам ограничен с помощью kube-rbac-proxy.
Falco audit rules также выполнены в виде Custom Resource, что дает возможность проверки относительно OpenAPI спецификации, а также это просто удобнее — работать с примитивами Kubernetes:
apiVersion: deckhouse.io/v1alpha1
kind: FalcoAuditRules
metadata:
name: host-audit-custom
spec:
rules:
- list:
name: cli_proc_names
items: [crictl, docker]
- macro:
name: spawned_process
condition: (evt.type in (execve, execveat) and evt.dir=<)
- rule:
name: Crictl or docker cli are executed
desc: Detect ctl or docker are executed in cluster
condition: spawned_process and proc.name in (crictl, docker)
output: Crictl or docker are executed (user=%user.name user_loginuid=%user.loginuid command=%proc.cmdline pid=%proc.pid parent_process=%proc.pname)
priority: Warning
tags: [host]
Созданные Falco audit rules на лету трансформируются в обычные Falco-правила:
- items:
- crictl
- docker
list: cli_proc_names
- condition: (evt.type in (execve, execveat) and evt.dir=<)
macro: spawned_process
- condition: spawned_process and proc.name in (cli_proc_names)
desc: Detect crictl or docker are executed in cluster
enabled: true
output: Crictl or docker are executed (user=%user.name user_loginuid=%user.loginuid
command=%proc.cmdline pid=%proc.pid parent_process=%proc.pname)
priority: Warning
rule: Crictl or docker cli are executed
source: syscall
tags:
- host
Falco-правило содержит листы и макросы, позволяющие переиспользовать кусочки коды и писать более понятные и лаконичные условия.
По приведенному выше правилу в логах мы увидим следующее:
{
"hostname": "demo-master-0",
"output": "14:17:03.188306224: Warning Crictl or docker are executed (user=<NA> user_loginuid=1000 command=crictl ps pid=273049 parent_process=bash) k8s.ns=<NA> k8s.pod=<NA> container=host",
"priority": "Warning",
"rule": "Crictl or docker cli are executed",
"source": "syscall",
"tags": [
"host"
],
"time": "2023-03-14T14:17:03.188306224Z",
"output_fields": {
"container.id": "host",
"evt.time": 1678803423188306200,
"k8s.ns.name": null,
"k8s.pod.name": null,
"proc.cmdline": "crictl ps",
"proc.pid": 273049,
"proc.pname": "bash",
"user.loginuid": 1000,
"user.name": "<NA>"
}
}
{
"hostname": "demo-master-0",
"output": "14:43:34.760338878: Warning Crictl or docker are executed (user=<NA> user_loginuid=1000 command=crictl stop 067bd732737af pid=307453 parent_process=bash) k8s.ns=<NA> k8s.pod=<NA> container=host",
"priority": "Warning",
"rule": "Crictl or docker cli are executed",
"source": "syscall",
"tags": [
"host"
],
"time": "2023-03-14T14:43:34.760338878Z",
"output_fields": {
"container.id": "host",
"evt.time": 1678805014760339000,
"k8s.ns.name": null,
"k8s.pod.name": null,
"proc.cmdline": "crictl stop 067bd732737af",
"proc.pid": 307453,
"proc.pname": "bash",
"user.loginuid": 1000,
"user.name": "<NA>"
}
}
Пользователь с UID 1000 запустил команду crictl ps
или crictl stop
.
Правила Kubernetes Audit выглядят примерно так же:
- required_plugin_versions:
- name: k8saudit
version: 0.1.0
- macro: kevt_started
condition: (jevt.value[/stage]=ResponseStarted)
- macro: pod_subresource
condition: ka.target.resource=pods and ka.target.subresource exists
- macro: kcreate
condition: ka.verb=create
- rule: Attach/Exec Pod
desc: >
Detect any attempt to attach/exec to a pod
condition: kevt_started and pod_subresource and kcreate and ka.target.subresource in (exec,attach)
output: Attach/Exec to pod (user=%ka.user.name pod=%ka.target.name resource=%ka.target.resource ns=%ka.target.namespace action=%ka.target.subresource command=%ka.uri.param[command])
priority: NOTICE
source: k8s_audit
tags: [k8s]
В них также есть макросы, листы и все то, что умеет Falco. Например, это правило в логах отобразит, что кто-то пошел в под через Kubernetes API:
{
"hostname": "demo-master-0",
"output": "18:27:29.160641000: Notice Attach/Exec to pod (user=admin@example.com pod=deckhouse-77f868d554-8zpt4 resource=pods ns=d8-system action=exec command=mkdir)",
"priority": "Notice",
"rule": "Attach/Exec Pod",
"source": "k8s_audit",
"tags": [
"k8s"
],
"time": "2023-03-14T18:27:29.160641000Z",
"output_fields": {
"evt.time": 1678818449160641000,
"ka.target.name": "deckhouse-77f868d554-8zpt4",
"ka.target.namespace": "d8-system",
"ka.target.resource": "pods",
"ka.target.subresource": "exec",
"ka.uri.param[command]": "mkdir",
"ka.user.name": "admin@example.com"
}
}
Но просто генерировать метрики недостаточно. Администратор кластера должен получать информацию о том, что в кластере происходит подозрительная активность. Поскольку все метрики отправляются в Prometheus, нужно создать для них правило:
apiVersion: deckhouse.io/v1
kind: CustomPrometheusRules
metadata:
name: runtime-audit-pod-exec
spec:
groups:
- name: runtime-audit-pod-exec
rules:
- alert: RuntimeAuditPodExecAlerts
annotations:
description: |
There are suspicious attach/exec operations.
Check your events journal for more details.
summary: Falco detected a security incident
expr: |
sum(rate(falco_events{rule="Attach/Exec Pod"}[5m])) > 0
Их можно настроить либо на конкретные события Falco, либо на события с определенным уровнем критичности.
Самое ценное, что есть в Falco — это Falco-правила. Они глубоко кастомизируемые, их нужно писать под конкретные приложения и под конкретную инфраструктуру. У нас есть набор базовых правил, которые можно посмотреть на GitHub.
Перечень полезных правил:
Я часто слышу, что аудит и регистрация событий нужны только компаниям enterprise-уровня для прохождения сертификаций, но это не совсем так. Falco очень полезен для разбора инцидентов, в том числе не связанных с безопасностью. Например, для поиска сбойных операторов в кластере, осуществивших нелегитимное удаление каких-то подов.
Одним их самых важных элементов аудита и регистрации событий является реагирование на угрозы — администратор должен сразу получать уведомления о том, что в кластере происходит что-то, что требует внимания.
Заключение
Безопасность — это циклический процесс. Всегда есть куда двигаться.
Постоянно появляются новые угрозы, новые технологии. Вопрос безопасности Kubernetes до конца не решен и требует внимания.
Сам по себе Kubernetes — это большая система, в которую необходимо устанавливать дополнительные компоненты, чтобы повысить безопасность. И здесь самое главное не забывать уделять время организации этой безопасности. Очень часто бизнес хочет как можно быстрее развернуть Kubernetes, быстрее перевести туда приложения, а на безопасность не остается времени. В таком случае нужно внедрить хотя бы часть описанных выше решений, которые закроют основные проблемы.
Если времени совсем нет, стоит посмотреть в сторону готовых решений. Есть как отечественные вендоры, поставляющие софт для безопасности в кластере, так и зарубежные. Также можно рассмотреть вариант с готовыми платформами, которые способны закрыть многие вопросы безопасности.
Видео и слайды
Видеозапись выступления (~50 минут):
Презентация:
P.S.
Читайте также в нашем блоге: