Прим. перев.: Автор статьи — Reuven Harrison — имеет более 20 лет опыта в разработке программного обеспечения, а на сегодняшний день является техническим директором и соучредителем компании Tufin, создающей решения для управления политиками безопасности. Рассматривая сетевые политики Kubernetes как достаточно мощное средство для сегментации сети в кластере, он в то же время считает, что они не так просты в применении на практике. Данный материал (довольно объёмный) призван улучшить осведомлённость специалистов в этом вопросе и помочь им в создании необходимых конфигураций.
Сегодня многие компании все чаще выбирают Kubernetes для запуска своих приложений. Интерес к этому ПО настолько высок, что некоторые называют Kubernetes «новой операционной системой для центров обработки данных». Постепенно Kubernetes (или k8s) начинает восприниматься как критически важная часть бизнеса, которая требует организации зрелых бизнес-процессов, в том числе обеспечения сетевой безопасности.
Для специалистов по безопасности, которых озадачили работой с Kubernetes, настоящим открытием может стать политика этой платформы по умолчанию: разрешить всё.
Это руководство поможет разобраться во внутреннем устройстве сетевых политик; понять, чем они отличаются от правил для обычных брандмауэров. Также будет рассказано о некоторых подводных камнях и будут даны рекомендации, которые помогут защитить приложения в Kubernetes.
balance
открывается доступ к postgres
:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.postgres
namespace: default
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- podSelector:
matchLabels:
app: balance
policyTypes:
- Ingress
kubectl create -f policy.yaml
podSelector
: определяет pod'ы, затрагиваемые этой политикой (цели) — обязательный;policyTypes
: указывает, какие типы политик включены в данную: ingress и/или egress — необязательный, однако я рекомендую его явно прописывать во всех случаях;ingress
: определяет разрешенный входящий трафик в целевые pod'ы — необязательный;egress
: определяет разрешенный исходящий трафик из целевых pod'ов — необязательный.role
на app
), показывает, как используются все четыре элемента:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector: # <<<
matchLabels:
app: db
policyTypes: # <<<
- Ingress
- Egress
ingress: # <<<
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress: # <<<
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
podSelector
, остальные параметры можно использовать по желанию.policyTypes
, политика будет интерпретироваться следующим образом:policyTypes
.ingress
и/или egress
опущены, политика будет запрещать весь трафик (см. «Правило зачистки» ниже).metadata
можно прописать, какому именно пространству принадлежит политика:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: my-namespace # <<<
spec:
...
namespace=default
):kubectl apply -n my-namespace -f namespace.yaml
podSelector
в политике будет выбирать pod’ы из пространства имен, к которому принадлежит политика (он лишен доступа к pod'ам из другого пространства имён).namespaceSelector
(об этом пойдет речь в разделе «Фильтр по пространствам имен и pod'ам»).apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.postgres # <<<
namespace: default
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- podSelector:
matchLabels:
app: admin
policyTypes:
- Ingress
podSelector:
matchLabels:
role: db
namespaceSelector:
matchLabels:
project: myproject
namespaceSelector
убедитесь, что выбираемые пространства имен содержат в себе нужный лейбл. Имейте в виду, что встроенные пространства имен, такие как default
и kube-system
, по умолчанию не содержат в себе лейблов.kubectl label namespace default namespace=default
metadata
должен ссылаться на фактическое имя пространства, а не на лейбл:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default # <<<
spec:
...
default
с лейблом с ключом app
и значением db
:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
app: db # <<<
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
ingress
в этой политике открывает входящий трафик к целевым pod’ам. Другими словами, ingress выступает источником, а цель — соответствующим адресатом. Аналогичным образом egress является адресатом, а цель — его источником.balance
обращаться к DNS:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.balance
namespace: default
spec:
podSelector:
matchLabels:
app: balance
egress:
- to:
- podSelector:
matchLabels:
app: postgres
policyTypes:
- Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.balance
namespace: default
spec:
podSelector:
matchLabels:
app: balance
egress:
- to:
- podSelector:
matchLabels:
app: postgres
- to: # <<<
ports: # <<<
- protocol: UDP # <<<
port: 53 # <<<
policyTypes:
- Egress
to
— пустой, и поэтому он косвенно выбирает все pod'ы во всех пространствах имен, позволяя balance
посылать DNS-запросы в соответствующую службу Kubernetes (обычно она работает в пространстве kube-system
).namespaceSelector
:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.balance
namespace: default
spec:
podSelector:
matchLabels:
app: balance
egress:
- to:
- podSelector:
matchLabels:
app: postgres
- to:
- namespaceSelector: {} # <<<
ports:
- protocol: UDP
port: 53
policyTypes:
- Egress
kube-system
.kube-system
: kubectl label namespace kube-system namespace=kube-system
— и прописать ее в политике с помощью namespaceSelector
:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.balance
namespace: default
spec:
podSelector:
matchLabels:
app: balance
egress:
- to:
- podSelector:
matchLabels:
app: postgres
- to:
- namespaceSelector: # <<<
matchLabels: # <<<
namespace: kube-system # <<<
ports:
- protocol: UDP
port: 53
policyTypes:
- Egress
kube-system
. В разделе «Фильтр по пространствам имен И pod'ам» будет рассказано, как этого добиться.apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.dns
namespace: default
spec:
podSelector: {} # <<<
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: UDP
port: 53
policyTypes:
- Egress
podSelector
выбирает все pod’ы в пространстве имен.apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: default
spec:
podSelector: {}
policyTypes:
- Ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
ingress
:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all
namespace: default
spec:
podSelector: {}
ingress: # <<<
- {} # <<<
policyTypes:
- Ingress
default
. Подобное поведение включено по умолчанию, поэтому обычно его не нужно определять дополнительно. Однако иногда может потребоваться временно отключить некоторые конкретные разрешения для диагностики проблемы.app:balance
) в пространстве имен default
:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all-to-balance
namespace: default
spec:
podSelector:
matchLabels:
app: balance
ingress:
- {}
policyTypes:
- Ingress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all
spec:
podSelector: {}
ingress:
- {}
egress:
- {}
policyTypes:
- Ingress
- Egress
from
и to
можно определить три типа элементов (все они комбинируются с помощью ИЛИ):namespaceSelector
— выбирает пространство имен целиком;podSelector
— выбирает pod’ы;ipBlock
— выбирает подсеть.from
/to
не ограничено. Все они будут объединены логическим ИЛИ.apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.postgres
namespace: default
spec:
ingress:
- from:
- podSelector:
matchLabels:
app: indexer
- podSelector:
matchLabels:
app: admin
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress
может иметь множество элементов from
(объединяются логическим ИЛИ). Аналогичным образом раздел egress
может включать множество элементов to
(также объединяются дизъюнкцией):apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.postgres
namespace: default
spec:
ingress:
- from:
- podSelector:
matchLabels:
app: indexer
- from:
- podSelector:
matchLabels:
app: admin
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
policyTypes
(Ingress
или Egress
). Политики, определяющие ingress (или egress), перезапишут друг друга.namespaceSelector
:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database.postgres
namespace: database
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- namespaceSelector: # <<<
matchLabels:
namespace: default
policyTypes:
- Ingress
default
получат доступ к pod'ам postgres
в пространстве имен database
. Но что, если вы хотите открыть доступ к postgres
только конкретным pod'ам в пространстве имен default
?namespaceSelector
и podSelector
с помощью логического И. Выглядит это следующим образом:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database.postgres
namespace: database
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- namespaceSelector:
matchLabels:
namespace: default
podSelector: # <<<
matchLabels:
app: admin
policyTypes:
- Ingress
podSelector
не начинается с дефиса. В YAML это означает, что podSelector
и стоящий перед ним namespaceSelector
относятся к одному и тому же элементу списка. Поэтому они объединяются логическим И.podSelector
приведет к возникновению нового элемента списка, который будет комбинироваться с предшествующим namespaceSelector
с помощью логического ИЛИ.namespaceSelector
:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database.postgres
namespace: database
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- namespaceSelector: {}
podSelector:
matchLabels:
app: admin
policyTypes:
- Ingress
Host_1
ИЛИ Host_2
:| Source | Destination | Service | Action |
| ----------------------------------------|
| Host_1 | Subnet_A | HTTPS | Allow |
| Host_2 | | | |
| ----------------------------------------|
podSelector
или namespaceSelector
объединяются логическим И. Например, следующее правило выберет pod’ы, обладающие обеими лейблами, role=db
И version=v2
:podSelector:
matchLabels:
role: db
version: v2
ipBlocks
) используются при управлении входящими (ingress) или исходящими (egress) внешними (North-South) подключениями. К примеру, эта политика открывает всем pod'ам из пространства имен default
доступ к DNS-сервису Google:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-dns
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 8.8.8.8/32
ports:
- protocol: UDP
port: 53
ipBlocks
и podSelectors
являются взаимоисключающими, поскольку внутренние IP-адреса pod'ов не используются в ipBlocks
. Указав внутренние IP pod'ов, вы фактически разрешите подключения к/от pod'ов с этими адресами. На практике вы не будете знать, какой IP-адрес использовать, именно поэтому их не стоит применять для выбора pod'ов.apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-any
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-any
namespace: default
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.16.0.0/14
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.postgres
namespace: default
spec:
ingress:
- from:
- podSelector:
matchLabels:
app: indexer
- podSelector:
matchLabels:
app: admin
ports: # <<<
- port: 443 # <<<
protocol: TCP # <<<
- port: 80 # <<<
protocol: TCP # <<<
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ports
применяется ко всем элементам в блоке to
или from
, в котором содержится. Чтобы указать разные порты для различных наборов элементов, разбейте ingress
или egress
на несколько подразделов с to
или from
и в каждом пропишите свои порты:apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default.postgres
namespace: default
spec:
ingress:
- from:
- podSelector:
matchLabels:
app: indexer
ports: # <<<
- port: 443 # <<<
protocol: TCP # <<<
- from:
- podSelector:
matchLabels:
app: admin
ports: # <<<
- port: 80 # <<<
protocol: TCP # <<<
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ports
), это означает все протоколы и все порты;protocol
), это означает TCP;port
), это означает все порты.kubernetes get networkpolicy <policy-name> -o yaml
К сожалению, не доступен сервер mySQL