SOPS เก็บ Secret ไว้ใน Git ได้โดยที่ไม่ต้องกังวลว่า Secret จะหลุด
19 Aug 2023 19:00
Written by: Yosapol Jitrak
ปกติเราจะโดนห้ามไม่ให้ 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 กันนะครับ