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 - доменное имя
- HTTP - протокол HTTP
- DNS Policy and IP Discovery - доступ к доменам
Получить список конечных точек, нод и объектов вашего кластера можно с помощью команды:
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