Прим. перев.: Автор статьи — Reuven Harrison — имеет более 20 лет опыта в разработке программного обеспечения, а на сегодняшний день является техническим директором и соучредителем компании Tufin, создающей решения для управления политиками безопасности. Рассматривая сетевые политики Kubernetes как достаточно мощное средство для сегментации сети в кластере, он в то же время считает, что они не так просты в применении на практике. Данный материал (довольно объёмный) призван улучшить осведомлённость специалистов в этом вопросе и помочь им в создании необходимых конфигураций.

Сегодня многие компании все чаще выбирают Kubernetes для запуска своих приложений. Интерес к этому ПО настолько высок, что некоторые называют Kubernetes «новой операционной системой для центров обработки данных». Постепенно Kubernetes (или k8s) начинает восприниматься как критически важная часть бизнеса, которая требует организации зрелых бизнес-процессов, в том числе обеспечения сетевой безопасности.

Для специалистов по безопасности, которых озадачили работой с Kubernetes, настоящим открытием может стать политика этой платформы по умолчанию: разрешить всё.

Это руководство поможет разобраться во внутреннем устройстве сетевых политик; понять, чем они отличаются от правил для обычных брандмауэров. Также будет рассказано о некоторых подводных камнях и будут даны рекомендации, которые помогут защитить приложения в Kubernetes.

Сетевые политики Kubernetes


Механизм сетевых политик Kubernetes позволяет управлять взаимодействием развернутых на платформе приложений на сетевом уровне (третьем в модели OSI). Сетевые политики лишены некоторых передовых функций современных брандмауэров, таких как контроль на 7 уровне OSI и обнаружение угроз, однако они обеспечивают базовый уровень сетевой безопасности, выступающий неплохой отправной точкой.

Сетевые политики контролируют коммуникации между pod'ами


Рабочие нагрузки в Kubernetes распределяются по pod’ам, которые состоят из одного или нескольких контейнеров, развернутых совместно. Kubernetes присваивает каждому pod’у IP-адрес, доступный из других pod'ов. Сетевые политики Kubernetes задают права доступа для групп pod'ов таким же образом, как группы безопасности в облаке используются для управления доступом к экземплярам виртуальных машин.

Определение сетевых политик


Как и остальные ресурсы Kubernetes, сетевые политики задаются на языке YAML. В приведенном ниже примере приложению balance открывается доступ к postgres:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.postgres
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: balance
  policyTypes:
  - Ingress



(Прим. перев.: этот скриншот, как и все последующие аналогичные, создан не родными средствами Kubernetes, а с помощью инструмента Tufin Orca, за разработкой которого стоит компания автора оригинальной статьи и который упоминается в конце материала.)

Для определения собственной сетевой политики потребуются базовые знания YAML. Этот язык основан на отступах (задаваемых пробелами, а не табуляцией). Элемент с отступом принадлежит ближайшему элементу с отступом над ним. Новый элемент списка начинается с дефиса, все остальные элементы имеют вид ключ-значение.

Описав политику на YAML, используйте kubectl, чтобы создать ее в кластере:

kubectl create -f policy.yaml

Спецификация сетевой политики


Спецификация сетевой политики Kubernetes включает в себя четыре элемента:

  1. podSelector: определяет pod'ы, затрагиваемые этой политикой (цели) — обязательный;
  2. policyTypes: указывает, какие типы политик включены в данную: ingress и/или egress — необязательный, однако я рекомендую его явно прописывать во всех случаях;
  3. ingress: определяет разрешенный входящий трафик в целевые pod'ы — необязательный;
  4. egress: определяет разрешенный исходящий трафик из целевых pod'ов — необязательный.

Пример, позаимствованный с сайта Kubernetes (я заменил role на app), показывает, как используются все четыре элемента:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:    # <<<
    matchLabels:
      app: db
  policyTypes:    # <<<
  - Ingress
  - Egress
  ingress:        # <<<
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:         # <<<
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978




Обратите внимание, что все четыре элемента включать необязательно. Обязательным является только podSelector, остальные параметры можно использовать по желанию.

Если опустить policyTypes, политика будет интерпретироваться следующим образом:

  • По умолчанию предполагается, что она определяет ingress-сторону. Если явных указаний на этот счет в политике не содержится, система будет считать, что весь трафик запрещен.
  • Поведение на egress-стороне будет определяться наличием или отсутствием соответствующего egress-параметра.

Чтобы избежать ошибок, я рекомендую всегда явно указывать policyTypes.

