Kube/Pythonproduction course
Курс · RU08-security-and-multi-tenancy.md

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.

Самопроверка

  1. Чем RoleBinding отличается от ClusterRoleBinding?
  2. Почему создание Pod может повысить доступ к учётным данным?
  3. Что делает automountServiceAccountToken: false?
  4. Применяет ли SecurityContext политику самостоятельно?
  5. Почему namespace не является жёсткой границей безопасности?
  6. Где появляется pods/log?
  7. Заменяет ли digest образа сканирование уязвимостей?

Ответы: 1) разрешение в namespace и разрешение во всём кластере; 2) Pod может смонтировать доступный ServiceAccount или Secret и использовать возможности узла; 3) не проецирует токен API по умолчанию; 4) это настройки среды исполнения, политика проверяет admission; 5) общая плоскость управления, общие узлы и ресурсы уровня кластера; 6) отдельный подресурс RBAC; 7) нет, digest только фиксирует байты.

Резюме и источники

Безопасность — это идентичность, минимальные привилегии, admission, среда исполнения, сеть, цепочка поставки и аудит. Далее: наблюдаемость и диагностика.