Перейти к содержимому
GUARDCORE Инженерная база знаний Поиск

Блог

Ingress и Egress в Kubernetes: почему закрыть вход недостаточно

РазделБлог
ФорматПрактический разбор
ЦиклGuardCore
Чтение8 минут

Кратко

Цикл: «Сетевая безопасность Kubernetes: от основ до практической защиты» · материал 8 из 12Формат: практический разборДля кого: ИБ-специалист, DevOps-инженер, архитектор и разработчик. Кратко В Kubernetes словом ingress называют и входящий трафик к workload, и API-объект для публикации HTTP/HTTPS. Это не одно и…

Цикл: «Сетевая безопасность Kubernetes: от основ до практической защиты» · материал 8 из 12
Формат: практический разбор
Для кого: ИБ-специалист, DevOps-инженер, архитектор и разработчик.

Кратко

В Kubernetes словом ingress называют и входящий трафик к workload, и API-объект для публикации HTTP/HTTPS. Это не одно и то же. Ingress API направляет внешний HTTP(S)-трафик к Service; он не заменяет NetworkPolicy, WAF, RBAC и контроль исходящих соединений.

Egress — трафик из Pod к другим системам: DNS, БД, Kubernetes API, внутреннему proxy, облачному metadata service или Интернету. Без отдельной egress-политики скомпрометированный Pod сохраняет канал для загрузки инструмента, командного управления и вывода данных, даже если вход к приложению хорошо защищён.

Безопасная цель — не «запретить Интернет везде», а описать каждую необходимую связь: источник, назначение, протокол, порт, владелец, способ проверки и допустимый путь. Для стандартной NetworkPolicy это обычно Pod/namespace/IP/CIDR и порт; правило «разрешить только api.example.com» требует функции конкретного CNI либо контролируемого proxy/gateway.

Не смешивайте три границы

Рассмотрим опубликованное приложение frontend.

Internet → Gateway / Ingress controller → frontend Pod → backend Pod → внешняя платёжная система

Здесь есть три разные задачи:

Граница Вопрос Типичные механизмы
Внешняя публикация Кто и по какому HTTP(S)-маршруту приходит к приложению? Gateway API или Ingress controller, WAF, TLS, LoadBalancer
Внутренний ingress Какие источники могут подключиться к Pod? NetworkPolicy ingress, Service mesh, CNI
Egress Куда Pod может установить соединение? NetworkPolicy egress, egress proxy/gateway, CNI-specific policy, NGFW

Ingress API предназначен для HTTP/HTTPS-доступа к Service извне. Kubernetes рекомендует для новой функциональности Gateway API: Ingress API остаётся поддерживаемым, но заморожен и не развивается. Это не повод срочно переписывать работающий Ingress, но новый дизайн лучше строить с учётом Gateway API и конкретной реализации контроллера.

Главная ошибка — считать, что закрытый Ingress закрывает приложение целиком. NetworkPolicy ingress не ограничивает egress, а default deny только для ingress не меняет исходящие соединения.

Почему egress — реальная граница ущерба

После выполнения кода в контейнере нарушитель действует от имени этого Pod. Если он может обращаться куда угодно, ему доступны внешние каналы. В зависимости от окружения это могут быть загрузка дополнительного кода, обратное соединение, передача данных, обращение к внутренним административным сервисам и туннелирование через DNS.

Но и без атаки открытый egress мешает расследованию: нет ожидаемого набора направлений, поэтому новое TLS-соединение к незнакомому адресу трудно отличить от рабочей зависимости. Egress allow-list одновременно уменьшает достижимость и делает аномалию заметной.

Это не означает, что нужно выдавать один шаблон deny all всему кластеру. У приложений есть зависимости, которые команда часто не учитывает: DNS, proxy, IdP, БД, мониторинг, журналирование, backup, внутренний API, обновление сертификатов. Блокировка без карты потоков быстро приводит к аварийному allow all.

Начинайте с карты, а не с YAML

Для одного workload составьте минимальную таблицу до изменения политики.

Источник Назначение Протокол/порт Зачем Допустимый путь Как проверить
frontend CoreDNS UDP+TCP/53 разрешение имён прямо к DNS Pod nslookup
frontend backend TCP/8080 API приложения Service внутри кластера HTTP-тест
backend PostgreSQL TCP/5432 данные private subnet сценарий приложения
backend платёжный API TCP/443 бизнес-функция только через proxy тестовая транзакция

«Нужен Интернет» — не строка матрицы. Нужны конкретная роль, направление и путь. Если назначение меняет IP или обслуживается SaaS, сначала определяют, чем именно будут выражать правило: устойчивым private endpoint, корпоративным proxy, egress gateway или FQDN-функцией конкретного CNI. Стандартный Kubernetes API здесь не даёт универсального решения.

Базовая модель egress в NetworkPolicy

Pod становится изолированным для egress, когда его выбирает политика с policyTypes: [Egress]. Разрешения всех применимых политик складываются. Следовательно, политика с пустым egress не является «приоритетным запретом», а переводит выбранные Pod в состояние, где допускаются только соединения, разрешённые хотя бы одной подходящей egress-политикой.

Минимальный запрет исходящего трафика для одного namespace выглядит так:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: app
spec:
  podSelector: {}
  policyTypes:
  - Egress

Этот манифест не затрагивает ingress. Он также блокирует DNS. Поэтому применять его в production до готовых разрешений и тестового окна нельзя.

DNS — первое обязательное разрешение

В кластере приложения обычно используют имена Service. После egress deny curl http://backend:8080 может сообщать не об ошибке backend, а о невозможности резолвинга. Kubernetes прямо предупреждает: default deny egress требует отдельной политики к cluster DNS.

