Секреты в кластере: шифрование, External Secrets, Vault

Рейтинг: 68.5% · 14 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 55
Зарегистрирован: 12 май 2026, 03:23

Секреты в кластере: шифрование, External Secrets, Vault

Сообщение anton_k8s »

Оглавление курса (48)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
  20. Архитектура control plane: api-server, etcd, scheduler
  21. Узел кластера: kubelet, kube-proxy и container runtime
  22. Декларативная модель: reconciliation, контроллеры, CRD
  23. kubectl профессионально: get, describe, explain, jsonpath
  24. Под глубже: init, sidecar, lifecycle hooks и QoS
  25. Метки, селекторы и организация ресурсов
  26. Сервисы и kube-proxy глубже: типы, IPVS, EndpointSlices
  27. Ingress, ingress-контроллеры и Gateway API
  28. Секреты в кластере: шифрование, External Secrets, Vault (вы здесь)
  29. Хранилище глубже: PV, StorageClass, CSI и StatefulSet
  30. Сеть кластера: CNI, NetworkPolicy и CoreDNS
  31. Планировщик: affinity, taints, topology spread
  32. Ресурсы, QoS и вытеснение: requests, limits, eviction
  33. Helm глубже: шаблоны, хуки, зависимости, OCI
  34. Kustomize и управление конфигурацией без шаблонов
  35. RBAC и аутентификация глубже
  36. Admission и Pod Security: контроль на входе
  37. Policy as code: Kyverno и OPA Gatekeeper
  38. GitOps: Argo CD и Flux
  39. Операторы и CRD: расширяем Kubernetes
  40. Сервис-меш: Istio, Linkerd и когда он нужен
  41. Эксплуатация кластера: апгрейд, узлы, бэкап etcd
  42. Где запускать кластер: managed, self-hosted, k3s, Deckhouse
  43. Траблшутинг кластера: Pending, CrashLoop, узлы NotReady
  44. Стоимость и эффективность кластера: FinOps
  45. CI/CD в Kubernetes: сборка, деплой, прогрессивные релизы
  46. Лучшие практики и антипаттерны Kubernetes
  47. Сквозной проект и путь дальше: от манифеста до прода
  48. Деплой приложений через Argo CD: Application, sync и App-of-Apps
Ты задеплоил приложение, прокинул пароль от базы через объект Secret и спишь спокойно. А зря. Открой консоль и сделай одну команду - ты увидишь свой пароль в открытом виде за две секунды. В этом уроке разбираемся, почему kubernetes secret сам по себе НЕ защита, как включить настоящее шифрование секретов kubernetes, как подтянуть пароли из внешних хранилищ через external secrets, sealed secrets и связку vault kubernetes, и как с этим всем жить в GitOps без утечек.

Главное заблуждение: Secret в Kubernetes не зашифрован

Самая дорогая ошибка новичка - думать, что объект Secret что-то скрывает. Он не скрывает. Он кодирует. base64 - это не шифрование, это просто другой способ записать те же байты, обратимый одной командой без всякого ключа. Давай посмотрим живьём. Создаём секрет:

Код: Выделить всё

kubectl create secret generic db-creds \
  --from-literal=username=app \
  --from-literal=password='S3cr3t!Pass'
Теперь достаём его наружу:

Код: Выделить всё

kubectl get secret db-creds -o jsonpath='{.data.password}'
UzNjcjN0IVBhc3M=
Выглядит зашифрованно, да? А теперь:

Код: Выделить всё

kubectl get secret db-creds -o jsonpath='{.data.password}' | base64 -d
S3cr3t!Pass
Вот и весь пароль. Любой, у кого есть доступ к чтению секретов в этом namespace, получает их открытым текстом. Более того - по умолчанию данные Secret лежат в etcd (хранилище состояния кластера) ровно так же, в base64, БЕЗ шифрования. Кто получил дамп etcd или бэкап диска control-plane - получил все пароли, токены и приватные ключи кластера разом.

Чем Secret реально отличается от ConfigMap, если оба читаются легко? Отличий три, и они не про криптографию. Первое: Secret хранится в API-сервере и kubelet в памяти типа tmpfs (в RAM, не на диске ноды), а не в обычном файле. Второе: значения не светятся в kubectl describe и в логах вывода. Третье: на Secret отдельно навешивается RBAC и можно включить шифрование at-rest. То есть Secret - это правильное МЕСТО для пароля и набор крючков для защиты, но защиту надо включить руками. Из коробки её нет.

Изображение

Шифрование секретов Kubernetes at-rest: EncryptionConfiguration и KMS

