Network Policies

Network Policies

В данной статье мы расскажем, как настраиваем сетевую безопасность для CI/CD платформы Gitorion. Рассмотрим общие принципы, структуру и алгоритмы построения сетевых политик Network Policies для кластера Kubernetes. Разберем, как защищаем ноды кластера Kubernetes и изолируем контуры development, staging и production

1. Реализации сетевой безопасности

Все компоненты CI/CD платформы Gitorion и разрабатываемое с помощью нее приложение развертываются в кластере Kubernetes. Поэтому сетевую безопасность будем настраивать для кластера Kubernetes

Kubernetes предоставляет собственную реализацию сетевых политик - Network Policies. В данном случае управление сетевой безопасностью берет на себя kube-proxy - одна из подсистем Kubernetes. Kube-proxy добавляет правила в сетевой фильтр netfilter (iptables) на основе сетевых политик, заданных администратором. Однако реализация от Kubernetes позволяет строить сетевые политики только на уровнях L3-L4 OSI

Мы используем Cilium в качестве сетевого плагина для кластера Kubernetes. Cilium предоставляет собственную реализацию сетевых политик CiliumNetworkPolicy как для уровней L3-L4 OSI, так и для уровня L7 OSI

В этом случае мы переводим кластер Kubernetes в режим работы без kube-proxy и удаляем все правила iptables, а управление сетевой безопасностью на себя берут модули Cilium, создавая правила в межсетевом экране eBPF (Extended Berkeley Packet Filter)

2. Установка

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

helm install cilium cilium/cilium --version 1.18.2 \
  --namespace kube-system \
  --set policyAuditMode=true \
  --set hostFirewall.enabled=false \
  --set policyEnforcementMode=never \
  --set kubeProxyReplacement=true \
  --set k8sServiceHost=kubeapi.gitorion.ru \
  --set k8sServicePort=6443 \
  --set gatewayAPI.enabled=true \
  --set nodePort.enabled=true \
  --set bpf.masquerade=true

policyAuditMode=true - переводит Cilium из режима блокировки трафика в режим аудита. Не переводите в режим блокировки, пока не добавите сетевые политики, чтобы не нарушить работу кластера

hostFirewall.enabled=false - отключает фаервол хостов Kubernetes. Не включайте пока не добавите сетевые политики, чтобы не потерять удаленный доступ к нодам Kubernetes

policyEnforcementMode=never - задает режим принудительного применения сетевых политик. Не изменяйте, пока не добавите сетевые пролитики, чтобы не нарушить работу кластера

kubeProxyReplacement=true - задает режим работы Cilium без kube-proxy

k8sServiceHost и k8sServicePort=6443 - показывает Cilium где искать API Kubernetes после удаления kube-proxy

gatewayAPI.enabled и nodePort.enabled - эти два параметра подробно освещены в нашей статье про GatewayAPI

bpf.masquerade=true - используйте eBPF для маскарадинга IP-адресов

После установки Cilium удалите kube-proxy из кластера командами:

kubectl -n kube-system delete ds kube-proxy
kubectl -n kube-system delete cm kube-proxy

На каждой ноде Kubernetes удалите правила iptables командой:

sudo iptables-save | grep -v KUBE | sudo iptables-restore

3. Конечные точки и ноды

Сетевые политики Cilium создаются для двух типов объектов:

  • Конечные точки (Endpoints) - сетевые политики CiliumNetworkPolicy применяются к модулям Kubernetes и используют namespace. Выберите модуль, для которого создаете сетевую политику, задав его метку Labels в endpointSelector. Если нужно создать сетевую политику для всех модулей из выбранного namespace, используйте endpointSelector: {}

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: forgejo-coredns
  namespace: forgejo
spec:
  description: "forgejo -> coredns"
  #endpointSelector: {}
  endpointSelector:
    matchLabels:
      app.kubernetes.io/name: forgejo
  egress:
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
          rules:
            dns:
              - matchPattern: "*"

  • Ноды (Nodes) - сетевые политики CiliumClusterwideNetworkPolicy применяются к нодам Kubernetes и не используют namespase. Выберите ноду Kubernetes, для которой создаете сетевую политику, задав её метку Labels в nodeSelector. Если нужно создать политику для всех нод используйте nodeSelector: {}

