Available for day contractsFrom 21st September I have availability for day and half day contracts. Please contact for more information.

Contact →
mikepreston.org

SOPS

A secrets management tool that encrypts files while keeping keys and structure visible for version control and GitOps workflows.

SOPS Cheatsheet

A secrets management tool that encrypts files while keeping keys and structure visible for version control and GitOps workflows.

Overview

SOPS (Secrets OPerationS) is an editor of encrypted files that supports YAML, JSON, ENV, INI, and binary formats. It integrates with cloud KMS providers (AWS, GCP, Azure), PGP, and age for key management, making it ideal for storing secrets in Git repositories.

Encrypted OutputEncrypted FileKeys VisibleValues EncryptedKey ProvidersAWS KMSGCP KMSAzure Key VaultPGP/GPGageSOPS EncryptionParse FileIdentify ValuesEncrypt ValuesAdd MetadataInput FilesYAMLJSONENVINIBinaryEncrypted OutputEncrypted FileKeys VisibleValues EncryptedKey ProvidersAWS KMSGCP KMSAzure Key VaultPGP/GPGageSOPS EncryptionParse FileIdentify ValuesEncrypt ValuesAdd MetadataInput FilesYAMLJSONENVINIBinary

File Encryption and Decryption

Key Concepts

  • Value-level encryption: SOPS encrypts only the values, leaving keys and structure intact for easy diffing
  • MAC (Message Authentication Code): Ensures file integrity and prevents tampering
  • Metadata: Encrypted files contain a sops section with encryption details and key information
  • In-place encryption: Files can be encrypted/decrypted in place or to stdout

Common Commands

# Encrypt a file (requires .sops.yaml config or explicit key)
sops --encrypt secrets.yaml > secrets.enc.yaml

# Encrypt in place
sops --encrypt --in-place secrets.yaml

# Decrypt to stdout
sops --decrypt secrets.enc.yaml

# Decrypt in place
sops --decrypt --in-place secrets.enc.yaml

# Decrypt specific key to stdout
sops --decrypt --extract '["database"]["password"]' secrets.enc.yaml

# Encrypt with specific key type
sops --encrypt --age age1... secrets.yaml > secrets.enc.yaml
sops --encrypt --pgp FINGERPRINT secrets.yaml > secrets.enc.yaml
sops --encrypt --kms arn:aws:kms:... secrets.yaml > secrets.enc.yaml

# Rotate data key (re-encrypt with new data key)
sops --rotate --in-place secrets.enc.yaml

# Check encryption status (the sops metadata block is plaintext in the
# file itself, so just read it; there is no --show-metadata flag)
sops filestatus secrets.enc.yaml   # {"encrypted":true}
grep -A20 '^sops:' secrets.enc.yaml

Examples

Basic encryption workflow:

# Create a plain secrets file
cat > secrets.yaml << EOF
database:
  host: localhost
  username: admin
  password: supersecret123
api:
  key: abc123def456
EOF

# Encrypt with age key
export SOPS_AGE_KEY_FILE=~/.sops/age-key.txt
sops --encrypt --age age1abc123... secrets.yaml > secrets.enc.yaml

# View encrypted file (keys visible, values encrypted)
cat secrets.enc.yaml
# database:
#     host: ENC[AES256_GCM,data:...,tag:...]
#     username: ENC[AES256_GCM,data:...,tag:...]
#     password: ENC[AES256_GCM,data:...,tag:...]
# api:
#     key: ENC[AES256_GCM,data:...,tag:...]
# sops:
#     age:
#         - recipient: age1abc123...
#           enc: |
#             -----BEGIN AGE ENCRYPTED FILE-----
#             ...

Partial decryption:

# Extract only the database password
sops --decrypt --extract '["database"]["password"]' secrets.enc.yaml
# Output: supersecret123

# Extract entire database section as JSON
sops --decrypt --extract '["database"]' --output-type json secrets.enc.yaml

Key Management

Key Concepts

