DevCloudLab More courses at DevCloudLab.com →
FLUX CD · SECTION 5 · LECTURE 3

DevCloudLab

Flux CD’s Kustomization integration with Mozilla SOPS

Section 5, Lecture 3, from the Flux CD course by DevCloudLab.


What you’ll learn

Sealed Secrets vs. SOPS

The previous lecture covered Sealed Secrets: a self-contained scheme where the private key lives inside the cluster and a matching public key encrypts locally. It works well, but it assumes you’re happy generating and storing that key pair yourself. Some organizations already run a key management system and would rather plug into it than run a second one just for GitOps.

  Sealed Secrets SOPS
Key storage Private key inside the cluster External: GPG, AWS KMS, Azure Key Vault, GCP KMS, or HashiCorp Vault
Best fit Self-contained clusters, no existing KMS Organizations with an existing key management system
Encryption scope Whole Secret object Configurable, typically just data / stringData

What is SOPS

SOPS (Secrets OPerationS) is an open-source tool for encrypting, decrypting, and editing files that contain sensitive data. Instead of a bespoke workflow, SOPS lets you work with an encrypted file almost as if it were plaintext: it decrypts on open, re-encrypts on save, and can also decrypt a file for a script or CI job to read.

SOPS doesn’t hold its own keys. It integrates with existing key management systems and encrypts each file’s data with a data key, then encrypts that data key with one or more master keys from whichever backend you configure: GPG, AWS KMS, Azure Key Vault, or Google Cloud KMS.

Since this video was recorded: SOPS is no longer a Mozilla project. It moved to the Cloud Native Computing Foundation and now lives at github.com/getsops/sops rather than under the mozilla GitHub organization. The tool and its file formats are unchanged; only the home has moved. This lecture keeps the title “Mozilla SOPS” because that’s the name used in the video.

Configuring SOPS: .sops.yaml

SOPS reads its encryption rules from a .sops.yaml file at the root of the directory tree it’s run against. The rules decide which files get encrypted, which fields inside them, and which key backend to use:

creation_rules:
  - path_regex: .*\.yaml$
    encrypted_regex: ^(data|stringData)$
    pgp: <YOUR_GPG_FINGERPRINT>

encrypted_regex is the important line for Kubernetes Secrets: it tells SOPS to encrypt only the data and stringData fields, leaving apiVersion, kind, and metadata in plaintext. This matters for Flux, covered below.

Swap the pgp: line for the backend you actually use:

kms: arn:aws:kms:us-east-1:111122223333:key/<key-id>        # AWS KMS
azure_kv: https://<vault-name>.vault.azure.net/keys/<key>   # Azure Key Vault
gcp_kms: projects/<project>/locations/global/keyRings/<ring>/cryptoKeys/<key>  # GCP KMS

Encrypting a secret

With .sops.yaml in place, encrypting a file is a single command. SOPS reads the matching rule and picks the key backend for you:

sops --encrypt --in-place secret.yaml

Commit the resulting file to Git as usual. The apiVersion, kind, and metadata fields stay readable in diffs and pull requests; only data/stringData is ciphertext.

Wiring SOPS into a Flux Kustomization

A Flux Kustomization has an optional spec.decryption field. When it’s set, Flux decrypts the matching SOPS-encrypted files before applying them:

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 10m
  path: ./apps/my-app
  sourceRef:
    kind: GitRepository
    name: my-app
  decryption:
    provider: sops
    secretRef:
      name: sops-keys

Two fields are required: provider, which today only accepts sops, and secretRef.name, the Kubernetes Secret that holds the decryption keys. sops-keys above is just an example name, so call it whatever you like, as long as secretRef.name matches.

Since this video was recorded: the Kustomization API used in this example is kustomize.toolkit.fluxcd.io/v1. The older v1beta2, which this video may show, has been superseded by v1 and is deprecated. If you are following along on a current Flux install, use v1 as written above.

One detail worth remembering: decryption only works against SOPS-encrypted values inside a manifest, not against a manifest that is entirely ciphertext. That’s exactly what the encrypted_regex in .sops.yaml controls: encrypt the whole file and Flux has nothing readable to parse as YAML; encrypt just data/stringData and it works.

The decryption Secret: how Flux tells keys apart

The Secret named in secretRef holds one or more decryption keys, and Flux (through SOPS) identifies each key’s type by its field name suffix:

Key backend Field name suffix Example
GPG .asc identity.asc
Age .agekey age.agekey
HashiCorp Vault sops.vault-token sops.vault-token
AWS KMS sops.aws-kms sops.aws-kms
Azure Key Vault sops.azure-kv sops.azure-kv
GCP KMS sops.gcp-kms sops.gcp-kms

For a GPG-backed setup, the Secret would look like this once created:

kubectl create secret generic sops-keys \
  --namespace=flux-system \
  --from-file=identity.asc=./private.asc

For AWS, Azure, or GCP, that Secret typically holds cloud credentials rather than a private key, and it’s only needed when you’re not using the cluster’s own identity. A pod running under IRSA on EKS, or an equivalent workload identity on AKS/GKE, can authenticate to KMS without any static credential in the Secret at all.

Where HashiCorp Vault fits

SOPS supports Vault’s Transit engine directly, through sops --hc-vault-transit. Flux authenticates to Vault with a sops.vault-token key in the decryption Secret; see the Vault and OpenBao lecture later in this section.

What’s next

Sealed Secrets and SOPS solve the same problem from different directions, so pick whichever matches the key management your organization already has. The coming lectures build on this decryption field with hands-on setups for specific backends: GPG, Age, and HashiCorp Vault (with OpenBao).

Further reading


DevCloudLab

Built by DevCloudLab
Hands-on cloud-native courses: Kubernetes, GitOps, CI/CD and the cloud.
Visit DevCloudLab.com →