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; перезапечатайте для правильного namespacecontroller not found— неверный--controller-namespace; имя контроллера должно совпадать сfullnameOverride
Если после обновления контроллер не может расшифровать ни один секрет — возможно, потеряны ключи. Проверить:
bashkubectl get secret -n flux-system -l sealedsecrets.bitnami.com/sealed-secrets-key