Шифрование данных в Kubernetes: секреты, etcd и CSI-драйверы. Практическое руководство | AdminWiki

Шифрование данных в Kubernetes: секреты, etcd и CSI-драйверы. Практическое руководство

17 сентября 2026 15 мин. чтения
Содержание статьи

Почему шифрование данных в Kubernetes - это не опция, а необходимость

Kubernetes по умолчанию хранит все объекты кластера, включая Secret, в etcd в кодировке base64 и без шифрования. Пароль от базы данных, токен API и приватный ключ TLS лежат в хранилище состояния как обычный текст: любой, кто получил доступ к etcd или к его снапшоту, читает их напрямую. Защита собирается из трех независимых слоев: шифрование данных в покое в etcd через EncryptionConfiguration, вынос секретов за пределы кластера (HashiCorp Vault, Sealed Secrets) и шифрование постоянных томов средствами CSI-драйвера на базе LUKS или облачного KMS.

Рабочий порядок действий такой: включить провайдер шифрования secretbox или kms для kube-apiserver, перезаписать существующие Secret, чтобы старые записи тоже закрылись криптографией, ограничить сетевой доступ к порту 2379, затем настроить управление секретами и шифрование томов. Ни один из шагов не требует простоя приложений, если заранее сделан бэкап etcd и не меняется содержимое Secret.

Ниже собраны проверенные команды и манифесты: версии API, флаги kube-apiserver, примеры StorageClass с шифрованием, интеграция Vault через sidecar и CSI-драйвер, а также перечень ошибок, которые чаще всего приводят к потере данных или к отказу API-сервера после перезапуска.

Требования регуляторов делают шифрование обязательным, а не желательным. PCI DSS требует защищать хранимые данные держателей карт, GDPR - применять соразмерные технические меры защиты персональных данных, ФЗ-152 - обеспечивать конфиденциальность при обработке. Проверка, которая проходит на аудите, выглядит одинаково: снапшот etcd и бэкап тома не содержат читаемых секретов. Общий подход к защите данных на разных уровнях разобран в материале про безопасность систем хранения и обработки данных.

Что и где хранится: etcd, секреты и постоянные тома

etcd - единственный источник истины о состоянии кластера. В нем лежат Pod, Deployment, ConfigMap, Secret, ServiceAccount, роли RBAC, Custom Resource и служебные объекты. Ключи хранятся в виде путей: например, Secret с именем mysecret в namespace default лежит по ключу /registry/secrets/default/mysecret. Значение самого объекта Secret - те же YAML-поля, только data закодирован в base64.

ДанныеГде лежатЗащита по умолчанию
Secret: пароли, токены, ключиetcd, ключ /registry/secrets/<namespace>/<name>base64, без шифрования
ConfigMap, манифесты, метаданныеetcdоткрытый текст
Данные приложенийлокальные диски узлов или внешняя СХД (PV)зависит от драйвера и файловой системы
Трафик между компонентамипорты 6443, 2379, 2380TLS или mTLS, если сертификаты настроены
Бэкапы etcdфайл снапшотанет, если шифровать отдельно

Трафик между kube-apiserver, kubelet и etcd обычно уже закрыт TLS. Соединения клиентов к API-серверу идут на 6443, клиентский трафик к etcd - на 2379, обмен между узлами etcd - на 2380. Шифрование при передаче защищает от перехвата в сети, но не влияет на то, как байты лежат на диске. Это два разных периметра, и закрывать их нужно по отдельности.

Модель угроз: доступ к etcd, бэкапы и перехват трафика

Практических векторов атак немного, и каждый закрывается конкретной мерой:

  • Прямой доступ к etcd. Незакрытый порт 2379 в сети или отсутствие клиентской аутентификации дает полный доступ ко всем секретам кластера. Лечится ограничением сети и обязательным mTLS, шифрование etcd здесь дополняет защиту.
  • Кража снапшота etcd. Файл бэкапа, сохраненный в S3 или на NFS без шифрования, содержит все секреты в открытом виде. Шифрование данных в покое делает снапшот бесполезным без ключа.
  • Физический или административный доступ к диску узла. Снятый диск или снимок виртуальной машины с etcd и томами приложений читается на любой другой машине.
  • Перехват трафика (MITM). Закрывается проверкой сертификатов, отказом от параметра --insecure-port и строгим TLS на всех компонентах.
  • Избыточные права внутри кластера. Пользователь с правом get secrets в широком namespace читает секреты легально, без взлома. Здесь работает RBAC и вынос секретов во внешнее хранилище.

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

