ข้ามไปยังเนื้อหา

Sensitive Data and Team Workflows

การตั้ง variable หรือ output เป็น sensitive = true แค่ซ่อนค่านั้นจาก CLI output แต่ค่านั้นยังถูกเก็บแบบ plaintext อยู่ใน state file ดังนั้นการป้องกัน state เองต่างหากคือปัญหาที่แยกออกไป และสำคัญกว่าด้วย

คุณตั้ง 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 ระหว่างทางก็ตาม

เพราะ 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 เข้ามาอย่างปลอดภัยทุกครั้งที่รัน

บทก่อนหน้าใน 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
A sensitive variable still lands in state as plaintext, while a data source pulls a secret at apply time instead of storing it as a variable
การตั้ง variable เป็น sensitive = true ป้องกันอะไรได้จริง
วิธีป้องกัน GCS state bucket ให้ปลอดภัยจริง ๆ ตอน at rest คืออะไร
ทำไมการดึง secret จาก Secret Manager ผ่าน data source มักถูกเลือกใช้มากกว่า Terraform variable สำหรับ credential ที่อยู่ยาว
ทีมหนึ่งมี shared GCS backend ที่มี locking built-in อยู่แล้ว ยังมีช่องว่างอะไรเหลือสำหรับทีมที่กำลังโต