проверять и ограничивать каждое соединение между подами

Сеть с нулевым доверием в Kubernetes: сетевые политики и mTLS с Cilium

14 минут

В Kubernetes по умолчанию любой под достигает любого другого. Сочетайте запрет по умолчанию через NetworkPolicy, взаимную аутентификацию на SPIRE и принуждение в eBPF через Cilium — сегментируйте внутрикластерный трафик и доказывайте идентичность сервиса без sidecar на каждом поде.

Почему «плоская» сеть Kubernetes не тянет продакшен

Большинство кластеров всё ещё работают в режиме «разрешено по умолчанию»: любой под может достичь любого другого в соседних пространствах имён, пока это явно не запрещено. В небольшом staging с доверенными сервисами это удобно. В продакшене с десятками команд, границами соответствия требованиям и мультитенантными нагрузками — уязвимость. Три проблемы ведут к инцидентам. Горизонтальное перемещение: скомпрометированный под в платежах сканирует базы, внутренние API и кэши в других namespace, потому что граница доверия по умолчанию плоская. Нет идентичности сервиса: TLS на ingress защищает трафик «север–юг», а вызовы «восток–запад» часто идут по под-сети без шифрования и без доказательства, что вызывающий авторизован — любая нагрузка в сети может выдать себя за клиента. Операционное сопротивление: сегментация на ручных правилах iptables или закрытых API вендора не успевает за ежедневными деплоями. Нужна политика рядом с кодом приложения, в Git, с единым принуждением на всех кластерах. Без слоя сети с нулевым доверием периметр крепкий, а двор внутри открыт.

Авторизация, идентичность и принуждение в eBPF — одна модель

Нулевое доверие здесь — три дополняющих слоя. NetworkPolicy — авторизация: какие поды с какими собеседниками, на каких портах и из каких namespace; правила файрвола в терминах объектов Kubernetes, а не таблиц IP. Запрет по умолчанию на уровне namespace плюс явные разрешения по меткам дают сильную мультитенантную сегментацию без неуправляемых списков CIDR. mTLS — идентичность: политика говорит, кому можно подключаться, но только взаимный TLS доказывает, кто реально шлёт байты. Обе стороны предъявляют сертификаты; подмена и атаки «запутанного заместителя» на транспортном уровне отсекаются до логики приложения. Cilium — движок принуждения: программы eBPF применяют NetworkPolicy, взаимную аутентификацию и наблюдаемость Hubble на уровне сокета, без длинных цепочек iptables. Правила опираются на метки подов, а не на хрупкие диапазоны IP. Hubble показывает потоки L3/L4/L7 и вердикты политик без лишних агентов. Прозрачная взаимная аутентификация через SPIRE избавляет от sidecar на каждом поде для базового доверия «восток–запад», а встроенный Envoy включает L7-правила HTTP там, где нужны метод и путь.

Этап 0: карта реального трафика в Hubble до любых запретов

Главная причина провала миграции — запрет по умолчанию до понимания легитимных потоков. Поставьте Cilium с Hubble relay, UI и метриками отброшенных пакетов, наблюдайте минимум один полный бизнес-цикл — пакетные задания, ночные cron, обработка конца месяца. Выгрузите уникальные пары «источник — назначение» в чертёж политик. Только после этого включайте принуждение по одному namespace за раз.

Bash · установка Cilium с Hubble через Helm
helm repo add cilium https://helm.cilium.io
helm repo update

helm upgrade --install cilium cilium/cilium \
  --version 1.16.0 \
  --namespace kube-system \
  --set hubble.enabled=true \
  --set hubble.relay.enabled=true \
  --set hubble.ui.enabled=true \
  --set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution}"
Bash · выгрузка наблюдаемых потоков между подами
hubble observe --since 168h --output json | \
  jq -r '[.source.namespace, .source.pod.name, .destination.namespace, .destination.pod.name] | @tsv' | \
  sort -u > observed-flows.tsv

Этапы 1–2: запрет по умолчанию в namespace, затем явные разрешения

Включайте deny-all для ingress и egress в одном прикладном namespace за раз. Обязательно добавьте явное egress-правило на kube-dns — без него имя не резолвится, и это ломает всё молча. Проверяйте через hubble observe, что легитимные потоки проходят, прежде чем переходить к следующему namespace. По файлу observed-flows.tsv добавляйте точечные ingress-разрешения: шлюз frontend к API на 8080, API к Postgres на 5432. Для правил между namespace используйте метки namespace, внутри namespace — метки подов: метки namespace меняются редко, метки подов задают границы сервисов.