В соответствии с приведенной выше логикой в случае, если параметры ingress и/или egress опущены, политика будет запрещать весь трафик (см. «Правило зачистки» ниже).

Политика по умолчанию — разрешить


Если политики не определены, Kubernetes по умолчанию разрешает весь трафик. Все pod'ы свободно могут обмениваться информацией между собой. С точки зрения безопасности это может показаться нелогичным, но вспомните о том, что Kubernetes изначально создавался разработчиками с целью обеспечить взаимодействие приложений. Сетевые политики были добавлены позже.

Пространства имен


Пространства имен (Namespaces) — механизм коллективной работы Kubernetes. Они предназначены для изолирования логических окружений друг от друга, при этом обмен данными между пространствами по умолчанию разрешен.

Как и большинство компонентов Kubernetes, сетевые политики обитают в определенном пространстве имен. В блоке metadata можно прописать, какому именно пространству принадлежит политика:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: my-namespace  # <<<
spec:
...

Если пространство имен в метаданных явно не прописано, система будет использовать namespace, указанное в kubectl (по умолчанию namespace=default):

kubectl apply -n my-namespace -f namespace.yaml

Я рекомендую явно указывать namespace, если только вы не пишете политику, предназначенную сразу для нескольких пространств имен.

Основной элемент podSelector в политике будет выбирать pod’ы из пространства имен, к которому принадлежит политика (он лишен доступа к pod'ам из другого пространства имён).

Аналогичным образом podSelector'ы в блоках ingress и egress могут выбирать pod’ы только из своего пространства имен, если, конечно, вы не объедините их с помощью namespaceSelector (об этом пойдет речь в разделе «Фильтр по пространствам имен и pod'ам»).

Правила именования политик


Названия политик уникальны в рамках одного пространства имен. Двух политик с одинаковым названием в одном пространстве быть не может, но могут быть политики с одинаковыми названиями в разных пространствах. Это удобно, когда вы хотите повторно применить одну и ту же политику на нескольких пространствах.

Мне особенно нравится один из способов именования. Он состоит в том, чтобы объединять название пространства имен с целевыми pod’ами. Например:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.postgres  # <<<
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: admin
  policyTypes:
  - Ingress



Лейблы


К объектам Kubernetes, таким как pod’ы и пространства имен, можно прикреплять пользовательские лейблы. Лейблы (labels — метки) являются эквивалентом тегов в облаке. Сетевые политики Kubernetes используют лейблы для выбора pod'ов, к которым они применяются:

podSelector:
  matchLabels:
    role: db

… или пространств имен, к которым они применяются. В этом примере выбираются все pod'ы в пространствах имен с соответствующими лейблами:

namespaceSelector:
  matchLabels:
    project: myproject

Одно предостережение: при использовании namespaceSelector убедитесь, что выбираемые пространства имен содержат в себе нужный лейбл. Имейте в виду, что встроенные пространства имен, такие как default и kube-system, по умолчанию не содержат в себе лейблов.

Добавить лейбл к пространству можно следующим образом:

kubectl label namespace default namespace=default

При этом namespace в разделе metadata должен ссылаться на фактическое имя пространства, а не на лейбл:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default   # <<<
spec:
...

Источник и адресат


Политики для брандмауэров состоят из правил с источниками и адресатами. Сетевые политики Kubernetes определяются для цели — набора из pod'ов, к которым они применяются, а затем устанавливают правила для входящего (ingress) и/или исходящего (egress) трафика. В нашем примере целью политики будут все pod’ы в пространстве имен default с лейблом с ключом app и значением db:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: db   # <<<
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978




Подраздел ingress в этой политике открывает входящий трафик к целевым pod’ам. Другими словами, ingress выступает источником, а цель — соответствующим адресатом. Аналогичным образом egress является адресатом, а цель — его источником.



Это эквивалентно двум правилам для брандмауэра: Ingress > Цель; Цель > Egress.

Egress и DNS (важно!)


Ограничивая исходящий трафик, особое внимание обратите на DNS — Kubernetes использует эту службу для сопоставления сервисов с IP-адресами. Например, следующая политика не сработает, поскольку вы не разрешили приложению balance обращаться к DNS:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.balance
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: balance
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgres
  policyTypes:
  - Egress



Исправить ее можно, открыв доступ к сервису DNS:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.balance
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: balance
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgres
  - to:               # <<<
    ports:            # <<<
    - protocol: UDP   # <<<
      port: 53        # <<<
  policyTypes:
  - Egress



