В кластере 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 <none> 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 <none> 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
---------------------------------------------------------------------------------
<!DOCTYPE html>
<html>
<head> #Successfully Accessed
<title>Welcome to nginx!</title>
<style>
...
---------------------------------------------------------------------------------
# Try to access a service(clusterIP) named "database", resides in the database-tier
>> curl database.database
---------------------------------------------------------------------------------
# curl: (28) Failed to connect to database.database port 80: Connection timed out
---------------------------------------------------------------------------------
Теперь проверим сетевые политики через pod на бэкенде.
# Dive into a pod of the "backend" deployment
> kubectl exec -it backend-5c5c74cbf6-h4d9p -n backend bash
# Try to access a service(clusterIP) named "frontend", resides in the frontend-tier
>> curl frontend.frontend
---------------------------------------------------------------------------------
# curl: (28) Failed to connect to frontend.frontend port 80: Connection timed out
---------------------------------------------------------------------------------
# Try to access a service(clusterIP) named "database", resides in the database-tier
>> curl database.database
----------------------------------------------------------------------------------
<!DOCTYPE html>
<html>
<head> #Successfully Accessed
<title>Welcome to nginx!</title>
<style>
...
----------------------------------------------------------------------------------
Наконец, сделаем то же самое из pod’а в базе данных.
# Dive into a pod of the "database" deployment
> kubectl exec -it database-7d94797799-b9sdw -n database bash
# Try to access a service(clusterIP) named "backend", resides in the backend-tier
>> curl backend.backend
---------------------------------------------------------------------------------
# curl: (28) Failed to connect to backend.backend port 80: Connection timed out
---------------------------------------------------------------------------------
# Try to access a service(clusterIP) named "frontend", resides in the frontend-tier
>> curl frontend.frontend
---------------------------------------------------------------------------------
# curl: (28) Failed to connect to frontend.frontend port 80: Connection timed out
---------------------------------------------------------------------------------
Как видите, все наши сетевые политики работают, как ожидалось.
Безопасность в Kubernetes: как прокачать навыки
В учебном центре Слёрм открыта запись на курс «Безопасность в Kubernetes» для инженеров безопасности, DevOps’ов, SRE и разработчиков, самостоятельно работающих в Kubernetes.
На курсе вы познакомитесь с основными моделями угроз, а также узнаете как им противостоять и что делать, чтобы контейнер запустился в срок, а безопасность была на всех этапах — от разработки до отправки на сервер и последующего разворачивания.
Все знания вы можете закрепить на практике на стендах — проработаете теорию и будете уверены в решениях.
Если вы уже имеете базовые знания и хотите потрогать внутрянку Kuba, приходите на «Kubernetes Мега». Вас ждут 6 часов практики, приправленной щепоткой теории от спикеров.
Что будет?
авторизация в кластере
настройка autoscaling
резервное копирование
Stateful приложения в кластере
интеграция Kubernets и Vault для хранения секретов
HorizontalPodAutoscaler
ротация сертификатов в кластере
Blue-Green Deploy и Canary Deploy
настройка Service mesh
«Мега» подойдет всем, кому предстоит запускать Kubernetes в продакшн и отвечать за работу проекта в дальнейшем: специалистам по безопасности, системным инженерам, администраторам, архитекторам, DevOps и др. Пройдите бесплатный курс, чтобы научиться устанавливать Kubernetes в ручном режиме.
Комплектом дешевле:
Мы предлагаем комплекты видеокурсов (тариф "Стандарт") со скидкой от 20%
«Безопасность» + «Мега» = 90 000 рублей вместо 130 000
Узнать подробности и записаться: «Безопасность в Kubernetes», «Kubernetes Мега».