apiVersion: "cilium.io/v2"
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: "worker-node-input"
spec:
  description: "Minimum allowed ports for worker node"
  #nodeSelector: {}
  nodeSelector:
    matchLabels:
      type: worker-node
  ingress:
  - fromEntities:
    - remote-node
    - health
  - toPorts:
    - ports:
      - port: "22"
        protocol: TCP
      - port: "6443"
        protocol: TCP
      - port: "2379"
        protocol: TCP
      - port: "4240"
        protocol: TCP
        port: "8472"
        protocol: UDP

4. Структура YAML-манифеста сетевой политики

Сетевые политики для конечных точек и нод имеют одинаковую структуру. C помощью селектора задайте метку Label конечной точки или ноды, для которой создаете сетевую политику, и ниже задайте секции ingress и egress. В секции ingress задайте объекты, трафик из которых разрешен на вход выбранной конечной точки или ноды. В секции egress задайте объекты, на которые разрешен трафик, выходящий из заданной конечной точки или ноды

5. Объекты сетевых политик

Далее мы расскажем об объектах, которые можно использовать в секциях ingress и egress. Для каждого объекта мы приведем ссылку на официальное руководство Cilium с подробной информацией и YAML-манифестами, от которых вы сможете оттолкнуться при построении собственных сетевых политик:

  • Уровень L3 OSI:
    • Endpoints - конечные точки Endpoints в кластере Kubernetes
    • Services - службы Service в кластере Kubernetes
    • Entities - сущности без меток
      • host - локальный хост
      • remote-node - любая нода, кроме локального хоста
      • kube-apiserver - API Kubernetes
      • ingress - точка входа в кластер Ingress или GatewayAPI
      • cluster - все модули Kubernetes разом
      • init - конечные точки в фазе загрузки
      • health - конечные точки работоспособности
      • unmanaged - конечные точки, не управляемые Cilium
      • world - всё за пределами кластера Kubernetes (аналог 0.0.0.0/0)
      • all - все возможные объекты
    • Node - ноды Kubernetes
    • IP/CIDR - IP-адрес или блок IP-адресов
    • DNS - доменное имя
  • Уровень L4 OSI:
    • Port - порт TCP/UDP
    • ICPM Type - тип ICMP пакета
    • TLS SNI - разрешить TLS-трафик по имени домена
  • Уровень L7 OSI:

Получить список конечных точек, нод и объектов вашего кластера можно с помощью команды:

kubectl -n kube-system exec daemonset/cilium -- cilium endpoint list

Список будет длинный. Мы приведем только небольшой фрагмент для примера:

ENDPOINT   POLICY (ingress)   POLICY (egress)   IDENTITY   LABELS (source:key[=value])                                                       IPv6   IPv4         STATUS
1121       Enabled            Enabled           58705      k8s:app.kubernetes.io/instance=forgejo                                                   10.0.0.200   ready   
                                                           k8s:app.kubernetes.io/managed-by=Helm                                                                         
                                                           k8s:app.kubernetes.io/name=forgejo                                                                            
                                                           k8s:app.kubernetes.io/version=11.0.1                                                                          
                                                           k8s:app=forgejo                                                                                               
                                                           k8s:helm.sh/chart=forgejo-12.3.1                                                                              
                                                           k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=forgejo                                        
                                                           k8s:io.cilium.k8s.namespace.labels.shared-gateway-access=true                                                 
                                                           k8s:io.cilium.k8s.policy.cluster=default                                                                      
                                                           k8s:io.cilium.k8s.policy.serviceaccount=default                                                               
                                                           k8s:io.kubernetes.pod.namespace=forgejo                                                                       
							   k8s:version=11.0.1
238        Enabled            Enabled           41148      k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=kube-system               10.0.0.233   ready
                                                           k8s:io.cilium.k8s.policy.cluster=default
                                                           k8s:io.cilium.k8s.policy.serviceaccount=coredns
                                                           k8s:io.kubernetes.pod.namespace=kube-system
                                                           k8s:k8s-app=kube-dns
1714       Enabled            Enabled           8          reserved:ingress                                                                         10.0.0.17    ready
1924       Enabled            Enabled           1          k8s:node-role.kubernetes.io/control-plane                                                             ready
                                                           k8s:node.kubernetes.io/exclude-from-external-load-balancers
                                                           reserved:host
