SealedSecrets: храним зашифрованные kubeconfigs в git

Published: 2026-02-17

Каждый kubeconfig, учётные данные registry и TLS-сертификаты кластера живут в git — в зашифрованном виде. Это возможно благодаря SealedSecrets от Bitnami и асимметричному шифрованию: публичный ключ в репозитории, приватный — только на hub-кластере. Vault для этого паттерна не нужен, как и подстановка секретов через CI/CD.


Как это работает

plaintext Secret
       │
       ▼  kubeseal + pub cert
  SealedSecret (зашифрован, безопасно коммитить)
       │
       ▼  git push → Flux → hub cluster
  SealedSecret CRD применён к hub
       │
       ▼  sealed-secrets-controller расшифровывает приватным ключом
  обычный Secret в кластере

Контроллер работает в flux-system на hub-кластере. Он расшифровывает каждый SealedSecret и создаёт из него обычный Kubernetes Secret. Приватный ключ никто не хранит локально.

Почему это безопасно

Поле encryptedData в SealedSecret зашифровано связкой RSA-4096/OAEP + AES-256-GCM. Даже если кто-то клонирует GitOps-репозиторий, без приватного ключа контроллера расшифровать секреты не получится. А приватный ключ никогда не покидает кластер.


Установка SealedSecrets

yamlapiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: sealed-secrets
  namespace: flux-system
spec:
  chart:
    spec:
      chart: sealed-secrets
      version: "2.*"
      sourceRef:
        kind: HelmRepository
        name: sealed-secrets
        namespace: flux-system
  values:
    fullnameOverride: sealed-secrets-controller

Запечатывание нового секрета

bash# 1. Записать секрет в открытом виде во временный файл (НЕ КОММИТИТЬ)
cat > /tmp/my-secret.yaml <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
  namespace: dev
type: Opaque
stringData:
  password: "hunter2"
EOF

# 2. Запечатать (kubeseal получит публичный сертификат с hub-кластера)
kubeseal \
  --format yaml \
  --controller-namespace flux-system \
  --kubeconfig .kubeconfigs/infra.yaml \
  < /tmp/my-secret.yaml \
  > fluxcd/projects/dev/kustomization/spoke/secrets/my-secret.yaml

# 3. Удалить файл с открытым секретом
rm /tmp/my-secret.yaml

# 4. Добавить в kustomization.yaml и закоммитить

SealedSecret привязан к namespace — данные, зашифрованные для namespace dev, нельзя расшифровать в другом namespace. Если секрет нужен в нескольких namespace, запечатайте его отдельно для каждого.

Запечатывание с локальным сертификатом (офлайн)

bash# Получить и сохранить сертификат
kubeseal \
  --fetch-cert \
  --controller-namespace flux-system \
  --kubeconfig .kubeconfigs/infra.yaml \
  > .kubeconfigs/sealed-secrets-infra.pem

# Запечатать офлайн, используя сохранённый сертификат
kubeseal \
  --format yaml \
  --cert .kubeconfigs/sealed-secrets-infra.pem \
  < /tmp/my-secret.yaml \
  > sealed-my-secret.yaml

Файл .pem можно хранить в репозитории — он не секретный. Запечатывать может кто угодно, расшифровывает только контроллер кластера.


Обновление существующего секрета

SealedSecret нельзя обновить частично — только перезапечатать целиком:

bashkubeseal \
  --format yaml \
  --controller-namespace flux-system \
  --kubeconfig .kubeconfigs/infra.yaml \
  < /tmp/new-values.yaml \
  > fluxcd/projects/dev/kustomization/spoke/secrets/my-secret.yaml

Новый encryptedData заменяет старый. Контроллер обнаружит изменение и пересоздаст обычный Secret.

Антипаттерн — править поле encryptedData вручную. Шифротекст не делится по ключам: при изменении любого значения меняется весь блок данных. Просто перезапечатайте секрет.


Где живут секреты в репозитории

Что Путь
Spoke kubeconfigs fluxcd/{env}-kustomization.yaml — встроенный SealedSecret
Docker registry pull secrets fluxcd/projects/{env}/kustomization/spoke/secrets/
TLS-сертификаты fluxcd/projects/{env}/kustomization/spoke/secrets/
Vault token для Terraform fluxcd/projects/infra/kustomization/terraform/vault-token-sealed.yaml

Spoke kubeconfigs встроены в env-kustomization

Самые важные секреты — kubeconfig spoke-кластеров. Каждый встроен прямо в fluxcd/{env}-kustomization.yaml вместе с Namespace и Flux Kustomization:

yaml# fluxcd/dev-kustomization.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: dev
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: dev-kubeconfig
  namespace: dev
spec:
  encryptedData:
    value: AgB3eVxk...очень-длинный-base64...
  template:
    metadata:
      name: dev-kubeconfig
      namespace: dev
    type: Opaque
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: dev-apps
  namespace: dev
spec:
  path: ./fluxcd/projects/dev/kustomization/hub/
  kubeConfig:
    secretRef:
      name: dev-kubeconfig

Namespace, SealedSecret и ссылающаяся на него Kustomization — в одном файле. Новый spoke-кластер добавляется так: cp dev-kustomization.yaml staging-kustomization.yaml плюс запечатать kubeconfig для staging.


Ротация ключей контроллера

По умолчанию sealed-secrets-controller ротирует ключ каждые 30 дней. Старые ключи сохраняются, поэтому существующие SealedSecrets продолжают расшифровываться.

Но если удалить контроллер и переустановить, старые приватные ключи пропадут — и расшифровать существующие SealedSecrets будет нечем. Всегда делайте резервную копию ключей контроллера:

bashkubectl get secret \
  -n flux-system \
  -l sealedsecrets.bitnami.com/sealed-secrets-key \
  -o yaml > sealed-secrets-keys-backup.yaml

Для восстановления:

bashkubectl apply -f sealed-secrets-keys-backup.yaml
kubectl rollout restart deployment/sealed-secrets-controller -n flux-system

Миграция кластера

При переезде на новое железо приватный ключ меняется, и каждый SealedSecret нужно перезапечатать:

bash# 1. Получить сертификат нового кластера
kubeseal --fetch-cert \
  --kubeconfig new-cluster.yaml \
  --controller-namespace flux-system \
  > sealed-secrets-new.pem

# 2. Перезапечатать каждый секрет
kubeseal --cert sealed-secrets-new.pem --format yaml < original-secret.yaml > new-sealed-secret.yaml

Альтернатива: скопировать ключи контроллера из старого кластера в новый до пересоздания секретов.


Устранение ошибок расшифровки

bash# Логи контроллера
kubectl logs \
  -n flux-system \
  -l app.kubernetes.io/name=sealed-secrets-controller \
  --context=infra-k8s

# Список всех SealedSecret
kubectl get sealedsecret -A

# Статус конкретного
kubectl describe sealedsecret my-secret -n dev

Частые сообщения об ошибках:

  • no key could decrypt secret — секрет запечатан другим сертификатом; перезапечатайте текущим
  • failed to decrypt — не совпадает namespace; перезапечатайте для правильного namespace
  • controller not found — неверный --controller-namespace; имя контроллера должно совпадать с fullnameOverride

Если после обновления контроллер не может расшифровать ни один секрет — возможно, потеряны ключи. Проверить:

bashkubectl get secret -n flux-system -l sealedsecrets.bitnami.com/sealed-secrets-key