Learning Platform
Глоссарий Troubleshooting
Урок 07.03 · 22 мин
Продвинутый
Secretbase64encryption at restetcdKMSSecret typeskubernetes.io/tls

Secrets: типы, base64 vs encryption, etcd encryption at rest

Secret в Kubernetes выглядит как ConfigMap — те же поля, те же способы подключения к Pod. Но это иллюзия безопасности. Внутри — несколько важных отличий, и одно главное правило: base64 это НЕ шифрование. Понимать это — обязательно, чтобы не сделать кластер дырявым.


Docker Compose secrets и configs: безопасная передача паролей

Структура Secret

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: production
type: Opaque
data:
  db_password: c3VwZXJzZWNyZXQxMjM=
  api_key: YWJjZGVmZ2hpams=
stringData:
  jwt_secret: "plain-text-here"

Поля:

  • type — категория Secret. Default Opaque. Влияет на ожидаемые ключи (например, kubernetes.io/tls требует tls.crt и tls.key).
  • data — словарь string → base64. Каждое значение должно быть валидным base64 (echo -n "supersecret123" | base64).
  • stringData — словарь string → plain text. API server автоматически кодирует в base64 и кладёт в data при создании. Удобно для написания YAML вручную. После apply stringData исчезает — остаётся только data.

При коллизии (ключ есть и в data, и в stringData) — stringData побеждает. API server конвертирует stringData → data, перетирая.


БАЗА: base64 это не encryption

data:
  db_password: c3VwZXJzZWNyZXQxMjM=

c3VwZXJzZWNyZXQxMjM= выглядит как зашифрованный текст. Это не зашифрованный текст. Это просто кодирование (encoding), которое любой человек, любая программа, любой пайплайн декодирует за миллисекунду:

echo "c3VwZXJzZWNyZXQxMjM=" | base64 -d
# supersecret123

base64 — это alphabet-to-bytes representation. Нужно для двух целей в Kubernetes Secrets:

  1. Поддержка binary content в YAML (бинарь не валидный UTF-8, его нельзя положить в YAML напрямую)
  2. Совместимость с системами, которые не толерируют управляющие символы (newlines, null bytes) в значениях

base64 не защищает данные. Любой, кто получил YAML файл или сделал kubectl get secret, легко его декодирует.

DANGER

“У нас Secret, значит безопасно” — самая частая ошибка джунов. Если у пользователя есть RBAC-доступ get secrets в namespace — он видит данные в открытом виде после base64 decode (это тривиальная операция). Secret защищён ровно настолько, насколько защищён RBAC и encryption at rest. base64 — никакая не защита.


etcd encryption at rest

Secret хранится в etcd. По умолчанию etcd хранит plaintext (после base64 decode):

# На master-node
ETCDCTL_API=3 etcdctl get /registry/secrets/default/app-secrets
# /registry/secrets/default/app-secrets
# k8s
# ...
# db_password\x12supersecret123      ← в открытом виде

То есть кто угодно с доступом к etcd (root на master node, backup etcd, copy etcd.db) видит Secret-значения как plaintext. Это серьёзная проблема для compliance (PCI-DSS, HIPAA, SOC2).

Решение — encryption at rest: настроить kube-apiserver шифровать Secret-данные перед записью в etcd, и расшифровывать при чтении. etcd хранит ciphertext, не plaintext.

Конфигурация на kube-apiserver

# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded 32-byte key>
      - identity: {}

И флаг kube-apiserver: --encryption-provider-config=/etc/kubernetes/encryption-config.yaml.

Provider-ы

  • identity — нет шифрования (plaintext, default если encryption-config не настроен). Используется как fallback в списке провайдеров.
  • aescbc — AES-CBC с PKCS#7 padding. 32-байтный ключ. Быстрый, надёжный. Рекомендуется для большинства case-ов.
  • aesgcm — AES-GCM. Authenticated encryption. Требует ротации ключей каждые ~200 000 записей (иначе risk nonce collision).
  • secretbox — XSalsa20+Poly1305. Modern, secure. Быстрее aescbc для маленьких данных.
  • kms — внешний KMS (AWS KMS, GCP KMS, HashiCorp Vault, Azure Key Vault). DEK envelope encryption: данные шифруются локальным DEK, DEK шифруется master key в KMS. Production-grade решение для highly regulated сред.

