[Перевод] Сетевые политики на защите рабочих нагрузок в кластере Kubernetes

33954dfedb04fbff0b09a2f33295ceb0.png

В кластере Kubernetes нам доступен любой сервис в любом пространстве имён, то есть по умолчанию pod открыт для любого трафика.

Мы можем определить сетевую политику для пространства имён или pod«а, чтобы защитить рабочие нагрузки в кластере. Например, разделить рабочие нагрузки в мультитенантном кластере по проектам, командам или организациям.

Сценарий

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

Фронтенд будет общедоступным. Приложения будут предоставляться через балансировщик нагрузки, поэтому обращаться к фронтенду мы будем по DNS-имени или IP-адресу этого балансировщика.

Бэкенд будет содержать всю логику приложения.

База данных будет базой данных.

Мы знаем, что по умолчанию любое пространство имён может отправлять трафик любому пространству имён и принимать любой трафик. Без сетевых политик наша трёхуровневая архитектура будет выглядеть так:

b6c47e3fb2ab2f0ccb1f6dd6f77866ec.jpg

Давайте настроим архитектуру, создав три новых пространства имён, в которых мы будем развёртывать сервисы и развёртывания.

Для простоты мы будем использовать образ 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 Решение

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

a6c48e4b97fdc50ad6c8923e3b1257fe.jpg

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
---

Вот что мы в итоге получаем:

1ff14397f2b7c660c29b63b4854e649f.jpg

Как видите, бэкенд может отправлять исходящий трафик к базе данных,  но пока не принимает никакой входящий трафик.

● Разрешаем входящий трафик из фронтенда
Применим к уровню бэкенда ещё одну политику, чтобы разрешить входящий трафик только с фронтенда.

# 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
---

Вот что мы получаем теперь:

f8f456310788966efe66c528a471d3f2.jpg

● Разрешаем входящий трафик из интернета
Раз мы обращаемся к фронтенду через сервис балансировки нагрузки, запрос будет поступать с 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. Вот как всё работает теперь:

dd7fd49d5849c064cd48b8ea0952af7a.jpg

5. Проверяем, что получилось

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

● Для начала попробуем получить к приложению доступ через внешний IP-адрес сервиса балансировки нагрузки.

f42c867992e0609244f6dbb89ca5ed3a.jpg

Мы видим, что приложение во фронтенде доступно с внешнего IP-адреса.

7aa9702293e67d90ca5501eaf10e93f4.png

Практический курс, охватывает все аспекты безопасности проекта на 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!