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