Encrypting Kubernetes Secrets in Git with Flux and Sealed Secrets is a way to store sensitive data like API keys and passwords in your Git repository without actually exposing them in plain text.
Imagine you have a Kubernetes cluster and you need to deploy an application that requires a database password. Normally, you’d put this password in a Kubernetes Secret object, which is then stored in your Git repository. If your Git repository gets compromised, so do all your secrets. This is where Sealed Secrets comes in.
Here’s how it works in practice. We’ll walk through setting up Sealed Secrets and Flux, and then encrypting and deploying a secret.
First, install the Sealed Secrets controller into your cluster. You can do this with Helm:
helm install sealed-secrets sealed-secrets/sealed-secrets \
--namespace kube-system \
--create-namespace
This command deploys the Sealed Secrets controller into the kube-system namespace. The controller is responsible for decrypting secrets that have been encrypted by the Sealed Secrets tool. It has a private key that only it knows, and a public key that you can use to encrypt secrets.
Next, we need to install Flux. Flux is a GitOps tool that synchronizes your Git repository with your Kubernetes cluster.
flux bootstrap github \
--owner=<your-github-username> \
--repository=<your-github-repo-name> \
--branch=main \
--path=./clusters/my-cluster \
--personal
This command bootstraps Flux on your GitHub repository. It creates a clusters/my-cluster directory where you’ll store your Kubernetes manifests. Flux will watch this directory for changes and apply them to your cluster.
Now, let’s create a sample Kubernetes Secret object. This is the secret we want to encrypt.
apiVersion: v1
kind: Secret
metadata:
name: my-database-secret
namespace: default
type: Opaque
data:
password: <your-base64-encoded-password>
Replace <your-base64-encoded-password> with your actual password, base64 encoded. For example, if your password is "supersecret123", you’d run echo -n "supersecret123" | base64 and use the output.
With the Sealed Secrets controller installed, you can now encrypt this secret using the kubeseal command-line tool. You’ll need to fetch the public key from your Sealed Secrets controller first.
kubectl get secret -n kube-system sealed-secrets-key -o jsonpath='{.data.tls\.crt}' | base64 --decode > sealed-secrets.pem
Now, encrypt your secret:
kubeseal --format yaml < my-secret.yaml > sealed-secret.yaml
The sealed-secret.yaml file will look something like this:
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: my-database-secret
namespace: default
spec:
encryptedData:
password: AgC123... # This is the encrypted data
template:
metadata:
creationTimestamp: null
name: my-database-secret
namespace: default
The encryptedData field contains your secret, but it’s encrypted using the Sealed Secrets public key. The template section contains the original metadata of the secret.
Commit this sealed-secret.yaml file to your Git repository in the directory Flux is watching (e.g., ./clusters/my-cluster/secrets/).
git add clusters/my-cluster/secrets/sealed-secret.yaml
git commit -m "Add encrypted database secret"
git push
Flux will detect the new file, download it, and apply it to your cluster. The Sealed Secrets controller in your cluster will then intercept the SealedSecret object, use its private key to decrypt the encryptedData, and create a regular Kubernetes Secret object in your cluster.
This entire process ensures that your sensitive data never resides in plain text in your Git repository.
When you later need to update a secret, you’ll repeat the process: edit the original Secret manifest, re-encrypt it with kubeseal, and commit the new SealedSecret to Git. Flux will handle the rest.
The most surprising thing about this setup is that the SealedSecret object itself is what you commit to Git. You’re not committing the encrypted data, you’re committing a custom resource that contains the encrypted data and tells the Sealed Secrets controller how to reconstruct the original Kubernetes Secret.
This approach decouples the process of encrypting secrets from the process of deploying them. You can encrypt a secret once and then use Flux to deploy it to multiple environments, each with its own Sealed Secrets controller.
The underlying mechanism relies on asymmetric encryption. The kubeseal tool uses the public key of the Sealed Secrets controller to encrypt the secret data. Only the Sealed Secrets controller, possessing the corresponding private key, can decrypt this data and transform it back into a standard Kubernetes Secret.
Flux then acts as the delivery mechanism. It monitors your Git repository and, upon detecting changes to the SealedSecret manifest, applies it to the cluster. The Sealed Secrets controller, running within the cluster, is the only component that can perform the decryption.
The next challenge you’ll likely face is managing multiple secrets for different applications or environments, and organizing them effectively within your Git repository.