SOPS คืออะไร
SOPS (Secrets OPerationS) เป็นเครื่องมือ open source จาก Mozilla สำหรับเข้ารหัส secrets ในไฟล์ config เช่น YAML, JSON, ENV, INI จุดเด่นคือเข้ารหัสเฉพาะ values ไม่ใช่ทั้งไฟล์ ทำให้ยัง diff, review และ merge ได้ใน Git ปกติ
SOPS รองรับ encryption backends หลายตัว ได้แก่ AWS KMS, GCP KMS, Azure Key Vault, HashiCorp Vault, age (modern replacement for PGP) และ PGP เหมาะสำหรับ GitOps workflow ที่ต้องเก็บ secrets ใน Git repository อย่างปลอดภัย
ข้อดีของ SOPS เมื่อเทียบกับ alternatives เช่น Sealed Secrets (Kubernetes only), Vault (complex infrastructure), .env files (unencrypted) คือ SOPS ทำงานได้กับทุก platform ไม่ผูกกับ orchestrator ใดๆ ใช้ง่าย integrate กับ CI/CD ได้ดี
ติดตั้งและตั้งค่า SOPS
Setup SOPS กับ age encryption
# === SOPS Installation ===
# 1. Install SOPS
# macOS:
brew install sops
# Linux:
curl -LO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64
chmod +x sops-v3.9.0.linux.amd64
sudo mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops
# 2. Install age (encryption backend)
# macOS:
brew install age
# Linux:
sudo apt install age
# 3. Generate age key pair
age-keygen -o ~/.sops/age-key.txt
# Output: Public key: age1xxxxxxxxxxxxxxx
# Set environment variable
export SOPS_AGE_KEY_FILE=~/.sops/age-key.txt
# 4. Create SOPS config (.sops.yaml)
cat > .sops.yaml << 'EOF'
creation_rules:
# Encrypt Kubernetes secrets
- path_regex: k8s/secrets/.*\.yaml$
age: >-
age1abc123...,
age1def456...
encrypted_regex: "^(data|stringData)$"
# Encrypt environment files
- path_regex: \.env\.encrypted$
age: >-
age1abc123...
# Encrypt Terraform variables
- path_regex: terraform/.*\.tfvars\.json$
age: >-
age1abc123...
encrypted_regex: "^(password|secret|api_key|token)$"
# Default rule
- age: >-
age1abc123...
EOF
# 5. AWS KMS (alternative backend)
cat >> .sops.yaml << 'EOF'
- path_regex: aws/.*\.yaml$
kms: "arn:aws:kms:ap-southeast-1:123456789:key/uuid-here"
aws_profile: production
EOF
echo "SOPS installed and configured"
เข้ารหัส Secrets ด้วย SOPS
ใช้ SOPS เข้ารหัสไฟล์ต่างๆ
# === Encrypting Secrets ===
# 1. Create plaintext secrets file
cat > secrets.yaml << 'EOF'
database:
host: db.example.com
port: 5432
username: admin
password: super-secret-password
connection_string: postgresql://admin:super-secret-password@db.example.com:5432/mydb
api_keys:
stripe: sk_live_xxxxxxxxxxxx
sendgrid: SG.xxxxxxxxxxxx
github: ghp_xxxxxxxxxxxx
redis:
url: redis://:redis-password@redis.example.com:6379
EOF
# 2. Encrypt with SOPS
sops --encrypt secrets.yaml > secrets.enc.yaml
# Encrypted file looks like:
# database:
# host: ENC[AES256_GCM, data:abc123..., iv:..., tag:...]
# port: ENC[AES256_GCM, data:def456..., iv:..., tag:...]
# username: ENC[AES256_GCM, data:ghi789..., iv:..., tag:...]
# password: ENC[AES256_GCM, data:jkl012..., iv:..., tag:...]
# Keys are visible, only values are encrypted!
# 3. Decrypt
sops --decrypt secrets.enc.yaml
# 4. Edit encrypted file (decrypt → edit → re-encrypt)
sops secrets.enc.yaml
# Opens in $EDITOR with decrypted values
# Saves encrypted automatically
# 5. Encrypt specific keys only
sops --encrypt --encrypted-regex '^(password|secret|api_key|token)$' \
secrets.yaml > secrets.partial.yaml
# 6. Kubernetes Secret
cat > k8s/secrets/db-secret.yaml << 'EOF'
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: admin
password: super-secret-password
connection-string: postgresql://admin:super-secret-password@db:5432/mydb
EOF
sops --encrypt k8s/secrets/db-secret.yaml > k8s/secrets/db-secret.enc.yaml
# 7. .env file
cat > .env.plaintext << 'EOF'
DB_PASSWORD=secret123
API_KEY=sk_live_xxx
JWT_SECRET=my-jwt-secret
REDIS_URL=redis://:pass@localhost:6379
EOF
sops --encrypt .env.plaintext > .env.encrypted
# Decrypt to use:
sops --decrypt .env.encrypted > .env
echo "Secrets encrypted"
SOPS กับ GitOps Workflow
Integrate SOPS เข้ากับ GitOps
#!/usr/bin/env python3
# gitops_sops.py — SOPS GitOps Integration
import json
import logging
from typing import Dict, List
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("gitops")
class SOPSGitOpsWorkflow:
def __init__(self):
self.workflows = {}
def flux_integration(self):
"""Flux CD with SOPS decryption"""
return {
"setup_steps": [
"1. Create age key: age-keygen -o age.agekey",
"2. Create K8s secret: kubectl create secret generic sops-age --from-file=age.agekey -n flux-system",
"3. Configure Flux Kustomization with decryption",
],
"kustomization": {
"apiVersion": "kustomize.toolkit.fluxcd.io/v1",
"kind": "Kustomization",
"metadata": {"name": "app-secrets", "namespace": "flux-system"},
"spec": {
"interval": "10m",
"path": "./k8s/secrets",
"prune": True,
"sourceRef": {"kind": "GitRepository", "name": "app-repo"},
"decryption": {
"provider": "sops",
"secretRef": {"name": "sops-age"},
},
},
},
}
def argocd_integration(self):
"""ArgoCD with SOPS plugin"""
return {
"setup_steps": [
"1. Install SOPS plugin in ArgoCD repo-server",
"2. Configure age key as K8s secret",
"3. Use helm-secrets or kustomize-sops plugin",
],
"plugin_config": {
"apiVersion": "argoproj.io/v1alpha1",
"kind": "Application",
"spec": {
"source": {
"plugin": {
"name": "sops",
"env": [{"name": "SOPS_AGE_KEY_FILE", "value": "/sops/age-key.txt"}],
},
},
},
},
}
def ci_cd_pipeline(self):
"""CI/CD pipeline with SOPS"""
return {
"github_actions": {
"name": "Deploy with SOPS",
"steps": [
{"name": "Checkout", "uses": "actions/checkout@v4"},
{"name": "Install SOPS", "run": "curl -LO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64 && chmod +x sops-v3.9.0.linux.amd64 && sudo mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops"},
{"name": "Decrypt secrets", "run": "sops --decrypt secrets.enc.yaml > secrets.yaml", "env": {"SOPS_AGE_KEY": "}"}},
{"name": "Deploy", "run": "kubectl apply -f secrets.yaml"},
{"name": "Cleanup", "run": "rm -f secrets.yaml"},
],
},
}
workflow = SOPSGitOpsWorkflow()
flux = workflow.flux_integration()
print("Flux:", json.dumps(flux["kustomization"]["spec"]["decryption"], indent=2))
pipeline = workflow.ci_cd_pipeline()
print("\nCI/CD:", json.dumps(pipeline["github_actions"]["steps"][2], indent=2))
Key Management และ Rotation
จัดการ encryption keys
# === Key Management ===
# 1. Multiple Recipients (Team Access)
# ===================================
# Generate key per team member:
# Alice: age-keygen → age1alice...
# Bob: age-keygen → age1bob...
# CI/CD: age-keygen → age1cicd...
# .sops.yaml with multiple recipients:
cat > .sops.yaml << 'EOF'
creation_rules:
- path_regex: .*\.enc\.yaml$
age: >-
age1alice_public_key,
age1bob_public_key,
age1cicd_public_key
EOF
# Anyone with their private key can decrypt
# 2. Key Rotation
# ===================================
# When team member leaves:
# 1. Generate new age key
# 2. Update .sops.yaml (remove old, add new)
# 3. Re-encrypt all files:
find . -name "*.enc.yaml" -exec sops updatekeys {} \;
# This re-encrypts with new key set
# Old key holders can no longer decrypt new versions
# 3. AWS KMS Key Rotation
# ===================================
# AWS KMS supports automatic annual rotation
# aws kms enable-key-rotation --key-id
#
# Manual rotation:
# 1. Create new KMS key
# 2. Update .sops.yaml with new key ARN
# 3. Re-encrypt: sops updatekeys file.enc.yaml
# 4. Schedule old key deletion (7-30 day waiting period)
# 4. Multi-cloud Key Management
cat > .sops.yaml << 'EOF'
creation_rules:
- path_regex: aws/.*$
kms: "arn:aws:kms:ap-southeast-1:123:key/uuid"
- path_regex: gcp/.*$
gcp_kms: "projects/myproject/locations/global/keyRings/sops/cryptoKeys/sops-key"
- path_regex: azure/.*$
azure_keyvault: "https://myvault.vault.azure.net/keys/sops-key/version"
- path_regex: .*$
age: "age1default..."
EOF
# 5. Backup Keys
# ===================================
# CRITICAL: Backup encryption keys securely
# Options:
# - Hardware security module (HSM)
# - Password manager (1Password, Bitwarden)
# - Printed paper key in safe deposit box
# - Split key among team members (Shamir's Secret Sharing)
#
# NEVER store age private key in Git!
# Add to .gitignore:
echo "*.agekey" >> .gitignore
echo ".sops/age-key.txt" >> .gitignore
echo "Key management configured"
Best Practices และ Security
Security best practices สำหรับ SOPS
#!/usr/bin/env python3
# sops_security.py — SOPS Security Best Practices
import json
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("security")
class SOPSSecurity:
def __init__(self):
self.checks = []
def security_checklist(self):
return {
"key_management": [
"ใช้ age แทน PGP (simpler, more secure)",
"แยก key per environment (dev, staging, prod)",
"ใช้ KMS สำหรับ production (AWS/GCP/Azure)",
"Backup private keys อย่างปลอดภัย",
"Rotate keys เมื่อ team member ออก",
"ห้ามเก็บ private key ใน Git",
],
"encryption": [
"ใช้ encrypted_regex เข้ารหัสเฉพาะ sensitive values",
"ตรวจสอบว่า .sops.yaml ครอบคลุมทุก path",
"ใช้ pre-commit hook ตรวจว่าไม่มี plaintext secrets",
"ทดสอบ decrypt ได้จริงก่อน commit",
],
"gitops": [
"เก็บ encrypted files ใน Git ปกติ",
"ใช้ .gitignore สำหรับ decrypted files",
"CI/CD decrypt ด้วย service account key",
"Cleanup decrypted files หลัง deploy",
],
"monitoring": [
"Audit log ทุกครั้งที่ decrypt",
"Alert เมื่อมีการ decrypt นอกเวลาทำงาน",
"Monitor KMS key usage",
"ตรวจสอบ key expiry",
],
}
def pre_commit_hook(self):
return {
"file": ".pre-commit-config.yaml",
"config": {
"repos": [
{
"repo": "local",
"hooks": [
{
"id": "sops-check",
"name": "Check SOPS encryption",
"entry": "bash -c 'for f in $(git diff --cached --name-only | grep -E \"secrets/.*\\.yaml$\"); do if ! grep -q \"sops\" \"$f\"; then echo \"ERROR: $f is not encrypted!\"; exit 1; fi; done'",
"language": "system",
"pass_filenames": False,
},
],
},
],
},
}
security = SOPSSecurity()
checklist = security.security_checklist()
print("Key Management:", json.dumps(checklist["key_management"], indent=2, ensure_ascii=False))
hook = security.pre_commit_hook()
print("\nPre-commit:", json.dumps(hook["config"]["repos"][0]["hooks"][0]["name"], indent=2))
FAQ คำถามที่พบบ่อย
Q: SOPS กับ HashiCorp Vault ต่างกันอย่างไร?
A: SOPS เป็น file-based encryption เข้ารหัส secrets ในไฟล์แล้วเก็บใน Git ไม่ต้อง run server ง่าย เหมาะสำหรับ GitOps workflow Vault เป็น secrets management platform ต้อง run server จัดการ secrets แบบ dynamic (auto-rotate), access control ละเอียด, audit logging ครบ ซับซ้อนกว่า เหมาะสำหรับ enterprise ที่ต้องการ centralized secrets management ทีมเล็กเริ่มด้วย SOPS ทีมใหญ่พิจารณา Vault ใช้ร่วมกันได้ SOPS encrypt files ใน Git, Vault จัดการ dynamic secrets
Q: age กับ PGP เลือกอันไหน?
A: แนะนำ age เสมอ age ออกแบบมาเพื่อแทน PGP ง่ายกว่ามาก key เป็น single string ไม่มี key server, web of trust, key expiry ที่ซับซ้อน PGP มี legacy support กว้างกว่า แต่ซับซ้อนมาก key management ยุ่งยาก มี pitfalls เยอะ SOPS รองรับทั้งคู่ แต่ official recommendation คือใช้ age สำหรับ local keys และ cloud KMS สำหรับ production
Q: ถ้า private key หาย decrypt ได้ไหม?
A: ไม่ได้ ถ้า private key หายและไม่มี backup secrets ที่เข้ารหัสไว้จะ decrypt ไม่ได้ตลอดไป ดังนั้นต้อง backup keys อย่างปลอดภัยเสมอ ถ้าใช้ multiple recipients อย่างน้อย 1 คนในทีมยังมี key ก็ decrypt ได้ ถ้าใช้ cloud KMS (AWS/GCP/Azure) key อยู่ใน cloud provider ไม่หายง่าย แนะนำใช้ทั้ง age (backup) และ cloud KMS (primary) เป็น double protection
Q: SOPS ทำงานกับ Helm Charts อย่างไร?
A: ใช้ helm-secrets plugin ติดตั้ง helm plugin install https://github.com/jkroepke/helm-secrets เก็บ values file เป็น encrypted (secrets.yaml → secrets.enc.yaml) deploy ด้วย helm secrets install myapp ./chart -f secrets.enc.yaml plugin จะ decrypt อัตโนมัติก่อน helm install ใช้กับ ArgoCD ได้ด้วย โดย config ArgoCD ให้ใช้ helm-secrets เป็น plugin
