Using SOPS Secrets with Age in My Homelab
In my Homelab, I needed a secure way to store secrets without exposing plain credentials in my GitOps repository. After evaluating three options:
- Using a secret manager like Hashicorp Vault
- Using AWS Secrets Manager
- Installing a tool directly on my Kubernetes cluster
I decided to go with the third option for now, with plans to switch to AWS Secrets Manager later when I feel ready.
According to the official FluxCD documentation, two recommended tools for this approach are:
Below is my setup process.
Installing SOPS and Age
To install SOPS and Age on my Linux cluster, I used Homebrew:
brew install sops age
Next, I generated a key pair (public & private) and exported the public key as an environment variable:
age-keygen -o age.agekey
cat age.agekey
export AGE_PUBLIC=<age.agekey public key>
Encrypting a Generic Secret
As an example, I created a Kubernetes secret using kubectl
and saved it to a file. The --dry-run=client
flag ensures the resource is not applied directly to the cluster:
kubectl create secret generic <secret_name> \
--from-literal=user=admin \
--from-literal=password=\!Abc123 \
--dry-run=client \
-o yaml > secret.yaml
Then, I encrypted the secret file. The regex ensures that everything inside the data
property is encrypted:
sops --age=$AGE_PUBLIC \
--encrypt --encrypted-regex '^(data|stringData)$' --in-place secret.yaml
This produces an encrypted secret. You can see an example in my Homelab repository.
Configuring the Cluster & FluxCD
1. Creating the Age Secret in the Cluster
The Age secret must be manually created in the cluster:
cat age.agekey | \
kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=/dev/stdin
2. Defining Encryption Rules
In the clusters/environment
directory of the GitOps repository, I created a .sops.yaml
file to define encryption rules:
creation_rules:
- path_regex: .*.yaml
encrypted_regex: ^(data|stringData)$
age: <age.agekey public key>
3. Configuring Decryption in FluxCD
For any Kustomization resource that requires decryption, I added the following under the spec
section:
decryption:
provider: sops
secretRef:
name: sops-age