[Перевод] Сетевые политики на защите рабочих нагрузок в кластере Kubernetes
В кластере Kubernetes нам доступен любой сервис в любом пространстве имён, то есть по умолчанию pod открыт для любого трафика.
Мы можем определить сетевую политику для пространства имён или pod«а, чтобы защитить рабочие нагрузки в кластере. Например, разделить рабочие нагрузки в мультитенантном кластере по проектам, командам или организациям.
Сценарий
Представьте, что в пространствах имён Kubernetes мы развёртываем приложение на трёх уровнях: фронтенд, бэкенд и база данных.
Фронтенд будет общедоступным. Приложения будут предоставляться через балансировщик нагрузки, поэтому обращаться к фронтенду мы будем по DNS-имени или IP-адресу этого балансировщика.
Бэкенд будет содержать всю логику приложения.
База данных будет базой данных.
Мы знаем, что по умолчанию любое пространство имён может отправлять трафик любому пространству имён и принимать любой трафик. Без сетевых политик наша трёхуровневая архитектура будет выглядеть так:
Давайте настроим архитектуру, создав три новых пространства имён, в которых мы будем развёртывать сервисы и развёртывания.
Для простоты мы будем использовать образ nginxдля развёртывания pod«ов в развёртываниях.
1. Настраиваем новые пространства имён
Создаём пространства имён и добавляем метки к каждому, чтобы потом можно было применять сетевые политики по этим меткам.
# Create "frontend" namespace and add a label
> kubectl create ns frontend
> kubectl label ns frontend tier=frontend
# Create "backend" namespace and add a label
> kubectl create ns backend
> kubectl label ns backend tier=backend
# Create "database" namespace and add a label
> kubectl create ns database
> kubectl label ns database tier=database
2. Развёртываем сервисы и развёртывания
2.1 Уровень базы данных
# Deploy a deployment named "database" with 2 replicas
> kubectl create deploy database -n database --image=nginx --replicas=2
# List the pods of "database" deployments
> kubectl get pods -n database
----------------------------------------------------------------------------------------------------
NAME READY STATUS RESTARTS AGE
database-7d94797799-b9sdw 1/1 Running 0 23h
database-7d94797799-jc4xt 1/1 Running 0 23h
----------------------------------------------------------------------------------------------------
# Create a service (cluster ip) named "database" for accessing the pods of the "database" deployment
> kubectl create service clusterip database --tcp=80 -n database
# List the service
> kubectl get svc -n database
----------------------------------------------------------------------------------------------------
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
database ClusterIP 10.106.137.165 80/TCP 23h
----------------------------------------------------------------------------------------------------
2.2 Уровень бэкенда
# Deploy a deployment named "backend" with 2 replicas
> kubectl create deploy backend -n backend --image=nginx --replicas=2
# List the pods of "backend" deployments
> kubectl get pods -n backend
----------------------------------------------------------------------------------------------------
NAME READY STATUS RESTARTS AGE
backend-5c5c74cbf6-h4d9p 1/1 Running 0 23h
backend-5c5c74cbf6-jf6fj 1/1 Running 0 23h
----------------------------------------------------------------------------------------------------
# Create a service (cluster ip) named "backend" for accessing the pods of the "backend" deployment
> kubectl create service clusterip backend --tcp=80 -n backend
# List the service
> kubectl get svc -n backend
----------------------------------------------------------------------------------------------------
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
backend ClusterIP 10.101.154.161 80/TCP 23h
----------------------------------------------------------------------------------------------------
2.3 Уровень фронтенда
# Deploy a deployment named "frontend" with 2 replicas
> kubectl create deploy frontend -n frontend --image=nginx --replicas=2
# List the pods of "frontend" deployments
> kubectl get pods -n frontend
----------------------------------------------------------------------------------------------------
NAME READY STATUS RESTARTS AGE
frontend-5d7445bdb8-g8rpb 1/1 Running 0 24h
frontend-5d7445bdb8-kqtnc 1/1 Running 0 24h
----------------------------------------------------------------------------------------------------
# Create a service (load balancer) named "frontend" for accessing the pods of the "frontend" deployment,
# From internet through the load balancer IP or DNS
> kubectl create service loadbalancer frontend --tcp=80:80 -n frontend
# List the service, and note down the external IP
> kubectl get svc -n frontend
----------------------------------------------------------------------------------------------------
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend LoadBalancer 10.102.187.203 45.76.197.98 80:30008/TCP 30m
----------------------------------------------------------------------------------------------------
Фронтенд мы предоставляем через балансировщик нагрузки. Мы указываем EXTERNAL-IP сервиса балансировки нагрузки, чтобы пользователи могли обращаться к приложению на фронтенде.
3. Риски для безопасности
3.1 Проблема
Сейчас к pod«ам и пространствам имён не применяются никакие политики. Все pod«ы в кластере могут общаться друг с другом. А что если это мультитенантный кластер или там хранятся конфиденциальные данные? Любой злоумышленник с уровня фронтенда получит прямой доступ к базе данных и всем пространствам имён в кластере. Это серьёзный риск для безопасности.
3.2 Решение
Чтобы защититься от этих рисков, можно использовать сетевые политики. С их помощью мы можем изолировать все три уровня друг от друга, ограничить входящий трафик для базы данных и бэкенда, а также разрешить трафик с фронтенда только на бэкенд, чтобы злоумышленник не мог с фронтенда получить прямой доступ к базе данных и другим пространствам имён. Вот как это будет выглядеть на схеме:
4. Применяем сетевые политики
4.1 Уровень базы данных
По умолчанию запрещаем весь входящий и исходящий трафик
Мы может создать политику по умолчанию, которая будет запрещать весь входящий и исходящий трафик в этом пространстве имён.
# Deny all ingress and egress traffic
> kubectl create -f \
https://raw.githubusercontent.com/shamimice03/Network_Policies_Kubernetes/main/netpol-deny-all-database.yaml
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-all-database
namespace: database
spec:
podSelector:
matchLabels: {}
policyTypes:
- Ingress
- Egress
---
Уровень базы данных теперь будет изолирован от остальных пространств имён в кластере.
Разрешаем входящий трафик из бэкенда
Применим к уровню базы данных ещё одну политику, чтобы разрешить входящий трафик только с бэкенда.
# Allow ingress traffic from "backend-tier" using the following manifest file:
> kubectl create -f \
https://raw.githubusercontent.com/shamimice03/Network_Policies_Kubernetes/main/netpol-allow-ingress-from-backend.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-from-backend
namespace: database
spec:
podSelector:
matchLabels: {}
ingress:
- from:
- podSelector:
matchLabels: {}
namespaceSelector:
matchLabels:
tier: backend
---
4.2 Уровень бэкенда
● По умолчанию запретим весь входящий и исходящий трафик
Применим такую же политику, как для базы данных, чтобы изолировать бэкенд от других пространств имён.
# Deny all ingress and egress traffic
> kubectl create -f \
https://raw.githubusercontent.com/shamimice03/Network_Policies_Kubernetes/main/netpol-deny-all-backend.yaml
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-all-backend
namespace: backend
spec:
podSelector:
matchLabels: {}
policyTypes:
- Ingress
- Egress
---
● Разрешим исходящий трафик к базе данных
На уровне базы данных мы разрешили входящий трафик только с бэкенда, а на предыдущем шаге запретили весь трафик в обе стороны для бэкенда. Поэтому уровень базы данных готов принимать входящий трафик от бэкенда, а самому бэкенду запрещено отправлять исходящий трафик. Чтобы наладить взаимодействие между бэкендом и базой данных, нужно разрешить трафик в этом направлении.
Плюс мы используем сервисы для доступа к pod«ам, а значит нужно создать для исходящего трафика ещё одно правило, чтобы разрешать DNS-имена сервисов. В кластере Kubernetes сервер DNS представляет собой набор pod«ов в пространстве имён kube-system. Получается, нужно разрешить исходящий трафик к пространству kube-system, но не ко всему, а только к уровню kube-dns. Теперь pod«ы на бэкенде смогут разрешать DNS-имена сервисов.
Пишем политику, чтобы разрешить исходящий трафик с бэкенда в базу данных, а также на порт 53 пространства имён kube-system:
# allow egress from backend-tier to database-tier and allow dns resolving of services
> kubectl create -f \
https://raw.githubusercontent.com/shamimice03/Network_Policies_Kubernetes/main/netpol-allow-egress-to-database.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-to-database
namespace: backend
spec:
podSelector:
matchLabels: {}
egress:
- to:
- podSelector:
matchLabels: {}
namespaceSelector:
matchLabels:
tier: database
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
---
Вот что мы в итоге получаем:
Как видите, бэкенд может отправлять исходящий трафик к базе данных, но пока не принимает никакой входящий трафик.
● Разрешаем входящий трафик из фронтенда
Применим к уровню бэкенда ещё одну политику, чтобы разрешить входящий трафик только с фронтенда.
# Allow ingress from frontend-tier
> kubectl create -f \
https://raw.githubusercontent.com/shamimice03/Network_Policies_Kubernetes/main/netpol-allow-ingress-from-frontend.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-from-frontend
namespace: backend
spec:
podSelector:
matchLabels: {}
ingress:
- from:
- podSelector:
matchLabels: {}
namespaceSelector:
matchLabels:
tier: frontend
---
Таким образом бэкенд будет принимать входящий трафик с фронтенда и отправлять исходящий трафик в базу данных.
4.3 Уровень фронтенда
● По умолчанию запрещаем весь трафик
Как и для предыдущих уровней, применим сетевую политику, которая полностью запретит весь входящий и исходящий трафик.
# Deny all ingress and egress traffic
> kubectl create -f \
https://raw.githubusercontent.com/shamimice03/Network_Policies_Kubernetes/main/netpol-deny-all-frontend.yaml
---
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
name: default-deny-all-frontend
namespace: frontend
spec:
podSelector:
matchLabels: {}
policyTypes:
- Ingress
- Egress
---
● Разрешаем исходящий трафик на бэкенд
Сейчас весь трафик для фронтенда запрещён. Для отправки трафика на бэкенд нужно разрешить исходящий трафик на бэкенд и в пространство имён kube-system, чтобы разрешать DNS-имена сервисов, как мы говорили чуть выше.
# Allow egress to backend-tier
> kubectl create -f \
https://github.com/shamimice03/Network_Policies_Kubernetes/blob/main/netpol-allow-egress-to-backend.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-to-backend
namespace: frontend
spec:
podSelector:
matchLabels: {}
egress:
- to:
- podSelector:
matchLabels: {}
namespaceSelector:
matchLabels:
tier: backend
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
---
Вот что мы получаем теперь:
● Разрешаем входящий трафик из интернета
Раз мы обращаемся к фронтенду через сервис балансировки нагрузки, запрос будет поступать с IP-адреса внешнего балансировщика нагрузки. Мы запретили весь входящий трафик, так что пока не сможем получить доступ к приложению на фронтенде через внешний IP. Нужно создать сетевую политику, которая разрешит входящий трафик из любого места, кроме частных IP-адресов pod«ов. Так мы запретим трафик с pod«ов, которые находятся в других пространствах имён.
# Allow ingress from everywhere except pod-network
> kubectl create -f \
https://raw.githubusercontent.com/shamimice03/Network_Policies_Kubernetes/main/netpol-allow-ingress-from-everywhere.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-from-loadbalancer
namespace: frontend
spec:
podSelector:
matchLabels: {}
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0
except: # Restrict the Private IP CIDR Block of your pod network.
- 10.0.0.0/8 # So that, pods from other namespaces cannot send ingress traffic.
ports:
- protocol: TCP
port: 80
---
Мы применили все сетевые политики, которые нужны для защиты и изоляции рабочих нагрузок Kubernetes. Вот как всё работает теперь:
5. Проверяем, что получилось
Давайте протестируем нашу систему и убедимся, что сетевые политики работают, как ожидалось.
● Для начала попробуем получить к приложению доступ через внешний IP-адрес сервиса балансировки нагрузки.
Мы видим, что приложение во фронтенде доступно с внешнего IP-адреса.
Практический курс, охватывает все аспекты безопасности проекта на Kubernetes.
● Давайте попробуем через pod во фронтенде получить доступ к сервисам в бэкенде и базе данных. Первое должно получиться, второе — нет.
# Dive into a pod of the "frontend" deployment
> kubectl exec -it frontend-5d7445bdb8-g8rpb -n frontend bash
# Try to access a service(clusterIP) named "backend", resides in the backend-tier
>> curl backend.backend
---------------------------------------------------------------------------------
#Successfully Accessed
Welcome to nginx!