08. Безопасность и мультитенантность
Незнакомый термин? Откройте словарь Kubernetes и терминов курса.
Назначение, предварительные знания и результаты обучения
Нужны главы 00–07 и базовый namespace. После главы вы сможете:
- разделить аутентификацию, авторизацию и admission;
- написать Role/RoleBinding с минимальными привилегиями в namespace;
- объяснить проецируемый токен ServiceAccount и отключить ненужное монтирование;
- сделать Pod совместимым с Pod Security
restricted; - выполнить положительные и отрицательные проверки авторизации;
- описать меры защиты образов, сети, секретов и аудита;
- выбрать namespace или отдельный кластер как границу арендатора.
Ментальная модель: уровни запроса и рабочей нагрузки
Запрос API:
TLS → аутентификация (кто?) → авторизация (разрешена ли операция?)
→ мутация admission-контроллерами → схема/значения по умолчанию → проверка admission-контроллерами
→ сохранение → запись аудита
Аутентификация не выдаёт разрешения. В Kubernetes обычно нет объектов User в
namespace: идентичности пользователей приходят из клиентского сертификата,
OIDC или вебхук; ServiceAccount — идентичность рабочей нагрузки Kubernetes.
Режимов авторизации может быть несколько, наиболее распространён RBAC.
Связка RBAC: субъект → RoleBinding → правила Role → группа API, ресурс, имя и
операции. Role действует в одном namespace; правила ClusterRole имеют
область кластера, но RoleBinding может ограничить их применение одним namespace.
ClusterRoleBinding даёт правила во всём кластере и не должен быть коротким
путём по умолчанию.
Admission проверяет содержимое уже авторизованного запроса. Встроенный Pod
Security Admission (PSA) применяет Pod Security Standards (PSS) по меткам
namespace. baseline предотвращает известные повышения привилегий;
restricted требует усиленной защиты. Удалённый PodSecurityPolicy не
используйте: он устарел с v1.21 и удалён в v1.25.
Полный RBAC с минимальными привилегиями
apiVersion: v1
kind: ServiceAccount
metadata:
name: python-api
namespace: kube-course
labels:
app.kubernetes.io/name: python-api
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: python-api-config-reader
namespace: kube-course
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["python-api-config"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: python-api-config-reader
namespace: kube-course
subjects:
- kind: ServiceAccount
name: python-api
namespace: kube-course
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: python-api-config-reader
Role и RoleBinding — полные манифесты API, хотя у них есть поля rules и subjects, а
не spec. Полный ресурс-потребитель рабочей нагрузки со spec:
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-api-secure
namespace: kube-course
labels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: security-lab
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: security-lab
template:
metadata:
labels:
app.kubernetes.io/name: python-api
app.kubernetes.io/instance: security-lab
spec:
serviceAccountName: python-api
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: kube-python-course:1.0.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8000
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
- name: data
mountPath: /data
volumes:
- name: tmp
emptyDir: {sizeLimit: 64Mi}
- name: data
emptyDir: {sizeLimit: 64Mi}
Role бесполезна процессу приложения, пока токен не смонтирован: это намеренная
защита. Если приложение действительно вызывает API, смонтируйте короткоживущий
проецируемый токен с конкретным audience и сроком действия либо осознанно включите
стандартное автоматическое монтирование. Современное монтирование ServiceAccount
— ограниченный проецируемый токен, а не долгоживущий токен Secret.
Pod Security restricted
Метки Namespace:
apiVersion: v1
kind: Namespace
metadata:
name: kube-course
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.36
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: v1.36
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: v1.36
spec: {}
Фиксируйте версию, чтобы обновление незаметно не меняло политику; затем планово
повышайте её. Для restricted важны запуск без root,
allowPrivilegeEscalation: false, seccomp RuntimeDefault/Localhost, удаление
всех capabilities (ALL), разрешённые типы томов, запрет host namespaces,
привилегированного режима и hostPath.
SecurityContext задаёт требования к среде исполнения, PSS и admission их проверяют, а среда исполнения и ядро обеспечивают. Один манифест не доказывает изоляцию. Контейнеры Linux делят ядро узла; для недоверенного кода рассмотрите отдельный кластер или изолированную среду исполнения.
Практическое упражнение: разрешить одно, запретить другое
Подготовка
kubectl apply -f manifests/base/namespace.yaml
kubectl apply -f manifests/base/configmap.yaml
kubectl apply -f manifests/base/secret.example.yaml
kubectl apply -f manifests/base/serviceaccount.yaml
kubectl apply -f manifests/base/rbac.yaml
Проверки авторизации только для чтения с impersonation (ваша идентичность
должна иметь разрешение impersonate; администратор kind его имеет):
SUBJECT='system:serviceaccount:kube-course:python-api'
kubectl auth can-i get configmap/python-api-config \
-n kube-course --as="$SUBJECT"
kubectl auth can-i list configmaps \
-n kube-course --as="$SUBJECT"
kubectl auth can-i get secrets \
-n kube-course --as="$SUBJECT"
kubectl auth can-i get configmap/python-api-config \
-n default --as="$SUBJECT"
Ожидаются yes, затем no, no, no. resourceNames + get не даёт list.
Проверьте фактическую идентичность внутри Pod:
kubectl apply -f manifests/labs/rbac-check.yaml
kubectl wait pod/rbac-check -n kube-course \
--for=jsonpath='{.status.phase}'=Succeeded --timeout=120s
kubectl logs pod/rbac-check -n kube-course
Pod временно устанавливает automountServiceAccountToken: true, читает
конкретный ConfigMap и ожидает запрет на список Secrets; команда завершается
кодом 0, только если оба исхода
верны. После:
kubectl delete pod rbac-check -n kube-course
Негативная проверка admission
apiVersion: v1
kind: Pod
metadata:
name: forbidden-root
namespace: kube-course
spec:
containers:
- name: shell
image: busybox:1.37.0
securityContext:
privileged: true
resources:
requests: {cpu: 10m, memory: 16Mi}
limits: {cpu: 100m, memory: 64Mi}
kubectl apply --dry-run=server -f /tmp/forbidden-root.yaml
Ожидается ненулевой код, а сообщение admission перечисляет нарушения PSS. Это успех серверной политики, а не провал лабораторной. Клиентский dry-run может принять объект, что демонстрирует разницу уровней проверки.
Многоуровневая защита
Образы
Не используйте latest; в эксплуатационной среде фиксируйте digest и проверяйте подписи,
свидетельства происхождения, SBOM и результаты сканирования через реестр, CI и политику
admission. Базовый Kubernetes не сканирует образ. imagePullPolicy не является
политикой доверия. Учётные данные частного реестра ограничивайте конкретными
namespace и ServiceAccount; кеш узла тоже является поверхностью атаки.
Сеть и Secrets
NetworkPolicy уменьшает боковое перемещение, но требует совместимого CNI и не
заменяет TLS и аутентификацию. Для Secret нужны чтение API с минимальными
привилегиями, шифрование хранимых данных через KMS, внешняя ротация и запрет
журналирования. exec, port-forward, эфемерные контейнеры и создание Pod
часто позволяют косвенно получить смонтированные учётные данные — ограничивайте
не только get secrets.
Admission и аудит
Встроенный admission включает PSA, ResourceQuota, LimitRanger и другие средства
управления; ValidatingAdmissionPolicy и вебхуки могут применять правила
организации. Вебхук — зависимость плоскости управления: обязательны тайм-аут,
failurePolicy, доступность, совместимость версий и предотвращение циклов.
Политика аудита записывает субъекта, операцию, ресурс и результат; тела запросов и
ответов могут содержать Secrets, поэтому уровни и хранилище защищают. Журнал
аудита не предотвращает действие.
Мультитенантность
Namespace — строительный блок мягкой мультитенантности. Для доверенных команд используйте namespace, RBAC, квоты и лимиты, PSS, NetworkPolicy типа default-deny, политику секретов и admission. Защитите ресурсы уровня кластера, CRD, вебхуки, узлы, Ingress/Gateway и наблюдаемость от утечек между арендаторами.
Для враждебных арендаторов, требований соответствия и разных окон обновления обычно надёжнее отдельные кластеры, учётные записи и VPC, возможно изолированная среда исполнения. Цена — больше плоскостей управления, дублирование политик и эксплуатации. Выбирайте границу по модели угроз, а не по числу команд.
Самостоятельная задача
Создайте ServiceAccount support-reader и Role только get/list/watch
Pods и get pods/log, без exec, Secrets и delete. Проверьте все положительные
и отрицательные auth can-i, включая другой namespace. Критерий приёмки: ни
шаблона *,
ни ClusterRoleBinding, ни cluster-admin.
Сломанный сценарий: неверные разрешения
Измените resourceName Role на python-api-confg (опечатка), пересоздайте
rbac-check. Ожидается Pod в Failed; журналы:
Error from server (Forbidden): configmaps "python-api-config" is forbidden
Доказательства:
kubectl logs rbac-check -n kube-course
kubectl auth can-i get configmap/python-api-config \
-n kube-course --as=system:serviceaccount:kube-course:python-api
kubectl get role,rolebinding -n kube-course -o yaml
Исправьте точное resourceNames, примените Role и пересоздайте упавший Pod. Не
расширяйте
resources: ["*"], verbs: ["*"].
Дерево диагностических решений
Операция API завершается ошибкой
├─ Unauthorized → учётные данные/токен/audience/срок действия/kubeconfig
├─ Forbidden
│ ├─ точные операция/ресурс/подресурс? → logs и pods/log, exec и pods/exec
│ ├─ субъект/namespace RoleBinding? → строка идентификации
│ └─ несовместимые `resourceName`/`list`? → семантика правила RBAC
└─ admission отклоняет запрос
├─ PSS → поля SecurityContext/тома/host
├─ квота/лимит → запрошенный объект/использование namespace
└─ вебхук/политика → сообщение, status политики/исправность контроллера
Среда исполнения Pod сообщает `permission denied`
├─ файловая система UID/GID/fsGroup/readOnly root
├─ capability Linux/seccomp/AppArmor/SELinux
├─ API ServiceAccount возвращает Forbidden
└─ NetworkPolicy/TLS/авторизация приложения
Типичные ошибки: приравнивать аутентификацию к авторизации; использовать слишком широкий ClusterRoleBinding; полагаться на ServiceAccount по умолчанию; монтировать токен приложению без потребности в API; разрешать пользовательскому приложению создавать Pods, что повышает доступ к учётным данным; использовать манифесты PSP; обходить права тома запуском от root; выбирать fail-open для admission-вебхук без решения о риске.
Эксплуатационные компромиссы, безопасность и стоимость
Средства безопасности влияют на доступность и стоимость: отказы admission, вебхук, CNI или KMS могут блокировать развёртывание, трафик и чтение секретов. Проектируйте высокую доступность, тайм-ауты и аварийный доступ с аудитом, сроком действия и проверенным восстановлением. Один кластер дешевле, но увеличивает радиус поражения; отдельные кластеры изолируют, но добавляют постоянную стоимость. Исключения политики должны иметь владельца и срок действия. Минимальные привилегии измеряются отрицательными проверками, а не длиной YAML.
Самопроверка
- Чем
RoleBindingотличается отClusterRoleBinding? - Почему создание Pod может повысить доступ к учётным данным?
- Что делает
automountServiceAccountToken: false? - Применяет ли SecurityContext политику самостоятельно?
- Почему namespace не является жёсткой границей безопасности?
- Где появляется
pods/log? - Заменяет ли digest образа сканирование уязвимостей?
Ответы: 1) разрешение в namespace и разрешение во всём кластере; 2) Pod может смонтировать доступный ServiceAccount или Secret и использовать возможности узла; 3) не проецирует токен API по умолчанию; 4) это настройки среды исполнения, политика проверяет admission; 5) общая плоскость управления, общие узлы и ресурсы уровня кластера; 6) отдельный подресурс RBAC; 7) нет, digest только фиксирует байты.
Резюме и источники
Безопасность — это идентичность, минимальные привилегии, admission, среда исполнения, сеть, цепочка поставки и аудит. Далее: наблюдаемость и диагностика.