Порядок провайдеров

Список провайдеров читается по порядку:

  • Чтение: пробуется каждый провайдер до тех пор, пока один не расшифрует.
  • Запись: используется первый провайдер.

Это позволяет ротацию: добавить новый провайдер первым, потом перешифровать существующие Secret-ы командой kubectl get secrets -A -o json | kubectl replace -f -.

Secret write path с encryption at rest
kubectl applyКлиент отправляет Secret YAML. Данные base64-encoded в data полях, передаются по TLS (если cluster настроен с TLS, что обязательно).
POST /api/v1/secrets
kube-apiserverПроверяет authentication, authorization, admission. Затем — если encryption-provider-config есть — шифрует data поля первым активным провайдером перед записью.
encrypted bytes
etcdХранит зашифрованные данные. Префикс ciphertext указывает провайдера: k8s:enc:aescbc:v1:key1:<ciphertext>. Если кто-то снимет etcd backup, plaintext недоступен без encryption key.
kubectl get secret
kube-apiserver (read)При чтении API server извлекает данные из etcd, видит префикс провайдера, расшифровывает соответствующим провайдером, возвращает клиенту base64-encoded plain data.
WARNING

Если encryption-config настроен, но до этого в кластере уже были созданы Secrets — они хранятся в etcd как plaintext. Они начнут шифроваться только при следующем write. Чтобы перешифровать все существующие Secret-ы, нужно kubectl get secrets --all-namespaces -o json | kubectl replace -f - (forces rewrite). Без этого старые Secret-ы остаются в plaintext.


Встроенные типы Secret

Поле type в Secret — не просто метка. Оно сообщает controller-ам и kubelet о назначении и ожидаемой структуре. Семь built-in типов:

1. Opaque (default)

Generic Secret. Никаких ограничений на ключи и значения.

apiVersion: v1
kind: Secret
metadata: { name: app-secrets }
type: Opaque
data:
  db_password: ...
  api_key: ...

2. kubernetes.io/service-account-token

Auto-managed. Привязан к ServiceAccount, содержит JWT-токен для аутентификации Pod в API server.

type: kubernetes.io/service-account-token
metadata:
  annotations:
    kubernetes.io/service-account.name: my-sa

С Kubernetes v1.24+ эти Secret-ы не создаются автоматически при создании ServiceAccount (раньше создавались). Теперь токен генерируется ad-hoc через TokenRequest API и mount-ится через projected volume. Это безопаснее: токены короткоживущие и автоматически ротируются.

Если очень нужен long-lived токен (для CI/CD, например) — можно создать вручную с этой аннотацией. API server сам сгенерирует и положит JWT в data поле.

3. kubernetes.io/dockerconfigjson

Для imagePullSecrets — авторизация в private registries.

type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: <base64 of ~/.docker/config.json>

Создаётся через kubectl create secret docker-registry:

kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=user \
  --docker-password=pass \
  [email protected]

4. kubernetes.io/tls

Для TLS-сертификатов. Обязательные ключи: tls.crt (сертификат + chain) и tls.key (private key).

type: kubernetes.io/tls
data:
  tls.crt: <base64 cert>
  tls.key: <base64 key>

Создаётся через kubectl create secret tls:

kubectl create secret tls web-tls \
  --cert=./tls.crt \
  --key=./tls.key

Используется Ingress контроллерами (Ingress.spec.tls.secretName), service mesh-ами (Istio, Linkerd), webhook серверами.

5. kubernetes.io/basic-auth

Username + password в фиксированных ключах.

type: kubernetes.io/basic-auth
stringData:
  username: admin
  password: s3cret
kubectl create secret generic basic --type=kubernetes.io/basic-auth \
  --from-literal=username=admin --from-literal=password=s3cret

6. kubernetes.io/ssh-auth

SSH private key. Обязательный ключ ssh-privatekey.

