Skip to content

Глава 14. Kubernetes: глубокое погружение

«Kubernetes — не безопасен по умолчанию. Но он предоставляет все примитивы, чтобы построить платформу с нулевым доверием.»

В предыдущих главах мы рассматривали отдельные аспекты Zero Trust в Kubernetes: SPIFFE/SPIRE (глава 7), микросегментацию (глава 10), service mesh (глава 12), admission control (глава 13). Эта глава объединяет все элементы в целостный стек и углубляется в три темы, которые ещё не были раскрыты: RBAC, управление секретами и мультитенантность.

14.1. Зачем: Kubernetes как поверхность атаки

Kubernetes-кластер содержит десятки компонентов с собственными attack vectors:

КомпонентРискМеханизм защиты
kube-apiserverНесанкционированный доступ к APIRBAC + admission control + аудит
etcdЧтение секретов в открытом видеEncryption at rest + mTLS
kubeletВыполнение произвольных командNode authorization + certificate rotation
PodsLateral movement между нагрузкамиNetworkPolicy + PSS + SPIFFE
SecretsУтечка credentialsVault CSI + External Secrets
Container runtimeContainer escapeUser namespaces + seccomp + AppArmor

Kubernetes 1.33–1.35 (2025) принесли ключевые улучшения безопасности:

  • K8s 1.33: User namespaces включены по умолчанию — изоляция UID/GID контейнеров от хоста.
  • K8s 1.35: Pod certificates для mTLS (KEP-4317, beta) — нативная поддержка workload identity без внешних контроллеров. Constrained impersonation (KEP-5284, alpha) — защита от spoofing нод.

14.2. RBAC: минимальные привилегии

14.2.1. Модель RBAC

Kubernetes RBAC (rbac.authorization.k8s.io/v1) определяет четыре ресурса:

РесурсНазначение
Role / ClusterRoleОпределяют набор разрешений (verbs + resources)
RoleBinding / ClusterRoleBindingПривязывают роли к субъектам (users, groups, SA)
  • Role / RoleBinding — область видимости: один namespace.
  • ClusterRole / ClusterRoleBinding — область видимости: весь кластер.

14.2.2. Best practices для Zero Trust

1. Отключите автоматическое монтирование токенов:

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
  namespace: production
automountServiceAccountToken: false

По умолчанию каждый Pod получает токен ServiceAccount, смонтированный в /var/run/secrets/kubernetes.io/serviceaccount/token. Если приложение не обращается к Kubernetes API, этот токен — лишняя поверхность атаки.

2. Один ServiceAccount на приложение:

Не используйте default ServiceAccount. Создайте отдельный SA для каждого Deployment с минимально необходимыми разрешениями.

3. Ограничьте глаголы и ресурсы:

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-reader
  namespace: production
rules:
  # Только чтение ConfigMap и Secret — ничего больше
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch"]
  # Доступ к конкретным секретам по имени
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["app-config", "db-credentials"]
    verbs: ["get"]

4. Запретите wildcards (*):

yaml
# ПЛОХО: даёт доступ ко всему
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]

# ХОРОШО: явное перечисление
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch"]

5. Используйте resourceNames для точечного доступа:

Если приложению нужен один конкретный Secret, ограничьте доступ именем, а не выдавайте доступ ко всем Secret в namespace.

14.2.3. Аудит RBAC

Инструменты для выявления избыточных прав:

ИнструментНазначениеУстановка
rbac-tool (Alcide/Rapid7)Визуализация RBAC, поиск who-can, генерация политик из аудит-логовkubectl krew install rbac-tool
kubectl auth can-iПроверка разрешений конкретного субъектаВстроенный в kubectl
kubectl auth whoamiИнформация о текущем пользователе (K8s 1.27+)Встроенный в kubectl
bash
# Кто может удалять pods в namespace production?
kubectl auth can-i delete pods -n production --list

# Визуализация RBAC графа (rbac-tool)
kubectl rbac-tool viz --outformat dot | dot -Tpng -o rbac.png

# Генерация минимальных ролей из аудит-логов
kubectl rbac-tool auditgen --audit-log /var/log/kubernetes/audit.log

14.2.4. RBAC в управляемых кластерах