Первый рубеж обороны - шифрование в etcd. Делается это через EncryptionConfiguration - файл, который ты передаёшь kube-apiserver флагом --encryption-provider-config. Управляемые кластеры (Yandex Managed Kubernetes, VK Cloud, EKS, GKE) обычно дают эту опцию в настройках, в self-hosted и k3s ты правишь манифест apiserver сам.

Минимальный конфиг с локальным ключом выглядит так:

Код: Выделить всё

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <BASE64_32_BYTE_KEY>
      - identity: {}
Разбор по полям. resources перечисляет, что шифруем - тут только secrets, можно добавить configmaps. providers - список В ПОРЯДКЕ ПРИОРИТЕТА. Первый провайдер используется для ЗАПИСИ, читать API-сервер умеет любым из перечисленных. identity - это no-op, открытый текст. Ключевой момент: identity стоит ПОСЛЕДНИМ. Если бы он стоял первым, всё писалось бы открытым текстом, а это типичная грабля. По провайдерам: aesgcm - быстрый и современный, но требует строгой ротации ключей; aescbc - распространённый дефолт; secretbox (XSalsa20+Poly1305) - тоже годится. Но самый правильный продакшен-вариант - kms.

Почему kms лучше локального ключа? При aescbc/aesgcm ключ шифрования лежит на диске control-plane рядом с тем, что он шифрует. Украл диск - украл и замок, и ключ. KMS-провайдер выносит мастер-ключ во внешнюю систему (облачный KMS, Vault через плагин), а в etcd попадает только зашифрованный data encryption key. На 2026 актуален KMS v2: начиная с Kubernetes 1.29 KMS v1 выключен по умолчанию (deprecated с 1.28), и включать его обратно флагом --feature-gates=KMSv1=true без причины не стоит. KMS v2 заметно производительнее: он генерирует DEK через KDF из seed плюс случайные данные, не дёргая внешний KMS на каждый секрет.

Важнейшая деталь, на которой все спотыкаются: включение шифрования НЕ перешифровывает то, что уже лежит в etcd. Старые секреты остаются открытыми, пока их не перезапишут. После настройки конфига обязательно прогони перешифровку:

Код: Выделить всё

kubectl get secrets --all-namespaces -o json | kubectl replace -f -
Эта команда читает все секреты и записывает их обратно - на записи они шифруются текущим первым провайдером. Проверить, что в etcd реально лежит шифротекст, можно так (на ноде control-plane):

Код: Выделить всё

ETCDCTL_API=3 etcdctl get /registry/secrets/default/db-creds | hexdump -C
Если в начале значения видишь k8s:enc:aescbc:v1: или k8s:enc:kms:v2: - шифрование работает. Если читается открытый username=app - нет.

RBAC на секреты и дефолтный токен ServiceAccount

Шифрование at-rest спасает от кражи диска, но не от того, кто имеет права в кластере. Второй рубеж - RBAC. Право get/list/watch на secrets надо раздавать точечно. Особенно опасен list по всему кластеру: он отдаёт значения всех секретов разом. Правило простое - давай доступ к конкретным секретам по имени через resourceNames, а не к ресурсу secrets целиком:

Код: Выделить всё

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: read-db-creds
  namespace: prod
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["db-creds"]
    verbs: ["get"]
Отдельная мина - токен ServiceAccount. Если под не общается с API Kubernetes, токен ему не нужен, а смонтированный токен - это лишняя дверь для злоумышленника, пробравшегося в контейнер. Отключай монтирование явно:

Код: Выделить всё

apiVersion: v1
kind: ServiceAccount
metadata:
  name: web
automountServiceAccountToken: false
То же поле можно ставить и в spec пода - настройка на уровне пода перекрывает настройку SA. С версии 1.24 Kubernetes больше не плодит вечные токены-секреты под каждый SA автоматически; токены теперь короткоживущие, проецируются через TokenRequest API и ротируются. Если тебе всё же нужен долгоживущий токен (например, для внешней CI), его создают явным секретом с аннотацией kubernetes.io/service-account.name - но это осознанный выбор, а не дефолт.

External Secrets: как не держать пароли в кластере вообще

Шифрование и RBAC закрывают дыры, но не решают корневую проблему: секрет всё равно живёт в кластере, его надо где-то хранить, ротировать, не закоммитить в git. Зрелый подход - вынести источник правды наружу, в специализированное хранилище (HashiCorp Vault, Yandex Lockbox, AWS Secrets Manager, GCP Secret Manager), а в кластер их только синхронизировать. Здесь на сцену выходит External Secrets Operator (ESO).