Встроенное шифрование секретов и etcd: настройка EncryptionConfiguration

EncryptionConfiguration - это YAML-файл, который читает kube-apiserver. В нем перечислены провайдеры шифрования и ключи. Порядок провайдеров в списке определяет приоритет: первый используется для записи новых объектов, остальные - для чтения старых. Провайдер identity означает отсутствие шифрования; он должен идти последним, иначе данные продолжат писаться в открытом виде.

Выбор провайдера шифрования: aescbc, secretbox или KMS

Доступные провайдеры различаются стойкостью и моделью хранения ключа:

  • identity - шифрования нет, используется как fallback для чтения старых объектов.
  • aescbc - AES в режиме CBC с дополнением PKCS#7, ключ длиной 32 байта. Из-за отсутствия проверки целостности и особенностей дополнения для новых кластеров этот режим не рекомендуют.
  • secretbox - XSalsa20-Poly1305, аутентифицированное шифрование, ключ 32 байта. Оптимальный выбор для большинства кластеров: ключ лежит в файле на control-plane, криптография стойкая.
  • aesgcm - AES-GCM, ключ 32 байта. Быстрее secretbox, но требует ротации ключа примерно каждые 200 000 записей из-за риска повторного использования nonce.
  • kms - внешний сервис управления ключами (HashiCorp Vault Transit, облачный KMS). Ключ шифрования ключей не покидает KMS, в файле конфигурации хранится только endpoint. Используйте apiVersion v2: версия v1 объявлена устаревшей.

Провайдеры комбинируются. Типовая схема для продакшена: kms первым, secretbox вторым как локальный резерв, identity последним на время миграции. Когда все объекты перезаписаны, identity убирают.

Пошаговая настройка: генерация ключа, правка манифеста, проверка

Шаг 1. Сгенерируйте ключ длиной 32 байта и сохраните его в защищенном месте вне кластера:

head -c 32 /dev/urandom | base64

Шаг 2. Создайте файл /etc/kubernetes/enc/encryption-config.yaml на control-plane с правами 600 и владельцем root:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
    providers:
      - kms:
          apiVersion: v2
          name: vault-kms
          endpoint: unix:///var/run/kmsplugin/socket.sock
          timeout: 3s
      - secretbox:
          keys:
            - name: key1
              secret: <сгенерированный base64-ключ>
      - identity: {}

Шаг 3. Добавьте в /etc/kubernetes/manifests/kube-apiserver.yaml флаг и монтирование файла. Правка манифеста статического пода автоматически перезапустит kube-apiserver, поэтому работы планируйте на окно обслуживания.

spec:
  containers:
    - name: kube-apiserver
      command:
        - kube-apiserver
        - --encryption-provider-config=/etc/kubernetes/enc/encryption-config.yaml
        - --encryption-provider-config-automatic-reload=true
      volumeMounts:
        - name: enc
          mountPath: /etc/kubernetes/enc
          readOnly: true
  volumes:
    - name: enc
      hostPath:
        path: /etc/kubernetes/enc
        type: DirectoryOrCreate

Для кластеров, собранных через kubeadm, файл находится по этому же пути. Если control-plane несколько, повторите шаги на каждом узле: ключи должны совпадать, иначе объекты, записанные на одном узле, не расшифруются на другом.

Шаг 4. Убедитесь, что API-сервер поднялся: kubectl get pods -n kube-system и kubectl get --raw='/readyz?verbose'.

Шаг 5. Создайте тестовый секрет и прочитайте его напрямую из etcd:

kubectl create secret generic demo-secret --from-literal=password='SuperSecret123'

ETCDCTL_API=3 etcdctl \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  get /registry/secrets/default/demo-secret

В выводе перед полезной нагрузкой появится префикс вида k8s:enc:secretbox:v1:key1:. Если видите читаемый YAML с base64-значением, шифрование не применилось: проверьте порядок провайдеров, путь к файлу и логи kube-apiserver.

Шаг 6. Перезапишите все существующие секреты, потому что новые настройки действуют только на операции записи:

kubectl get secrets --all-namespaces -o json | kubectl replace -f -

Команда перезапишет объекты с новыми ревизиями, после чего kube-apiserver зашифрует их текущим ключом. Часть системных секретов может конфликтовать при replace: такие объекты пересоздаются их контроллерами, ошибки в выводе стоит разобрать отдельно.

Ротация ключей и миграция данных без простоя