EncryptionData KeyEncryptEncrypt Data KeyMaster KeysAWS KMSGCP KMSAzure KVPGP Keyage KeySymmetric Data KeyGenerated per fileEncrypt Valueswith Data KeyEncryptionData KeyEncryptEncrypt Data KeyMaster KeysAWS KMSGCP KMSAzure KVPGP Keyage KeySymmetric Data KeyGenerated per fileEncrypt Valueswith Data Key
  • Master keys: External keys (KMS, PGP, age) that encrypt the data key
  • Data key: Symmetric key generated per file, encrypts actual values
  • Key groups: Require multiple keys from different groups for decryption
  • Shamir's Secret Sharing: Split data key across multiple key groups

Common Commands

# Add/remove keys: the --add-*/--rm-* flags only take effect alongside
# --rotate (-r), which also re-encrypts with a fresh data key
sops --rotate -i --add-age age1newkey... secrets.enc.yaml
sops --rotate -i --add-pgp NEWFINGERPRINT secrets.enc.yaml
sops --rotate -i --add-kms arn:aws:kms:region:account:key/id secrets.enc.yaml
sops --rotate -i --rm-age age1oldkey... secrets.enc.yaml
sops --rotate -i --rm-pgp OLDFINGERPRINT secrets.enc.yaml

# Preferred: declare keys in .sops.yaml, then sync without rotating the
# data key (no --rotate needed)
sops updatekeys secrets.enc.yaml

# List keys in file (the sops metadata block is plaintext)
grep -A 100 "^sops:" secrets.enc.yaml

Examples

Multi-key configuration with key groups:

# .sops.yaml
creation_rules:
  # Production secrets require keys from both groups
  - path_regex: production/.*\.yaml$
    key_groups:
      - pgp:
          - FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4  # Security team
        age:
          - age1security...
      - kms:
          - arn:aws:kms:eu-west-1:123456789:key/prod-key
    shamir_threshold: 2  # Need keys from 2 groups

  # Development secrets - single key sufficient
  - path_regex: development/.*\.yaml$
    age: age1dev...

  # Default for other files
  - path_regex: .*\.yaml$
    pgp: FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4

AWS KMS key policy for SOPS:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789:role/sops-role"
      },
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:GenerateDataKey"
      ],
      "Resource": "*"
    }
  ]
}

Integration with GitOps Workflows

Key Concepts

  • Git-friendly encryption: Keys remain visible for meaningful diffs
  • Flux/ArgoCD integration: Native support for SOPS-encrypted secrets
  • Kubernetes Secrets: Decrypt at deployment time or use operators
  • Pre-commit hooks: Prevent committing unencrypted secrets

Common Patterns

# Flux Kustomization with SOPS decryption
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: app-secrets
  namespace: flux-system
spec:
  interval: 10m
  path: ./secrets
  prune: true
  sourceRef:
    kind: GitRepository
    name: app-repo
  decryption:
    provider: sops
    secretRef:
      name: sops-age  # Contains age private key
# ArgoCD with SOPS using Helm secrets plugin
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
spec:
  source:
    repoURL: https://github.com/org/repo
    path: charts/my-app
    plugin:
      name: helm-secrets
      env:
        - name: HELM_SECRETS_SOPS_PATH
          value: /usr/bin/sops

Examples

Pre-commit hook to prevent plain secrets:

#!/bin/bash
# .git/hooks/pre-commit

# Check for unencrypted secrets files
for file in $(git diff --cached --name-only | grep -E 'secrets.*\.yaml$'); do
  if ! grep -q "^sops:" "$file"; then
    echo "ERROR: $file appears to be unencrypted!"
    echo "Run: sops --encrypt --in-place $file"
    exit 1
  fi
done

Flux SOPS secret setup:

# Generate age key pair
age-keygen -o age-key.txt

# Create Kubernetes secret with private key
kubectl create secret generic sops-age \
  --namespace=flux-system \
  --from-file=age.agekey=age-key.txt

# Add public key to .sops.yaml
cat >> .sops.yaml << EOF
creation_rules:
  - path_regex: .*\.yaml$
    encrypted_regex: ^(data|stringData)$
    age: $(grep "public key:" age-key.txt | cut -d: -f2 | tr -d ' ')
EOF

Editing Encrypted Files

Key Concepts

  • Direct editing: SOPS decrypts, opens editor, re-encrypts on save
  • Editor selection: Uses $EDITOR, falls back to vim
  • Atomic operations: Changes only saved if encryption succeeds
  • Format preservation: Maintains YAML/JSON structure and comments

Common Commands

# Edit encrypted file (decrypts, opens editor, re-encrypts)
sops secrets.enc.yaml

# Specify editor
EDITOR=nano sops secrets.enc.yaml

# Edit with VS Code (requires waiting for close)
EDITOR="code --wait" sops secrets.enc.yaml

# Set specific values without opening editor
sops --set '["database"]["password"] "newpassword"' secrets.enc.yaml

# Set nested values
sops --set '["api"]["keys"][0] "newkey"' secrets.enc.yaml

Examples

Automated secret updates in scripts:

#!/bin/bash
# update-db-password.sh

NEW_PASSWORD=$(openssl rand -base64 32)

# Update password in encrypted file
sops --set "[\"database\"][\"password\"] \"$NEW_PASSWORD\"" secrets.enc.yaml

# Also update in password manager/vault
vault kv put secret/database password="$NEW_PASSWORD"

echo "Database password rotated successfully"

Comparing encrypted files:

# Decrypt and diff two files
diff <(sops -d secrets-old.enc.yaml) <(sops -d secrets-new.enc.yaml)

# Git diff driver for SOPS files
# Add to .gitattributes: *.enc.yaml diff=sops
git config diff.sops.textconv "sops -d"

age Support

Key Concepts

  • Modern alternative to PGP: Simpler, smaller, and more secure
  • No key servers: Keys are standalone files or strings
  • Native X25519: Fast, secure elliptic curve cryptography
  • Identity files: Private keys stored in simple text files

Common Commands

# Generate age key pair
age-keygen -o ~/.sops/age-key.txt
# Outputs public key: age1abc123...

# Set age key for SOPS
export SOPS_AGE_KEY_FILE=~/.sops/age-key.txt

# Or embed key directly (not recommended)
export SOPS_AGE_KEY="AGE-SECRET-KEY-1..."

# Encrypt with age
sops --encrypt --age age1abc123... secrets.yaml > secrets.enc.yaml

# Multiple age recipients
sops --encrypt \
  --age age1user1...,age1user2...,age1ci... \
  secrets.yaml > secrets.enc.yaml

Examples

Complete age setup for team:

# Each team member generates their key
age-keygen -o ~/.sops/key.txt

# Collect public keys in repository
mkdir -p .sops/keys
echo "age1alice..." > .sops/keys/alice.pub
echo "age1bob..." > .sops/keys/bob.pub
echo "age1ci..." > .sops/keys/ci.pub

# Create .sops.yaml with all recipients
cat > .sops.yaml << EOF
creation_rules:
  - path_regex: .*\.enc\.yaml$
    age: >-
      age1alice...,
      age1bob...,
      age1ci...
EOF

# Encrypt new secrets (automatically uses all recipients)
sops --encrypt secrets.yaml > secrets.enc.yaml

Converting from PGP to age:

# Decrypt with old PGP key
sops --decrypt secrets.enc.yaml > secrets.yaml

# Re-encrypt with new age key
sops --encrypt --age age1newkey... secrets.yaml > secrets.enc.yaml

# Verify and clean up
sops --decrypt secrets.enc.yaml
rm secrets.yaml

Best Practices for Secrets Management

Key Concepts

  • Least privilege: Only necessary systems should have decryption access
  • Key rotation: Regularly rotate both master and data keys
  • Audit trail: Log all decryption operations
  • Separation of concerns: Different keys for different environments

Configuration Patterns

# .sops.yaml - Environment separation
creation_rules:
  # Production - strict access
  - path_regex: ^production/.*$
    kms: arn:aws:kms:eu-west-1:123456789:key/prod-key
    gcp_kms: projects/prod/locations/global/keyRings/sops/cryptoKeys/prod

  # Staging - moderate access
  - path_regex: ^staging/.*$
    kms: arn:aws:kms:eu-west-1:123456789:key/staging-key

  # Development - team access
  - path_regex: ^development/.*$
    age: age1devteam...

  # Encrypted regex for Kubernetes secrets
  - path_regex: .*secret.*\.yaml$
    encrypted_regex: ^(data|stringData)$
    age: age1k8s...

Examples

Structured secrets repository:

secrets/
├── .sops.yaml                 # SOPS configuration
├── .gitattributes            # Git diff driver
├── production/
│   ├── database.enc.yaml
│   ├── api-keys.enc.yaml
│   └── certificates.enc.yaml
├── staging/
│   ├── database.enc.yaml
│   └── api-keys.enc.yaml
└── development/
    └── local.enc.yaml

Key rotation script:

#!/bin/bash
# rotate-keys.sh

# Find all encrypted files
find . -name "*.enc.yaml" | while read -r file; do
  echo "Rotating keys for: $file"

  # Update keys based on .sops.yaml
  sops updatekeys --yes "$file"

  # Rotate data key
  sops --rotate --in-place "$file"
done

echo "Key rotation complete"

Encrypted regex for partial encryption:

# .sops.yaml - Only encrypt specific fields
creation_rules:
  - path_regex: .*\.yaml$
    encrypted_regex: ^(password|secret|key|token|credential)$
    age: age1...

Integration with CI/CD Pipelines

Key Concepts

CI/CD PipelineAuthenticateKey AccessCloud KMSvia IAM Roleage Keyvia CI SecretCheckout CodeDecrypt SecretsBuild/DeployCleanup SecretsCI/CD PipelineAuthenticateKey AccessCloud KMSvia IAM Roleage Keyvia CI SecretCheckout CodeDecrypt SecretsBuild/DeployCleanup Secrets
  • Ephemeral decryption: Decrypt only when needed, clean up immediately
  • IAM roles: Use cloud IAM for KMS access instead of static credentials
  • Masked outputs: Ensure decrypted values don't appear in logs
  • Limited scope: Decrypt only required files

Examples

GitHub Actions workflow:

name: Deploy with SOPS
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write  # For AWS OIDC
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/github-actions
          aws-region: eu-west-1

      - name: Install SOPS
        run: |
          curl -LO https://github.com/getsops/sops/releases/download/v3.11.0/sops-v3.11.0.linux.amd64
          chmod +x sops-v3.11.0.linux.amd64
          sudo mv sops-v3.11.0.linux.amd64 /usr/local/bin/sops

      - name: Decrypt secrets
        run: |
          sops --decrypt config/secrets.enc.yaml > secrets.yaml

      - name: Deploy application
        run: |
          ./deploy.sh

      - name: Cleanup
        if: always()
        run: rm -f secrets.yaml

GitLab CI with age:

# .gitlab-ci.yml
variables:
  SOPS_AGE_KEY_FILE: /tmp/age-key.txt

deploy:
  stage: deploy
  before_script:
    # Write age key from CI variable
    - echo "$SOPS_AGE_KEY" > $SOPS_AGE_KEY_FILE
    - chmod 600 $SOPS_AGE_KEY_FILE
  script:
    - sops --decrypt secrets.enc.yaml > secrets.yaml
    - kubectl apply -f secrets.yaml
  after_script:
    # Always clean up secrets
    - rm -f secrets.yaml $SOPS_AGE_KEY_FILE

Jenkins pipeline:

pipeline {
    agent any

    environment {
        SOPS_AGE_KEY = credentials('sops-age-key')
    }

    stages {
        stage('Decrypt Secrets') {
            steps {
                script {
                    // Write key to file
                    writeFile file: '/tmp/age-key.txt', text: SOPS_AGE_KEY

                    // Decrypt secrets
                    sh '''
                        export SOPS_AGE_KEY_FILE=/tmp/age-key.txt
                        sops --decrypt secrets.enc.yaml > secrets.yaml
                    '''
                }
            }
        }

        stage('Deploy') {
            steps {
                sh './deploy.sh'
            }
        }
    }

    post {
        always {
            sh 'rm -f secrets.yaml /tmp/age-key.txt'
        }
    }
}

Quick Reference

Operation Command
Encrypt file sops --encrypt secrets.yaml > secrets.enc.yaml
Encrypt in place sops --encrypt --in-place secrets.yaml
Decrypt to stdout sops --decrypt secrets.enc.yaml
Decrypt in place sops --decrypt --in-place secrets.enc.yaml
Edit encrypted file sops secrets.enc.yaml
Set value sops --set '["key"] "value"' file.enc.yaml
Extract value sops -d --extract '["key"]' file.enc.yaml
Rotate data key sops --rotate --in-place file.enc.yaml
Update keys from config sops updatekeys file.enc.yaml
Add age key sops -r -i --add-age age1... file.enc.yaml
Remove age key sops -r -i --rm-age age1... file.enc.yaml
Check encryption status sops filestatus file.enc.yaml
Generate age key age-keygen -o key.txt
Encrypt with age sops --encrypt --age age1... file.yaml
Encrypt with KMS sops --encrypt --kms arn:aws:kms:... file.yaml
Encrypt with PGP sops --encrypt --pgp FINGERPRINT file.yaml

Common Issues and Solutions

Issue: "could not decrypt data key"

Cause: Missing or incorrect decryption key

Solution:

# Check which keys are required (metadata block is plaintext in the file)
grep -A50 '^sops:' secrets.enc.yaml

# Verify age key is set
echo $SOPS_AGE_KEY_FILE
cat $SOPS_AGE_KEY_FILE

# Verify KMS access
aws kms describe-key --key-id arn:aws:kms:...

# Verify PGP key is imported
gpg --list-secret-keys FINGERPRINT

Issue: "MAC mismatch" error

Cause: File was modified without re-encrypting or corrupted

Solution:

# If you have the original data, re-encrypt
sops --encrypt original.yaml > new.enc.yaml

# Or decrypt ignoring MAC (use with caution)
sops --decrypt --ignore-mac corrupted.enc.yaml

Issue: Files not being encrypted with correct rules

Cause: .sops.yaml regex patterns not matching

Solution:

# Test which rule matches
sops --verbose --encrypt test-file.yaml

# Check .sops.yaml syntax and regex patterns
# Ensure path_regex matches from repository root
# Use anchors: ^path/.*\.yaml$

Issue: "age: no identity matched any of the recipients"

Cause: Public key in encrypted file doesn't match private key

Solution:

# List recipients in encrypted file (metadata block is plaintext)
grep recipient file.enc.yaml

# Check your public key
grep "public key:" ~/.sops/key.txt

# Add your key to the file (requires access to another valid key;
# --add-age only applies with --rotate)
sops --rotate -i --add-age $(grep "public key:" ~/.sops/key.txt | cut -d: -f2 | tr -d ' ') file.enc.yaml

Issue: Kubernetes secrets show encrypted values

Cause: Not using encrypted_regex for Kubernetes secrets

Solution:

# .sops.yaml
creation_rules:
  - path_regex: .*secret.*\.yaml$
    encrypted_regex: ^(data|stringData)$
    age: age1...

Issue: Large files are slow to encrypt/decrypt

Cause: SOPS processes each value individually

Solution:

# Split into smaller files by service/component

# Or use unencrypted_regex to skip non-sensitive data
# .sops.yaml
creation_rules:
  - path_regex: .*\.yaml$
    unencrypted_regex: ^(metadata|kind|apiVersion)$
    age: age1...

Issue: AWS KMS "AccessDeniedException"

Cause: IAM permissions insufficient for KMS operations

Solution:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:GenerateDataKey"
      ],
      "Resource": "arn:aws:kms:region:account:key/key-id"
    }
  ]
}

Related Topics

The following topics would complement this SOPS cheatsheet:

  1. HashiCorp Vault - Dynamic secrets management, secrets injection, and enterprise-grade secrets infrastructure
  2. Sealed Secrets (Bitnami) - Kubernetes-native encrypted secrets that can only be decrypted by the cluster controller
  3. External Secrets Operator - Kubernetes operator that synchronises secrets from external providers (Vault, AWS Secrets Manager)
  4. age encryption - Deep dive into age key management, identity files, and advanced encryption patterns
  5. Flux CD - GitOps toolkit with native SOPS integration for Kubernetes deployments
  6. AWS KMS / GCP Cloud KMS - Cloud key management services for enterprise key rotation, access policies, and audit logging