Jitrak Blog

SOPS: Secrets OPerationS

SOPS เก็บ Secret ไว้ใน Git ได้โดยที่ไม่ต้องกังวลว่า Secret จะหลุด

19 Aug 2023 19:00

Written by: Yosapol Jitrak

Tags:

Encrypt

Key management system

AWS KMS

GCP KMS

ปกติเราจะโดนห้ามไม่ให้ Commit secret เข้าไปใน Git เพราะกลัว Secret ต่าง ๆ หลุดออกไป แต่จริง ๆ แล้วเราสามารถเก็บ Secret ไว้ใน Git ได้ โดยที่ไม่ต้องกังวลว่า Secret จะหลุดออกไป บทความนี้จะมาแนะนำ SOPS ซึ่งเป็นเครื่องมือที่ช่วยในการ Encrypt และ Decrypt โดยเราจะใช้ SOPS ในการเข้ารหัส Secret และทำให้สามารถเก็บ Secret ไว้ใน Git ได้เลย

SOPS จะทำการ Encrypt และ Decrypt ได้ผ่าน Key management system (KMS) อย่าง AWS KMS, GCP KMS, Azure Key Vault, age, และ PGP โดยส่วนตัวผมมีประสบการ์ณใช้แค่ AWS KMS และ GCP KMS เท่านั้น ซึ่งในบทความนี้ผมจะยกตัวอย่างแค่ 2 ตัวนี้ครับ แต่ถ้าใครสนใจใช้ KMS อื่น ๆ ก็สามารถอ่านเอกสารของ SOPS ได้ที่ GitHub SOPS: Secrets OPerationS

ก่อนที่เราจะใช้งาน SOPS ได้เราจะต้องสร้าง KMS ของเราก่อน ซึ่งผมจะไม่กล่าวถึงวิธีในการสร้าง KMS ณ ที่นี้ เพราะว่ามีวิธีการสร้างที่หลากหลาย ซึ่งในยุคนี้แล้วด้วย Infra as Code (IaC) มีหลายตัวเลือก ให้เราเลือกใช้ ซึ่งผมก็ไม่รู้คุณผู้อ่านจะใช้ตัวไหนอยู่ ดังนั้นผมจึงไม่กล่าวถึงวิธีการสร้าง KMS ในบทความนี้ และนอกจากนี้เราจะต้องมีสิทธิ์ในการใช้งาน KMS ผ่าน Command-line ด้วยนะครับ

ทำการติดตั้ง SOPS ผ่าน Homebrew

brew install sops

เราลองสร้างไฟล์ที่เก็บ Secret แบบยังเป็น Plain text อยู่ โดยยกตัวอย่างเป็น YAML (จะใช้ไฟล์อื่น อย่าง JSON, dotenv ก็ได้)

โดยชื่อไฟล์ตั้งว่า secrets.dec.yaml

mySecret: abc123456
myPassword: Qwerty

เราจะทำการ Encrypt ไฟล์นี้ผ่าน AWS KMS โดยใช้คำสั่ง

sops --kms arn:aws:kms:ap-southeast-1:123227328132:key/ee4a84b2-6b1a-4800-8d57-6a5aed656a6c --encrypt secrets.dec.yaml > secrets.enc.yaml

ผลลัพธ์จะเก็บอยู่ในไฟล์ secrets.enc.yaml ลอง cat ไฟล์ secrets.enc.yaml ดู

cat secrets.enc.yaml

ผลลัพธ์ที่ได้จะเป็นดังนี้

