Основы безопасности Kubernetes: cетевое взаимодействие

Сетевое взаимодействие в Kubernetes охватывает широкий спектр настроек, которые зависят от окружения, конфигурации кластера и особенностей его использования. Здесь мы рассмотрим стандартный Kubernetes-кластер с оверлейной сетью (overlay network) и использованием kube-proxy для маршрутизации сервисов.

Несмотря на разнообразие подходов к сетевым настройкам, один принцип остаётся неизменным: по умолчанию все pod-ы в Kubernetes-кластере могут взаимодействовать друг с другом без ограничений.


Это делает Kubernetes удобным для управления распределёнными приложениями — такая свобода взаимодействия, в сочетании с возможностями сервис-дискавери, позволяет динамически распределять рабочие нагрузки по узлам. Однако, открытость сетевого взаимодействия без ограничений создаёт риски для безопасности, так как по умолчанию сеть Kubernetes представляет собой "плоское" пространство без барьеров.

Зоны сетевого доверия (Network Trust Zones)

При обсуждении различных дистрибутивов Kubernetes полезно различать управляемые (managed) и неуправляемые (unmanaged) кластеры.


  • Управляемые Kubernetes-кластеры (AKS, EKS, GKE)

Управляются облачными провайдерами, которые контролируют control plane (плоскость управления) и предоставляют дополнительные сервисы. Это снижает сложность эксплуатации для DevOps-команд, но ограничивает гибкость настройки сети.


  • Неуправляемые Kubernetes-кластеры

Администрируются самостоятельно, и оператор кластера управляет всеми узлами. Такой кластер может быть развернут на физических серверах (on-premises) или в облачных виртуальных машинах (например, AWS EC2).


Зоны доверия в неуправляемом Kubernetes-кластере


В неуправляемом кластере можно выделить две основные зоны сетевого доверия:

  1. Сетевая зона узлов (Node Network) – включает control plane и worker-ноды. Здесь находятся сервисы управления кластером и облачные метаданные.
  2. Сетевая зона 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-ов меняются динамически.


Основные правила сетевых политик


  1. По умолчанию pod не имеет ограничений на входящий и исходящий трафик.
  2. Если на pod применена любая Network Policy, то разрешается только тот трафик, который явно указан в правилах.
  3. 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
Ответим на все вопросы по переносу, сборке, оркестрации и развертывании.
Настроим ваш Kubernetes