Ниже — типовой вариант только для кластера, где DNS Pod находятся в kube-system и имеют метку k8s-app: kube-dns. Перед применением проверьте реальные labels и endpoint своего DNS.

kubectl -n kube-system get svc kube-dns
kubectl -n kube-system get pods -l k8s-app=kube-dns --show-labels
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: app
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

Проверка должна идти из Pod целевого namespace, а не с node администратора:

kubectl -n app run dnsutils --image=registry.k8s.io/e2e-test-images/agnhost:2.39 \
  --restart=Never --command -- sleep infinity
kubectl -n app wait --for=condition=Ready pod/dnsutils --timeout=60s
kubectl -n app exec dnsutils -- nslookup backend.app.svc.cluster.local

Если dnsutils не проходит, не добавляйте 0.0.0.0/0. Сначала проверьте selector, namespace, UDP и TCP/53, фактические DNS endpoints и поддержку NetworkPolicy используемым CNI.

Как разрешить конкретный внешний адрес

Для стабильного корпоративного сервиса или egress proxy стандартный API позволяет использовать ipBlock. Пример ниже не является готовой конфигурацией: адрес 10.90.10.25 и порт 443 должны быть заменены на подтверждённые параметры вашего proxy.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-to-corporate-proxy
  namespace: app
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes: [Egress]
  egress:
  - to:
    - ipBlock:
        cidr: 10.90.10.25/32
    ports:
    - protocol: TCP
      port: 443

Такое правило ограничивает destination IP и порт, но не доказывает, что на 443 расположен именно ожидаемый сервис и не описывает HTTP host/path. Проверку проводят настоящим бизнес-запросом через предназначенный proxy, а затем повторяют отрицательный тест к другому адресу/порту. Для ipBlock не используйте Pod CIDR: IP Pod динамичны, а поведение при Service/NAT зависит от реализации сети.

FQDN, proxy и egress gateway: где заканчивается стандарт Kubernetes

Стандартная NetworkPolicy не умеет правило по DNS-имени. Если внешний API часто меняет адреса, выбор между вариантами архитектурный:

  • Corporate egress proxy. Workload соединяется только с одним контролируемым proxy, а разрешения по имени, TLS и журналирование выполняются там. Это самый переносимый способ для HTTP(S).
  • Private endpoint или фиксированный CIDR. Подходит, если поставщик гарантирует сеть и есть процесс изменения адресов.
  • FQDN policy конкретного CNI. Например, Cilium имеет DNS-based policy, но это расширение CNI: нужно отдельно проверять DNS-путь, кэширование, наблюдаемость и отказоустойчивость.
  • Egress gateway конкретной реализации. Он может вывести выбранные Pod через выделенный node/IP. Это не встроенная функция Kubernetes NetworkPolicy и не заменяет policy на самом workload.

Не выбирайте FQDN policy только потому, что она звучит точнее. DNS становится частью security boundary: нужно сохранять события разрешения имени, понимать время жизни ответа и проверять, что приложение не обходит ожидаемый DNS/proxy путь.

Проверка до и после изменения

Минимальная приёмка egress-policy состоит из пяти наблюдений:

  1. Разрешённый внутренний вызов приложения проходит.
  2. DNS резолвит нужное имя.
  3. Разрешённая внешняя зависимость проходит только ожидаемым путём.
  4. Неподтверждённое назначение или порт не проходят.
  5. В flow telemetry или журнале CNI видны источник, назначение, время и verdict.

Не используйте ping вместо проверки приложения: для ICMP поведение NetworkPolicy зависит от сетевой реализации. Проверяйте TCP/UDP-протокол, который реально использует зависимость. И не тестируйте только один Pod: после пересоздания Deployment повторите allow/deny-проверки, чтобы убедиться, что policy привязана к labels, а не к исчезнувшему IP.

Что часто ломают

Самая опасная ошибка — начать с allow all egress, а потом обещать «ужесточить позднее». Это почти всегда становится постоянным исключением. Вторая ошибка — полагаться только на внешний firewall, который видит worker node как источник и не различает Pod/ServiceAccount. Третья — разрешить 0.0.0.0/0:443 «потому что всё сейчас HTTPS». Это оставляет почти неограниченный канал наружу.

Отдельно проверяйте cloud metadata service. Kubernetes рекомендует ограничивать доступ Pod к metadata API, если он не нужен: такие данные могут дать доступ к инфраструктурным учётным данным. Это отдельная строка матрицы и отдельное отрицательное испытание.

Итог

Ingress определяет, как запрос приходит к приложению. Egress определяет, куда приложение может уйти. Устойчивая защита требует обеих границ, карты зависимостей и функциональных allow/deny-проверок.

Начинайте с одного namespace и одной понятной зависимости. Сначала наблюдение, затем узкие разрешения, пилот и только потом enforcement. Следующая статья покажет, какую телеметрию нужно собрать, чтобы не строить эти политики вслепую.

Официальные источники


Навигация по циклу:
← ServiceAccount и RBAC: почему сетевая защита Kubernetes невозможна без контроля API · Начало цикла · Как увидеть, что происходит внутри Kubernetes: flow logs, Audit, DNS и observability →

Редакция GuardCore

Материал подготовлен на основе указанных в тексте нормативных источников и практических оснований. При существенном обновлении дата актуализации отображается рядом с датой публикации.

Обсуждение материала

Дополните материал практическим опытом или сообщите о неточности. Комментарий появится после проверки.

Форма защищена от автоматических публикаций. Все комментарии проходят модерацию.