Последний элемент to — пустой, и поэтому он косвенно выбирает все pod'ы во всех пространствах имен, позволяя balance посылать DNS-запросы в соответствующую службу Kubernetes (обычно она работает в пространстве kube-system).

Этот подход работает, однако он чрезмерно разрешающий и небезопасный, поскольку позволяет направлять DNS-запросы за пределы кластера.

Улучшить его можно тремя последовательными шагами.

1. Разрешить DNS-запросы только внутри кластера, добавив namespaceSelector:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.balance
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: balance
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgres
  - to:
    - namespaceSelector: {} # <<<
    ports:
    - protocol: UDP
      port: 53
  policyTypes:
  - Egress



2. Разрешить DNS-запросы только в пространстве имен kube-system.

Для этого нужно добавить лейбл в пространство имен kube-system: kubectl label namespace kube-system namespace=kube-system — и прописать ее в политике с помощью namespaceSelector:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.balance
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: balance
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: postgres
  - to:
    - namespaceSelector:         # <<<
        matchLabels:             # <<<
          namespace: kube-system # <<<
    ports:
    - protocol: UDP
      port: 53
  policyTypes:
  - Egress



3. Параноики могут пойти еще дальше и ограничить DNS-запросы определенной DNS-службой в kube-system. В разделе «Фильтр по пространствам имен И pod'ам» будет рассказано, как этого добиться.

Другой вариант — разрешить DNS на уровне пространства имен. В этом случае его не нужно будет открывать для каждой службы:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.dns
  namespace: default
spec:
  podSelector: {} # <<<
  egress:
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53
  policyTypes:
  - Egress

Пустой podSelector выбирает все pod’ы в пространстве имен.



Первое соответствие и порядок правил


В обычных брандмауэрах действие («Разрешить» или «Запретить») в отношении пакета определяется первым правилом, которому он удовлетворяет. В Kubernetes порядок политик не имеет никакого значения.

По умолчанию, когда политики не заданы, коммуникации между pod'ами разрешены и они могут свободно обмениваться информацией. Как только вы начинаете формулировать политики, каждый pod, затронутый хотя бы одной из них, становится изолированным в соответствии с дизъюнкцией (логическим ИЛИ) всех политик, которые его выбрали. Pod’ы, не затронутые какой-либо политикой, остаются открытыми.

Изменить подобное поведение можно с помощью правила зачистки.

Правило зачистки («Запретить»)


Политики брандмауэров обычно запрещают любой явным образом не разрешенный трафик.

В Kubernetes нет действия «запретить» (deny), однако аналогичного эффекта можно добиться с обычной (разрешающей) политикой, выбрав пустую группу pod'ов-источников (ingress):

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



Эта политика выбирает все pod'ы в пространстве имен и оставляет ingress неопределенным, запрещая весь входящий трафик.

Похожим образом можно ограничить весь исходящий трафик из пространства имен:

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



Учтите, что любые дополнительные политики, разрешающие трафик к pod'ам в пространстве имен, будут иметь приоритет над этим правилом (аналогично добавлению разрешающего правила перед запрещающим в конфигурации брандмауэра).

Разрешить все (Any-Any-Any-Allow)


Чтобы создать политику «Разрешить все», необходимо дополнить приведенную выше запретительную политику пустым элементом ingress:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all
  namespace: default
spec:
  podSelector: {}
  ingress: # <<<
  - {}     # <<<
  policyTypes:
  - Ingress



Она открывает доступ со всех pod'ов во всех пространствах имен (и всех IP) к любому pod’у в пространстве имен default. Подобное поведение включено по умолчанию, поэтому обычно его не нужно определять дополнительно. Однако иногда может потребоваться временно отключить некоторые конкретные разрешения для диагностики проблемы.

Правило можно сузить и разрешить доступ только к определенному набору pod'ов (app:balance) в пространстве имен default:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all-to-balance
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: balance
  ingress: 
  - {}
  policyTypes:
  - Ingress



Следующая политика разрешает весь входящий (ingress) И исходящий (egress) трафик, включая доступ к любому IP за пределами кластера:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-all
spec:
  podSelector: {}
  ingress:
  - {}
  egress:
  - {}
  policyTypes:
  - Ingress
  - Egress




Объединение нескольких политик


Политики объединяются с помощью логического ИЛИ на трех уровнях; разрешения каждого pod'а устанавливаются в соответствии с дизъюнкцией всех политик, которые его затрагивают:

1. В полях from и to можно определить три типа элементов (все они комбинируются с помощью ИЛИ):

  • namespaceSelector — выбирает пространство имен целиком;
  • podSelector — выбирает pod’ы;
  • ipBlock — выбирает подсеть.

При этом количество элементов (даже одинаковых) в подразделах from/to не ограничено. Все они будут объединены логическим ИЛИ.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.postgres
  namespace: default
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: indexer
    - podSelector:
        matchLabels:
          app: admin
  podSelector:
    matchLabels:
      app: postgres
  policyTypes:
  - Ingress



2. Внутри политики раздел ingress может иметь множество элементов from (объединяются логическим ИЛИ). Аналогичным образом раздел egress может включать множество элементов to (также объединяются дизъюнкцией):

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.postgres
  namespace: default
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: indexer
  - from:
    - podSelector:
        matchLabels:
          app: admin
  podSelector:
    matchLabels:
      app: postgres
  policyTypes:
  - Ingress



3. Различные политики также объединяются логическим ИЛИ

Но при их объединении существует одно ограничение, на которое указал Chris Cooney: Kubernetes может комбинировать политики только с различными policyTypes (Ingress или Egress). Политики, определяющие ingress (или egress), перезапишут друг друга.

Связь между пространствами имен


По умолчанию обмен информацией между пространствами имен разрешен. Изменить это можно с помощью запретительной политики, которая ограничит исходящий и/или входящий трафик в пространство имен (см. «Правило зачистки» выше).

Заблокировав доступ в пространство имен (см. «Правило зачистки» выше), вы можете внести исключения в запретительную политику, разрешив подключения с определенного пространства имен с помощью namespaceSelector:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database.postgres
  namespace: database
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
  - from:
    - namespaceSelector: # <<<
        matchLabels:
          namespace: default
  policyTypes:
  - Ingress



В результате все pod’ы в пространстве имен default получат доступ к pod'ам postgres в пространстве имен database. Но что, если вы хотите открыть доступ к postgres только конкретным pod'ам в пространстве имен default?

Фильтр по пространствам имен И pod'ам


Kubernetes версии 1.11 и выше позволяет комбинировать операторы namespaceSelector и podSelector с помощью логического И. Выглядит это следующим образом:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database.postgres
  namespace: database
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          namespace: default
      podSelector: # <<<
        matchLabels:
          app: admin
  policyTypes:
  - Ingress



Почему это трактуется как И вместо привычного ИЛИ?

Обратите внимание, что podSelector не начинается с дефиса. В YAML это означает, что podSelector и стоящий перед ним namespaceSelector относятся к одному и тому же элементу списка. Поэтому они объединяются логическим И.

Добавление дефиса перед podSelector приведет к возникновению нового элемента списка, который будет комбинироваться с предшествующим namespaceSelector с помощью логического ИЛИ.

Чтобы выбрать pod'ы с определенным лейблом во всех пространствах имен, впишите пустой namespaceSelector:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database.postgres
  namespace: database
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
  - from:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          app: admin
  policyTypes:
  - Ingress



Множественные лейблы объединяются с И


Правила для брандмауэра со множеством объектов (хостами, сетями, группами) комбинируются с помощью логического ИЛИ. Следующее правило сработает, если источник пакета совпадает с Host_1 ИЛИ Host_2:

| Source | Destination | Service | Action |
| ----------------------------------------|
| Host_1 | Subnet_A    | HTTPS   | Allow  |
| Host_2 |             |         |        |
| ----------------------------------------|

Наоборот, в Kubernetes различные лейблы в podSelector или namespaceSelector объединяются логическим И. Например, следующее правило выберет pod’ы, обладающие обеими лейблами, role=db И version=v2:

podSelector:
  matchLabels:
    role: db
    version: v2

Та же логика применяется ко всем типам операторов: селекторам целей политики, селекторам pod'ов и селекторам пространств имен.

Подсети и IP-адреса (IPBlocks)


Для сегментирования сети брандмауэры используют VLAN, IP-адреса и подсети.

В Kubernetes IP-адреса присваиваются pod'ам автоматически и могут часто меняться, поэтому для выбора pod'ов и пространств имен в сетевых политиках используются лейблы.

Подсети (ipBlocks) используются при управлении входящими (ingress) или исходящими (egress) внешними (North-South) подключениями. К примеру, эта политика открывает всем pod'ам из пространства имен default доступ к DNS-сервису Google:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: egress-dns
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 8.8.8.8/32
    ports:
    - protocol: UDP
      port: 53



