securityContext, права процесса в контейнере:
По умолчанию контейнер запускается так, как собран образ, часто от рута и с лишними capabilities. securityContext задается на уровне пода и контейнера, контейнерный перекрывает подовый. Рабочий минимум для продакшена: не рут, без эскалации привилегий, без capabilities, корневая файловая система только на чтение, seccomp-профиль рантайма.
Код: Выделить всё
# deploy-api.yaml, фрагмент шаблона пода
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: registry.local/shop/api:1.8.3
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
Pod Security Standards, запрет на уровне неймспейса:
Писать securityContext руками в каждом манифесте мало, нужен механизм, который не пустит нарушителя в кластер. PodSecurityPolicy выпилили в 1.25, ее место занял встроенный в apiserver контроллер Pod Security Admission. Он сверяет поды с тремя профилями: privileged (без ограничений), baseline (запрещает hostNetwork, hostPath, привилегированные контейнеры) и restricted (требует примерно то, что мы прописали выше). Управляется лейблами неймспейса в трех режимах: enforce блокирует создание, warn ругается в ответе kubectl, audit пишет в audit-лог.
Правильный порядок внедрения: сначала warn и audit, неделю смотрим что ломается, потом enforce.
Код: Выделить всё
kubectl label ns shop \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
# репетиция enforce: покажет всех нарушителей, ничего не меняя
kubectl label --dry-run=server --overwrite ns shop \
pod-security.kubernetes.io/enforce=restricted
NetworkPolicy, сегментация трафика:
Из коробки любой под может постучаться в любой другой, включая чужие неймспейсы. Скомпрометированный фронтенд получает прямой доступ к базе биллинга. NetworkPolicy это исправляет, но с оговоркой: политики исполняет CNI-плагин. Calico и Cilium умеют, flannel в чистом виде нет, и политики молча игнорируются. Проверяйте живым трафиком через kubectl exec и curl, а не по факту наличия yaml.
Базовый паттерн: default deny на весь неймспейс плюс точечные разрешения. Политики аддитивны, они только разрешают, запретить что-то поверх разрешенного нельзя.
Код: Выделить всё
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: shop
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow
namespace: shop
spec:
podSelector:
matchLabels:
app: api
policyTypes: ["Ingress", "Egress"]
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- port: 5432
- to:
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
Шифрование секретов в etcd:
base64 это кодирование, а не шифрование. Любой, у кого есть доступ к etcd или его бэкапам, читает секреты открытым текстом. Лечится через EncryptionConfiguration для kube-apiserver:
Код: Выделить всё
# /etc/kubernetes/enc/enc.yaml
# подключается к kube-apiserver флагом:
# --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets"]
providers:
- aescbc:
keys:
- name: key1
secret: <base64-строка 32-байтового ключа>
- identity: {}
Код: Выделить всё
kubectl get secrets -A -o json | kubectl replace -f -
Типичные грабли:
NetworkPolicy лежат в репозитории, CNI их не исполняет, команда живет с иллюзией сегментации. identity стоит первым в списке провайдеров, и секреты пишутся в открытом виде, порядок в EncryptionConfiguration критичен. enforce=restricted повесили, а старые поды-нарушители спокойно работают до первого пересоздания. readOnlyRootFilesystem уронил приложение, которое пишет логи в файл рядом с бинарником. В Dockerfile написано USER appuser вместо USER 10001, и runAsNonRoot не может доказать, что это не рут.
Итог:
Безопасность кластера собирается слоями: RBAC ограничивает API, securityContext и PSS ограничивают сам под, NetworkPolicy ограничивает сеть, шифрование защищает данные в покое. Каждый слой дешев по отдельности и бесполезен в одиночку. На этом курс закончен. Дальше есть смысл копать в GitOps (Argo CD, Flux), операторы и сертификации CKA и CKS, там та же безопасность разбирается еще глубже.