Ключ меняется без остановки API. Добавьте новый ключ key2 в список keys первым, перезапустите kube-apiserver (через правку манифеста или перезагрузку конфигурации при включенном автоматическом перечитывании), выполните перезапись всех секретов повторно, затем удалите key1 из конфигурации. До удаления старого ключа убедитесь, что в etcd не осталось объектов с префиксом k8s:enc:secretbox:v1:key1: - иначе их чтение сломается.

Перед любой ротацией делайте свежий снапшот etcd и храните его вместе с копией ключа. Перезапись нескольких тысяч секретов дает заметную нагрузку на etcd: следите за метрикой etcd_disk_backend_commit_duration_seconds и разносите операцию по namespace.

Внешние решения для управления секретами: HashiCorp Vault и Sealed Secrets

Встроенное шифрование закрывает данные на диске, но оставляет ключ на control-plane, не дает аудита обращений и не умеет выдавать короткоживущие учетные данные. Когда секретов много, они меняются каждые несколько часов или требуют разграничения доступа по командам, в игру вступают внешние хранилища. Подробный разбор всех вариантов с таблицей сравнения есть в руководстве про хранение паролей в Kubernetes: Secrets, External Secrets и Vault.

HashiCorp Vault: интеграция через Agent Injector и CSI

Vault хранит секреты вне etcd, ведет журнал аудита и умеет выдавать динамические учетные данные с TTL: например, логин и пароль к PostgreSQL, живущие 15 минут. Установка в кластер выполняется через Helm:

helm repo add hashicorp <официальный репозиторий HashiCorp>
helm install vault hashicorp/vault --namespace vault --create-namespace
vault auth enable kubernetes
vault write auth/kubernetes/config kubernetes_host="https://kubernetes.default.svc"

Первый способ доставки - Agent Injector. Он подставляет в Pod sidecar, который получает секрет из Vault и пишет его в общий emptyDir. Приложение читает файл, секрета в etcd нет вообще:

metadata:
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "app"
    vault.hashicorp.com/agent-inject-secret-db: "secret/data/app/db"
    vault.hashicorp.com/agent-inject-template-db: |
      {{- with secret "secret/data/app/db" -}}
      postgres://{{ .Data.data.user }}:{{ .Data.data.password }}@db:5432/app
      {{- end }}

Второй способ - Vault Secrets Store CSI Driver. Он монтирует секреты как файлы в том через драйвер secrets-store.csi.k8s.io и описывается объектом SecretProviderClass. Вариант удобен тем, что меняет только spec.volumes в Deployment, а не добавляет sidecar-контейнеры. Для синхронизации секретов Vault в обычные Secret (например, чтобы подхватывались через envFrom) применяют External Secrets Operator: он создает и обновляет Secret из внешнего хранилища по расписанию.

Sealed Secrets: шифрование секретов для GitOps

Sealed Secrets решает другую задачу: позволяет безопасно хранить секреты в Git. В кластере работает контроллер, который генерирует пару ключей RSA. Публичным ключом утилита kubeseal шифрует Secret, приватным контроллер расшифровывает его в кластере и создает обычный Secret.

kubectl create secret generic app-secret \
  --from-literal=password='S3cr3t' \
  --dry-run=client -o yaml | kubeseal --format yaml > sealedsecret.yaml

kubectl apply -f sealedsecret.yaml

Результат можно коммитить в Git: без приватного ключа контроллера расшифровать его нельзя. Ограничения: SealedSecret привязан к namespace и имени объекта, поэтому перенос между средами требует перешифрования; приватный ключ лежит в Secret sealed-secrets-key в kube-system, и его потеря означает невозможность расшифровать все ранее созданные SealedSecret. Резервную копию ключа храните в защищенном хранилище, а ротацию ключа контроллера планируйте вместе с перешифрованием всех манифестов. Базовая работа с самими объектами Secret, включая генераторы и проверку содержимого, разобрана в руководстве по Kubernetes Secrets.

Шифрование томов с помощью CSI-драйверов

Секреты в etcd - только часть картины. Данные приложений лежат на PersistentVolume, и снапшот такого тома читается как обычная файловая система. Container Storage Interface (CSI) стандартизирует подключение хранилищ, а часть драйверов умеет шифровать тома прозрачно для приложения: на уровне хранилища или через dm-crypt на узле.

