CiliumNetworkPolicy: модель default-deny и allowlist

Published: 2026-02-28

Kubernetes NetworkPolicy — хорошая идея с неудачной реализацией: синтаксис многословный, семантика запутанная (ingress/egress — с точки зрения пода), а кластерный DNS работает загадочным образом. CiliumNetworkPolicy расширяет стандартный API правилами на основе идентичностей, L7-фильтрацией и egress по FQDN.


Проблема отсутствия политик

По умолчанию каждый под может достучаться до любого другого в кластере. Скомпрометированное приложение свободно обращается к:

  • Vault API
  • Prometheus API (в лейблах метрик — внутренние имена сервисов)
  • базам данных из других неймспейсов
  • внешним эндпоинтам без ограничений

Сеть по модели zero trust начинается с default-deny. Радиус поражения любого скомпрометированного пода ограничен тем, что явно разрешено политикой.


Шаг 1: default-deny для namespace

yamlapiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: default-deny
  namespace: app
specs:
  - description: "Default deny all ingress and egress"
    endpointSelector: {}
    ingress:
      - {}
    egress:
      - {}

ingress: [{}] выглядит как «разрешить всё», но в семантике CiliumNetworkPolicy пустой список — это deny all. Один {} без fromEndpoints или fromCIDR не соответствует ничему. В результате весь ingress- и egress-трафик в неймспейсе отбрасывается.

После применения поды не смогут обращаться даже к DNS. Исправляем следующим правилом.


Шаг 2: разрешить DNS

yamlapiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: allow-dns
  namespace: app
spec:
  endpointSelector: {}
  egress:
    - toEndpoints:
        - matchLabels:
            "k8s:io.kubernetes.pod.namespace": kube-system
            "k8s:k8s-app": kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
            - port: "53"
              protocol: TCP

Без этого ломается service discovery — поды не стартуют из-за ошибок резолва DNS.


Шаг 3: разрешить ingress от ingress controller

yamlapiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: allow-from-ingress
  namespace: app
spec:
  endpointSelector:
    matchLabels:
      app: my-service
  ingress:
    - fromEndpoints:
        - matchLabels:
            "k8s:io.kubernetes.pod.namespace": ingress-apisix

До my-service может достучаться только ingress-контроллер APISIX. Прямой трафик между подами из других неймспейсов заблокирован.


Шаг 4: межсервисное взаимодействие

Frontend должен обращаться к backend:

yamlapiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: app
spec:
  endpointSelector:
    matchLabels:
      app: backend
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: frontend
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP

Только поды frontend и только порт 8080.


Шаг 5: egress к базам данных

yamlapiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: allow-backend-to-postgres
  namespace: app
spec:
  endpointSelector:
    matchLabels:
      app: backend
  egress:
    - toEndpoints:
        - matchLabels:
            "k8s:io.kubernetes.pod.namespace": databases
            app: postgresql
      toPorts:
        - ports:
            - port: "5432"
              protocol: TCP

Egress по FQDN (внешние API)

yamlapiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: allow-external-api
  namespace: app
spec:
  endpointSelector:
    matchLabels:
      app: my-service
  egress:
    - toFQDNs:
        - matchName: "api.stripe.com"
        - matchName: "api.sendgrid.com"
        - matchPattern: "*.s3.amazonaws.com"
      toPorts:
        - ports:
            - port: "443"
              protocol: TCP

Cilium резолвит FQDN на лету и обновляет eBPF-карты. Поды могут достучаться только до явно разрешённых внешних эндпоинтов.


L7-политика для HTTP

Cilium умеет применять политику на уровне HTTP:

yamlegress:
  - toEndpoints:
      - matchLabels:
          app: payment-service
    toPorts:
      - ports:
          - port: "8080"
            protocol: TCP
        rules:
          http:
            - method: "POST"
              path: "/api/v1/charge"

Попытка GET /admin будет отброшена на сетевом уровне, до того как запрос достигнет пода.


Отладка через Hubble

bash# Все отброшенные соединения в неймспейсе
hubble observe --namespace app --verdict DROPPED

# Трафик в реальном времени
hubble observe --namespace app -f

# Что отбрасывается у конкретного пода
hubble observe --namespace app --from-pod app/my-service --verdict DROPPED

Hubble показывает под-источник, под-цель или FQDN, порт и политику с вердиктом POLICY_DENIED. Добавлять логирование в приложения не нужно.


Постепенное внедрение

Не применяйте default-deny к продакшен-неймспейсам сразу. Рабочий процесс:

  1. Применить default-deny к непродакшен-неймспейсу
  2. Наблюдать за потоками DROPPED в Hubble
  3. Добавить разрешающие правила для каждого легитимного потока
  4. Повторять, пока неожиданные дропы не исчезнут
  5. Перенести политики в продакшен

Cilium поддерживает режим audit на уровне политики — трафик разрешается, а нарушения логируются. Используйте его для инвентаризации потоков перед включением блокировки.