SOPS Encryption¶
SOPS (Secrets OPerationS) is the cornerstone of secret management in RCIIS DevOps, enabling GitOps workflows while maintaining security. This guide covers implementation, best practices, and troubleshooting.
Overview¶
SOPS encrypts YAML, JSON, and other structured data files while preserving their structure and enabling partial encryption. This allows secrets to be stored in Git repositories safely while maintaining the benefits of GitOps workflows.
graph LR
subgraph "Developer Workflow"
PLAIN[Plain Secret] --> EDIT[Edit with SOPS]
EDIT --> ENCRYPT[Encrypted Secret]
ENCRYPT --> GIT[Git Repository]
end
subgraph "Deployment Workflow"
GIT --> ARGO[ArgoCD]
ARGO --> KSOPS[KSOPS Plugin]
KSOPS --> DECRYPT[Decrypt Secret]
DECRYPT --> K8S[Kubernetes Secret]
end
subgraph "Key Management"
AGE[Age Keys]
GPG[GPG Keys]
CLOUD[Cloud KMS]
AGE -.-> EDIT
GPG -.-> EDIT
CLOUD -.-> EDIT
end
SOPS Configuration¶
Global Configuration (.sops.yaml)¶
The repository root contains a .sops.yaml file that defines encryption rules:
creation_rules:
# Rule for all encrypted files
- path_regex: '\.enc\.ya?ml$'
unencrypted_regex: '^(apiVersion|metadata|kind|type)$'
age: >-
age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290km,
age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs7890ab
# Environment-specific rules
- path_regex: 'apps/rciis/secrets/local/.*\.ya?ml$'
unencrypted_regex: '^(apiVersion|metadata|kind|type)$'
age: age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290km
- path_regex: 'apps/rciis/secrets/staging/.*\.ya?ml$'
unencrypted_regex: '^(apiVersion|metadata|kind|type)$'
age: >-
age1staging1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs456def,
age1backup1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs789ghi
- path_regex: 'apps/rciis/secrets/production/.*\.ya?ml$'
unencrypted_regex: '^(apiVersion|metadata|kind|type)$'
kms: >-
arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012
age: >-
age1prod1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs123abc,
age1backup1qyqszqgpqyqszqgpqyqszqgpqyqs789ghi,
age1dr1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs456xyz
Configuration Options¶
| Option | Purpose | Example |
|---|---|---|
path_regex |
Files matching this pattern | '\.enc\.ya?ml$' |
unencrypted_regex |
Fields to leave unencrypted | '^(apiVersion\|metadata\|kind\|type)$' |
encrypted_regex |
Fields to always encrypt | '^(data\|stringData)$' |
age |
Age public keys for encryption | age1abc...xyz |
pgp |
PGP fingerprints | FBC7B9E2A4F9289AC0C1D4843D16CEE4A27381B4 |
kms |
Cloud KMS keys | arn:aws:kms:region:account:key/key-id |
Age Key Management¶
Key Generation¶
Key Storage Best Practices¶
Local Development¶
# Store keys in standard location
mkdir -p ~/.config/sops/age
chmod 700 ~/.config/sops/age
# Set proper permissions
chmod 600 ~/.config/sops/age/keys.txt
# Environment variable (optional)
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
Production Environment¶
- Hardware Security Modules (HSM): For maximum security
- Key Management Services: AWS KMS, Azure Key Vault, Google Cloud KMS
- Secure File Systems: Encrypted filesystems with restricted access
- Backup Strategy: Multiple secure locations with encryption
Working with Encrypted Secrets¶
Creating New Secrets¶
Step 1: Create Plain Secret¶
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: nucleus-config
namespace: rciis-staging
type: Opaque
stringData:
database-url: "postgresql://user:password@localhost:5432/nucleus"
api-key: "super-secret-api-key-12345"
jwt-secret: "jwt-signing-secret-67890"
Step 2: Encrypt with SOPS¶
# Encrypt and save as .enc.yaml
sops -e secret.yaml > secret.enc.yaml
# Remove plain file
rm secret.yaml
# Verify encryption
cat secret.enc.yaml
Step 3: Encrypted Result¶
apiVersion: v1
kind: Secret
metadata:
name: nucleus-config
namespace: rciis-staging
type: Opaque
stringData:
database-url: ENC[AES256_GCM,data:QmVzdCBwcmFjdGljZXM=,iv:abcd1234,tag:efgh5678,type:str]
api-key: ENC[AES256_GCM,data:c2VjdXJlIGFwaS1rZXk=,iv:ijkl9012,tag:mnop3456,type:str]
jwt-secret: ENC[AES256_GCM,data:and0LXNpZ25pbmctc2VjcmV0,iv:qrst7890,tag:uvwx1234,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290km
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxQzJMWE...
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-01-15T10:30:00Z"
mac: ENC[AES256_GCM,data:abc123,iv:def456,tag:ghi789,type:str]
pgp: []
version: 3.8.1
Editing Encrypted Secrets¶
# Edit encrypted file directly
sops secret.enc.yaml
# SOPS will decrypt, open editor, and re-encrypt on save
Decrypting for Debugging¶
# View decrypted content
sops -d secret.enc.yaml
# Decrypt to file (be careful!)
sops -d secret.enc.yaml > secret-decrypted.yaml
# Remember to delete after use!
rm secret-decrypted.yaml
KSOPS Integration¶
KSOPS (Kustomize SOPS) enables Kustomize and ArgoCD to work with SOPS-encrypted files.
Secret Generator Configuration¶
# secret-generator.yaml
apiVersion: viaduct.ai/v1
kind: ksops
metadata:
name: nucleus-secrets
annotations:
config.kubernetes.io/function: |
exec:
path: ksops
files:
- database-config.enc.yaml
- api-keys.enc.yaml
- certificates.enc.yaml
Kustomization Integration¶
# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../../base
generators:
- secret-generator.yaml
generatorOptions:
annotations:
argocd.argoproj.io/compare-options: IgnoreExtraneous
Building with KSOPS¶
# Local build with KSOPS
kustomize build --enable-alpha-plugins --enable-exec .
# Verify secrets are decrypted
kustomize build --enable-alpha-plugins --enable-exec . | grep -A 10 "kind: Secret"
Advanced SOPS Features¶
Partial Encryption¶
Use encrypted_regex and unencrypted_regex to control what gets encrypted:
# .sops.yaml
creation_rules:
- path_regex: '\.enc\.ya?ml$'
# Only encrypt data and stringData fields
encrypted_regex: '^(data|stringData)$'
# Keep metadata readable
unencrypted_regex: '^(apiVersion|metadata|kind|type)$'
age: age1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs3290km
Result:
apiVersion: v1 # Unencrypted
kind: Secret # Unencrypted
metadata: # Unencrypted
name: my-secret
namespace: default
type: Opaque # Unencrypted
stringData: # Encrypted
password: ENC[...]
api-key: ENC[...]
Multiple Recipients¶
# .sops.yaml - Multiple age keys
creation_rules:
- path_regex: 'production/.*\.ya?ml$'
age: >-
age1admin1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs123abc,
age1backup1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs456def,
age1ops1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs789ghi
Key Rotation¶
# Rotate to new age key
sops updatekeys secret.enc.yaml
# Rotate all files in directory
find . -name "*.enc.yaml" -exec sops updatekeys {} \;
# Add new key to existing file
sops -r -i --add-age age1new... secret.enc.yaml
# Remove old key
sops -r -i --rm-age age1old... secret.enc.yaml
Cloud KMS Integration¶
AWS KMS¶
# .sops.yaml
creation_rules:
- path_regex: 'production/.*\.ya?ml$'
kms: arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012
age: age1backup... # Fallback for local development
Azure Key Vault¶
creation_rules:
- path_regex: 'production/.*\.ya?ml$'
azure_kv: https://myvault.vault.azure.net/keys/mykey/version
Google Cloud KMS¶
creation_rules:
- path_regex: 'production/.*\.ya?ml$'
gcp_kms: projects/myproject/locations/global/keyRings/mykeyring/cryptoKeys/mykey
Security Best Practices¶
Key Management¶
- Principle of Least Privilege: Only grant access to necessary keys
- Key Rotation: Regularly rotate encryption keys (quarterly recommended)
- Backup Strategy: Secure backup of keys in multiple locations
- Access Logging: Monitor key usage and access patterns
- Secure Deletion: Properly destroy old keys and temporary files
File Security¶
- Never Commit Plain Secrets: Always encrypt before committing
- Verify Encryption: Check that files are properly encrypted
- Audit Trail: Track who has access to decrypt secrets
- Environment Separation: Use different keys per environment
- Regular Reviews: Periodically review and clean up secrets
Operational Security¶
# Pre-commit hook to prevent unencrypted secrets
#!/bin/bash
# .git/hooks/pre-commit
# Check for potential secrets in staged files
if git diff --cached --name-only | grep -E "\.(yaml|yml)$" | xargs grep -l "password\|secret\|key" | grep -v "\.enc\."; then
echo "ERROR: Potential unencrypted secrets detected!"
echo "Please encrypt secrets with SOPS before committing."
exit 1
fi
Troubleshooting¶
Common Issues¶
SOPS Can't Decrypt Files¶
# Check age key file exists and has correct permissions
ls -la ~/.config/sops/age/keys.txt
# Verify age key matches recipients
age-keygen -y ~/.config/sops/age/keys.txt
# Check SOPS configuration
sops --version
cat .sops.yaml
KSOPS Plugin Not Found¶
# Verify KSOPS is installed and in PATH
which ksops
ksops --help
# Install KSOPS if missing
curl -s https://raw.githubusercontent.com/viaduct-ai/kustomize-sops/master/scripts/install-ksops-archive.sh | bash
Permission Denied Errors¶
# Fix age key permissions
chmod 600 ~/.config/sops/age/keys.txt
chmod 700 ~/.config/sops/age/
# Check file ownership
ls -la ~/.config/sops/age/keys.txt
ArgoCD Sync Failures¶
# Check ArgoCD has access to age keys
kubectl get secret sops-age -n argocd -o yaml
# Verify KSOPS is installed in ArgoCD repo server
kubectl logs -n argocd deployment/argocd-repo-server | grep ksops
# Check ArgoCD application logs
kubectl logs -n argocd deployment/argocd-application-controller
Debugging Commands¶
# Test SOPS encryption/decryption
echo "test: secret" | sops -e --age $(age-keygen -y ~/.config/sops/age/keys.txt) /dev/stdin
# Test KSOPS functionality
echo "apiVersion: v1
kind: Secret
metadata:
name: test
stringData:
key: value" | sops -e --age $(age-keygen -y ~/.config/sops/age/keys.txt) /dev/stdin > test.enc.yaml
# Test with KSOPS
cat > test-generator.yaml << EOF
apiVersion: viaduct.ai/v1
kind: ksops
metadata:
name: test
annotations:
config.kubernetes.io/function: |
exec:
path: ksops
files:
- test.enc.yaml
EOF
# Build with kustomize
kustomize build --enable-alpha-plugins --enable-exec .
Monitoring and Alerting¶
# Example Prometheus rule for SOPS failures
groups:
- name: sops-encryption
rules:
- alert: SOPSDecryptionFailure
expr: increase(argocd_app_sync_total{phase="Failed"}[5m]) > 0
labels:
severity: warning
annotations:
summary: "ArgoCD sync failed, possibly due to SOPS decryption error"
description: "Application {{ $labels.name }} failed to sync"
Migration Strategies¶
From Plain Secrets to SOPS¶
#!/bin/bash
# migrate-secrets.sh
for file in $(find . -name "*.yaml" -exec grep -l "stringData\|data:" {} \;); do
if [[ ! "$file" =~ \.enc\.yaml$ ]]; then
echo "Encrypting $file..."
sops -e "$file" > "${file%.yaml}.enc.yaml"
git add "${file%.yaml}.enc.yaml"
git rm "$file"
fi
done
Key Migration¶
# Update all files to use new age key
find . -name "*.enc.yaml" -exec sops updatekeys {} \;
# Verify all files can be decrypted
find . -name "*.enc.yaml" -exec sops -d {} > /dev/null \;
This comprehensive SOPS implementation ensures that secrets are handled securely throughout the RCIIS DevOps lifecycle while maintaining the benefits of GitOps workflows.