Пустой селектор pod'ов в этом примере означает «выбрать все pod’ы в пространстве имен».

Данная политика открывает доступ только к 8.8.8.8; доступ к любому другому IP запрещен. Таким образом, по сути, вы заблокировали доступ к внутренней службе DNS Kubernetes. Если вы все же хотите его открыть, укажите это явно.

Обычно ipBlocks и podSelectors являются взаимоисключающими, поскольку внутренние IP-адреса pod'ов не используются в ipBlocks. Указав внутренние IP pod'ов, вы фактически разрешите подключения к/от pod'ов с этими адресами. На практике вы не будете знать, какой IP-адрес использовать, именно поэтому их не стоит применять для выбора pod'ов.

В качестве контр-примера следующая политика включает все IP и, следовательно, разрешает доступ ко всем другим pod'ам:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: egress-any
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0



Можно открыть доступ только к внешним IP, исключив внутренние IP-адреса pod'ов. Например, если подсеть вашего pod'а 10.16.0.0/14:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: egress-any
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 10.16.0.0/14



Порты и протоколы


Обычно pod'ы слушают один порт. Это означает, что можно просто не указывать номера портов в политиках и оставить все по умолчанию. Впрочем, политики рекомендуется делать максимально ограничительными, поэтому в некоторых случаях все же можно указывать порты:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.postgres
  namespace: default
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: indexer
    - podSelector:
        matchLabels:
          app: admin
    ports:             # <<<
      - port: 443      # <<<
        protocol: TCP  # <<<
      - port: 80       # <<<
        protocol: TCP  # <<<
  podSelector:
    matchLabels:
      app: postgres
  policyTypes:
  - Ingress



Заметьте, что селектор ports применяется ко всем элементам в блоке to или from, в котором содержится. Чтобы указать разные порты для различных наборов элементов, разбейте ingress или egress на несколько подразделов с to или from и в каждом пропишите свои порты:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default.postgres
  namespace: default
spec:
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: indexer
    ports:             # <<<
     - port: 443       # <<<
       protocol: TCP   # <<<
  - from:
    - podSelector:
        matchLabels:
          app: admin
    ports:             # <<<
     - port: 80        # <<<
       protocol: TCP   # <<<
  podSelector:
    matchLabels:
      app: postgres
  policyTypes:
  - Ingress



Работа портов по умолчанию:

  • Если вы полностью опускаете определение портов (ports), это означает все протоколы и все порты;
  • Если вы опускаете определение протокола (protocol), это означает TCP;
  • Если вы опускаете определение порта (port), это означает все порты.

Лучшая практика: не полагайтесь на значения по умолчанию, указывайте нужное вам явно.

Обратите внимание, что необходимо использовать порты pod'ов, а не сервисов (подробнее об этом в следующем параграфе).

Политики определены для pod'ов или сервисов?


Обычно pod’ы в Kubernetes обращаются друг к другу через сервис — виртуальный балансировщик нагрузки, перенаправляющий трафик к pod'ам, реализующим сервис. Можно подумать, что сетевые политики контролируют доступ к сервисам, но это не так. Сетевые политики Kubernetes работают с портами pod'ов, а не сервисов.

Например, если сервис слушает 80-й порт, но перенаправляет трафик на порт 8080 своих pod'ов, в сетевой политике необходимо указать именно 8080.