type: kubernetes.io/ssh-auth
data:
  ssh-privatekey: <base64 of private key>

Используется GitOps-операторами (Flux, Argo CD) для git-доступа.

7. bootstrap.kubernetes.io/token

Для kubeadm bootstrap — токены, которые позволяют новым node присоединяться к кластеру.

type: bootstrap.kubernetes.io/token
metadata:
  name: bootstrap-token-abcdef
  namespace: kube-system
data:
  token-id: abcdef
  token-secret: ...

Кроме встроенных, можно создать любой пользовательский тип (example.com/my-type). Но без специальной обработки controller-ами он функционально равен Opaque.

TIP

Поле type нельзя изменить после создания. Если ошиблись — удалить и создать заново. Тип влияет на validation: например, kubernetes.io/tls без tls.crt/tls.key API server отклонит.


Создание Secret: imperative и declarative

Imperative (быстрее для CKAD)

# Generic (Opaque)
kubectl create secret generic app-secrets \
  --from-literal=db_password=supersecret123 \
  --from-literal=api_key=abcdef

# Из файла
kubectl create secret generic ssh-key --from-file=id_rsa

# TLS
kubectl create secret tls web-tls --cert=tls.crt --key=tls.key

# Docker registry
kubectl create secret docker-registry regcred \
  --docker-server=quay.io \
  --docker-username=$USER \
  --docker-password=$PASS \
  --docker-email=$EMAIL

# Basic-auth
kubectl create secret generic creds --type=kubernetes.io/basic-auth \
  --from-literal=username=admin --from-literal=password=s3cret

Declarative с stringData

apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
type: Opaque
stringData:
  db_password: supersecret123
  api_key: abcdef

stringData удобнее для ручного написания, чем data с base64. API server автоматически кодирует.


Лимит размера

Тот же что у ConfigMap — 1 MiB. Etcd лимит распространяется на все объекты. Для больших sensitive данных (большие certs, keystore-ы, encrypted blobs) — разбивать на несколько Secret-ов, либо использовать external secret store (Vault, AWS Secrets Manager).


Inspection

# Список
kubectl get secret

# Detail (значения скрыты)
kubectl describe secret app-secrets
# data:
# ====
# db_password: 14 bytes
# api_key:     6 bytes

# Получить значение (вернёт base64)
kubectl get secret app-secrets -o jsonpath='{.data.db_password}'
# c3VwZXJzZWNyZXQxMjM=

# Декодировать
kubectl get secret app-secrets -o jsonpath='{.data.db_password}' | base64 -d
# supersecret123

kubectl describe намеренно показывает только размер, не значение — чтобы случайно не утечь в логи или скриншоты.


Проверка знанийKnowledge check
Команда `kubectl get secret app-secrets -o yaml | grep db_password` показывает строку 'db_password: c3VwZXJzZWNyZXQxMjM='. Безопасно ли отдать этот YAML коллеге для review через email?
ОтветAnswer
Нет, абсолютно небезопасно. c3VwZXJzZWNyZXQxMjM= — это просто base64 кодирование строки 'supersecret123', а не шифрование. Любой получатель этого YAML декодирует его за миллисекунду командой `echo c3VwZXJzZWNyZXQxMjM= | base64 -d`. base64 в Kubernetes Secret используется для двух технических причин: поддержка binary content в YAML и совместимость с системами, которые не толерируют управляющие символы — а не для безопасности. Правильные подходы: 1) Зашифрованные Secret-ы в git через SOPS (Mozilla SOPS интегрируется с KMS), Sealed Secrets (Bitnami) или SealedSecrets controller; 2) External Secrets Operator с ссылками на Vault/AWS SM/GCP SM; 3) Если YAML обязательно нужен — отправлять только metadata и структуру, без data, используя `kubectl get secret -o yaml | yq 'del(.data)'`. Реальная защита Secret-ов в кластере — это encryption at rest (--encryption-provider-config на kube-apiserver), RBAC на get/list secrets, audit log.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Утверждение: Secret в Kubernetes шифрует sensitive данные через base64 encoding в поле data. Верно?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5