Обзор CSI-драйверов с поддержкой шифрования

  • AWS EBS CSI - шифрование через KMS, для новых томов включено по умолчанию, ключ задается параметром StorageClass.
  • GCP Persistent Disk CSI - шифрование ключами CMEK, ключ указывается при создании диска.
  • Azure Disk CSI - шифрование ключами из Key Vault, поддерживается смена ключа без пересоздания тома.
  • Longhorn - шифрование томов через LUKS/dm-crypt, требуется пакет cryptsetup на узлах и ключ в Secret.
  • Rook Ceph - шифрование OSD через dm-crypt, включается в манифесте CephCluster параметром encrypted для устройств.
  • Локальные CSI-драйверы (например, для прямых дисков) - шифрование чаще делают на уровне хоста через LUKS или mdadm с crypt, а драйвер лишь предоставляет устройство.

Проверяйте документацию конкретного драйвера перед выбором: не все поддерживают шифрование, а часть облачных решений шифрует только данные на диске, но не трафик между узлом и СХД. Общая логика подключения хранилищ, типы томов и динамический провижионинг описаны в статье про интеграцию СХД с Kubernetes через CSI и Persistent Volumes.

Настройка StorageClass с шифрованием: пример для Longhorn

Сначала создайте Secret с парольной фразой шифрования в namespace Longhorn, затем StorageClass со ссылкой на него. Ключ передается как параметр csi.storage.k8s.io/node-publish-secret-name:

apiVersion: v1
kind: Secret
metadata:
  name: longhorn-crypto
  namespace: longhorn-system
stringData:
  CRYPTO_KEY_VALUE: "замените-на-длинную-парольную-фразу"
  CRYPTO_KEY_PROVIDER: "secret"
  CRYPTO_KEY_CIPHER: "aes-xts-plain64"
  CRYPTO_KEY_HASH: "sha256"
  CRYPTO_KEY_SIZE: "256"
  CRYPTO_PBKDF: "argon2i"
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: longhorn-encrypted
provisioner: driver.longhorn.io
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
  numberOfReplicas: "3"
  staleReplicaTimeout: "30"
  encrypted: "true"
  csi.storage.k8s.io/node-publish-secret-name: longhorn-crypto
  csi.storage.k8s.io/node-publish-secret-namespace: longhorn-system

Дальше создается PVC со ссылкой на этот класс:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-data
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: longhorn-encrypted
  resources:
    requests:
      storage: 10Gi

Проверка: в объекте тома Longhorn должно быть encrypted: true, а на узле, где том подключен, команда dmsetup ls --target crypt покажет устройство с типом crypt. Если устройства нет, проверьте, установлен ли cryptsetup на каждом узле и включено ли шифрование в настройках драйвера. Парольную фразу храните вне кластера: при ее потере данные тома не восстановит ни один бэкап.

Пошаговый план включения шифрования в работающем кластере

Последовательность, проверенная на боевых кластерах: оценить текущее состояние, сделать бэкап etcd, включить шифрование данных в покое, вынести секреты во внешнее хранилище, закрыть тома, проверить приложения, настроить мониторинг, провести ротацию ключей. Изменения лучше сначала прогнать на staging с копией манифестов, а на прод переносить после успешной проверки восстановления из снапшота.

Проверка текущего состояния и бэкап etcd

Сначала выясните, включено ли шифрование. Признаки простые: наличие флага --encryption-provider-config в манифесте kube-apiserver и префикс k8s:enc: в значениях объектов etcd.

kubectl -n kube-system get pod kube-apiserver-<node> -o yaml | grep encryption

ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%F-%H%M).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-2026-09-17-1200.db --write-out=table

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

Тестирование и мониторинг после включения

После применения настроек проверьте четыре вещи: kube-apiserver запущен и готов, тестовый Secret читается приложением, значение в etcd имеет префикс k8s:enc:, старые секреты перезаписаны. Для контроля состояния держите в мониторинге метрики API-сервера apiserver_encryption_config_controller_automatic_reload_success_total и apiserver_encryption_config_controller_automatic_reload_failure_total: рост второй метрики означает, что конфигурация не перечиталась и новые данные пишутся старым ключом. Ошибки расшифровки видны в логах kube-apiserver по ключевым словам failed to decrypt и decrypt: проверьте их после ротации ключей. Алерт стоит ставить на любую ошибку шифрования, а не на порог: такие сбои быстро приводят к недоступности объектов.

