Попробуй решить на первый взгляд простую инженерную задачу: у тебя в кластере Kubernetes работает платёжный сервис checkout, которому разрешено общаться во внешнем мире ровно с двумя эндпоинтами — API Stripe и webhook-сервером GitHub. Любой другой исходящий трафик должен быть заблокирован, чтобы при взломе контейнера злоумышленник не слил данные на сторонний сервер.
Ты открываешь спецификацию ресурса Kubernetes NetworkPolicy, смотришь в секцию egress и упираешься в стену. Стандартный механизм умеет фильтровать трафик только по IP-адресам и маскам CIDR через ipBlock. Но у Stripe, GitHub или AWS S3 нет фиксированных статичных адресов: они живут за CDN с Anycast-маршрутизацией, где адреса сменяются каждые несколько минут, а TTL в DNS составляет считанные секунды.
В итоге перед инженером встаёт дилемма: либо разрешить подам доступ во весь интернет (0.0.0.0/0), либо затевать сложнейшую миграцию кластера на специализированный CNI вроде Cilium или внедрять тяжёлый Service Mesh с sidecar-прокси Envoy в каждом поде.
Открытый контроллер fqdn-network-policy от разработчика Kunal решает эту проблему без лишних костылей. Инструмент вводит ресурс FQDNNetworkPolicy, который берёт на себя мониторинг DNS-имён и автоматически генерирует под капотом стандартные правила NetworkPolicy, сохраняя совместимость с любым CNI.
В чём боль стандартного Egress: почему CIDR не дружит с современным интернетом
В материале Настройка Network Policies в Kubernetes мы детально разбирали паттерн «Default Deny» и трансляцию правил политик в таблицы iptables.
Ядро Linux и сетевые фильтры работают на уровне пакетов L3/L4, проверяя IP назначения. Когда приложение вызывает fetch('https://api.stripe.com'):
- Приложение запрашивает CoreDNS и получает актуальный IP (например,
3.18.12.10). - Приложение открывает TCP-соединение на этот IP через порт 443.
Пакет NetworkPolicy не знает доменное имя: он видит только попытку отправить SYN-пакет на 3.18.12.10. Если зашить этот IP вручную, через полчаса CDN изменит маршрут, трафик пойдёт на 3.18.12.11, и платежи упадут по таймауту.
Архитектура MultiResolver и автоматическая защита от DNS Rebinding
Контроллер fqdn-network-policy написан на Go на базе controller-runtime и библиотеки miekg/dns, работая в качестве классического Kubernetes-оператора:
- Стратегия MultiResolver:
Разные DNS-серверы могут возвращать разные Anycast IP для одного домена. Чтобы избежать рассинхронизации между подом и контроллером,
fqdn-network-policyпараллельно опрашивает четыре публичных резолвера: Cloudflare (1.1.1.1), Google (8.8.8.8), Quad9 (9.9.9.9) и OpenDNS (208.67.222.222). Результаты объединяются, перекрывая до 85% расхождений CDN. - Защита от DNS Rebinding:
Если внешний домен внезапно отрезолвится в адрес внутренней сети (вектор SSRF), контроллер автоматически отбросит такие ответы, фильтруя приватные подсети RFC1918 (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16), петлевые адреса (127.0.0.0/8) и link-local (169.254.0.0/16).
Если внешнее API меняет адреса каждую минуту, а кэш пода обновляется быстрее контроллера, первый запрос может быть сброшен. Для таких сервисов переопределяйте интервал опроса параметром resolutionTTLOverride.
Пошаговая настройка FQDNNetworkPolicy и трансляция в iptables
Контроллер устанавливается через Helm-чарт с включением Leader Election:
# Развёртывание контроллера в кластере
helm install fqdn-network-policy charts/fqdn-network-policy/ \
--namespace fqdn-network-policy-system \
--create-namespace \
--set replicaCount=2 \
--set leaderElection.enabled=true \
--set multiResolver.enabled=true
После установки мы объявляем желаемые правила исходящего трафика на понятном языке доменов:
apiVersion: netsec.kunal.dev/v1alpha1
kind: FQDNNetworkPolicy
metadata:
name: allow-payment-apis
namespace: payments
spec:
mode: Enforce
podSelector:
matchLabels:
app: checkout
egress:
- match: api.stripe.com
ports:
- port: 443
protocol: TCP
- match: api.github.com
ports:
- port: 443
protocol: TCP
resolutionTTLOverride: 30
Контроллер подхватывает CRD, запрашивает DNS, разрешает трафик к CoreDNS (порт 53) и генерирует в том же namespace стандартный объект networking.k8s.io/v1 NetworkPolicy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: fqdnnp-allow-payment-apis
namespace: payments
spec:
podSelector:
matchLabels:
app: checkout
policyTypes:
- Egress
egress:
- ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
- to:
- ipBlock:
cidr: 3.18.12.10/32
- ipBlock:
cidr: 140.82.121.6/32
ports:
- port: 443
protocol: TCP
Твой сетевой плагин (Calico, Flannel, AWS VPC CNI или Azure CNI) подхватывает сгенерированный манифест как обычную родную политику.
Баланс между удобством и безопасностью: где ждать подводных камней
Главное достоинство fqdn-network-policy — нулевой порог входа. Не нужно переустанавливать кластер или запускать Envoy-сайдкары, съедающие память.
Но инженер обязан трезво видеть границы применимости:
- Это фильтрация L3/L4, а не L7: контроллер не инспектирует HTTP-заголовки или пути URI. Для точечной валидации методов потребуется API Gateway или Service Mesh.
- Fail-Closed поведение: если контроллер потеряет связь с DNS-серверами, старые IP продолжат действовать до истечения кэша, после чего исходящий трафик будет заблокирован. Поэтому в проде запускайте контроллер минимум в двух репликах.
Для подавляющего большинства сервисов, которым нужно просто ограничить исходящий периметр до разрешённых SaaS-платформ, этот инструмент закрывает давний пробел в стандартном стеке Kubernetes буквально за пятнадцать минут.
