Примечание переводчика: Перевели небольшое руководство для тех, кто хочет узнать об управлении сетевым трафиком Kubernetes на основе политик. В статье Скотт Ригби, мейнтейнер Helm и Flux, рассказывает о политиках L4 и L7, их плюсах и минусах и о том, когда стоит комбинировать оба типа политик для лучшей защиты K8s-кластеров. Ему слово.
В мире Kubernetes сетевые политики играют ключевую роль в управлении трафиком внутри вашего кластера. Но что они собой представляют? Зачем, когда и как их стоит использовать?
Если вы уже используете Kubernetes и хотите лучше понять работу сети или вы сетевой инженер, стремящийся адаптировать свои знания к Kubernetes, то пришли по адресу.
Это руководство для тех, кто хочет узнать больше об управлении сетевым трафиком Kubernetes на основе политик. Вы узнаете о разных типах политик и о том, почему они важны, о плюсах и минусах каждой из них и о том, как их определять и когда следует комбинировать.
Что такое сетевые политики
Сетевые политики помогают определить, какому трафику разрешено входить (ingress) в кластер, выходить (egress) из него и перемещаться между подами.
Как и другие ресурсы в Kubernetes, сетевые политики — это декларативные конфигурации. Компоненты кластера будут опираться на них, принимая решения о том, какой трафик разрешён или запрещён и при каких условиях.
Но как только вы определитесь с политиками и придёт время задать их в манифесте ресурсов Kubernetes, перед вами откроются несколько вариантов. Сделать это можно будет как с помощью нативного ресурса NetworkPolicy, так и с помощью кастомных ресурсов от инструментов вроде Cilium, Calico, Istio и главного героя этой статьи — Linkerd. Но мы не будем их сравнивать. Вместо этого рассмотрим два основных подхода к сетевым политикам в Kubernetes.
Вообще, сетевые политики можно разделить на два уровня — L4 и L7. Знаю, о чём вы подумали: семислойный буррито! Хотелось бы, но нет. Речь идёт о семи уровнях модели Open Systems Interconnection (OSI).
(Забавный факт: вдохновением для семислойного буррито послужил семислойный дип. Он был широко популярен в 1980-х, но появился менее чем через год после публикации модели OSI в 1980 году. Совпадение? Оставлю вам факты из истории интернета, включая оригинальную критику модели OSI, RFC документа по сетям от IEFT и небольшую отсылку в комиксе xkcd.)
Модель OSI разбивает сетевое взаимодействие на семь уровней. Хотя она может считаться несколько устаревшей — многократно отмечалось, что интернет на самом деле так не работает, — уровни 4 и 7 остаются полезным подспорьем для наших целей.
L4 относится к четвёртому, транспортному уровню модели OSI — на нём работают протоколы вроде TCP. L7 соответствует (вы угадали!) седьмому, прикладному уровню OSI, который включает HTTP, gRPC и другие специфичные для приложений протоколы. В контексте сетевых политик Kubernetes политики L4 работают на уровне кластера, а политики L7 — на уровне приложений. Как и сами уровни OSI, сетевые политики L4 и L7 должны работать в тандеме.
В этом руководстве мы рассмотрим оба типа политик: как они определяются, их преимущества и ограничения, а также роль в модели «нулевого доверия» (Zero Trust).
Сетевые политики L4
Сетевые политики четвёртого уровня реализуются плагинами Container Network Interface (CNI) и представлены в кластере в виде ресурсов NetworkPolicy.networking.k8s.io
. Под капотом они работают на уровне IP-адресов и портов, хотя, как это заведено в Kubernetes, настраивать их приходится с помощью селекторов лейблов подов. Такое несоответствие приводит к некоторым уязвимостям, о которых мы поговорим ниже.
Как работают сетевые политики L4
Политики L4 реализуются с помощью плагинов CNI и настраивают пакетный фильтр ядра — например, с помощью Netfilter/iptables или eBPF — на IP-адреса и номера портов, соответствующие подам в разных сервисах. Так, для политики «сервис A не может обращаться на порт 8080 сервиса B» система будет динамически подстраивать правила под изменения сервисов и их соответствующих подов. При этом требуемые пространства имён или поды будут находиться по их лейблам с помощью podSelector.
Вот пример простой сетевой политики L4, определяющей, что только TCP-трафик с service-a
разрешён на порт 8080 для подов в service-b
. Как только создаётся NetworkPolicy с policyTypes
«Ingress», которой соответствуют поды в service-b
, эти поды считаются «изолированными для Ingress». Это означает, что будет разрешён только входящий трафик, соответствующий комбинации всех ингресс-списков. Ответный трафик для этих разрешённых соединений неявно разрешён. Есть и другие варианты, включая явные правила для egress, описанные на странице сетевых политик в документации Kubernetes, но для того, чтобы показать, как в целом устроены политики L4 в K8s, достаточно этого простого примера:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: service-a-to-b
namespace: default
spec:
podSelector:
matchLabels:
app: service-b
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: service-a
ports:
- protocol: TCP
port: 8080
Плюсы и минусы сетевых политик L4
Плюсы:
Встроены в большинство CNI-плагинов, что делает их легкодоступными.
Минусы:
Можно задавать только через лейблы подов и порты — детализировать маршруты или имена сервисов не получится.
Ограничены в типах правил — например, нельзя сделать так, чтобы весь трафик проходил через общий шлюз.
Не умеют логировать события безопасности.
Уязвимы для сценариев split-brain, когда нарушается соответствие между подами и IP-адресами, что потенциально позволяет злоумышленникам получить несанкционированный доступ.
Возможность спуфинга IP-адресов и портов. Современные парадигмы безопасности уже вышли за рамки таких простых конструкций.
Когда следует использовать сетевые политики L4
Сетевые политики L4 просты для понимания и широко доступны в Kubernetes. Однако небезопасность, ограниченная выразительность и неспособность логировать события безопасности не оставляют им никакого шанса: политики L4 относятся к категории «необходимых, но недостаточных» мер контроля. Их следует комбинировать с другими мерами, например с сетевыми политиками уровня L7.
Сетевые политики L7
Сетевые политики L7 работают на прикладном уровне, понимая такие протоколы, как HTTP и gRPC. L7 отличаются от L4 большей детализацией — например, с их помощью можно разрешить сервису A вызывать эндпоинт foo/bar сервиса B или сервису B общаться только с mTLS-сервисами.
Реализация политик L7 с помощью Linkerd
В Kubernetes политики L7 обычно реализуются через service mesh, такой как Linkerd, и представлены в виде кастомных ресурсов, таких как Server.policy.linkerd.io
. Linkerd умеет работать с высокоуровневыми протоколами, а потому прекрасно проксирует вызовы HTTP и gRPC и применяет политики соответствующим образом. Для получения более детальной информации обратитесь к документации по серверным политикам Linkerd.
Как работают политики L7
Linkerd работает на стороне сервера. Это означает, что прокси ограничивают доступ к сервису независимо от источника соединения. Политики основаны на идентификаторах сервисов, которые определяются в процессе взаимного TLS (mTLS). Это позволяет использовать для авторизации криптографические свойства соединения вместо IP-адресов (которые в этом плане ненадёжны, их легко фальсифицировать). Метод разработан для бесперебойной работы в распределённых системах вроде открытого интернета, в которых координация между системами нестабильна.
Важно отметить, что в Linkerd существуют два типа политик:
По умолчанию (Default).
Динамические (Dynamic).
Политики по умолчанию задаются при установке или обновлении Linkerd. Они позволяют разрешать или запрещать трафик в зависимости от того, находятся ли клиенты внутри service mesh или в пределах одного кластера (это может пригодиться, когда service mesh охватывает несколько кластеров). Существует также политика для полного запрета трафика.
Если ни одна из этих политик не указана, весь трафик разрешён (чтобы мир не «сломался» при установке Linkerd). Можно переопределить политики для конкретного пространства имён или нагрузки. Подробности о политиках по умолчанию смотрите в документации. Для этой же статьи достаточно знать, что они существуют и работают в тандеме с динамическими политиками. (Прим. редакции: Linkerd — очень верхнеуровневый инструмент. Его поле proxy.defaultInboundPolicy
может принимать одно из пяти значений: all-unauthenticated
, all-authenticated
, cluster-authenticated
, cluster-unauthenticated
и deny
.)
Динамические политики позволяют менять поведение политики на лету, обновляя кастомные ресурсы, которые контролируют эти политики. Динамические политики также называют «детальными» — с их помощью можно управлять трафиком для конкретных сервисов, портов, маршрутов и так далее.
В отличие от единственного ресурса для L4 (NetworkPolicy.networking.k8s.io
), динамические политики Linkerd описываются множеством CRD (custom Kubernetes resource, кастомными ресурсами K8s). Это позволяет задавать более детализированные правила и переиспользовать их в связанных политиках, а также также снизить нагрузку на API и более эффективно разграничить права доступа к этим ресурсам.
Один набор CRD позволяет определить, куда направится трафик: к Server
или подмножеству его трафика, называемому HTTPRoute
. Другой набор CRD представляет правила аутентификации (либо mesh-идентификаторы MeshTLSAuthentication
, либо набор IP-подсетей клиентов NetworkAuthentication
), которые должны быть удовлетворены в рамках политики. И наконец, существует CRD, представляющий саму политику AuthorizationPolicy
, которая связывает кастомный ресурс из первого набора CRD (целевой трафик, подлежащий защите) с одним из CRD второго типа (аутентификация перед тем, как доступ к цели будет разрешён).
Вот пример кастомных ресурсов для Linkerd L7, которые делают то же, что и пример для L4, приведённый выше: Server
, MeshTLSAuthentication
и AuthorizationPolicy
, которая их связывает.
Получатель трафика:
apiVersion: policy.linkerd.io/v1beta1
kind: Server
metadata:
name: service-b
namespace: default
spec:
podSelector:
matchLabels:
app: service-a
port: 8080
proxyProtocol: "HTTP/2"
Правила аутентификации:
apiVersion: policy.linkerd.io/v1alpha1
kind: MeshTLSAuthentication
metadata:
name: service-a
namespace: default
spec:
identityRefs:
- kind: ServiceAccount
name: service-a
Политика авторизации:
apiVersion: policy.linkerd.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: service-a-to-b
namespace: default
spec:
targetRef:
group: policy.linkerd.io
kind: Server
name: service-b
requiredAuthenticationRefs:
- name: service-a
kind: MeshTLSAuthentication
group: policy.linkerd.io
Документация Linkerd по Authorization Policy описывает дополнительные возможности. Наш же пример даёт представление о детализации, гибкости и возможности переиспользования политик L7 в Linkerd.
Плюсы и минусы сетевых политик L7
Плюсы:
Позволяют создавать тонко настроенные политики. Например, разрешать только зашифрованные соединения с mTLS и принимать решения на основе путей, заголовков, параметров запроса. Так, можно разрешить только подам с лейблом
app: foo
обращаться по пути/bar
в сервисе.Не подвержены сценариям split-brain.
В сочетании со взаимным TLS обеспечивают аутентификацию и шифрование.
Могут логировать события безопасности (например, попытки обойти политику).
Минусы:
Работают только с service mesh, поскольку не встроены нативно в Kubernetes.
Не применяются к трафику, отправляемому на рабочие нагрузки вне mesh (даже если рабочие нагрузки — источники трафика находятся в mesh).
Не работают с UDP- или другим не-TCP-трафиком.
Комбинирование политик L7 и L4
Одни сценарии лучше решать с помощью политик L4, другие — с помощью политик L7. К счастью, нет нужды выбирать что-то одно. Можно использовать оба типа политик в кластере одновременно. Описанные выше преимущества и недостатки помогут понять, когда и какую политику применять.
Как упоминалось ранее, если ресурс не находится в mesh, стоит выбрать политику L4. Ещё L4-политики пригодятся, когда требуется единая политика для кластера, которая будет работать независимо от того, объединены ли конкретные рабочие нагрузки в service mesh.
Иногда возможностей установленного service mesh может не хватить. Например, Linkerd добавил поддержку IPv6 в версии 2.16 — более ранние версии не умели работать с IPv6. В некоторых случаях нужная функциональность не доступна вообще ни в одной из версий service mesh. Так, Linkerd пока не поддерживает UDP- или не-TCP-трафик, потому что конкретный сценарий использования для этого ещё не создан. Политика L4 будет здесь хорошим вариантом.
Политики L7 позволяют реализовать более сложные сценарии, требующие более детального контроля, что открывает множество новых возможностей. Можно создавать allow- или deny-правила для конкретных клиентов, правила доступа к отдельным эндпоинтам рабочей нагрузки, проверки наличия или отсутствия зашифрованного mTLS-соединения, аутентификации в определённой сети, проверки того, является ли клиент частью mesh или находится в том же кластере, а также настраивать многие другие параметры.
Наконец, в некоторых случаях разумно комбинировать дублирующие функции политик L7 и L4, дополнительно повышая безопасность. Используя приведённые выше примеры, можно объединить политики L4 и L7, задав и политику авторизации сервера Linkerd, и NetworkPolicy Kubernetes, тем самым ограничив всю сетевую коммуникацию с сервисом (service-b
) указанным клиентским сервисом (service-a
). Подобное многоуровневое сочетание сетевых политик является примером стратегии «глубинной защиты» (defense in depth), которая предусматривает резервные меры на случай отказа одного из элементов управления безопасностью.
Заключение
Сетевые политики — основополагающий аспект защиты кластеров Kubernetes. Политики L4 обеспечивают базовый контроль над трафиком на основе IP-адресов и портов, в то время как политики L7 позволяют гранулярно контролировать трафик на уровне приложений с помощью надёжной криптографической идентификации. Комбинируя оба типа политик и используя service mesh, можно реализовать модель «нулевого доверия», отвечающую современным вызовам в области безопасности.
P. S.
Оригинал статьи Скотт Ригби опубликовал в блоге компании Buoyant. Как и всегда, в благодарность за интересный контент оставляем ссылку на их продукт — Buoyant Enterprise для Linkerd.
Читайте также в нашем блоге: