Sensitive Data and Team Workflows
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”การตั้ง variable หรือ output เป็น sensitive = true แค่ซ่อนค่านั้นจาก CLI output แต่ค่านั้นยังถูกเก็บแบบ plaintext อยู่ใน state file ดังนั้นการป้องกัน state เองต่างหากคือปัญหาที่แยกออกไป และสำคัญกว่าด้วย
sensitive = true ทำอะไรจริง ๆ
หัวข้อที่มีชื่อว่า “sensitive = true ทำอะไรจริง ๆ”คุณตั้ง variable หรือ output เป็น sensitive ได้
variable "db_password" { type = string sensitive = true}
output "db_password" { value = var.db_password sensitive = true}พอตั้งแบบนี้แล้ว terraform plan และ terraform apply จะ print (sensitive value) แทนที่จะโชว์ password จริงใน terminal และค่านั้นถูก redact จาก CI log ในแบบเดียวกัน นี่เป็น feature ที่มีประโยชน์จริง เพราะหยุดไม่ให้ secret ไปโผล่ใน build log ที่คนสิบกว่าคนอ่านได้ หรือขึ้นจอตอน pairing session
แต่ก็เป็นสิ่งที่คนเข้าใจผิดบ่อยเหมือนกัน sensitive = true เป็น feature ระดับ display-redaction ไม่ใช่ feature ด้าน encryption ค่าจริงยังถูกเขียนลง terraform.tfstate แบบ plaintext เป๊ะ ๆ ตามที่คุยไว้ในบทแรกของ module นี้ ใครก็ตามที่อ่าน state file ดิบได้ ก็อ่าน password ได้เหมือนกัน ไม่ว่าจะตั้ง resource หรือ variable กี่ตัวเป็น sensitive ระหว่างทางก็ตาม
ป้องกัน state file ให้ปลอดภัยจริง ๆ
หัวข้อที่มีชื่อว่า “ป้องกัน state file ให้ปลอดภัยจริง ๆ”เพราะ redaction ไม่ได้ป้องกันไฟล์ตอน at rest การป้องกันจริงต้องเกิดที่ storage layer แทน
- พึ่ง server-side encryption ของ Cloud Storage สำหรับ state bucket GCS เข้ารหัส object แบบ at rest ให้เป็น default อยู่แล้ว และคุณเพิ่ม customer-managed Cloud KMS key ทับเข้าไปได้อีกชั้นผ่าน argument
kms_encryption_keyของ backend ถ้าองค์กรคุณต้องการ - จำกัดว่าใครอ่าน bucket ได้ด้วย IAM ให้แคบที่สุดเท่าที่ทำได้ ให้
roles/storage.objectViewerบน state bucket ตัวที่ระบุเจาะจง กับ principal ที่ระบุเจาะจงที่ต้องใช้จริง ๆ — service account ของ CI, ทีม platform เล็ก ๆ — แทนที่จะให้ access กว้าง ๆ ระดับ project ปฏิบัติกับ state file เหมือนเป็น secret เพราะในทางปฏิบัติแล้วก็คือ secret ตัวหนึ่ง
ถ้าเป็นไปได้ หลีกเลี่ยงการใส่ secret ที่อยู่ยาว ๆ ลงใน Terraform configuration หรือ state เลย แทนที่จะให้ variable เก็บ password ดิบ ๆ ให้ดึงค่านั้นมาตอน apply จาก secrets manager แทน
data "google_secret_manager_secret_version" "db_password" { secret = "db-password"}
resource "google_sql_user" "app" { name = "app_admin" instance = google_sql_database_instance.main.name password = data.google_secret_manager_secret_version.db_password.secret_data}วิธีนี้ไม่ได้ทำให้ค่านั้นหายไปจาก state ทั้งหมด — attribute ของ resource ที่ Terraform ตั้งไว้ก็ยังถูกบันทึกเหมือนเดิมตลอดมา สิ่งที่เปลี่ยนคือที่ที่ source of truth ระยะยาวของ secret นั้นอยู่ นั่นคือ Secret Manager ที่มี access policy, audit logging และเรื่อง rotation ของตัวเอง แทนที่จะเป็น Terraform variable ที่ต้องมีใครสักคนจำไว้ให้ pass เข้ามาอย่างปลอดภัยทุกครั้งที่รัน
จุดที่ shared backend เริ่มไม่พอแล้ว
หัวข้อที่มีชื่อว่า “จุดที่ shared backend เริ่มไม่พอแล้ว”บทก่อนหน้าใน module นี้พา state ไปอยู่บน shared GCS backend ที่มี locking built-in มาให้ ซึ่งแก้ปัญหาทีมที่แย่ที่สุดไปแล้ว — local file ที่ต่างกันไปเรื่อย ๆ และการเขียนพร้อมกันที่ corrupt กัน แต่ยังไม่ได้แก้ปัญหาว่าทีมที่กำลังโตควรทำงานร่วมกันยังไงให้ครบ ถ้าทุกคนยังรัน terraform apply จาก laptop ตัวเองเมื่อไหร่ก็ตามที่อยากรัน การ apply พร้อมกันก็แค่ต่อคิวกันหลัง lock แทนที่จะชนกัน และก็ยังไม่มีจุดตามธรรมชาติที่คนที่สอง review การเปลี่ยนแปลงก่อนที่จะมีผลจริงกับ infrastructure
ช่องว่างนั้น — ที่ที่ review ได้ audit ได้ สำหรับให้การเปลี่ยนแปลงถูก approve ก่อน apply จะรันกับ shared state — คือสิ่งที่ CI/CD สำหรับ infrastructure มาปิดช่องว่างนี้พอดี และเรื่องนี้จะพูดถึงในบทหลัง ๆ ของคอร์สนี้
flowchart LR varInput["variable db_password (sensitive = true)"] -->|redacted in CLI output| cli["terraform plan / apply output"] varInput -->|value still written| state["terraform.tfstate (plaintext)"] secretManager["Secret Manager secret version"] -->|read via data source at apply time| resource["google_sql_user.app"] resource --> state