120        Enabled            Enabled           4          reserved:health                                                                          10.0.0.40    ready
                                                                                                                                                                                                                                            

Для построения сетевых политик вам потребуется следующая информация о конечной точке, ноде или объекте:

  • ENDPOINT - идентификатор ID
  • IDENTITY - идентификатор безопасности
  • LABELS - список меток
  • IPv4 - IP-адрес

Для примера мы вывели информацию о модулях forgejo и kube-dns, а также сущностях ingress, host и health. Модуль forgejo с ID 1121 и IDENTITY 58705 имеет одну из меток "app=forgejo". Модуль kube-dns c ID 238 и IDENTITY 41148 имеет одну из меток "k8s-app=kube-dns"

6. Аудит трафика

Теперь вам нужно получить представление о трафике в вашем кластере Kubernetes. Направьте в файл вывод команды:

kubectl -n kube-system exec daemonset/cilium -- cilium monitor -t policy-verdict > /tmp/cilium-traff.log

Команда в реальном времени выдает информацию о всех пакетах, которыми в данный момент обмениваются объекты вашего кластера Kubernetes. В файле будет много строк, каждая из которых несет информацию об одном пакете. Мы выведем лишь несколько строк для пояснения. Ниже приведем пример взаимодействия модулей forgejo и kube-dns из предыдущего пункта

Policy verdict log: flow 0x3a2fa560 local EP ID 1121, remote ID 41148, proto 17, egress, action redirect, auth: disabled, match L3-L4, 10.0.0.200:48119 -> 10.0.0.233:53 udp
Policy verdict log: flow 0x8ccc4e1 local EP ID 238, remote ID 58705, proto 17, ingress, action allow, auth: disabled, match L3-Only, 10.0.0.200:48119 -> 10.0.0.233:53 udp

  • "local EP ID" - идентификатор ENDPOINT (п.5) конечной точки, к которой относится пакет
  • "remote ID" - идентификатор безопасности IDENTITY (п.5) конечной точки или объекта, с которым взаимодействует "local EP ID"
  • "ingress/egress" - входящий или исходящий пакет для "local EP ID"

Далее следуют IP-адреса, порты и протоколы, которые тоже могут вам пригодиться при построении сетевых политик

7. Алгоритм построения сетевых политик

Теперь вы можете строить сетевые политики, имея представление о конечных точках/нодах/объектах и трафике между ними, по следующему алгоритму:

  • Определитесь с конечной точкой или нодой "local EP ID", для которой вы строите сетевую политику из п.6
  • Найдите метки в столбце LABELS п.5 конечной точки или ноды, у которой ENDPOINT п.5 равен "local EP ID" п.6
  • Задайте метку из LABELS п.5 в "spec.endpointSelector", если строите сетувую политику для конечной точки, или в "spec.nodeSelector", если строите сетевую политику для ноды
  • В поле "remote ID" п.6 выясните, с чем взаимодействует "local EP ID"
  • Найдите метку в столбце LABELS п.5 конечной точки/ноды/объекта, у которой IDENTITY п.5 равен "remote ID" п.6
  • Задайте метку из LABELS п.5 для "remote ID" в секции ingress/egress сетевой политики. За полем "remote ID" п.6 следует поле кода протокола, а за ним следует поле, определяющее секцию ingress или egress

8. Обязательные сетевые политики

Прежде чем включать блокировку трафика с помощью сетевых политик, вы должны провести аудит трафика п.6 и добавить необходимые сетевые политики п.7

Cетевые политики разнятся от кластера к кластеру, и вам придется создавать политики конкретно для вашего кластера следуя пунктам п.5-7. Ниже мы приведем сетевые политики, которые нужно обязательно добавить в любой кластер Kubernetes, чтобы он не крашнулся.

  • Сетевая политика для нод Kubernetes
  • Для примера можно использовать сетевую политику для нод Kubernetes из официального репозитория Cilium. Разрешите служебный трафик между нодами Kubernetes на портах 8472, 2379, 2380, 6443, 10250, 4240. В примере от Cilium разрешен весь трафик между нодами Kubernetes и Интернетом ( - world). Мы разрешаем весь исходящий трафик из нод Kubernetes в Интернет. А входной трафик для нод Kubernetes из Интернета разрешаем только на необходимые порты 22(SSH) и 443(HTTPs)

