Skip to main content

SOPS Configuration Reference

Purpose: For platform engineers, provides the .sops.yaml structure, Age key paths, creation rules, and per-directory encryption scoping.

Overview​

SOPS uses .sops.yaml files to determine how to encrypt new files. When you run sops --encrypt, it walks up the directory tree from the target file until it finds a .sops.yaml with a matching creation_rules entry. openCenter places .sops.yaml files at multiple levels in the customer repository to scope encryption rules per cluster and per directory.

.sops.yaml Schema​

creation_rules:
- path_regex: <regex matching file paths>
encrypted_regex: <regex matching YAML keys to encrypt>
age: <comma-separated Age public keys>

Field Reference​

FieldTypeRequiredDescription
creation_ruleslistyesOrdered list of rules. First matching rule wins.
path_regexstringyesGo-style regex matched against the file path relative to the .sops.yaml location.
encrypted_regexstringnoRegex matched against YAML keys. Only matching keys are encrypted. If omitted, all values are encrypted.
agestringyes (for Age)One or more Age public keys, comma-separated. Files are encrypted to all listed recipients.

Creation Rules​

Rules are evaluated top-to-bottom. The first rule whose path_regex matches the file path is used. Place more specific rules before general ones.

Typical Customer Repository Rules​

Root-level .sops.yaml (covers infrastructure secrets):

creation_rules:
- path_regex: infrastructure/clusters/k8s-sandbox/.*
encrypted_regex: "^(data|stringData)$"
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Application overlay .sops.yaml (covers service and app secrets):

creation_rules:
- path_regex: services/.*
encrypted_regex: "^(data|stringData)$"
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- path_regex: managed-services/.*
encrypted_regex: "^(data|stringData)$"
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

path_regex Patterns​

PatternMatches
.*All files relative to .sops.yaml location
services/keycloak/.*Only files under services/keycloak/
.*secret.*Any file with "secret" in the path
infrastructure/clusters/k8s-sandbox/inventory/credentials/.*Kubespray credential files for a specific cluster

Paths are matched relative to the .sops.yaml file's directory, not the repository root.

encrypted_regex Patterns​

PatternEffect
^(data|stringData)$Encrypts only Kubernetes Secret values (recommended)
^(password|token|secret)$Encrypts specific named keys
(omitted)Encrypts all values in the file

The recommended pattern ^(data|stringData)$ keeps metadata (name, namespace, labels, annotations) in plaintext. This allows FluxCD, Kustomize, and git diff to work with the file without decryption.

Age Key Format​

Public Key​

Used in .sops.yaml as the age recipient. Format:

age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

Always starts with age1, followed by a Bech32-encoded string.

Private Key File​

Stored at secrets/age/<cluster>_keys.txt. Format:

# created: 2025-04-15T10:30:00Z
# public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
AGE-SECRET-KEY-1XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

This file is referenced by:

  • SOPS_AGE_KEY_FILE environment variable (for manual sops commands)
  • The sops-age Kubernetes Secret in flux-system namespace (for FluxCD decryption)

Dual-Key Format (During Rotation)​

During key rotation, the key file contains both old and new keys:

# created: 2025-04-15T10:30:00Z
# public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
AGE-SECRET-KEY-1OLDKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# created: 2025-07-14T09:00:00Z
# public key: age1newkeyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AGE-SECRET-KEY-1NEWKEYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

And .sops.yaml lists both public keys:

creation_rules:
- path_regex: .*
encrypted_regex: "^(data|stringData)$"
age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,age1newkeyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

File Locations in Customer Repository​

customers/<org>/
├── .sops.yaml # Root rules (infrastructure)
├── infrastructure/clusters/<cluster>/.sops.yaml # Cluster-specific rules
├── applications/overlays/<cluster>/.sops.yaml # Application overlay rules
└── secrets/age/<cluster>_keys.txt # Age private key (not committed)

The secrets/ directory is listed in .gitignore. Private keys are distributed to the cluster via opencenter cluster setup or manual kubectl create secret.

Behaviors and Edge Cases​

  • If multiple .sops.yaml files exist in the directory tree, only the nearest one (closest to the file) is used. SOPS does not merge rules across files.
  • If no path_regex matches, sops --encrypt fails with "no matching creation rule."
  • Encrypting an already-encrypted file is a no-op (SOPS detects the sops: metadata block).
  • Decrypting requires any one of the listed Age private keys, not all of them.

Further Reading​