ПлатформаИнтеграция RBACКлючевые изменения
EKSAccess Entries API (замена aws-auth ConfigMap, deprecated)IAM → K8s RBAC через API, не через ConfigMap
GKEGoogle Groups for RBAC, Workload IdentityIAM roles → K8s ClusterRole автоматически
AKSAzure RBAC for K8s (GA)Entra ID groups → K8s roles, Azure Policy integration

EKS Access Entries — рекомендуемый метод с 2024:

bash
# Создание access entry для IAM-роли
aws eks create-access-entry \
  --cluster-name production \
  --principal-arn arn:aws:iam::123456789012:role/AppDeployRole \
  --type STANDARD

# Привязка к K8s-группе
aws eks associate-access-policy \
  --cluster-name production \
  --principal-arn arn:aws:iam::123456789012:role/AppDeployRole \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy \
  --access-scope type=namespace,namespaces=production

14.3. Управление секретами

14.3.1. Проблема нативных Secrets

Kubernetes Secrets по умолчанию хранятся в etcd в base64 (не зашифрованными). Даже с включённым encryption at rest (EncryptionConfiguration) секреты доступны любому, кто имеет RBAC-доступ к ресурсу secrets.

В модели Zero Trust секреты должны:

  • Храниться во внешнем хранилище (Vault, AWS Secrets Manager, Azure Key Vault).
  • Доставляться через проверенные каналы (CSI driver, оператор).
  • Ротироваться автоматически.
  • Быть доступны только авторизованным workloads.

14.3.2. Vault CSI Provider

Secrets Store CSI Driver (secrets-store.csi.x-k8s.io/v1) — стандартный Kubernetes-механизм для монтирования секретов из внешних хранилищ. Vault CSI Provider — реализация для HashiCorp Vault.

Архитектура:

Аутентификация: Pod предоставляет ServiceAccount JWT. Vault проверяет его через Kubernetes TokenReview API и выдаёт секреты согласно политике, привязанной к роли.

yaml
# SecretProviderClass: маппинг Vault-секретов
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-db-creds
  namespace: production
spec:
  provider: vault
  parameters:
    roleName: "app-role"
    vaultAddress: "https://vault.vault-system:8200"
    objects: |
      - objectName: "db-username"
        secretPath: "database/creds/app-db"
        secretKey: "username"
      - objectName: "db-password"
        secretPath: "database/creds/app-db"
        secretKey: "password"
  # Опционально: синхронизация в K8s Secret
  secretObjects:
    - secretName: app-db-creds
      type: Opaque
      data:
        - objectName: db-username
          key: username
        - objectName: db-password
          key: password
yaml
# Deployment: монтирование секретов
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  namespace: production
spec:
  template:
    spec:
      serviceAccountName: app-sa
      containers:
        - name: app
          image: ghcr.io/my-org/app:v1.2.3
          volumeMounts:
            - name: secrets
              mountPath: "/mnt/secrets"
              readOnly: true
          env:
            # Из синхронизированного K8s Secret
            - name: DB_USERNAME
              valueFrom:
                secretKeyRef:
                  name: app-db-creds
                  key: username
      volumes:
        - name: secrets
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: vault-db-creds

14.3.3. Vault Agent Injector vs CSI Provider

КритерийVault Agent InjectorVault CSI Provider
МеханизмMutating webhook + sidecarCSI volume driver
SidecarДа (vault-agent контейнер)Нет
РесурсыДополнительные CPU/RAM на PodМинимальные (DaemonSet)
РотацияАвтоматическая (agent обновляет файлы)При перемонтировании тома
Sync в K8s SecretНетДа (через secretObjects)
ШаблоныGo templates (гибкие)Только key-value

Рекомендация: CSI Provider — для простых сценариев (key-value секреты). Agent Injector — когда нужны шаблоны, автоматическая ротация или динамические credentials.

14.3.4. External Secrets Operator (ESO)

External Secrets Operator (ESO) — альтернативный подход: оператор синхронизирует секреты из внешних хранилищ в нативные Kubernetes Secrets.

Примечание: ESO активно развивается (v1.x в 2025–2026), но проект зависит от ограниченной команды мейнтейнеров. Перед внедрением проверьте актуальный статус поддержки на external-secrets.io.

Поддерживаемые провайдеры: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, HashiCorp Vault, 1Password, GitLab Variables, Doppler и другие.

yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: app-secrets
    creationPolicy: Owner
  data:
    - secretKey: db-password
      remoteRef:
        key: secret/data/app
        property: db-password

14.4. Мультитенантность

14.4.1. Уровни изоляции

УровеньМеханизмПрименение
NamespaceRBAC + ResourceQuota + LimitRange + NetworkPolicyБазовая изоляция команд
CapsuleTenant CRD: агрегация namespaces с наследованием политикSoft multi-tenancy для внутренних команд
vClusterВиртуальный control plane (API server + controller manager)Hard multi-tenancy для внешних пользователей
Multi-clusterОтдельные кластеры (Cluster API v1.12)Максимальная изоляция

14.4.2. Namespace-based tenancy

Минимальный набор для изоляции tenant:

yaml
# 1. Namespace с PSS
apiVersion: v1
kind: Namespace
metadata:
  name: tenant-a
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    tenant: team-alpha
---
# 2. ResourceQuota
apiVersion: v1
kind: ResourceQuota
metadata:
  name: tenant-quota
  namespace: tenant-a
spec:
  hard:
    requests.cpu: "10"
    requests.memory: "20Gi"
    limits.cpu: "20"
    limits.memory: "40Gi"
    pods: "50"
---
# 3. Default deny NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: tenant-a
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

14.4.3. Capsule: soft multi-tenancy

Capsule (v0.12.4, декабрь 2025) реализует мультитенантность через CRD Tenant, агрегирующий namespaces с автоматическим наследованием политик:

yaml
apiVersion: capsule.clastix.io/v1beta2
kind: Tenant
metadata:
  name: team-alpha
spec:
  owners:
    - name: alice
      kind: User
    - name: team-alpha-admins
      kind: Group
  namespaceOptions:
    quota: 5   # Максимум 5 namespaces
  networkPolicies:
    items:
      - policyTypes:
          - Ingress
          - Egress
        podSelector: {}
        # Все namespaces tenant'а получат deny-all по умолчанию
  resourceQuotas:
    items:
      - hard:
          requests.cpu: "20"
          requests.memory: "40Gi"
          pods: "100"
  limitRanges:
    items:
      - limits:
          - type: Container
            default:
              cpu: "500m"
              memory: "512Mi"
            defaultRequest:
              cpu: "100m"
              memory: "128Mi"

Пользователь alice может самостоятельно создавать namespaces в рамках tenant'а, и все политики применяются автоматически.

14.4.4. vCluster: hard multi-tenancy

vCluster (v0.31.0, январь 2026) создаёт полноценный виртуальный кластер внутри namespace хост-кластера:

bash
# Создание виртуального кластера
vcluster create tenant-external \
  --namespace vc-tenant-external \
  --set networking.advanced.isolateNetworkPolicy=true

# Подключение к виртуальному кластеру
vcluster connect tenant-external --namespace vc-tenant-external

# Внутри — полноценный K8s
kubectl get ns  # tenant видит только свои ресурсы

Каждый vCluster имеет собственный API server, controller manager и хранилище (SQLite по умолчанию). Tenant может устанавливать свои CRD, использовать любую версию Kubernetes и не влиять на другие tenant'ы.

Внимание: HNC (Hierarchical Namespace Controller) — архивирован в апреле 2025. Не используйте для новых проектов. Мигрируйте на Capsule или vCluster.

14.4.5. Выбор подхода

ВопросNamespace + RBACCapsulevCluster
Tenant'ы доверяют друг другу?ДаДаНе обязательно
Нужны собственные CRD?НетНетДа
Нужен свой API server?НетНетДа
Overhead на tenantМинимальныйНизкийСредний-высокий
СамообслуживаниеОграниченноеДа (в рамках Tenant)Полное

14.5. Полная архитектура Zero Trust для Kubernetes

Слои Zero Trust в Kubernetes:

СлойМеханизмГлава
1. Идентичность рабочей нагрузкиSPIFFE/SPIRE SVIDГл. 7
2. Admission controlPSS + Kyverno/GatekeeperГл. 13
3. RBACМинимальные привилегии для SAЭта глава
4. Сетевая изоляцияNetworkPolicy (Cilium/Calico)Гл. 10
5. mTLSService mesh (Istio/Linkerd)Гл. 12
6. СекретыVault CSI / ESOЭта глава
7. МультитенантностьCapsule / vClusterЭта глава

14.6. Lab: Zero Trust Kubernetes stack

Предварительные требования

  • Docker, kind, kubectl, Helm v3

Шаг 1: Создание кластера

bash
kind create cluster --name zt-k8s-lab --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
  - role: worker
EOF

Шаг 2: Namespace с PSS

bash
kubectl create namespace production
kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/warn=restricted

Шаг 3: RBAC — минимальные привилегии

bash
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
  namespace: production
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-minimal
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: app-minimal-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: app-sa
    namespace: production
roleRef:
  kind: Role
  name: app-minimal
  apiGroup: rbac.authorization.k8s.io
EOF

Шаг 4: ResourceQuota

bash
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: ResourceQuota
metadata:
  name: production-quota
  namespace: production
spec:
  hard:
    requests.cpu: "4"
    requests.memory: "8Gi"
    limits.cpu: "8"
    limits.memory: "16Gi"
    pods: "20"
EOF

Шаг 5: Default deny NetworkPolicy

bash
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
# Разрешить DNS
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to: []
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
EOF

Шаг 6: Тестовый workload

bash
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-app
  namespace: production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-app
  template:
    metadata:
      labels:
        app: test-app
    spec:
      serviceAccountName: app-sa
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: app
          image: nginx:1.27
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]
            readOnlyRootFilesystem: true
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "256Mi"
          volumeMounts:
            - name: tmp
              mountPath: /tmp
            - name: cache
              mountPath: /var/cache/nginx
            - name: run
              mountPath: /var/run
      volumes:
        - name: tmp
          emptyDir: {}
        - name: cache
          emptyDir: {}
        - name: run
          emptyDir: {}
EOF

Шаг 7: Проверка

bash
# Pod запущен с restricted PSS
kubectl get pods -n production

# RBAC: SA может читать ConfigMaps
kubectl auth can-i get configmaps \
  --as=system:serviceaccount:production:app-sa -n production
# yes

# RBAC: SA НЕ может читать Secrets
kubectl auth can-i get secrets \
  --as=system:serviceaccount:production:app-sa -n production
# no

# NetworkPolicy: default deny активна
kubectl get networkpolicy -n production
# NAME               POD-SELECTOR   AGE
# default-deny-all   <none>         ...
# allow-dns          <none>         ...

# ResourceQuota
kubectl describe resourcequota production-quota -n production

Очистка

bash
kind delete cluster --name zt-k8s-lab

14.7. Итоги

АспектИнструментПринцип
ИдентичностьRBAC + SPIFFEКаждый workload — уникальная идентичность
АвторизацияRBAC + OPA/KyvernoМинимальные привилегии, deny-by-default
СетьNetworkPolicy + meshМикросегментация, mTLS
СекретыVault CSI / ESOВнешнее хранение, динамическая доставка
ИзоляцияPSS + namespaces/vClusterDefense-in-depth

Zero Trust в Kubernetes — это не один инструмент, а слоёная архитектура, где каждый слой компенсирует ограничения других. В следующей главе мы рассмотрим безопасность CI/CD: OIDC-федерация, SLSA и Sigstore (глава 15).


Источники:

  • Kubernetes RBAC docs: kubernetes.io/docs/reference/access-authn-authz/rbac/
  • Kubernetes Multi-tenancy: kubernetes.io/docs/concepts/security/multi-tenancy/
  • Kubernetes PSS: kubernetes.io/docs/concepts/security/pod-security-standards/
  • EKS Access Entries: docs.aws.amazon.com/eks/latest/userguide/access-entries.html
  • Vault CSI Provider: developer.hashicorp.com/vault/docs/deploy/kubernetes/csi
  • Secrets Store CSI Driver: secrets-store-csi-driver.sigs.k8s.io
  • Capsule v0.12.4: capsule.clastix.io
  • vCluster v0.31.0: vcluster.com
  • Cluster API: github.com/kubernetes-sigs/cluster-api
  • K8s 1.35 security: cncf.io/blog/2025/12/15/kubernetes-security-2025-stable-features-and-2026-preview/
  • External Secrets Operator: external-secrets.io

Опубликовано под лицензией CC BY-SA 4.0