SiamCafe · Blog
SOPS Encryption Pod Scheduling — จัดการ Secrets
บทความ

SOPS Encryption Pod Scheduling — จัดการ Secrets

เผยแพร่ 28 พฤษภาคม 2569

SOPS + Pod Scheduling

SOPS Encryption Pod Scheduling Kubernetes Secrets GitOps Flux ArgoCD age AWS KMS Node Affinity Taints RBAC Secret Store CSI

ToolEncryptionGitOpsKey ManagementComplexity
SOPSEncrypt Values in YAML/JSONFlux native / ArgoCD pluginage KMS Vault PGPปานกลาง
Sealed SecretsEncrypt whole Secretkubectl applyController Keyต่ำ
External SecretsSync from External StoreOperatorAWS SM / Vault / GCP SMปานกลาง
Vault CSIMount from VaultCSI DriverHashiCorp Vaultสูง

SOPS Configuration

# === SOPS Encrypt/Decrypt ===

# Install
# brew install sops age
# # or download from GitHub releases

# Generate age key
# age-keygen -o key.txt
# # Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# # Private key in key.txt: AGE-SECRET-KEY-xxx

# .sops.yaml (Project Root)
# creation_rules:
#   - path_regex: secrets\..*\.yaml$
#     key_groups:
#       - age:
#           - age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
#   - path_regex: production/secrets\..*\.yaml$
#     key_groups:
#       - age:
#           - age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
#         aws_kms:
#           - arn: arn:aws:kms:ap-southeast-1:123456:key/xxx

# Encrypt
# sops --encrypt secrets.dev.yaml > secrets.dev.enc.yaml
# # or with .sops.yaml: sops --encrypt --in-place secrets.dev.yaml

# Decrypt
# export SOPS_AGE_KEY_FILE=key.txt
# sops --decrypt secrets.dev.enc.yaml

# Edit (decrypt → edit → re-encrypt automatically)
# sops secrets.dev.enc.yaml

from dataclasses import dataclass

@dataclass
class SOPSConfig:
    setting: str
    value: str
    purpose: str
    security_note: str

configs = [
    SOPSConfig(".sops.yaml creation_rules",
        "path_regex + key_groups",
        "กำหนด Rule Encrypt อัตโนมัติตาม Path",
        "แยก Key ตาม Environment dev/staging/prod"),
    SOPSConfig("age key",
        "age1xxx (public) AGE-SECRET-KEY-xxx (private)",
        "Encrypt/Decrypt Key ง่าย เร็ว",
        "เก็บ Private Key ปลอดภัย ไม่ Commit ไป Git"),
    SOPSConfig("AWS KMS",
        "arn:aws:kms:region:account:key/id",
        "ใช้ AWS KMS จัดการ Key ไม่ต้องเก็บ Key เอง",
        "IAM Role กำหนดสิทธิ์ Encrypt/Decrypt"),
    SOPSConfig("Multiple Keys",
        "key_groups with age + kms",
        "Encrypt ด้วยหลาย Key ถ้า Key หนึ่งหาย ใช้อีกตัว",
        "Production ควรใช้อย่างน้อย 2 Key"),
    SOPSConfig("encrypted_regex",
        "^(data|stringData)$",
        "Encrypt เฉพาะ Field ที่ต้องการ",
        "ลด Noise ไม่ Encrypt metadata"),
]

print("=== SOPS Config ===")
for c in configs:
    print(f"  [{c.setting}] = {c.value}")
    print(f"    Purpose: {c.purpose}")
    print(f"    Security: {c.security_note}")

GitOps + Flux Integration

# === Flux CD + SOPS ===

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

# Flux Kustomization with SOPS decryption
# apiVersion: kustomize.toolkit.fluxcd.io/v1
# kind: Kustomization
# metadata:
#   name: my-app
#   namespace: flux-system
# spec:
#   interval: 5m
#   path: ./clusters/production
#   prune: true
#   sourceRef:
#     kind: GitRepository
#     name: my-repo
#   decryption:
#     provider: sops
#     secretRef:
#       name: sops-age

# Encrypted Secret in Git
# apiVersion: v1
# kind: Secret
# metadata:
#   name: app-secrets
# stringData:
#   DATABASE_URL: ENC[AES256_GCM, data:xxx, type:str]
#   API_KEY: ENC[AES256_GCM, data:yyy, type:str]
# sops:
#   age:
#     - recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
#       enc: |
#         -----BEGIN AGE ENCRYPTED FILE-----

@dataclass
class GitOpsWorkflow:
    step: int
    action: str
    who: str
    command: str
    security: str

workflow = [
    GitOpsWorkflow(1, "Developer แก้ไข Secret",
        "Developer",
        "sops edit secrets.enc.yaml",
        "Decrypt ใน Memory แก้ Re-encrypt อัตโนมัติ"),
    GitOpsWorkflow(2, "Commit Encrypted File",
        "Developer",
        "git add secrets.enc.yaml && git commit && git push",
        "Git เก็บ Encrypted Values CI ไม่เห็น Plain Text"),
    GitOpsWorkflow(3, "Flux Detect Change",
        "Flux Controller",
        "GitRepository reconcile (poll/webhook)",
        "Flux ดึง Encrypted File จาก Git"),
    GitOpsWorkflow(4, "Flux Decrypt Secrets",
        "Flux + SOPS",
        "Decrypt ด้วย age key ใน sops-age Secret",
        "Decrypt เกิดขึ้นใน Flux Controller เท่านั้น"),
    GitOpsWorkflow(5, "Apply to Kubernetes",
        "Flux Controller",
        "kubectl apply (Decrypted Secret)",
        "Secret ถูกเก็บใน etcd (Encrypted at Rest)"),
    GitOpsWorkflow(6, "Pod Mount Secret",
        "Kubelet",
        "Mount Secret เป็น Volume หรือ Env",
        "Pod อ่าน Plain Text Secret ได้"),
]

print("=== GitOps Workflow ===")
for w in workflow:
    print(f"  Step {w.step}: {w.action} ({w.who})")
    print(f"    Command: {w.command}")
    print(f"    Security: {w.security}")

Pod Scheduling & Security

# === Secure Pod Scheduling ===

# Node Affinity for Sensitive Workloads
# spec:
#   affinity:
#     nodeAffinity:
#       requiredDuringSchedulingIgnoredDuringExecution:
#         nodeSelectorTerms:
#           - matchExpressions:
#               - key: security-tier
#                 operator: In
#                 values: ["high"]
#   tolerations:
#     - key: "sensitive"
#       operator: "Equal"
#       value: "true"
#       effect: "NoSchedule"

# RBAC - Least Privilege for Secrets
# apiVersion: rbac.authorization.k8s.io/v1
# kind: Role
# metadata:
#   name: secret-reader
#   namespace: my-app
# rules:
#   - apiGroups: [""]
#     resources: ["secrets"]
#     resourceNames: ["app-secrets"]  # specific secret only
#     verbs: ["get"]

@dataclass
class SecurityControl:
    control: str
    mechanism: str
    config: str
    protects_against: str

controls = [
    SecurityControl("Node Affinity",
        "Schedule sensitive Pods on labeled Nodes only",
        "nodeAffinity: security-tier=high",
        "Secrets ไม่ถูกส่งไป Node ที่ไม่ปลอดภัย"),
    SecurityControl("Taints/Tolerations",
        "Dedicated Nodes for sensitive workloads",
        "taint: sensitive=true:NoSchedule",
        "Non-sensitive Pods ไม่รันบน Secure Nodes"),
    SecurityControl("RBAC Least Privilege",
        "ServiceAccount อ่านเฉพาะ Secret ที่ต้องการ",
        "Role: get specific secret by resourceNames",
        "Pod อื่นอ่าน Secrets ของ App นี้ไม่ได้"),
    SecurityControl("Network Policy",
        "จำกัด Network ของ Pod ที่ใช้ Secrets",
        "NetworkPolicy: ingress/egress rules",
        "ป้องกัน Data Exfiltration"),
    SecurityControl("Pod Security Standards",
        "Restricted Mode สำหรับ Sensitive Pods",
        "PodSecurity: restricted",
        "ป้องกัน Container Escape Privilege Escalation"),
    SecurityControl("etcd Encryption",
        "Encrypt Secrets at Rest ใน etcd",
        "EncryptionConfiguration: aescbc/secretbox",
        "ถ้า etcd ถูก Compromise Secrets ยังปลอดภัย"),
]

print("=== Security Controls ===")
for s in controls:
    print(f"  [{s.control}] {s.mechanism}")
    print(f"    Config: {s.config}")
    print(f"    Protects: {s.protects_against}")

เคล็ดลับ

  • Multiple Keys: ใช้อย่างน้อย 2 Key (age + KMS) สำหรับ Production
  • .sops.yaml: กำหนด Rule ชัดเจน แยก Key ตาม Environment
  • Flux: ใช้ Flux SOPS Integration Decrypt อัตโนมัติ
  • RBAC: ตั้ง Least Privilege resourceNames เฉพาะ Secret ที่ต้องการ
  • Rotate: หมุน Key เป็นระยะ sops updatekeys re-encrypt ทุกไฟล์

SOPS คืออะไร

Secrets OPerationS Encrypt Decrypt YAML JSON age AWS KMS Vault PGP Git Safe Encrypt Values Only .sops.yaml Multiple Keys Mozilla