Sealed Secrets
Purpose: For platform engineers, shows how to set up Sealed Secrets controller and use seal/unseal workflow as an alternative to SOPS.
What Sealed Secrets Does
Sealed Secrets encrypts Kubernetes Secret manifests so they can be safely stored in Git. It uses asymmetric encryption: a public key (available to anyone) encrypts secrets into SealedSecret resources, and a private key (held only by the controller running in the cluster) decrypts them. This is an alternative to SOPS for teams that prefer a Kubernetes-native encryption workflow.
openCenter uses SOPS as the primary secrets encryption method. Sealed Secrets is available as an optional alternative for clusters where SOPS is not suitable.
How It's Deployed
Sealed Secrets is deployed via FluxCD from openCenter-gitops-base:
openCenter-gitops-base/applications/base/services/sealed-secrets/
├── namespace.yaml
├── source.yaml
├── helmrelease.yaml
└── helm-values/
└── hardened-values.yaml
Customer overlay:
applications/overlays/<cluster>/services/sealed-secrets/
├── kustomization.yaml
└── override-values.yaml
How It Works
- The Sealed Secrets controller generates an RSA key pair on first startup. The private key stays in the cluster; the public key is exported for use by developers.
- Developers use the
kubesealCLI to encrypt a standard Kubernetes Secret into a SealedSecret using the public key. - The SealedSecret manifest is committed to Git (safe — it can only be decrypted by the cluster's private key).
- FluxCD applies the SealedSecret to the cluster.
- The controller decrypts it and creates the corresponding Kubernetes Secret.
Key Configuration
Fetching the Public Key
After the controller is deployed, fetch the public key for your cluster:
kubeseal --fetch-cert \
--controller-name=sealed-secrets \
--controller-namespace=sealed-secrets \
> pub-cert.pem
Distribute this certificate to developers who need to seal secrets for this cluster.
Sealing a Secret
Create a standard Kubernetes Secret manifest, then seal it:
# Create a regular secret (do NOT commit this)
kubectl create secret generic db-creds \
--from-literal=username=admin \
--from-literal=password=s3cret \
--dry-run=client -o yaml > secret.yaml
# Seal it with the cluster's public key
kubeseal --format=yaml --cert=pub-cert.pem < secret.yaml > sealed-secret.yaml
# Commit the sealed version (safe for Git)
rm secret.yaml
git add sealed-secret.yaml
The resulting SealedSecret looks like:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: db-creds
namespace: default
spec:
encryptedData:
username: AgBy3i... # Encrypted with cluster's public key
password: AgCtr8...
Scope
SealedSecrets are scoped by default — a sealed secret for namespace production with name db-creds can only be decrypted into that exact namespace and name. This prevents someone from copying a SealedSecret to a different namespace.
SOPS vs. Sealed Secrets
| Aspect | SOPS (openCenter default) | Sealed Secrets |
|---|---|---|
| Encryption | Symmetric (Age keys) | Asymmetric (RSA) |
| Key management | Age keys in secrets/age/ | Controller manages keys |
| Tooling | sops CLI | kubeseal CLI |
| FluxCD integration | Native (decryption.provider: sops) | Via SealedSecret CRD |
| Key rotation | Manual via opencenter cluster rotate-keys | Controller auto-rotates (30 days default) |
Use SOPS when you want unified key management across the GitOps workflow. Use Sealed Secrets when you prefer a Kubernetes-native approach or need to delegate secret creation to teams without access to SOPS keys.
Verification
# Check Sealed Secrets controller
kubectl get pods -n sealed-secrets
# Verify a SealedSecret was decrypted
kubectl get secret db-creds -n default
# Check controller logs for decryption errors
kubectl logs -n sealed-secrets -l app.kubernetes.io/name=sealed-secrets
Common Customizations
- Key rotation interval: The controller rotates its key pair every 30 days by default. Adjust with
--key-renew-periodflag. - Backup sealing keys: Export the controller's private key for disaster recovery:
kubectl get secret -n sealed-secrets -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml > sealed-secrets-key-backup.yaml. Store this securely outside the cluster. - Cluster-wide scope: Use
kubeseal --scope cluster-wideto create secrets that can be decrypted in any namespace (less restrictive).