Security considerations and best practices for SOPS GitOps encrypted secrets.
.age/age.key(Private AGE key).envfiles with secrets- Unencrypted backup copies
- Debug output containing decrypted secrets
Private Key Protection:
# Set restrictive permissions on private key
chmod 600 .age/age.keyKey Rotation Strategy:
# Rotate keys regularly (recommended: every 90 days)
# 1. Generate new key pair
age-keygen -o .age/age-new.key
# 2. Update .sops.yaml with new public key
# 3. Re-encrypt only secrets marked for encryption
find . -name "*.yaml" -exec grep -l "def.ms/sops-encrypt.*true" {} \; | xargs git add --renormalize
# 4. Test decryption works
./run-e2e-tests.sh
# 5. Replace old key
mv .age/age-new.key .age/age.keyBackup and Recovery:
# Create encrypted backup of private key
age -p .age/age.key > age-key-backup.age
# Store backup securely (separate from main system)
# Test recovery process regularlyFor production environments with multiple team members:
# .sops.yaml - Multiple recipients
creation_rules:
- path_regex: '.*\.ya?ml$'
age: >-
age1apsxs6l3u4f6z4ejpcf97kd3st234hl6k9zle5y52fmsrv098q9skpemtf,
age1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9,
age1xyz789abc123def456ghi789jkl012mno345pqr678stu901vwx234yz5
encrypted_regex: '^(data|stringData)$'You can update .sops.yaml according to preferences for more granular access.
Benefits:
- Any team member can decrypt
- No single point of failure
- Individual key rotation possible
Setup Process:
# Each team member generates their own key
age-keygen -o .age/team-member-1.key
# Collect public keys and update .sops.yaml
# Re-encrypt only secrets marked for encryption
grep -l "def.ms/sops-encrypt.*true" *.yaml | xargs git add --renormalize
git commit -m "Re-encrypt secrets for all team members"Branch Protection:
# GitHub branch protection rules
branches:
main:
required_reviews: 2
require_code_owner_reviews: true
dismiss_stale_reviews: true
require_status_checks: true
required_status_checks:
- "security-scan"
- "secret-validation"CODEOWNERS Example:
# .github/CODEOWNERS
secrets/production/ @security-team @platform-team
secrets/staging/ @platform-team
.sops.yaml @security-team
sops-*.py @security-teamSOPS Secrets Operator Permissions:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: sops-secrets-operator
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["isindir.github.com"]
resources: ["sopssecrets"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sops-secrets-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sops-secrets-operator
subjects:
- kind: ServiceAccount
name: sops-secrets-operator
namespace: sops-secrets-operator-systemApplication Access (Least Privilege):
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: app-namespace
name: app-secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["app-database-secret", "app-api-secret"]
verbs: ["get"]Secret Scanning:
# Simple pre-commit hook to prevent unencrypted secrets
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
# Check for unencrypted secrets
if git diff --cached | grep -E "(password|secret|key):" | grep -v "ENC\["; then
echo "⚠️ Potential unencrypted secrets detected!"
echo "Add 'def.ms/sops-encrypt: true' annotation to encrypt"
exit 1
fi
EOF
chmod +x .git/hooks/pre-commitLog Secret Access:
# Monitor SOPS operations
export SOPS_LOGGING=1
# Track Git operations on secrets
git log --follow --patch *.yamlKubernetes Secret Monitoring:
# Example: Monitor secret access in Kubernetes
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
omitStages:
- RequestReceived
resources:
- group: ""
resources: ["secrets"]
namespaces: ["production", "staging"]Compromised AGE Key:
- Immediately rotate all AGE keys
- Re-encrypt all secrets with new keys
- Update all deployment environments
- Review and rotate application secrets
Add governance metadata to track secrets:
metadata:
annotations:
def.ms/sops-encrypt: "true"
owner: "platform-team@company.com"
last-rotated: "2024-08-08"
rotation-schedule: "monthly"
purpose: "Database credentials"Git History Protection:
# Prevent history rewriting on protected branches
git config receive.denyNonFastForwards true
git config receive.denyDeletes true
# Sign commits for authenticity
git config commit.gpgsign trueChange Tracking:
# Track who changed what secrets when
git log --pretty=format:"%h %an %ad %s" --date=short -- *.yaml
# Generate compliance reports
git log --since="2024-01-01" --until="2024-12-31" \
--pretty=format:"%h,%an,%ad,%s" --date=iso -- *.yaml > secrets-audit.csv- Generate AGE key:
age-keygen -o .age/age.key - Set key permissions:
chmod 600 .age/age.key - Configure .sops.yaml with public key
- Test with:
./run-e2e-tests.sh local
- Rotate AGE keys every 90 days
- Monitor for unencrypted secrets
- Review Git access logs
- Backup and test key recovery
- Set up multi-key encryption for team
- Configure branch protection
- Set up SOPS operator RBAC
- Implement secret scanning in CI/CD
- Document incident response