Сетевые политики (Network Policies) — это новая функциональность Kubernetes, которая за счет создания брандмауэров позволяет настроить сетевое взаимодействие между группами подов и других узлов сети. В этом руководстве я постараюсь объяснить особенности, не описанные в официальной документации по сетевым политикам Kubernetes.
Функциональность сетевых политик стабилизировалась в Kubernetes 1.7. В этой статье их работа объясняется в теории и на практике. При желании вы можете сразу перейти к репозиторию с примерами kubernetes-networkpolicy-tutorial или к документации.
Что можно делать с сетевыми политиками
В кластере Kubernetes трафик между подами по умолчанию не ограничивается. Это означает, что поды могут беспрепятственно подключаться друг к другу, и внутри кластера нет брандмауэров, которые могли бы им помешать.
Сетевые политики позволяют декларативно определять, какие поды к кому могут подключаться. При настройке политик есть возможность детализировать их до пространств имен или более точно, обозначая порты, для которых будут действовать выбранные политики.
В настоящее время исходящий от подов трафик таким образом контролировать невозможно. Эта функциональность запланирована в Kubernetes 1.8.
При этом проект с открытым исходным кодом под названием Istio является хорошей альтернативой с поддержкой фильтрации исходящего трафика, а также многими другими возможностями, включая встроенную поддержку Kubernetes.
Чем хороши сетевые политики
Сетевые политики — это еще одно имя для используемых в сфере ИТ уже не одно десятилетие списков контроля доступа (англ. ACL). В кластере Kubernetes с их помощью настраиваются списки контроля доступа для подов. Так же, как и все остальные ресурсы в кластере Kubernetes, сетевые политики настраиваются с помощью декларативных манифестов. Они являются частью приложения, расположены в его репозитории и разворачиваются в Kubernetes вместе с приложением.
Сетевые политики применяются практически в реальном времени. Если между подами есть открытые соединения, применение новой политики, запрещающей такие соединения, приведет к их немедленному разрыву. Правда, за такую оперативность приходится расплачиваться небольшими потерями производительности. См. более подробную информацию и результаты эталонных тестов в этой статье.
Примеры использования
Ниже представлено несколько распространенных примеров использования сетевых политик Kubernetes. Дополнительные примеры и соответствующие манифесты можно найти на GitHub: kubernetes-networkpolicy-tutorial.
Запрещение всего трафика к приложению (DENY)
Эта политика приведет к отбрасыванию (drop) всего трафика к подам приложения, выбранным с помощью селекторов подов (Pod Selector).
Сценарии использования:
- Вы хотите запустить под, но при этом запретить другим подам с ним взаимодействовать.
- Вы хотите временно изолировать трафик к сервису от других подов.
Пример
Запустим nginx-под с метками app=web
и env=prod
, а также откроем его 80-й порт:
kubectl run web --image=nginx --labels app=web,env=prod --expose --port 80
Запустим временный под и выполним запрос к сервису web
:
$ kubectl run --rm -i -t --image=alpine test-$RANDOM -- sh
/ # wget -qO- http://web
<!DOCTYPE html>
<html>
<head>
...
Работает! Теперь сохраним следующий манифест в файл web-deny-all.yaml
и применим его к кластеру:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: web-deny-all
spec:
podSelector:
matchLabels:
app: web
env: prod
$ kubectl apply -f web-deny-all.yaml
networkpolicy "access-nginx" created
Проверка
Запустите контейнер еще раз и попробуйте сделать запрос к сервису web
:
$ kubectl run --rm -i -t --image=alpine test-$RANDOM -- sh
/ # wget -qO- --timeout=2 http://web
wget: download timed out
Трафик заблокирован!
Замечания
В вышеприведенном манифесте для достижения желаемого мы назначили подам специальные метки app=web,env=prod
. Однако в этом файле не хватает поля spec.ingress
. Поэтому к поду запрещен весь трафик.
Если создать другую сетевую политику, которая позволит некоторым подам получить доступ к приложению напрямую или косвенно, первая сетевая политика потеряет силу.
Если есть хотя бы одна сетевая политика с разрешающими трафик правилами, этот трафик пойдет по разрешенному маршруту, невзирая на существование запрещающих правил.
Очистка
kubectl delete deploy web
kubectl delete service web
kubectl delete networkpolicy web-deny-all
Ограничение трафика к приложению (LIMIT)
Вы можете создать сетевую политику, ограничивающую трафик только от определенных подов.
Сценарии использования:
Предоставление доступа к сервису только тем микросервисам, которым это нужно.
Предоставление доступа к базе данных только использующим ее приложениям.
Пример
Предположим, что в нашем приложении есть сервер REST API c метками app=bookstore
и role=api
:
kubectl run apiserver --image=nginx --labels app=bookstore,role=api --expose --port 80
Сохраните следующую сетевую политику в файл api-allow.yaml
. Она разрешает доступ лишь подам (например, другим микросервисам) с меткой app=bookstore
:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: api-allow
spec:
podSelector:
matchLabels:
app: bookstore
role: api
ingress:
- from:
- podSelector:
matchLabels:
app: bookstore
$ kubectl apply -f api-allow.yaml
networkpolicy "api-allow" created
Проверка
Давайте убедимся, что подам без метки app=bookstore
доступ запрещен:
$ kubectl run test-$RANDOM --rm -i -t --image=alpine -- sh
/ # wget -qO- --timeout=2 http://apiserver
wget: download timed out
Трафик заблокирован!
А теперь давайте проверим, что от подов с меткой app=bookstore
трафик разрешен:
$ kubectl run test-$RANDOM --rm -i -t --image=alpine --labels app=bookstore,role=frontend -- sh
/ # wget -qO- --timeout=2 http://apiserver
<!DOCTYPE html>
<html><head>
Трафик разрешен.
Очистка
kubectl delete deployment apiserver
kubectl delete service apiserver
kubectl delete networkpolicy api-allow
Запрещение (DENY) всего не внесенного в белый список трафика в текущем пространстве имен
Сценарий использования
Это очень важная политика, которая блокирует весь трафик между подами, за исключением внесенного в белый список с помощью другой политики.
Всерьез подумайте о том, чтобы применить соответствующий манифест во всех пространствах имен, в которых развернута рабочая нагрузка (но не в kube-system
).
С помощью этой политики можно настроить доступ типа «отклонять все по умолчанию» (default "deny all"). Таким образом можно четко определить, какие компоненты зависят от других компонентов, и внедрить сетевые политики, по которым можно построить графы зависимостей между компонентами.
Манифест
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-all
namespace: default
spec:
podSelector:
matchLabels:
Несколько замечаний:
namespace
— по умолчанию применяйте эту политику в пространстве именdefault
.matchLabels
— не заполнено, это означает соответствие всем подам. Таким образом, политика будет применена ко всем подам в указанном пространстве имен.ingress
-правила не определены, поэтому отклоняться будет трафик, идущий к выбранным (всем) подам.
Сохраните этот манифест в файл default-deny-all.yaml
и примените политику:
$ kubectl apply -f default-deny-all.yaml
networkpolicy "default-deny-all" created
Очистка
kubectl delete networkpolicy default-deny-all
Запрет (DENY) всего трафика из других пространств имен
(также известно под именем LIMIT — ограничение трафика к текущему пространству имен)
Вы можете настроить сетевые политики таким образом, чтобы запретить весь трафик из других пространств имен, при этом разрешив локальный трафик в рамках пространства имен, в котором находится под.
Сценарии использования
- Вы не хотите, чтобы приложения из пространства имен
test
могли случайно направить какой-либо трафик сервисам или базам данных в пространстве именprod
. - У вас в разных пространствах имен размещены приложения разных клиентов, и вы хотите изолировать их друг от друга.
Пример
Создайте новое пространство имен под названием secondary
и запустите веб-сервис:
kubectl create namespace secondary
kubectl run web --namespace secondary --image=nginx --labels=app=web --expose --port 80
Сохраните следующий манифест в файл web-deny-other-namespaces.yaml
и примените его к кластеру:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
namespace: secondary
name: web-deny-other-namespaces
spec:
podSelector:
matchLabels:
ingress:
- from:
- podSelector: {}
$ kubectl apply web-deny-other-namespaces.yaml
networkpolicy "web-deny-other-namespaces" created"
Несколько замечаний по поводу манифеста:
namespace: secondary
приведет к применению манифеста в пространстве именsecondary
;- политика будет применена ко всем подам в пространстве имен
secondary
, посколькуspec.podSelector.matchLabels
значения не содержит, выбирая таким образом все поды; - разрешен трафик от всех подов в пространстве имен
secondary
, посколькуspec.ingress.from.podSelector
значения не содержит, что также означает соответствие всем подам.
Проверка
Отправьте запрос к этому веб-сервису из пространства имен default
:
$ kubectl run test-$RANDOM --namespace=default --rm -i -t --image=alpine -- sh
/ # wget -qO- --timeout=2 http://web.secondary
wget: download timed out
Трафик из пространства имен default
заблокирован!
При этом любой под из пространства имен secondary
доступ получит:
$ kubectl run test-$RANDOM --namespace=secondary --rm -i -t --image=alpine -- sh
/ # wget -qO- --timeout=2 http://web.secondary
<!DOCTYPE html>
<html>
Очистка
kubectl delete deployment web -n secondary
kubectl delete service web -n secondary
kubectl delete networkpolicy web-deny-other-namespaces -n secondary
kubectl delete namespace secondary
Разрешение (ALLOW) любого трафика из пространства имен
Эта политика похожа на разрешение трафика из всех пространств имен, при этом она позволяет выбрать определенное пространство имен.
Сценарии использования:
- Ограничение трафика к production-базе данных таким образом, чтобы он был разрешен только от пространств имен, в которых развернуты production-приложения.
- Развертывание средств мониторинга, которым разрешено собирать метрики текущего пространства имен в специально для этого созданном отдельном пространстве имен.
Пример
Запустите веб-сервер в пространстве имен по умолчанию:
kubectl run web --image=nginx --labels=app=web --expose --port 80
Теперь предположим, что у вас есть вот такие пространства имен:
default
— создано Kubernetes, здесь развернут ваш API;prod
— здесь развернуты другие production-сервисы; на него установлена меткаpurpose=prod
;dev
— это dev/test-окружение; на него установлена меткаpurpose=testing
.
Создайте пространства имен prod
и dev
:
kubectl create namespace dev
kubectl label namespace/dev purpose=testing
kubectl create namespace prod
kubectl label namespace/prod purpose=production
Следующий манифест разрешит трафик только от подов, находящихся в пространстве имен с меткой purpose=production
. Сохраните его в web-allow-prod.yaml
и примените к кластеру:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: web-allow-prod
spec:
podSelector:
matchLabels:
app: web
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: production
$ kubectl apply -f web-allow-prod.yaml
networkpolicy "web-allow-prod" created
Проверка
Сделайте запрос к веб-серверу из пространства имен dev
, убедитесь, что трафик заблокирован:
$ kubectl run test-$RANDOM --namespace=dev --rm -i -t --image=alpine -- sh
If you don't see a command prompt, try pressing enter.
/ # wget -qO- --timeout=2 http://web.default
wget: download timed out
(трафик заблокирован)
Теперь сделайте запрос из пространства имен prod
, убедитесь, что запрос проходит:
$ kubectl run test-$RANDOM --namespace=prod --rm -i -t --image=alpine -- sh
If you don't see a command prompt, try pressing enter.
/ # wget -qO- --timeout=2 http://web.default
<!DOCTYPE html>
<html>
<head>
...
(трафик разрешен)
Очистка
kubectl delete networkpolicy web-allow-prod
kubectl delete deployment web
kubectl delete service web
kubectl delete namespace {prod,dev}
Разрешение (ALLOW) трафика от внешних клиентов
Эта сетевая политика позволяет внешним клиентам получать доступ к поду через балансировщик нагрузки или напрямую из Интернет.
Сценарии использования:
- Вам необходимо настроить доступ к подам напрямую из Интернет, при этом запрещая весь трафик, не внесенный в белый список.
Пример
Запустите под и откройте его 80-й порт для доступа из Интернет через балансировщик нагрузки:
kubectl run web --image=nginx --labels=app=web --port 80
kubectl expose deployment/web --type=LoadBalancer
Дождитесь появления EXTERNAL-IP в выводе kubectl get service
. Откройте http://[EXTERNAL-IP]
в браузере и убедитесь в наличии доступа к ресурсу.
Следующий манифест разрешает трафик из любых источников (как внутри кластера, так и из внешних источников). Сохраните его в файл web-allow-external.yaml
и примените к кластеру:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: web-allow-external
spec:
podSelector:
matchLabels:
app: web
ingress:
- from: []
$ kubectl apply -f web-allow-external.yaml
networkpolicy "web-allow-external" created
Снова откройте в браузере http://[EXTERNAL-IP]
и убедитесь, что он по-прежнему работает.
Замечания
В этом манифесте определено одно ingress-правило для подов с меткой app=web
. Поскольку конкретные podSelector
или namespaceSelector
не указаны, будет пропускаться трафик от любых источников, в том числе и внешних.
Чтобы разрешить доступ извне только к 80-му порту, воспользуйтесь следующим ingress-правилом:
ingress:
- ports:
- port: 80
from: []
Очистка
kubectl delete deployment web
kubectl delete service web
kubectl delete networkpolicy web-allow-external
Как применяются сетевые политики
Сетевые политики не являются базовой функциональностью Kubernetes. И хотя вы можете отправить объект NetworkPolicy на мастер Kubernetes, политика не сработает, если соответствующая функциональность не реализована в сетевом плагине.
Примеры сетевых плагинов, поддерживающих сетевые политики, можно найти на этой странице. Также сетевые политики поддерживаются плагинами Calico и Weave Net.
В Google Container Engine (GKE) поддержка сетевых политик реализована в начальной стадии (alpha) путем предустановки сетевого плагина Calico.
Сетевые политики применяются к соединениям, а не к сетевым пакетам. Обратите внимание на то, что соединения подразумевают двунаправленную передачу сетевых пакетов. Например, если под А может подключиться к поду Б, под Б может ответить поду А в рамках текущего соединения. Однако это не означает, что под Б может инициировать соединение с подом А.
Анатомия NetworkPolicy
NetworkPolicy — это один из объектов Kubernetes API. В кластере может быть создано множество таких объектов. В NetworkPolicy есть две основные составляющие:
- Целевые поды — поды, входящие (ingress) сетевые соединения которых должны подчиняться соответствующим политикам. Эти поды отбираются по меткам.
- Входящие (ingress) правила определяют, какие поды могут подключаться к целевым подам. Они также отбираются по меткам или по пространствам имен.
Вот конкретный пример манифеста NetworkPolicy:
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: api-allow
spec:
podSelector:
matchLabels:
app: bookstore
role: api
ingress:
- from:
- podSelector:
matchLabels:
app: bookstore
- from:
- podSelector:
matchLabels:
app: inventory
В этой политике подам с метками app=bookstore
или app=inventory
разрешено подключаться к подам с метками app=bookstore
или role=api
. Ее можно озвучить следующим образом: «Предоставить микросервисам приложения bookstore доступ к bookstore API».
Как работают сетевые политики
Несмотря на то что проектная документация и справка по API сетевых политик весьма сложны для понимания, мне удалось выделить несколько простых правил:
- Если в NetworkPolicy выбран под, то предназначенный для этого пода трафик будет ограничиваться.
- Если для пода не определен объект NetworkPolicy, к этому поду смогут подключаться все поды из всех пространств имен. То есть, если для конкретного пода не определена сетевая политика, по умолчанию неявно подразумевается поведение «разрешить все (allow all)».
- Если трафик к поду А ограничен, а под Б должен к нему подключиться, необходимо создать объект NetworkPolicy, в котором выбран под А, а также есть ingress-правило, в котором выбран под Б.
Дело усложняется в том случае, когда надо настроить сетевое взаимодействие между различными пространствами имен. В двух словах это работает так:
- Сетевые политики действуют только на сетевые подключения к тем подам, которые находятся в одном пространстве имен с NetworkPolicy.
- В разделе ingress-правила
podSelector
можно выбрать только поды из того же пространства имен, в котором развернут объект NetworkPolicy. - Если поду А необходимо подключиться к находящемуся в другом пространстве имен поду Б и сетевые подключения к поду Б ограничены сетевой политикой, под А должен быть выбран в поле
namespaceSelector
сетевой политики пода Б.
Насколько безопасны сетевые политики?
Сетевые политики ограничивают сетевой обмен данными между подами и являются важной частью работы по обеспечению безопасности трафика и приложений в кластере Kubernetes. Однако, в отличие от брандмауэров, в рамках сетевых политик не выполняется глубокий анализ пакетов (deep packet inspection).
В деле обеспечения безопасности трафика между подами в кластере не стоит полагаться только на сетевые политики. Рекомендую также присмотреться к таким методам, как TLS (transport layer security) со взаимной аутентификацией, который позволяет зашифровать трафик и выполнять аутентификацию при взаимодействии микросервисов.
Взгляните на Google Cloud Security Whitepaper (выделение сделано мной):
Глубоко эшелонированная оборона (Defense in depth) описывает множество уровней системы безопасности, которая защищает сеть Google от атак извне. Проход разрешен только авторизованным сервисам и протоколам, отвечающим нашим требованиям безопасности. Все остальное отбрасывается автоматически. Для разделения сетей используются брандмауэры промышленного уровня и списки контроля доступа (ACLs). Весь трафик направляется через специальным образом настроенные серверы GFE (Google Front End), что позволяет выявлять и останавливать вредоносные запросы и DDoS-атаки. Также GFE-серверам разрешено взаимодействовать лишь с внутренними серверами, находящимися в особом списке. Такая политика типа «запретить по умолчанию (default deny)» позволяет предотвратить доступ с GFE-серверов к тем ресурсам, которые им не нужны. […]
При передаче через Интернет и внутренние сети данные становятся уязвимы, и к ним может быть осуществлен неавторизованный доступ. […] Серверы Google Front End (GFE) поддерживают надежные протоколы шифрования, такие как TLS, что позволяет обеспечить безопасность соединений между устройствами клиентов и веб-сервисами Google.
Упомянутые мною ранее связанные с сервисными сетками (service mesh) проекты, такие как Istio и linkerd, обещают качественные улучшения в этой области. Например, Istio может шифровать трафик между микросервисами с использованием TLS и применять сетевые политики прозрачно, без необходимости менять код приложения.
Дополнительная информация
Если вы хотите попробовать сетевые политики в действии, легче всего будет начать с создания кластера GKE. Также можно почитать следующее:
- Network Policy documentation;
- Network Policy Design document — здесь описано назначение функциональности сетевых политик, однако их реализация отражена недостаточно подробно;
- Network Policy API reference;
- NetworkPolicy benchmark от сетевого плагина Romana.
Благодарю Matthew DeLio и Daniel Nardo за проверку черновиков этой статьи.
Ссылки:
- Оригинал: Securing Kubernetes Cluster Networking.
disakov
Еще хороший обзор про безопасность платформы на BSidesMCR 17 — www.youtube.com/watch?v=b3qJwIttqqs