apiVersion: "cilium.io/v2"
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: "host-policy"
specs:
  - description: "Open required ports between nodes"
    nodeSelector: {}
    ingress:
    - fromEntities:
      - remote-node
      toPorts:
      - ports:
        - port: "80"
          protocol: TCP
        # VXLAN tunnels between nodes
        - port: "8472"
          protocol: UDP
        # etcd connections
        - port: "2379"
          protocol: TCP
        - port: "2380"
          protocol: TCP
        # kube-api server
        - port: "6443"
          protocol: TCP
        # kubelet
        - port: "10250"
          protocol: TCP
        # Health checks
        - port: "4240"
          protocol: TCP
    egress:
    - toEntities:
      - remote-node
      toPorts:
      - ports:
        - port: "80"
          protocol: TCP
        # VXLAN tunnels between nodes
        - port: "8472"
          protocol: UDP
        # etcd connections
        - port: "2379"
          protocol: TCP
        - port: "2380"
          protocol: TCP
        # kube-api server
        - port: "6443"
          protocol: TCP
        # kubelet
        - port: "10250"
          protocol: TCP
        # Health checks
        - port: "4240"
          protocol: TCP
  - description: "Allow all to/from health"
    nodeSelector: {}
    ingress:
    - fromEntities:
      - health
    egress:
    - toEntities:
      - health
  - description: "Allow 22,443 from world"
    nodeSelector: {}
    ingress:
    - fromEntities:
      - world
      toPorts:
        - ports:
          # ssh connections
          - port: "22"
            protocol: TCP
          # HTTPs connections
          - port: "443"
            protocol: TCP
    egress:
    - toEntities:
      - world
  - description: "Allow ICMP/ICMPv6 traffic on all nodes"
    nodeSelector: {}
    ingress:
    - icmps:
      - fields:
        - type: 8
          family: IPv4
        - type: 128
          family: IPv6
    egress:
    - icmps:
      - fields:
        - type: 8
          family: IPv4
        - type: 128
          family: IPv6

  • Сетевые политики для внутрикластерного DNS Kubernetes
  • Ниже приведены сетевые политики, без которых крашнутся модули kube-dns после включения сетевой безопасности. Разрешите чекинг модулей kube-dns на портах 8008, 8181. Разрешите доступ модулей kube-dns к API Kubernetes на порту 6443 и исходящий трафик на вышестоящие DNS-сервера 8.8.8.8 и 8.8.4.4

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: kube-dns
  namespace: kube-system
specs:
  - endpointSelector:
      matchLabels:
        k8s-app: kube-dns
    ingress:
    - fromEntities:
      - host
      toPorts:
      - ports:
        - port: '8080'
          protocol: TCP
        - port: '8181'
          protocol: TCP
    egress:
    - toEntities:
      - kube-apiserver
      - host
      toPorts:
      - ports:
        - port: '6443'
          protocol: TCP
    - toCIDR:
      - 8.8.8.8/32
      - 8.8.4.4/32
      toPorts:
      - ports:
        - port: '53'
          protocol: UDP
---
apiVersion: "cilium.io/v2"
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: control-plane-kube-dns
specs:
  - description: "control plane <- kube-dns"
    nodeSelector:
      matchLabels:
        node-role.kubernetes.io/control-plane: ''
    ingress:
    - fromEndpoints:
      - matchLabels:
          k8s-app: kube-dns
      toPorts:
      - ports:
        - port: "6443"
          protocol: TCP
---
apiVersion: "cilium.io/v2"
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: all-nodes-kube-dns
specs:
  - description: "all nodes -> kube-dns"
    nodeSelector: {}
    egress:
    - toEndpoints:
      - matchLabels:
         k8s-app: kube-dns
      toPorts:
      - ports:
        - port: "8181"
          protocol: TCP
        - port: "8080"
          protocol: TCP

9. Режимы принудительного применения сетевых политик Cilium

Теперь выберите режим применения сетевых политик Cilium

  • default - трафик для конечной точки не ограничивается, пока она не выбрана сетевой политикой
  • always - ограничение трафика включено для всех конечных точек, даже не выбранных сетевыми политиками
  • never - трафик не ограничен для всех конечных точек, даже если они выбраны сетевыми политиками