Идея ESO: ты описываешь в git не сам секрет, а ССЫЛКУ на него во внешнем хранилище. Оператор читает эту ссылку, ходит в хранилище, забирает значение и САМ создаёт обычный Kubernetes Secret в кластере. В git при этом нет ни одного пароля - только адрес. Две основные сущности (на 2026 API стабилизировался до external-secrets.io/v1, v1beta1 объявлен устаревшим и в свежих релизах удалён - смотри версию своего оператора):

SecretStore описывает, КУДА и КАК ходить (адрес Vault, способ аутентификации). ExternalSecret описывает, ЧТО забрать и в какой Secret положить. Пример связки с Vault через Kubernetes-аутентификацию:

Код: Выделить всё

apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: prod
spec:
  provider:
    vault:
      server: "https://vault.internal:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "prod-app"
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: db-creds
  namespace: prod
spec:
  refreshInterval: "1h"
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: db-creds
    creationPolicy: Owner
  data:
    - secretKey: password
      remoteRef:
        key: prod/database
        property: password
Разбор. refreshInterval: 1h - оператор раз в час перечитывает Vault; меняешь значение в Vault - через час оно само приедет в кластер, это и есть встроенная ротация. secretStoreRef связывает с хранилищем. target.name - имя итогового Secret, creationPolicy: Owner значит, что Secret полностью управляется оператором (правишь руками - перезатрёт). remoteRef.key - путь в Vault, property - конкретное поле. Проверяем статус:

Код: Выделить всё

kubectl get externalsecret db-creds -n prod
NAME       STORE           REFRESH   STATUS         READY
db-creds   vault-backend   1h        SecretSynced   True
READY True и STATUS SecretSynced означают, что Secret создан. Если видишь SecretSyncedError - смотри kubectl describe externalsecret, обычно это неверный путь, недостаточные права в Vault или сломанная аутентификация. ESO одинаково умеет ходить в Yandex Lockbox и другие облачные хранилища - меняется только блок provider в SecretStore.

Sealed Secrets и SOPS: секреты прямо в git, но безопасно

ESO требует внешнее хранилище. А если его нет и хочется GitOps, чтобы вообще всё лежало в репозитории? Тогда секрет надо положить в git ЗАШИФРОВАННЫМ так, чтобы расшифровать его мог только кластер. Два рабочих инструмента.

Sealed Secrets (от Bitnami). В кластере живёт контроллер с приватным ключом. Ты утилитой kubeseal шифруешь свой Secret публичным ключом - получается ресурс SealedSecret, который безопасно коммитить: расшифровать его может ТОЛЬКО этот контроллер этим приватным ключом, больше никто.

Код: Выделить всё

kubectl create secret generic db-creds \
  --from-literal=password='S3cr3t!Pass' \
  --dry-run=client -o yaml \
  | kubeseal --format yaml > sealed-db-creds.yaml
На выходе - SealedSecret вида:

Код: Выделить всё

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-creds
  namespace: prod
spec:
  encryptedData:
    password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...
Этот encryptedData бесполезен без приватного ключа кластера, его не страшно класть в публичный репозиторий. Грабля: приватный ключ контроллера - твоё всё. Потерял его (пересоздал кластер, не сохранив) - все SealedSecret в git превращаются в мусор, расшифровать нечем. Бэкапь этот ключ отдельно и надёжно.

SOPS (Secrets OPerationS) от Mozilla - другой подход: шифрует только ЗНАЧЕНИЯ в YAML/JSON, оставляя ключи и структуру читаемыми, через age, PGP или облачный KMS. Удобно для код-ревью: видно, какие поля изменились, без раскрытия самих значений. В GitOps SOPS дружит с Flux (нативно) и с Argo CD (через плагин). Выбор между ними простой: Sealed Secrets - проще, заточен под Kubernetes Secret; SOPS - гибче, шифрует любые файлы и красиво ложится в diff.

CSI Secrets Store: монтировать, а не хранить

Есть и четвёртый путь - вообще не создавать объект Secret, а монтировать секрет из Vault прямо в файловую систему пода как том. Это Secrets Store CSI Driver плюс провайдер (Vault, облачные KMS). Под получает секрет файлом в /mnt/secrets, в etcd при этом не пишется ничего. Описывается через SecretProviderClass, а в поде объявляется CSI-том. Полезные опции драйвера: enableSecretRotation периодически опрашивает хранилище (по умолчанию раз в 2 минуты) и обновляет смонтированный том, а syncSecret при желании дополнительно зеркалит данные в обычный Kubernetes Secret. Краевой случай, про который забывают: приложение читает секреты ОДИН раз на старте. Ротация обновит файл, но процесс продолжит работать со старым значением в памяти - под обновлённые значения часто нужен rolling restart деплоймента.

