Skip to content

SOPS and age Secret Workflow

This repository is being prepared to use SOPS with age for Git-friendly secret files used by Kubernetes, Rancher Fleet, and future Ansible automation. This first phase only adds repository configuration, examples, and local test commands. It does not encrypt existing files, replace current Secret manifests, replace Ansible Vault, or add a SOPS operator.

Key Material

The repository uses this age recipient:

age1qz3p7ugntvalmdcn8ggzx39ktw7nszmwe8lchxcpmv80u3argdws95w9s2

The private key should live only on trusted operator machines:

export SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/homelab.agekey"

Never commit the private key, key backups, decrypted files, temporary plaintext files, or filled local .env files. The root .gitignore blocks the expected local key paths and common decrypted output names, but Git ignore rules are only a safety net.

File Naming

Use these names when real migration starts later:

app-name.secret.sops.yaml
app-name.secret.sops.yml
app-name.sops.yaml
app-name.sops.yml
app-name.sops.env

Kubernetes Secret manifests should use the *.secret.sops.yaml or *.secret.sops.yml suffix. The root .sops.yaml keeps metadata readable and encrypts only data and stringData for those files.

Generic YAML files for Ansible variables or application secret settings should use *.sops.yaml or *.sops.yml. Dotenv-style secret files should use *.sops.env.

Dummy Local Tests

The example files under examples/sops/ contain fake values only and are intentionally plaintext. They exist so the workflow can be tested safely before real migration.

Check tools:

sops --version
age --version
export SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/homelab.agekey"

Encrypt the dummy Kubernetes Secret using the repo .sops.yaml demo rule:

sops --encrypt examples/sops/kubernetes/demo.secret.yaml > /tmp/demo.secret.sops.yaml

Equivalent explicit command:

sops --encrypt \
  --age age1qz3p7ugntvalmdcn8ggzx39ktw7nszmwe8lchxcpmv80u3argdws95w9s2 \
  --encrypted-regex '^(data|stringData)$' \
  examples/sops/kubernetes/demo.secret.yaml > /tmp/demo.secret.sops.yaml

Decrypt only locally:

sops --decrypt /tmp/demo.secret.sops.yaml

Dry-run the decrypted Kubernetes Secret without applying it:

sops --decrypt /tmp/demo.secret.sops.yaml | kubectl apply --dry-run=client -f -

Encrypt and decrypt the dummy Ansible-style YAML:

sops --encrypt \
  --age age1qz3p7ugntvalmdcn8ggzx39ktw7nszmwe8lchxcpmv80u3argdws95w9s2 \
  examples/sops/ansible/demo-secrets.yml > /tmp/demo-secrets.sops.yml

sops --decrypt /tmp/demo-secrets.sops.yml

The same workflow is available through Makefile targets:

make sops-demo-encrypt-kubernetes
make sops-demo-decrypt-kubernetes
make sops-demo-dry-run-kubernetes
make sops-demo-encrypt-ansible
make sops-demo-decrypt-ansible
make sops-demo-clean

Kubernetes and Fleet

Fleet should deploy normal manifests. SOPS-encrypted secrets are either manually decrypted/applied for now, or later handled by a dedicated SOPS operator if we choose that route. For this initial phase, do not add a SOPS operator.

Temporary manual workflow for a future real Secret:

sops -d vaultwarden/overlays/local/vaultwarden.secret.sops.yaml | kubectl apply -f -

Future naming examples using the current top-level service layout:

vaultwarden/overlays/local/vaultwarden.secret.sops.yaml
traefik/overlays/local/cloudflare.secret.sops.yaml
authelia/overlays/local/authelia.secret.sops.yaml

Fleet should continue managing non-secret resources normally:

ConfigMaps
Deployments
StatefulSets
Services
Ingresses
Helm releases
Fleet bundles

Do not commit decrypted Kubernetes Secret manifests. Do not replace existing Kubernetes Secret manifests in this phase.

Ansible

No standard ansible/group_vars/, ansible/host_vars/, group_vars/, host_vars/, or inventories/ tree was found in this repository during this preparation pass. The only tracked Ansible Vault marker found by path was pulp3/overlays/ozirepo/ansible_vault; it is not changed by this phase.

Future Ansible usage can load SOPS-encrypted YAML like this:

homelab_secrets: "{{ lookup('community.sops.sops', 'group_vars/all/secrets.sops.yml') | from_yaml }}"

The Ansible controller or AWX execution environment will need:

sops
age
community.sops Ansible collection
SOPS_AGE_KEY_FILE or SOPS_AGE_KEY

No repository-level Ansible requirements.yml was found, so this phase does not add community.sops. Add it later only where the repository's Ansible execution environment is defined.

Current Secret Locations

The current repository contains many secret-like tracked paths. This inventory was based on filenames, Kustomize references, and Kubernetes kind: Secret markers only; secret values were not opened, decrypted, printed, or classified.

Current patterns include:

  • Hand-written Kubernetes Secret manifests, including paths under argocd/, authelia/, csi-driver-nfs/, fleet/, home-assistant/, homepage/, robusta/, and velero/.
  • Kustomize secretGenerator env/file sources under workloads such as bichon/, cloudflared/, defectdojo/, forgejo/, gitea/, grafana-dashboard/, infisical/, k8s-monitoring/, minio/, openvas/, pulp3/, renovate/, semaphore/, traefik/, vaultwarden/, and wazuh/.
  • Credential text files such as *-creds.txt, *-credentials.txt, *-token.env, *-secrets.env, *-private.asc, and cloud/API token files across several overlays.
  • Example and placeholder files already used by some workloads, such as *.example and *.env.example.

Treat this as migration inventory, not approval to edit or print those values.

Safety Checks

Before introducing or migrating any real secret file, review the diff and run local scans. Do not paste scan output into chat or issue comments if it may contain real values.

git diff --check
git status --short
git grep -nE '(password|passwd|secret|token|api_key|apikey|private_key|smtp_password)' -- .

For a path-only review, use:

git grep -nIlE '(password|passwd|secret|token|api_key|apikey|private_key|smtp_password)' -- .

Possible future tooling, not required for this phase:

gitleaks
detect-secrets
pre-commit

Do not add a heavy dependency or blocking pre-commit policy until the current tracked secret-like files have been reviewed and a migration/rotation plan exists.

Later Migration Plan

Use small, reviewable units when moving real secrets:

  1. Pick one workload and identify its current secret source of truth by path, Secret name, namespace, and required keys.
  2. Create a new *.secret.sops.yaml, *.sops.yml, or *.sops.env file using existing values only from a safe operator workflow.
  3. Test decrypt locally and run kubectl apply --dry-run=client -f - for Kubernetes manifests.
  4. Apply the decrypted Secret manually, then confirm the workload still consumes the same Secret name and keys.
  5. Update the workload README, service docs, and runbook with the new secret creation and rotation workflow.
  6. Remove old plaintext sources only after rotation and rollback are planned.

Do not do these in this initial phase:

  • Encrypt existing Kubernetes secrets.
  • Encrypt existing Ansible vars.
  • Remove Ansible Vault.
  • Add sops-secrets-operator.
  • Add External Secrets Operator.
  • Change Fleet behavior.
  • Rotate production secrets.
  • Refactor applications to consume new secrets.