Несмотря на разнообразие подходов к сетевым настройкам, один принцип остаётся неизменным: по умолчанию все pod-ы в Kubernetes-кластере могут взаимодействовать друг с другом без ограничений.
Это делает Kubernetes удобным для управления распределёнными приложениями — такая свобода взаимодействия, в сочетании с возможностями сервис-дискавери, позволяет динамически распределять рабочие нагрузки по узлам. Однако, открытость сетевого взаимодействия без ограничений создаёт риски для безопасности, так как по умолчанию сеть Kubernetes представляет собой "плоское" пространство без барьеров.
При обсуждении различных дистрибутивов Kubernetes полезно различать управляемые (managed) и неуправляемые (unmanaged) кластеры.
Управляются облачными провайдерами, которые контролируют control plane (плоскость управления) и предоставляют дополнительные сервисы. Это снижает сложность эксплуатации для DevOps-команд, но ограничивает гибкость настройки сети.
Администрируются самостоятельно, и оператор кластера управляет всеми узлами. Такой кластер может быть развернут на физических серверах (on-premises) или в облачных виртуальных машинах (например, AWS EC2).
Зоны доверия в неуправляемом Kubernetes-кластере
В неуправляемом кластере можно выделить две основные зоны сетевого доверия:
Зоны доверия в управляемом Kubernetes-кластере
В управляемых кластерах control plane находится под управлением облачного провайдера.
CNI-плагины являются важным компонентом сетевой архитектуры Kubernetes.
По умолчанию Kubernetes не предоставляет сетевой функциональности, поэтому операторы кластера подключают сетевые плагины спецификации Container Network Interface (CNI).
Критически важно, поддерживают ли они Kubernetes Network Policies, поскольку это определяет возможности безопасности сети.
Flannel и Kindnet не поддерживают сетевые политики, что делает их менее безопасными. Calico и Cilium поддерживают встроенные Network Policies Kubernetes + имеют свои собственные расширенные политики.
Сетевые политики Kubernetes (Network Policies) позволяют операторам кластера ограничивать трафик на основе Kubernetes-объектов или, при необходимости, диапазонов IP-адресов. Обычно используются Kubernetes-объекты, так как IP-адреса pod-ов меняются динамически.
Основные правила сетевых политик
Пример применения сетевой политики
Для демонстрации можно развернуть среду с трехузловым кластером kind и CNI-плагином Calico.
В этой среде развернуто простое приложение:
Чтобы проверить доступность приложения, создадим новый 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.
С учетом понимания принципов работы сетевых политик возникает вопрос: "Как их использовать для обеспечения безопасности сети?" Точный ответ зависит от использования вашего кластера, но есть несколько общих принципов.
Сетевая безопасность — ключевая часть защиты 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