Типичные грабли и антипаттерны
  • Секрет в git открытым текстом. Самая частая утечка. Закоммитил Secret с base64 в values.yaml - считай, выложил пароль. base64 не считается за защиту. Лечится Sealed Secrets/SOPS/ESO.
  • Секрет в образе или в ENV логах. ARG/ENV в Dockerfile с паролем остаётся в слоях образа навсегда. А printenv в стартовом скрипте или дамп окружения при краше утащит секрет в логи и в систему сбора логов.
  • Секрет через args, а не env/файл. Аргументы команды видны в ps и в kubectl describe pod. Передавай чувствительное через переменные окружения из секрета или через файл-том.
  • Дефолтный токен SA в каждом поде. Прокинутый токен default SA - готовый плацдарм для повышения привилегий из взломанного контейнера. Отключай automountServiceAccountToken там, где он не нужен.
  • Включили шифрование, не перешифровали старое. EncryptionConfiguration не трогает уже лежащие в etcd секреты. Без kubectl get secrets ... | kubectl replace -f - половина данных так и останется открытой.
  • identity первым провайдером. Перепутал порядок в providers - и всё пишется открытым текстом, хотя конфиг вроде есть.
Мини-лаба: пройди руками
  • Создай Secret через kubectl create secret generic и докажи себе, что это не шифрование: вытащи значение через base64 -d.
  • В тестовом self-hosted кластере или k3s включи EncryptionConfiguration с провайдером aescbc, прогони перешифровку через kubectl replace и проверь шифротекст в etcd через etcdctl + hexdump (ищи префикс k8s:enc:aescbc:v1:).
  • Поставь Sealed Secrets контроллер, через kubeseal заверни секрет в SealedSecret и убедись, что encryptedData нечитаем без кластера.
  • Разверни External Secrets Operator, подними dev-Vault, опиши SecretStore + ExternalSecret и проверь, что ESO сам создал Secret (kubectl get externalsecret, статус SecretSynced/Ready).
Контрольные вопросы
  • Почему объект Secret в Kubernetes по умолчанию нельзя считать защищённым и что конкретно происходит с его данными в etcd?
  • Что делает EncryptionConfiguration, почему порядок провайдеров критичен и почему после его включения обязательно перешифровать существующие секреты?
  • В чём разница подходов External Secrets Operator, Sealed Secrets/SOPS и CSI Secrets Store - и когда какой выбрать?
  • Зачем отключать automountServiceAccountToken и чем рискует под с лишним токеном SA?
Итог

Secret в Kubernetes - это удобное место и набор крючков, но не защита: base64 читается мгновенно, в etcd по умолчанию открытый текст. Настоящая безопасность собирается слоями: шифрование at-rest через EncryptionConfiguration/KMS, жёсткий RBAC по resourceNames, отключение лишних токенов SA, и вынос источника правды наружу - External Secrets из Vault/Lockbox, Sealed Secrets или SOPS для GitOps, CSI Secrets Store для монтирования без хранения. И помни про ротацию и rolling restart: положить секрет мало, его ещё надо вовремя менять.
👍5 ❤️1 🔥1 😄 🤔
Аватара пользователя
rust_main
Сообщения: 1
Зарегистрирован: 30 май 2026, 19:11

Re: Секреты в кластере: шифрование, External Secrets, Vault

Сообщение rust_main »

Блин, реально вытащил свой прод-пароль через base64 -d за пару секунд. Думал Secret это сейф, а это просто коробка с наклейкой. Срочно иду включать шифрование at-rest.
👍 ❤️1 🔥1 😄 🤔2
Аватара пользователя
rodwest
Сообщения: 1
Зарегистрирован: 28 май 2026, 03:48

Re: Секреты в кластере: шифрование, External Secrets, Vault

Сообщение rodwest »

Вопрос по ESO с creationPolicy Owner: если я по ошибке руками поправлю Secret, который синкается из Vault, оператор его сразу перезатрёт обратно или только на следующем refreshInterval?
👍2 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Ingress, ingress-контроллеры и Gateway API
Следующая глава →
Хранилище глубже: PV, StorageClass, CSI и StatefulSet

Все главы курса «Kubernetes: оркестрация контейнеров от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: minikube или kind что выбрать для локального кластера kuberneteskubectl get describe logs основные команды для работы с кластеромчто такое service в kubernetes и как поды находят друг другаnamespace в kubernetes зачем нужен и как разделить ресурсыhelm chart как установить приложение в kuberneteshelm глубже шаблоны хуки и зависимости чартов

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя