Skip to main content

Overview

The project uses SOPS (Secrets OPerationS) with age encryption to securely store secrets in Git. All secrets in secrets/*.yaml are encrypted at rest and can only be decrypted with the age private key.

Quick Start

Generate age encryption key:
sops-init
Encrypt a secret file:
sops secrets/my-secret.yaml
View encrypted secret:
sops -d secrets/my-secret.yaml

Initial Setup

Generate Age Key

The sops-init command (scripts/sops-init.sh:4-18) generates a new age key pair: Location: ~/.config/sops/age/keys.txt What it does:
  1. Check for existing key:
    if [ -f "$AGE_KEY_FILE" ]; then
      echo "Age key already exists"
      age-keygen -y "$AGE_KEY_FILE"  # Show public key
      exit 0
    fi
    
  2. Generate new key pair:
    mkdir -p ~/.config/sops/age
    age-keygen -o ~/.config/sops/age/keys.txt
    
  3. Display instructions:
    Public key: age1xcdgea30eam4rnvuk545ye3hukxnzew06xghqmucwrjg8c5hw54qyx9vjv
    
    Add the public key above to .sops.yaml
    

Configure SOPS

The repository includes .sops.yaml configuration:
creation_rules:
  - path_regex: secrets/.*\.yaml$
    age: >-
      age1xcdgea30eam4rnvuk545ye3hukxnzew06xghqmucwrjg8c5hw54qyx9vjv
What this means:
  • All files matching secrets/*.yaml will be encrypted
  • Encrypted with the specified age public key
  • Only holders of the private key can decrypt

Working with Secrets

Create Encrypted Secret

Create and edit a new secret:
sops secrets/database-credentials.yaml
This opens your editor with:
# Unencrypted content - will be encrypted on save
postgresql:
  username: admin
  password: super-secret-password
  database: myapp
On save, SOPS:
  1. Encrypts all values with age
  2. Keeps keys in plaintext
  3. Adds metadata (key fingerprint, version)

Edit Encrypted Secret

Edit existing encrypted file:
sops secrets/database-credentials.yaml
SOPS automatically:
  1. Decrypts content using your private key
  2. Opens in your editor (unencrypted)
  3. Re-encrypts on save

View Secret (Read-Only)

Decrypt and display without editing:
sops -d secrets/database-credentials.yaml

Extract Specific Value

Get single value from encrypted file:
sops -d --extract '["postgresql"]["password"]' secrets/database-credentials.yaml

Encrypted File Format

After encryption, files look like:
postgresql:
    username: ENC[AES256_GCM,data:8F2w==,iv:...]
    password: ENC[AES256_GCM,data:9K3x==,iv:...]
    database: ENC[AES256_GCM,data:7H1y==,iv:...]
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age:
        - recipient: age1xcdgea30eam4rnvuk545ye3hukxnzew06xghqmucwrjg8c5hw54qyx9vjv
          enc: |
            -----BEGIN AGE ENCRYPTED FILE-----
            ...
            -----END AGE ENCRYPTED FILE-----
    lastmodified: "2026-03-08T12:00:00Z"
    mac: ENC[AES256_GCM,data:...]
    pgp: []
    version: 3.9.0
Structure:
  • Data: Encrypted values with AES256-GCM
  • Metadata: Encryption method, recipients, timestamps
  • MAC: Message authentication code for integrity

Kubernetes Integration

Create Secret from SOPS File

Decrypt and apply to cluster:
sops -d secrets/database-credentials.yaml | kubectl apply -f -

Use in Manifest Generation

In nixidy modules, you can reference SOPS-encrypted files:
# Example: Load encrypted secret during build
let
  decryptedSecret = builtins.fromJSON (
    builtins.readFile (pkgs.runCommand "decrypt-secret" {} ''
      ${pkgs.sops}/bin/sops -d ${./secrets/db.yaml} > $out
    '')
  );
in {
  # Use decryptedSecret in manifests
}

Team Collaboration

Share Access with Team Member

  1. Team member generates their key:
    age-keygen
    # Sends you their public key: age1abc123...
    
  2. Update .sops.yaml:
    creation_rules:
      - path_regex: secrets/.*\.yaml$
        age: >-
          age1xcdgea30eam4rnvuk545ye3hukxnzew06xghqmucwrjg8c5hw54qyx9vjv,
          age1abc123...  # New team member
    
  3. Re-encrypt all secrets:
    find secrets -name "*.yaml" -exec sops updatekeys {} \;
    
  4. Commit changes:
    git add secrets/ .sops.yaml
    git commit -m "Add team member to SOPS access"
    

Revoke Access

  1. Remove public key from .sops.yaml
  2. Re-encrypt all secrets with sops updatekeys
  3. Ensure removed key holder deletes their private key

Security Best Practices

Protect Your Private Key

Never commit ~/.config/sops/age/keys.txt to Git! Back up securely:
# Encrypt with GPG
gpg -e -r [email protected] ~/.config/sops/age/keys.txt

# Or use password manager
cat ~/.config/sops/age/keys.txt | pass insert -m sops/age-key

Rotate Secrets Regularly

# Edit secret with new values
sops secrets/database-credentials.yaml

# Apply to cluster
kubectl delete secret db-credentials
sops -d secrets/database-credentials.yaml | kubectl apply -f -

# Restart dependent pods
kubectl rollout restart deployment/myapp

Verify Encryption

Before committing, ensure secrets are encrypted:
# This should show encrypted content
cat secrets/database-credentials.yaml | grep ENC[

# This should fail without private key
SOPS_AGE_KEY_FILE=/dev/null sops -d secrets/database-credentials.yaml

Troubleshooting

”Failed to get the data key”

Cause: Private key not found or doesn’t match Solution:
# Check key location
ls -la ~/.config/sops/age/keys.txt

# Verify it matches public key in .sops.yaml
age-keygen -y ~/.config/sops/age/keys.txt

“no age key found in SOPS_AGE_KEY_FILE”

Cause: SOPS can’t find your key Solution: Set environment variable
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
sops -d secrets/my-secret.yaml
Or add to ~/.bashrc:
export SOPS_AGE_KEY_FILE="${HOME}/.config/sops/age/keys.txt"

“MAC mismatch”

Cause: File corrupted or tampered with Solution: Restore from Git history
git checkout HEAD~1 -- secrets/my-secret.yaml

Key Lost/Corrupted

If you lose your private key:
  1. Regenerate with sops-init
  2. Update .sops.yaml with new public key
  3. Manually re-create secrets (you’ll need plaintext versions)
  4. Encrypt with SOPS
Prevention: Always back up ~/.config/sops/age/keys.txt!

Reference

SOPS Commands

# Encrypt file in-place
sops -e -i secrets/file.yaml

# Decrypt file in-place (dangerous!)
sops -d -i secrets/file.yaml

# Rotate to new keys (after updating .sops.yaml)
sops updatekeys secrets/file.yaml

# Set specific value
sops --set '["key"] "value"' secrets/file.yaml

# Export as different format
sops -d --output-type json secrets/file.yaml

Age Commands

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

# Show public key from private key
age-keygen -y key.txt

# Encrypt file with age directly
age -r age1abc123... -o file.enc file.txt

# Decrypt file with age directly
age -d -i key.txt file.enc > file.txt

Build docs developers (and LLMs) love