mySecret: ENC[AES256_GCM,data:/GMhEKao5jLT,iv:9o6FQVlVrgKDjNGE7RhsnkoxinHUd/8sltDERaLyiic=,tag:/ZCTO6mTSzXMzuZ/XSnaSw==,type:str]
myPassword: ENC[AES256_GCM,data:h4GmhG+3,iv:Lzqh6XHjcAiCljTvrz35yLXi3L/4ZDu5D5VHgz/7xkI=,tag:+xIhvKkG9hsWiCeFx7/X6Q==,type:str]
sops:
  kms:
    - arn: arn:aws:kms:ap-southeast-1:123227328132:key/ee4a84b2-6b1a-4800-8d57-6a5aed656a6c
      created_at: '2023-08-19T11:48:36Z'
      enc: AQICAHhtzyZ03Tih0sQQxakU4RyiUWLFVseWF/qHPxNjc4MOhwEfcFxhHF3ZMGwpsSf7TevEAAAAfjB8BgkqhkiG9w0BBwagbzBtAgEAMGgGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMouQ9XXwXgvyhB8C8AgEQgDuWwVGZMU8R4z/PcrTp3kY5z5M+0nNdUY5AOduZcVvyhU/dScDFhNEvDcHxEO5U7SGHE/tf7sj/HOosYw==
      aws_profile: ''
  gcp_kms: []
  azure_kv: []
  hc_vault: []
  age: []
  lastmodified: '2023-08-19T11:48:36Z'
  mac: ENC[AES256_GCM,data:yjWliUZsNG1PxknMF/tzDpK9OwzCYeubnryARSYYp8JHYLworbqKMkYb3yuRbG+GxnjxbF5ZJ8A6py/o5TH5+mllNDnliAk3bX1wfbN+tNlJfEVFITX4nmqLXGfGyf4DFRoISa3c/S0/7YePG9N/2+VDflK8rgwhMUDCji6mL7I=,iv:ZijgTF+FCziscoRVFgKSNRpXb1807R+RplYVmziUgBE=,tag:SXEo4wx8DuV1RUH4+h62HQ==,type:str]
  pgp: []
  unencrypted_suffix: _unencrypted
  version: 3.7.3

ถ้าเป็นของ GCP KMS จะใช้คำสั่ง

sops --gcp-kms projects/jitrak/locations/global/keyRings/sops/cryptoKeys/sops-key -e secrets.dec.yaml > secrets.enc.gcp.yaml

ผลลัพธ์จะเก็บอยู่ในไฟล์ secrets.enc.gcp.yaml ลอง cat ไฟล์ secrets.enc.gcp.yaml ดู

cat secrets.enc.gcp.yaml

ผลลัพธ์ที่ได้จะเป็นดังนี้

mySecret: ENC[AES256_GCM,data:y6tHANBWVWXO,iv:dRIdyttAEW6ZFOcqmowxgjtK0ZiCW0yIgM6o/IOoT5o=,tag:Ye7Etl48e4q7peY8Rj3J6Q==,type:str]
myPassword: ENC[AES256_GCM,data:wX43gMPu,iv:JGG2ZuoVtnC7dQ430EZU0chO5Aq9C+MjqRmcbU9geuk=,tag:dJdncu1QffUEh0Eri12SqQ==,type:str]
sops:
  kms: []
  gcp_kms:
    - resource_id: projects/jitrak/locations/global/keyRings/sops/cryptoKeys/sops-key
      created_at: '2023-08-19T11:50:02Z'
      enc: CiQA/kMbNcKRm2KJVH3S5oDRHh47N6JEdGqLkM1FtMu7WzOCJHsSSQDyO+fZNOsIzjc/Vjl1SgLYNJClxnAdxlG3wjWat77mMWTAXhxlEXtHb3/Lbp2OC+jrKLJwVmUjLCmKlyGFFV5DZFgF/K0c+/Q=
  azure_kv: []
  hc_vault: []
  age: []
  lastmodified: '2023-08-19T11:50:02Z'
  mac: ENC[AES256_GCM,data:oY4O0vvwf4z2tLAzCSUfvRQDR0zsIZFT2kBu4VGvsmtl8xuYOO27EcBjFt9i0C5RYRFhCcmX2Vp+xG+xTWOf+Tc3xtIuKgpqKuokYR+mAvU5Zh5nkBAt0F3Y5dDDizhRDBk3TuXwDKn8wA6/9ohe8uRPltb8c9yLkVeioz7WL7Q=,iv:OAz3UmmAyMpSxm93D3NLyw/pyA1WOacLMOCzTq7FZ6w=,tag:QKFhmlZfNXDIMjoVFTOuhw==,type:str]
  pgp: []
  unencrypted_suffix: _unencrypted
  version: 3.7.3