YAML · запрет по умолчанию и разрешение DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
YAML · разрешение шлюза frontend к API платежей
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: payments-api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: frontend
          podSelector:
            matchLabels:
              app: web-gateway
      ports:
        - protocol: TCP
          port: 8080
YAML · доступ к Postgres только с API
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-postgres
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: postgres
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: payments-api
      ports:
        - protocol: TCP
          port: 5432

Этап 3: взаимная аутентификация через SPIRE и Cilium

Взаимная аутентификация Cilium (Beta) проверяет идентичность нагрузки через SPIFFE/SPIRE до передачи данных. Включите authentication и установку SPIRE в Helm, затем добавьте authentication.mode required в ingress-правила CiliumNetworkPolicy. Первый пакет нового соединения может кратко отбрасываться, пока агенты завершают рукопожатие — на холодных соединениях возможен короткий retry. Выпуск и ротация сертификатов — у агента Cilium и SPIRE; в образах приложений не храните файлы сертификатов. Для требований шифрования транспорта дополнительно к идентичности включите WireGuard или IPsec в Helm.

Bash · включение взаимной аутентификации в Helm
helm upgrade cilium cilium/cilium \
  --namespace kube-system \
  --reuse-values \
  --set authentication.enabled=true \
  --set authentication.mutual.spire.enabled=true \
  --set authentication.mutual.spire.install.enabled=true
YAML · CiliumNetworkPolicy с обязательной аутентификацией
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: mtls-payments-api
  namespace: payments
spec:
  endpointSelector:
    matchLabels:
      app: payments-api
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: web-gateway
            io.kubernetes.pod.namespace: frontend
      authentication:
        mode: "required"
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP

Этап 4: L7-правила HTTP для контроля поверхности API

Сетевые разрешения отсекают чужих собеседников, но не мешают авторизованному поду вызвать админские эндпоинты. Для HTTP CiliumNetworkPolicy задаёт метод и путь через встроенный Envoy на выбранных портах. Даже когда mTLS доказал идентичность, L7 ограничивает, какие URL ей доступны — глубина защиты для внутренних API.

YAML · L7 allow list для API платежей
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: l7-payments-api
  namespace: payments
spec:
  endpointSelector:
    matchLabels:
      app: payments-api
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: web-gateway
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP
          rules:
            http:
              - method: "GET"
                path: "/api/v1/(balance|transactions).*"
              - method: "POST"
                path: "/api/v1/transfer"

Практика: Git, уровни политик, мониторинг и безопасность выполнения

Храните NetworkPolicy и CiliumNetworkPolicy в том же Git-репозитории, что Helm-чарты или Kustomize — каждое изменение через ревью запроса на слияние с историей отката. Метрики drop и TCP-потоков Hubble включайте с первого дня: политика, блокирующая readiness probe, каскадом вызывает сбой — алертируйте на новые вердикты DROPPED в критичных namespace. В общих кластерах платформенные ограничения выносите в CiliumClusterwideNetworkPolicy — блок metadata облака, неожиданный egress из kube-system — чтобы политики арендаторов не ослабляли глобальную сегментацию. Ротацию сертификатов автоматизируйте: короткоживущие идентичности SPIRE лучше годовых сертификатов, после которых отзыв бессмысленен. Новые политики гоняйте в staging на захваченных логах потоков до продакшена; регрессии в три часа ночи дороги. Дополняйте сегментацию сети инструментами выполнения — Falco, Tetragon — для неожиданных процессов, записи в ФС и доступа к учётным данным внутри подов. Admission Policy as Code в Kyverno или Gatekeeper может требовать метки NetworkPolicy на каждом namespace до деплоя нагрузок — чтобы стандарты в документации совпадали с принуждением. Нулевое доверие проверяется на каждом слое, а не только на ingress.

Запрет по умолчанию дополняет базовую линию из практического руководства по усилению безопасности Kubernetes в продакшене.

Потоки в Hubble и datapath на eBPF подробнее разобраны в гайде по наблюдаемости ядра с eBPF и Cilium Hubble.