Подобный механизм следует признать неоптимальным: при изменении внутреннего устройства сервиса (порты которого слушают pod'ы) придется обновлять сетевые политики.

Новый архитектурный подход с использованием Service Mesh (например, см. про Istio ниже — прим. перев.) позволяет справиться с этой проблемой.

Необходимо ли прописывать как Ingress, так и Egress?


Короткий ответ — да, чтобы pod А мог связаться с pod’ом В, необходимо разрешить ему создавать исходящее соединение (для этого следует настроить egress-политику), а pod В должен иметь возможность принимать входящее соединение (для этого, соответственно, нужна ingress-политика).

Однако на практике можно положиться на политику по умолчанию, разрешающую соединения в одном или обоих направлениях.

Если некий pod-источник будет выбран одной или несколькими egress-политиками, накладываемые на него ограничения будут определяться их дизъюнкцией. В этом случае потребуется явно разрешить подключение к pod’у-адресату. Если pod не выбран какой-либо политикой, его исходящий (egress) трафик разрешен по умолчанию.

Аналогичным образом судьба pod'а-адресата, выбранного одной или несколькими ingress-политиками, будет определяться их дизъюнкцией. В этом случае необходимо явно разрешить ему получать трафик от pod'а-источника. Если pod не выбран какой-либо политикой, весь входящий (ingress) трафик для него разрешен по умолчанию.

См. пункт «Stateful или Stateless» ниже.

Логи


Сетевые политики Kubernetes не умеют журналировать трафик. Это затрудняет определение того, работает ли политика должным образом, и сильно осложняет анализ в области безопасности.

Контроль за трафиком к внешним сервисам


Сетевые политики Kubernetes не позволяют указывать полноценное доменное имя (DNS) в разделах egress. Этот факт приводит к значительному неудобству при попытке ограничить трафик к внешним адресатам, лишенным фиксированного IP-адреса (таким как aws.com).

Проверка политики


Брандмауэры предупредят вас или даже откажутся принять ошибочную политику. Kubernetes тоже проводит некоторую верификацию. При задании сетевой политики через kubectl Kubernetes может заявить, что она неверна, и отказаться ее принять. В других случаях Kubernetes примет политику и дополнит ее недостающими деталями. Их можно увидеть с помощью команды:

kubernetes get networkpolicy <policy-name> -o yaml

Имейте в виду, что система проверки Kubernetes не непогрешима и может пропускать некоторые типы ошибок.

Исполнение


Kubernetes не занимается реализацией сетевых политик самостоятельно, а является лишь API-шлюзом, возлагающим обременительную работу по контролю на нижележащую систему, называемую Container Networking Interface (CNI). Задание политик в кластере Kubernetes без назначения соответствующего CNI аналогично созданию политик на сервере управления брандмауэром без их последующей установки в брандмауэры. Вы сами должны убедиться в наличии достойного CNI или, в случае платформ Kubernetes, размещенных в облаке (со списком провайдеров можно ознакомиться здесь — прим. пер.), задействовать сетевые политики, которые установят CNI для вас.

Обратите внимание, что Kubernetes не предупредит вас, если вы зададите сетевую политику без соответствующего вспомогательного CNI.

Stateful или Stateless?


Все CNI Kubernetes, с которыми мне доводилось сталкиваться, хранят состояния (например, Calico использует Linux conntrack). Это позволяет pod’у получать ответы по инициированному им TCP-соединению без необходимости устанавливать его заново. При этом мне неизвестно о стандарте Kubernetes, который гарантировал бы хранение состояния (statefulness).

Продвинутое управление политикой безопасности


Вот несколько способов повысить эффективность исполнения политики безопасности в Kubernetes:

  1. Архитектурный паттерн Service Mesh использует sidecar-контейнеры для обеспечения подробной телеметрии и контроля за трафиком на уровне сервисов. В качестве примера можно взять Istio.
  2. Некоторые из поставщиков CNI дополнили свои инструменты, чтобы те вышли за рамки сетевых политик Kubernetes.
  3. Tufin Orca обеспечивает прозрачность и автоматизацию сетевых политик Kubernetes.

Пакет Tufin Orca управляет сетевыми политиками Kubernetes (и служит источником скриншотов, приведенных выше).

Дополнительная информация



Заключение


Сетевые политики Kubernetes предлагают неплохой набор инструментов для сегментации кластеров, однако они интуитивно непонятны и имеют множество тонкостей. Я считаю, что из-за этой сложности политики многих существующих кластеров содержат ошибки. Возможными решениями этой проблемы являются автоматизация определений политик или применение других средств сегментации.

Надеюсь, что это руководство поможет прояснить некоторые вопросы и решить проблемы, с которыми вы можете столкнуться.

P.S. от переводчика


Читайте также в нашем блоге:

Комментарии (4)


  1. gto
    30.04.2019 10:21

    Механизм сетевых политик Kubernetes позволяет управлять взаимодействием развернутых на платформе приложений на сетевом уровне (третьем в модели OSI)
    Там где упоминаются TCP/UDP порты речь, всё-таки, о 4-ом уровне. Извините за занудство.


    1. driusha Автор
      30.04.2019 14:25

      Справедливо.


      1. Gordio
        01.05.2019 17:13

        Просто интереса ради.
        Ошибка в переводе или в оригинале?


        1. driusha Автор
          01.05.2019 17:14

          Это оригинальное утверждение:

          Kubernetes provides a mechanism called Network Policies that can be used to enforce layer-3 segmentation for applications that are deployed on the platform.