จะเห็นว่า Command ที่เราพิมพ์ เราจะต้องคอยมาระบุ KMS ที่เราจะใช้อยู่เสมอ ซึ่งเวลาไปใช้งานจริงแล้วจะไม่ค่อยสะดวกเท่าไหร่ เราสามารถสร้างไฟล์ Config ให้รู้ว่าจะไปใช้ KMS จากไหนได้ครับ

สร้างไฟล์ชื่อ .sops.yaml อยู่ที่ Root ของ Git repository

creation_rules:
  - path_regex: aws/*
    kms: arn:aws:kms:ap-southeast-1:123227328132:key/ee4a84b2-6b1a-4800-8d57-6a5aed656a6c
  - path_regex: gcp/*
    gcp_kms: projects/pico-monster-84632/locations/global/keyRings/sops-16fbe26/cryptoKeys/sops-0b7e8b4

อธิบายครับว่า ถ้าเจอ Path ที่ Match กับ Regex ที่เรากำหนด จะใช้ KMS ตามที่เรากำหนดไว้ ยกตัวอย่างตาม .sops.yaml ข้างบนนี้ ถ้าเจอ Path ที่อยู่ใน aws directory จะใช้ KMS ของ AWS แต่ถ้าเจอ Path ที่อยู่ใน gcp directory จะใช้ KMS ของ GCP อันนี้ขึ้นอยู่กับว่าเราจะเอาประยุกต์ใช้อย่างไรนะครับ ยกตัวอย่างเคสใช้งานจริงก็จะเป็นการใช้ Key แยก Environment ครับ

sops -e aws/secrets.dec.yaml > aws/secrets.enc.yaml
sops -e gcp/secrets.dec.yaml > gcp/secrets.enc.yaml

หรือเราย้าย Directory เข้าไปเลยก็ได้นะครับ

cd aws
sops -e secrets.dec.yaml > secrets.enc.yaml

cd ..
cd gcp
sops -e secrets.dec.yaml > secrets.enc.yaml

ถัดไปเราจะลอง Decrypt ดูครับ

sops -d secrets.enc.yaml > secrets-1.dec.yaml

ลองดูเนื้อหาไฟล์ที่ถูก Decrypt ว่ากลับมาถูกต้องเหมือนเดิมหรือเปล่า

cat secrets-1.dec.yaml

เสร็จแล้วลบไฟล์ที่ลอง Decrypt ด้วยนะครับ

rm secrets-1.dec.yaml

เสร็จแล้ว เราอย่าลืมทำ Git ignore ไฟล์ที่เป็น Plain text ด้วยนะครับ

echo "**/secrets.dec.yaml" >> .gitignore

หลายคนอาจจะสังเกตเห็นว่าเราสามารถใช้ option —encrypt หรือ -e แทนกันได้ และ —decrypt หรือ -d แทนกันได้นะครับ

เสริม เราสามารถใช้นำ SOPS ไปใช้กับ IaC อย่าง Terragrunt ได้นะครับ

secrets = yamldecode(sops_decrypt_file("${get_terragrunt_dir()}/secrets.enc.yaml"))

ตัวอย่างที่น่าจะได้ใช้งานบ่อย ๆ อย่าง .env ที่มักจะมี Credential อย่าง Password ของ Database เป็นต้นครับ วิธีนี้ทำให้เราสามารถเก็บ Secret ไว้ใน Git Repository แล้วทำให้สามารถแชร์กับทีมเราได้

เพียงเท่านี้เราก็สามารถ Commit secret เข้าไปใน Git repository ได้แล้วครับ แต่อย่าลืม Encrypt และทำ Git ignore กันนะครับ