Типичные ошибки и как их избежать

  • Потеря ключа шифрования. Без ключа данные в etcd не читаются, восстановление возможно только из снапшота, сделанного до шифрования. Храните ключи в Vault, HSM или защищенном сейфе, отдельно от бэкапов.
  • identity первым в списке провайдеров. Новые объекты снова пишутся в открытом виде, шифрование не работает при формально примененной конфигурации.
  • Отсутствие перезаписи секретов. Старые записи остаются незашифрованными, аудит покажет расхождение.
  • Нет бэкапа before изменений. Любая правка kube-apiserver без снапшота - риск потерять кластер из-за опечатки в YAML.
  • Права на файл конфигурации. Файл с ключом должен быть доступен только root, иначе ключ читается любым пользователем узла.
  • Несовпадение ключей между control-plane узлами. Объекты, записанные на одном узле, не расшифровываются на другом.
  • Игнорирование ротации. Ключ, живущий годами без смены, повышает цену его компрометации.

Потеря ключей шифрования: как не остаться без данных

При утере ключа объекты в etcd остаются физически целыми, но бесполезными: API-сервер вернет ошибку расшифровки при чтении. Единственный путь восстановления - снапшот etcd, снятый до включения шифрования, либо копия ключа. Отсюда правило: бэкап etcd и ключ хранятся вместе как единый набор восстановления, но в разных системах доступа. При использовании провайдера kms ключ шифрования ключей находится во внешней системе, и потеря локального файла конфигурации не приводит к потере данных. Для парольных фраз томов действует та же логика: утеря CRYPTO_KEY_VALUE делает том нечитаемым.

Несовместимость версий и форматов конфигурации

Формат EncryptionConfiguration версионируется отдельно от Kubernetes. Актуальная версия apiserver.config.k8s.io/v1 работает начиная с Kubernetes 1.13, более старые beta-версии удалены, поэтому в манифестах старых кластеров их нужно заменить до обновления. Провайдер kms v2 стабилен с версии 1.29, а kms v1 объявлен устаревшим: при апгрейде кластера переносите конфигурацию на v2, иначе получите предупреждения и возможную остановку расшифровки. Перед обновлением Kubernetes проверьте три вещи: версию API в файле конфигурации, наличие плюгина KMS на узлах и совместимость CSI-драйвера с новой версией. Для CSI ориентируйтесь на матрицу поддержки драйвера и спецификацию CSI, которую он реализует.

Сравнение подходов и выбор оптимального стека

ПодходСложностьУровень защитыГде ключGitOps
EncryptionConfiguration (secretbox)НизкаяСреднийФайл на control-planeНет
Провайдер kms (Vault, облачный KMS)СредняяВысокийВо внешней системеНет
Sealed SecretsНизкаяСреднийПриватный ключ контроллераДа
External Secrets Operator + VaultВысокаяВысокийВ VaultДа, без самих секретов
CSI-шифрование томовЗависит от драйвераВысокий для данных на дискеKMS или парольная фразаНет

Критерии выбора: безопасность, сложность, производительность

Оценивайте четыре параметра. Первый - от кого защищаем: от кражи диска и бэкапа достаточно шифрования etcd и томов, от внутреннего нарушителя с правами в кластере нужно внешнее хранилище с аудитом. Второй - стоимость поддержки: Vault требует резервирования, обновлений и распечатывания unseal-ключей, Sealed Secrets обслуживается самим контроллером. Третий - производительность: secretbox и LUKS добавляют единицы процентов накладных расходов на CPU узлов, облачные KMS добавляют сетевой вызов при каждой операции записи секрета, поэтому для kms-провайдера важен кэш DEK в v2. Четвертый - требования compliance к ротации ключей и журналу доступа.

Рекомендации для разных сценариев

Для небольшого кластера или dev-среды хватит встроенного шифрования secretbox и Sealed Secrets для GitOps: настройка занимает час, ключи лежат в двух местах, аудит минимальный.

Для enterprise-контура разумный стек выглядит так: kms-провайдер v2 на базе Vault или облачного KMS, External Secrets Operator для доставки секретов, CSI-шифрование для томов с персональными данными, отдельный контур управления ключами и алерты на ошибки расшифровки.

Для облачных кластеров проще опираться на управляемый KMS провайдера и шифрование томов, включенное по умолчанию в StorageClass, а собственное шифрование etcd оставить как дополнительный слой. Если нужен кластер с управляемой control-plane и встроенной интеграцией с хранилищем, подойдет Timeweb Cloud: там доступны серверы, хранилище и Kubernetes, а ключи и тома удобно разделять между средами.

Практический шаг, который можно сделать сегодня: снять снапшот etcd, проверить наличие флага --encryption-provider-config, прогнать перезапись секретов в staging и убедиться, что в дампе etcd вместо пароля стоит префикс k8s:enc:. Дальше по списку - вынос секретов в Vault или Sealed Secrets и шифрование томов с данными, которые нельзя потерять.

Поделиться:
Сохранить гайд? В закладки браузера