10. Активация сетевых политик Cilium

Теперь, когда вы провели аудит трафика п.6 и добавили все необходимые сетевые политики, можно активировать блокировку трафика не боясь потерять удаленный доступ к нодам Kubernetes или краштуть кластер. Откройте конфиг Cilium для редактирования и задайте значения параметрам, приведенным ниже:

kubectl edit configmap cilium-config -n kube-system

enable-policy: always - задайте режим применения сетевых политик Cilium п.9

enable-host-firewall: "true" - активируйте сетевые политики для нод Kubernetes. Добавьте данный параметр, если он отсутствует в конфигурации Cilium

policy-audit-mode: "false" - переведите Cilium из режима аудита в режим блокировки трафика

Перезапустите модули Cilium, чтобы применить настройки:

kubectl -n kube-system rollout restart deployment/cilium-operator
kubectl -n kube-system rollout restart ds/cilium
kubectl -n kube-system rollout restart ds/cilium-envoy

После включения сетевой безопасности выполните аудит трафика п.6. Если в выводе команды аудита трафика появятся пакеты со статусом "action deny", добавьте для них разрешающие сетевые политики:

Policy verdict log: flow 0x0 local EP ID 1707, remote ID remote-node, proto 6, ingress, action deny, auth: disabled, match none, 10.10.6.154:50790 -> 10.10.5.184:4240 tcp SYN
Policy verdict log: flow 0x0 local EP ID 1707, remote ID remote-node, proto 6, ingress, action deny, auth: disabled, match none, 10.10.6.154:50790 -> 10.10.5.184:4240 tcp SYN

11. Изоляция контуров development, staging и production

Чтобы разработчики из контура development не могли получить доступ к контурам staging и production, изолируйте все 3 контура друг от друга. При переводе Cilium из режима аудита в режим блокировки трафика п.10 и установке enable-policy: "always" происходит изоляция всех запущенных модулей Kubernetes друг от друга. Остается создать сетевую политику, разрешающую трафик между модулями в пределах одного namespace. В приведенном ниже YAML-манифесте выберите все модули из namespace development и разрешите доступ на вход и выход ко всем модулям из namespace development

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "development-env"
  namespace: development
spec:
  description: "development environment"
  endpointSelector:
    matchLabels:
      {}
  ingress:
  - fromEndpoints:
    - matchLabels:
        {}
  egress:
  - toEndpoints:
    - matchLabels:
        {}

Создайте аналогичные сетевые политики для namespace staging и production. Подобную практику удобно использовать вообще для всех приложений, устанавливаемых в кластер Kubernetes. Каждое приложение устанавливаете в отдельный namespaces и разрешаете трафик между модулями приложения только в пределах собственного namespace. Затем, по мере надобности разрешаете трафик между модулями из разных namespace

12. Разрешаем трафик между модулями из разных namespace

Ниже приведен пример YAML-манифеста сетевой политики, разрешающей трафик из модуля бэкенда с меткой "app: backend" (namespace production) в модуль с меткой "k8s-app: kube-dns" (namespace kube-system). Чтобы разрешить соединение с модулем из другого namespace, задайте в секции ingress/egress метку модуля и метку namespace, в котором модуль запущен

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: backend-kube-dns
  namespace: production
spec:
  description: "backend -> kube-dns"
  endpointSelector:
    matchLabels:
      app: backend
  egress:
    - toEndpoints:
      - matchLabels:
          io.kubernetes.pod.namespace: kube-system
          k8s-app: kube-dns
      toPorts:
        - ports:
          - port: "53"
            protocol: UDP
          rules:
            dns:
              - matchPattern: "*"

Соответственно, модулю с меткой "k8s-app: kube-dns" из namespace kube-system нужно разрешить на вход пакеты от модуля с меткой "app: backend" из namespace production. Для примера мы разрешили запросы к внутрикластерному DNS от любого модуля кластера Kubernetes c помощью сущности " - cluster"

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: coredns-all-pods
  namespace: kube-system
spec:
  description: "coredns <- all pods"
  endpointSelector:
    matchLabels:
      k8s-app: kube-dns
  ingress:
  - fromEntities:
    - cluster