
Несмотря на разнообразие подходов к сетевым настройкам, один принцип остаётся неизменным: по умолчанию все pod-ы в Kubernetes-кластере могут взаимодействовать друг с другом без ограничений.
Это делает Kubernetes удобным для управления распределёнными приложениями — такая свобода взаимодействия, в сочетании с возможностями сервис-дискавери, позволяет динамически распределять рабочие нагрузки по узлам. Однако, открытость сетевого взаимодействия без ограничений создаёт риски для безопасности, так как по умолчанию сеть Kubernetes представляет собой "плоское" пространство без барьеров.
Зоны сетевого доверия (Network Trust Zones)
При обсуждении различных дистрибутивов Kubernetes полезно различать управляемые (managed) и неуправляемые (unmanaged) кластеры.
- Управляемые Kubernetes-кластеры (AKS, EKS, GKE)
Управляются облачными провайдерами, которые контролируют control plane (плоскость управления) и предоставляют дополнительные сервисы. Это снижает сложность эксплуатации для DevOps-команд, но ограничивает гибкость настройки сети.
- Неуправляемые Kubernetes-кластеры
Администрируются самостоятельно, и оператор кластера управляет всеми узлами. Такой кластер может быть развернут на физических серверах (on-premises) или в облачных виртуальных машинах (например, AWS EC2).
Зоны доверия в неуправляемом Kubernetes-кластере
В неуправляемом кластере можно выделить две основные зоны сетевого доверия:
- Сетевая зона узлов (Node Network) – включает control plane и worker-ноды. Здесь находятся сервисы управления кластером и облачные метаданные.
- Сетевая зона pod-ов (Pod Network) – используется для развертывания рабочих нагрузок. Возможно разделение на несколько зон доверия в зависимости от команд и типов приложений.
Зоны доверия в управляемом Kubernetes-кластере
В управляемых кластерах control plane находится под управлением облачного провайдера.
- API-server остаётся доступен DevOps-командам, но сеть узла, на котором он запущен, остаётся закрытой.
- У операторов нет доступа к конфигурации инфраструктурных сервисов control plane, что снижает риски, но ограничивает кастомизацию.
Container Network Interface (CNI)
CNI-плагины являются важным компонентом сетевой архитектуры Kubernetes.
По умолчанию Kubernetes не предоставляет сетевой функциональности, поэтому операторы кластера подключают сетевые плагины спецификации Container Network Interface (CNI).
Разновидности CNI-плагинов
Критически важно, поддерживают ли они Kubernetes Network Policies, поскольку это определяет возможности безопасности сети.
| CNI-плагин | Поддержка Network Policies | Расширенные сетевые функции |
|---|---|---|
| Flannel | Нет | Нет |
| Kindnet | Нет | Нет |
| Calico | Да | Да |
| Cilium | Да | Да |
Flannel и Kindnet не поддерживают сетевые политики, что делает их менее безопасными. Calico и Cilium поддерживают встроенные Network Policies Kubernetes + имеют свои собственные расширенные политики.
Управление сетевым доступом в Kubernetes
Сетевые политики Kubernetes (Network Policies) позволяют операторам кластера ограничивать трафик на основе Kubernetes-объектов или, при необходимости, диапазонов IP-адресов. Обычно используются Kubernetes-объекты, так как IP-адреса pod-ов меняются динамически.
Основные правила сетевых политик
- По умолчанию pod не имеет ограничений на входящий и исходящий трафик.
- Если на pod применена любая Network Policy, то разрешается только тот трафик, который явно указан в правилах.
- Network Policies работают на уровне Kubernetes-объектов, а не на уровне IP-адресов, так как IP pod-ов в кластере динамически меняются.
Пример применения сетевой политики
Для демонстрации можно развернуть среду с трехузловым кластером kind и CNI-плагином Calico.
В этой среде развернуто простое приложение:
- Два pod-а web-сервера
- Один pod базы данных
- Один pod клиента в другом namespace
Чтобы проверить доступность приложения, создадим новый pod в namespace по умолчанию.
kubectl run -n default -it client --image=raesene/alpine-containertools -- /bin/bash
curl web-app.netpol-demo.svc.cluster.local
Эта команда должна вернуть стандартную веб-страницу Nginx. После того как мы убедились, что приложение работает, можно применить первую сетевую политику.
# 7. Default deny all ingress traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: netpol-demo
spec:
podSelector: {}
policyTypes:
- Ingress
После применения политики повторный curl-запрос завершится таймаутом, так как больше невозможно подключиться из namespace по умолчанию, где находится наш pod-клиент, к развертыванию web-приложения.
Возникает вопрос: "Как именно Calico блокирует этот трафик?" Работа сетевых плагинов Kubernetes на низком уровне различается, но в случае с Calico можно проверить правила iptables на узлах кластера, используя, например, iptables-differ. Должно появиться правило, похожее на следующее.
+ -A cali-pi-_S8ASFUGmFkVQ9FwaawE -m comment --comment "cali:x_7bZcXsQE7OUSr9" -m comment --comment "Policy netpol-demo/knp.default.default-deny-ingress ingress"
Очевидно, что в реальном приложении нам не нужно блокировать весь входящий трафик, поэтому необходимо добавить дополнительные правила, разрешающие другим рабочим нагрузкам в кластере доступ к нему. Например, следующее правило позволяет pod-ам в любом namespace с меткой purpose: frontend взаимодействовать с pod-ами в namespace netpol-demo, имеющими метку app: web на порту 80/TCP.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-allow-frontend
namespace: netpol-demo
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
purpose: frontend
ports:
- protocol: TCP
port: 80
Важно помнить, что сетевые политики применяются также к трафику внутри одного namespace, поэтому доступ между pod-ами необходимо явно разрешать. В нашем примере можно задать политику, разрешающую приложению web-app доступ к базе данных.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
namespace: netpol-demo
spec:
podSelector:
matchLabels:
app: db
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- protocol: TCP
port: 5432
Здесь мы разрешаем доступ к pod-ам с меткой app: db от pod-ов с меткой app: web. Обратите внимание, что можно разместить базу данных в другом namespace Kubernetes и всё равно использовать эту сетевую политику для разрешения доступа к ней от pod-ов web-app.
Защита сетевого взаимодействия кластера
С учетом понимания принципов работы сетевых политик возникает вопрос: "Как их использовать для обеспечения безопасности сети?" Точный ответ зависит от использования вашего кластера, но есть несколько общих принципов.
- Настраивайте доступы ещё на этапе разработки. Создавайте сетевые политики вместе с манифестами приложений и тестируйте их до продакшена.
- Придерживайтесь модели "default deny". Запрещайте весь трафик по умолчанию, затем добавляйте разрешения. Обратный подход сложнее в управлении.
- Учитывайте как входящие (ingress), так и исходящие (egress) правила. В облачных средах неконтролируемый egress может позволить атакующим получить доступ к метадате инстанса.
- Ограничивайте использование host networking. Подключение через hostNetwork: true обходит сетевые политики, поэтому его стоит запрещать или использовать Cilium Host Firewall.
Заключение
Сетевая безопасность — ключевая часть защиты Kubernetes-кластера, в котором по умолчанию нет строгих настроек. Операторы должны сами решать, какие ограничения применять, чтобы предотвратить несанкционированный доступ.
Развертывание демонстрационного окружения
Для демонстрации используем kind, но его стандартная конфигурация не поддерживает сетевые политики. Мы создадим кластер с тремя узлами и без CNI.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
disableDefaultCNI: true
nodes:
- role: control-plane
- role: worker
- role: worker
Сохраните конфигурацию как multi-node-kind.yaml, затем создайте кластер: kind create cluster --name=multi-node --config=multi-node-kind.yaml
Как только у нас будет базовый кластер, мы можем развернуть пример рабочей нагрузки. Он состоит из веб-приложения на основе nginx и контейнера базы данных postgres.
# 1. Demo namespace
apiVersion: v1
kind: Namespace
metadata:
name: netpol-demo
---
# 2. Example web application deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: netpol-demo
spec:
selector:
matchLabels:
app: web
replicas: 2
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
---
# 3. Web application service
apiVersion: v1
kind: Service
metadata:
name: web-app
namespace: netpol-demo
spec:
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
# 4. Database secrets
apiVersion: v1
kind: Secret
metadata:
name: postgres-secrets
namespace: netpol-demo
type: Opaque
data:
postgres-password: cG9zdGdyZXNwYXNzMTIzNA== # base64 encoded 'postgrespass1234'
---
# 5. Database deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: database
namespace: netpol-demo
spec:
selector:
matchLabels:
app: db
replicas: 1
template:
metadata:
labels:
app: db
spec:
containers:
- name: postgres
image: postgres:13
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: "testdb"
- name: POSTGRES_USER
value: "postgres"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secrets
key: postgres-password
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
emptyDir: {}
---
# 6. Database service
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: netpol-demo
spec:
selector:
app: db
ports:
- protocol: TCP
port: 5432
targetPort: 5432
type: ClusterIP
