Sensitive Data and Team Workflows
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”การตั้งค่าใดค่าหนึ่งเป็น sensitive = true แค่ซ่อนค่านั้นจาก terminal ของคุณ แต่ค่านั้นก็ยังนั่งอยู่แบบ plaintext ใน state file เหมือนเดิม ทางแก้จริง ๆ คือปกป้อง state backend เอง และเก็บ secret ที่อยู่ยาวนานให้ห่างจาก Terraform ไปเลยตั้งแต่แรก
sensitive = true ทำอะไรจริง ๆ
หัวข้อที่มีชื่อว่า “sensitive = true ทำอะไรจริง ๆ”ทั้ง variable และ output block รับ argument sensitive ได้
variable "database_password" { type = string sensitive = true}
output "db_connect_string" { value = "Server=${azurerm_postgresql_flexible_server.main.fqdn};Password=${var.database_password}" sensitive = true}พอตั้ง sensitive = true แล้ว Terraform จะปิดบังค่านั้นทุกจุดที่ค่านั้นจะโผล่ขึ้นมาบน terminal หรือใน CI log — โดย print (sensitive value) ใน output ของ terraform plan และ terraform apply แทนสตริงจริง และจะไม่ยอม print ค่าดิบจาก terraform output เว้นแต่คุณจะขอตรง ๆ ด้วย terraform output db_connect_string นี่คือ guardrail ที่มีประโยชน์จริง เพราะช่วยกันไม่ให้ password เลื่อนผ่านหน้าจอใน shared CI log หรือ terminal ที่กำลัง screen-share อยู่โดยไม่ตั้งใจ
ประเด็นที่ควรพูดให้ชัดเจนไปเลยคือ sensitive = true เป็นฟีเจอร์ด้าน display ไม่ใช่ฟีเจอร์ด้านการเข้ารหัส ค่าถูกคำนวณ เก็บ และ diff โดย Terraform เหมือนกันทุกประการไม่ว่าคุณจะตั้ง sensitive หรือไม่ — ค่ายังไปอยู่ใน terraform.tfstate แบบ plaintext เต็ม ๆ ทั้ง tag ด้วย ใครก็ตามที่อ่านไฟล์ state ดิบได้ ไม่ว่าจากใน Azure Storage container, .tfstate บนเครื่อง local หรือจาก terraform state pull ก็เห็น password ตัวนั้นนั่งอยู่ใน JSON ตรง ๆ ไม่ว่าจะมี flag sensitive หรือไม่ก็ตาม
terraform state pull | grep -A3 db_connect_string# "db_connect_string": {# "value": "Server=app-db.postgres.database.azure.com;Password=S3cretPassword!",# "type": "string"ปกป้อง state ที่ rest
หัวข้อที่มีชื่อว่า “ปกป้อง state ที่ rest”เพราะการตั้งค่าเป็น sensitive ไม่ได้เอาค่านั้นออกจาก state การป้องกันจริง ๆ ต้องเกิดขึ้นที่ storage layer ให้ปฏิบัติกับ terraform.tfstate เหมือนเป็น secret ตัวหนึ่ง เหมือนที่คุณปฏิบัติกับไฟล์ .env ที่เต็มไปด้วย production credential
- Encryption at rest เปิดอยู่โดย default อยู่แล้ว Azure Storage เข้ารหัสทุก object ที่ rest โดยอัตโนมัติ รวมถึงทุก version ของ state blob ของคุณ ไม่ว่าใครจะเรียก storage account API ก็ตาม คุณไม่ต้องเปิดอะไรเพิ่มเอง — นี่คือ baseline ไม่ใช่คำตอบทั้งหมด
- จำกัดว่าใครอ่าน container นี้ได้ Azure RBAC คือสิ่งที่หยุดคนที่ไม่มีสิทธิ์ไม่ให้เข้าถึง plaintext ได้จริง ๆ ไม่ว่า encryption at rest จะมีอยู่หรือไม่ก็ตาม — encryption at rest ป้องกันคนที่ไปแตะ disk ดิบโดยตรง ไม่ใช่ป้องกันคนที่มี Azure identity ถูกต้องแล้วเรียก storage API จำกัด access ให้แคบด้วย role อย่าง
Storage Blob Data Readerที่ assign แค่ระดับ resource group หรือ storage account ที่เก็บ state เท่านั้น ไม่ใช่ทั้ง subscription
resource "azurerm_role_assignment" "state_reader" { scope = azurerm_storage_account.tfstate.id role_definition_name = "Storage Blob Data Reader" principal_id = data.azuread_group.platform_team.object_id}ถ้าเป็นไปได้ วิธีที่ดีกว่าคือหลีกเลี่ยงปัญหานี้ไปเลย อย่าใส่ secret ที่อยู่ยาวนานลงใน Terraform variable ตั้งแต่แรก ถ้าคุณดึงค่าตอน apply ได้แทน data source ที่อ่านจาก Azure Key Vault จะดึงค่าปัจจุบันตอนที่ Terraform รัน โดยที่คุณไม่ต้องพิมพ์ secret ลงไฟล์ .tf หรือ .tfvars เลย
data "azurerm_key_vault_secret" "db_password" { name = "app-db-password" key_vault_id = data.azurerm_key_vault.main.id}
resource "azurerm_postgresql_flexible_server" "main" { name = "app-db" resource_group_name = azurerm_resource_group.main.name location = azurerm_resource_group.main.location version = "15" sku_name = "GP_Standard_D2s_v3" administrator_login = "appadmin" administrator_password = data.azurerm_key_vault_secret.db_password.value}ต้องมองให้ชัดว่าวิธีนี้แก้อะไรได้และแก้อะไรไม่ได้ ค่า secret ที่ resolve แล้วยังไปจบที่ state entry ของ azurerm_postgresql_flexible_server.main อยู่ดี เพราะ Terraform ต้องบันทึก attribute ที่ตั้งไว้ เหมือนความเสี่ยง plaintext จากบทแรกของโมดูลนี้เป๊ะ สิ่งที่แก้ได้จริง ๆ คือ lifecycle ของ secret ตัวนั้นในที่อื่นทั้งหมด — ไม่เคยถูกพิมพ์ลง variable ไม่เคยอยู่ในไฟล์ .tfvars ที่อาจถูก commit เข้าไปโดยไม่ตั้งใจ และการ rotate ใน Key Vault ก็ไม่ต้องแตะ Terraform configuration เลย การปกป้อง state backend เองยังคงเป็นส่วนที่ต่อรองไม่ได้อยู่ดี
nonsensitive() ยกเลิกการ redact ค่าอย่างตั้งใจ
หัวข้อที่มีชื่อว่า “nonsensitive() ยกเลิกการ redact ค่าอย่างตั้งใจ”บางครั้ง sensitive = true แพร่กระจายไปไกลกว่าที่คุณต้องการ — expression ใด ๆ ที่แตะค่า sensitive จะกลายเป็น sensitive ไปด้วย แม้ผลลัพธ์จริง ๆ จะไม่ใช่ secret เลยก็ตาม ฟังก์ชัน nonsensitive() เอา marking นั้นออกจากค่าเดียวอย่างตั้งใจ สำหรับกรณีแบบนั้นเป๊ะ ๆ พิสูจน์จริงด้วย terraform console แสดงการ redact ก่อน แล้วค่อยเอาออก
$ echo 'var.db_password' | terraform console(sensitive value)$ echo 'nonsensitive(var.db_password)' | terraform console"S3cretPassword!"นั่นคือกลไกทั้งหมด nonsensitive() ไม่ได้เพิ่มการป้องกันอะไร และไม่ได้เอาค่าออกจาก state ไม่ว่ากรณีไหน — มันแค่เปลี่ยนว่าการ redact ของ Terraform จะใช้กับผลลัพธ์ของ expression ตัวนี้โดยเฉพาะต่อจากนี้หรือไม่เท่านั้น ใช้มันเฉพาะตอนที่คุณมั่นใจว่าค่านั้นไม่ได้เป็น secret จริง ๆ ในบริบทนั้น — เรียกมันกับ secret จริงแค่เพื่อปิด warning จะทำลายการป้องกันเดียวที่ sensitive = true มีให้
Ephemeral value ถูกกันออกจาก state และ plan โดยโครงสร้าง
หัวข้อที่มีชื่อว่า “Ephemeral value ถูกกันออกจาก state และ plan โดยโครงสร้าง”Terraform 1.10 เพิ่ม resource, variable และ output แบบ ephemeral — ค่าที่มีอยู่แค่ตลอด operation เดียวของ Terraform และไม่เคยถูกเขียนลง state หรือ saved plan file เลย ไม่ใช่ “เขียนแต่ redact” แบบที่ sensitive = true ทำ ตั้ง variable เป็น ephemeral แบบนี้
variable "session_token" { type = string ephemeral = true}azurerm provider เองก็ ship ephemeral resource ที่ตรงกันไว้ สำหรับอ่าน secret จาก Key Vault โดยไม่เคย record มันเลย: ephemeral "azurerm_key_vault_secret" "db" { name = "app-db-password"; key_vault_id = data.azurerm_key_vault.main.id } — resource จริงที่ document ไว้ (developer.hashicorp.com/terraform/language/manage-sensitive-data/ephemeral) ไม่ใช่สมมติฐาน คอร์สนี้ไม่มี Key Vault จริงให้อ่าน พิสูจน์แบบ offline ข้างล่างจึงใช้ pattern built-in แทน — ค่าอายุสั้นที่ generate สด ๆ ทุกครั้งที่รัน ไม่เคย persist ไว้ที่ไหนเลย
ephemeral "random_password" "db" { length = 16}เพราะ ephemeral value ไม่เคยถูก persist Terraform จึง enforce ว่า ที่ไหน ที่มันไหลไปได้ ตอน plan time ด้วย error จริง — ไม่ใช่แค่ convention พิสูจน์ ส่ง ephemeral.random_password.db.result เข้า argument ธรรมดา (ไม่ใช่ write-only) ของ resource
$ terraform planError: Invalid use of ephemeral value
with terraform_data.marker, on main.tf line 15, in resource "terraform_data" "marker": 15: input = ephemeral.random_password.db.result
Ephemeral values are not valid for "input", because it is not a write-onlyattribute and must be persisted to state.error นั้นระบุกฎจริงตรง ๆ ค่า ephemeral ไหลไปได้แค่ใน attribute ที่ provider ประกาศเป็น write-only ไว้ชัดเจนเท่านั้น เพราะ write-only attribute คือ argument ชนิดเดียวที่ Terraform รับประกันว่าจะไม่ persist เลย ข้อจำกัดจริงอีกข้อ พิสูจน์แบบเดียวกัน output แบบ ephemeral ใช้ใน root module ไม่ได้เลย —
$ terraform planError: Ephemeral outputs are not allowed in context of a root module— ephemeral output มีไว้ให้ child module ส่งค่า ephemeral กลับขึ้นไปให้ตัวที่เรียกมัน (เพื่อให้ผู้เรียกเอาไปใช้กับ write-only argument ของตัวเองหรือส่งต่อเป็น ephemeral chain) ไม่ใช่ให้ root module print มันออกมา
Write-only argument (_wo) อีกครึ่งหนึ่งของกลไก
หัวข้อที่มีชื่อว่า “Write-only argument (_wo) อีกครึ่งหนึ่งของกลไก”Terraform 1.11 เพิ่ม write-only argument — resource attribute ที่ตามธรรมเนียมลงท้ายด้วย _wo คู่กับ integer _wo_version ที่ provider สัญญาว่าจะไม่เขียนลง state หรือ saved plan เลย นี่ไม่ใช่สมมติฐานสำหรับ azurerm azurerm_key_vault_secret ประกาศคู่นี้จริง ตาม docs จริงของ provider (registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/key_vault_secret)
“
value_wo- (Optional, Write-Only) Specifies the value of the Key Vault Secret. … One ofvalueorvalue_womust be specified.” / “value_wo_version- (Optional) An integer value used to trigger an update forvalue_wo. This property should be incremented when updatingvalue_wo.”
ยืนยันตรง ๆ กับ schema ของ provider hashicorp/azurerm 5.7.0 ที่ pin ไว้ — ไม่ต้องมี Azure credential เลย terraform providers schema -json แค่อ่านสิ่งที่ init ดาวน์โหลดมาแล้ว
$ terraform providers schema -json | python3 -c "import json,sysd = json.load(sys.stdin)attrs = d['provider_schemas']['registry.terraform.io/hashicorp/azurerm']['resource_schemas']['azurerm_key_vault_secret']['block']['attributes']for k, v in attrs.items(): if v.get('write_only') or k.endswith('_wo_version'): print(k, v)"value_wo {'type': 'string', 'description_kind': 'plain', 'optional': True, 'write_only': True}value_wo_version {'type': 'number', 'description_kind': 'plain', 'optional': True}สิ่งที่ต้องการ credential จริงคือการ apply มันต่างหาก — สร้าง Key Vault Secret object จริงต้องมี Key Vault จริงกับ Azure identity ที่ authenticate แล้ว คำถามที่ต้องตอบตรง ๆ แทนคือ write-only support เป็นสากลทุก resource หรือ opt-in เช็คกับ random_password ของ hashicorp/random — resource ที่คอร์สนี้ apply ได้เต็มรูปแบบแบบ offline — ด้วย query เดียวกันเป๊ะ
$ terraform providers schema -json | python3 -c "import json,sysd = json.load(sys.stdin)attrs = d['provider_schemas']['registry.terraform.io/hashicorp/random']['resource_schemas']['random_password']['block']['attributes']print([k for k,v in attrs.items() if v.get('write_only')])"[]random_password ไม่มี write-only attribute เลยแม้แต่ตัวเดียว เหมือนกับ built-in terraform_data ภาพจริงที่พิสูจน์แล้วทั้งสองด้านข้างบนคือ write-only support ไม่ใช่การรับประกันทั่วทั้ง Terraform ที่ resource ไหนก็ได้ฟรีตั้งแต่ 1.11 — มันคือสิ่งที่ผู้เขียน provider แต่ละคนเพิ่มเข้ามาทีละ resource ทีละ attribute และตามเวอร์ชันที่ pin ไว้นี้ azurerm_key_vault_secret.value_wo มีจริง ในขณะที่ resource ที่ apply ได้แบบ offline เต็มรูปแบบของคอร์สนี้ไม่มีตัวไหนมีเลย สิ่งที่พิสูจน์ได้จริงแบบ offline ครบวงจร และพิสูจน์ไปแล้วข้างบนคือครึ่งหนึ่งของกลไกที่ทำให้ write-only argument มีประโยชน์ตั้งแต่แรก คือการ enforce ค่า ephemeral ว่าอะไรได้รับอนุญาตให้ไหลเข้า _wo argument บ้าง เมื่อ resource นั้นมี attribute แบบนี้แล้ว
จุดที่ shared state เริ่มไม่พอ
หัวข้อที่มีชื่อว่า “จุดที่ shared state เริ่มไม่พอ”บทก่อนหน้าตั้ง azurerm backend ที่มี locking ติดมาในตัว ซึ่งแก้ปัญหา “ทุกคนอ่านและเขียน state เดียวกันได้อย่างปลอดภัย” ได้แล้ว นั่นจำเป็น แต่ไม่พอเมื่อทีมโตเกินกว่าวิศวกรสองสามคน ถ้าไม่มีอะไรเพิ่มเติมนอกจากนั้น “ทุกคนรัน terraform apply จาก laptop ของตัวเองเมื่อไหร่ก็ได้ที่อยากรัน” ก็ยังมีช่องโหว่จริงอยู่
applyที่รันพร้อมกันจะไม่ทำให้อะไรเสียหายอีกต่อไปเพราะมี locking แล้ว แต่ก็ยังต้องต่อคิวกัน — run ของวิศวกรคนที่สองก็แค่รอเฉย ๆ โดยไม่รู้เลยว่า run แรกกำลังทำอะไรอยู่ หรือจะใช้เวลานานแค่ไหน- ไม่มีจุดไหนที่คนที่สอง review การเปลี่ยนแปลงก่อนขึ้น production จริง locking ป้องกันแค่ไม่ให้สอง apply แข่งกันเขียนพร้อมกัน แต่ไม่ได้หยุดวิศวกรคนหนึ่งจาก apply การเปลี่ยนแปลงที่ไม่มีใครดูมาก่อนเลย
- วิศวกรแต่ละคนต้องมี Azure credential ที่ valid และมีสิทธิ์พอจะรัน
applyจากเครื่องตัวเองได้ นั่นเป็น blast radius ที่กว้างกว่าที่ทีมส่วนใหญ่อยากแจกให้ทุก laptop
ทั้งหมดนี้ไม่ได้แปลว่า shared backend จากบทก่อนหน้าผิด — เป็นสิ่งที่ต้องมีก่อน ไม่ใช่ความผิดพลาด แค่ยังไม่ใช่เรื่องทั้งหมด การปิดช่องว่างนี้คือหน้าที่ของ CI/CD สำหรับ infrastructure โดยตรง — pipeline รัน plan กับทุกการเปลี่ยนแปลงที่เสนอมาเพื่อให้ review ก่อนใครจะ apply และ apply ก็รันจาก pipeline identity ที่ควบคุมได้ แทนที่จะรันจาก laptop ของแต่ละคน นั่นเป็นหัวข้อของบทหลังในคอร์สนี้ ไม่ใช่สิ่งที่ต้องแก้ตรงนี้ — ประเด็นตอนนี้คือแค่ให้รู้จักรูปร่างของช่องว่างนี้ไว้ก่อน จะได้ไม่แปลกใจทีหลัง
flowchart LR sv["variable with sensitive = true"] -->|redacted| cli["CLI output: (sensitive value)"] sv -->|full value written| state["terraform.tfstate: plaintext"] kv["Azure Key Vault"] -->|data source at apply time| res["Resource attribute"] res -->|attribute recorded| state
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”สามกลไกในบทนี้ดูคล้ายกันจากภายนอก — sensitive, ephemeral, และ write-only argument — แต่แก้ปัญหาคนละอย่างกันจริง ๆ และความต่างอยู่ตรงที่ อะไรถูกเขียนลง state sensitive = true เขียนค่าลง state เต็ม ๆ แล้ว redact แค่ terminal — พิสูจน์ข้างบน ค่าดิบห่างแค่ state pull | grep เดียว ค่า ephemeral ไม่เคยถูกเขียนลง state เลย และ Terraform enforce เรื่องนี้ตอน plan time ด้วย error จริงทันทีที่คุณพยายามส่งมันเข้า attribute ธรรมดา — พิสูจน์ข้างบน write-only argument คือครึ่งฝั่ง provider ของการรับประกันเดียวกันนั้น _wo attribute คือที่เดียวที่ ephemeral value ได้รับอนุญาตให้ไปถึง เพราะ provider สัญญาว่าจะไม่ persist มันเลย — พิสูจน์ข้างบนว่า azurerm_key_vault_secret.value_wo มีอยู่จริงใน schema ที่ pin ไว้ อ่านตามลำดับนี้ — display-only, กันออกจากโครงสร้าง, provider สัญญาว่าจะไม่ persist — ทั้งสามจะไม่ดูเหมือน “secret” สามแบบอีกต่อไป แต่กลายเป็นคำตอบสามแบบสำหรับคำถาม “ค่านี้ไปถึง disk ไหม”
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ทางเลือก | ได้อะไร | เสียอะไร |
|---|---|---|
sensitive = true | หยุดค่าเลื่อนผ่าน terminal หรือ CI log ที่แชร์กัน (พิสูจน์ข้างบน (sensitive value)) | ไม่มีผลกับ state เลย — พิสูจน์ข้างบน ค่าดิบยังห่างแค่ state pull เดียว |
variable/resource แบบ ephemeral | ไปไม่ถึง state หรือ saved plan ได้เลยโดยโครงสร้าง — enforce ด้วย error จริง ไม่ใช่แค่ convention (พิสูจน์ข้างบน) azurerm ยัง ship ephemeral "azurerm_key_vault_secret" ให้อ่าน secret โดยไม่ต้อง record เลยด้วย | ใช้ได้แค่บริบทแคบ ๆ (write-only argument, provider config, ephemeral value อื่น) ephemeral output ใน root module ถูกปฏิเสธตรง ๆ (พิสูจน์ข้างบน) |
write-only (_wo) argument | ค่านั้นไม่แตะ state เลยจริง ๆ แม้จะชั่วคราวก็ตาม — พิสูจน์ข้างบนว่า azurerm_key_vault_secret.value_wo มีอยู่จริงใน schema ที่ pin ไว้ | opt-in ต่อ resource ต่อ provider ไม่ใช่การรับประกันทั่ว 1.11 — พิสูจน์ข้างบน random_password ไม่มีเลยสักตัว ใช้จริงต้องมี Key Vault จริงกับ Azure credential |
nonsensitive() | ให้ค่าที่จริง ๆ ไม่ใช่ secret หลุดจากการ redact ที่ไม่จำเป็น (พิสูจน์ข้างบนผ่าน console) | เอาการป้องกันเดียวที่ sensitive = true มีให้ออกไป ถ้าใช้ผิดกับสิ่งที่เป็น secret จริง |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “
sensitive = trueกันไม่ให้ค่าไปอยู่ใน state file” พิสูจน์ข้างบนสองครั้ง ทั้งตัวอย่างstate pull | grepเดิม และข้อเท็จจริง baseline — มันเปลี่ยนแค่การแสดงผลบน CLI ไม่เคยเปลี่ยนวิธีเก็บ - “
ephemeral = trueเป็นแค่sensitive = trueเวอร์ชันเข้มกว่า” พิสูจน์ข้างบน คนละกลไกกันเลยsensitiveredact การแสดงผลแต่ยังเขียนลง stateephemeralถูกกันออกจาก state และ plan โดยโครงสร้าง enforce ด้วย error จริง “Ephemeral values are not valid for …” ทันทีที่พยายาม persist - “resource ไหนก็รับ ephemeral value ได้ถ้าอ้างถึงตรง ๆ” พิสูจน์ข้างบน
terraform planปฏิเสธด้วยเหตุผลที่ระบุชัดเจน — attribute เป้าหมาย “ไม่ใช่ write-only attribute และต้อง persist ลง state” มีแค่_woattribute ที่ provider สร้างไว้ให้ชัดเจนเท่านั้นที่ผ่านได้ - “write-only argument เป็น feature ทั่วไปของ Terraform 1.11 ใช้กับ resource ไหนก็ได้” พิสูจน์ข้างบนด้วยการ dump provider schema จริงสองตัว
azurerm_key_vault_secretประกาศvalue_wo/value_wo_versionจริง ในขณะที่random_passwordบน toolchain เวอร์ชันเดียวกันเป๊ะไม่มี write-only attribute เลย — มันคือ opt-in ต่อ resource ต่อ provider ไม่ใช่ความสามารถที่ทุก resource ได้ฟรีตั้งแต่ 1.11
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”ตอน review โค้ดที่ generate มาซึ่งแตะ secret หรือ ephemeral data เช็คสิ่งเหล่านี้
sensitive = trueถูกนำเสนอเป็น “ทางแก้” ของ credential โดยไม่พูดถึงว่า backend ยังเก็บมันแบบ plaintext อยู่ดี — พิสูจน์ข้างบน นี่คือช่องว่างที่บทนี้ปิดพอดี- ephemeral value ถูกอ้างถึงตรง ๆ ใน attribute ธรรมดา (ไม่ใช่
_wo) ของ resource สันนิษฐานว่า “ก็แค่ใช้ได้” เพราะ compile ผ่าน — พิสูจน์ข้างบนterraform planปฏิเสธด้วย error เฉพาะเจาะจง ไม่มีทางไปถึงapplyเลย - root-module
outputที่ตั้งephemeral = trueตั้งใจส่ง secret ที่ generate มาให้สิ่งภายนอก Terraform — พิสูจน์ข้างบน สิ่งนี้ถูกปฏิเสธตรง ๆ ephemeral output มีไว้ให้ child module ส่งค่ากลับขึ้นไปให้ผู้เรียกเท่านั้น nonsensitive()ถูกใช้ห่อค่าที่จริง ๆ เป็น secret แค่เพื่อปิด error “sensitive value in output” แทนที่จะตั้ง output เป็นsensitive = true— สิ่งนี้ทำลายการป้องกันเดียวที่sensitiveมีให้โดยไม่ได้อะไรคืนมาจริง- argument แบบ
value_woบนazurerm_key_vault_secretถูกใช้โดยไม่เพิ่มvalue_wo_versionคู่กัน แล้วมีรายงานว่า “ค่า secret ใหม่ไม่เคย apply เลย” — พิสูจน์ข้างบน เลข version คือ signal เดียวที่ Terraform มีว่าต้องส่งค่า write-only ใหม่ เพราะมันไม่เคยเก็บค่าเองไว้ diff
🐛 หา bug ในโค้ดนี้
ephemeral "random_password" "db" {length = 16}
resource "azurerm_key_vault_secret" "db" {name = "app-db-password"key_vault_id = data.azurerm_key_vault.main.idvalue = ephemeral.random_password.db.result}เฉลย
พิสูจน์ข้างบนด้วย error จริงสำหรับความผิดพลาดแบบเดียวกันบน terraform_data.marker — value บน azurerm_key_vault_secret เป็น attribute ธรรมดาที่ persist ไม่ใช่ write-only Terraform ปฏิเสธ plan นี้ด้วย “Ephemeral values are not valid for …, because it is not a write-only attribute and must be persisted to state” ทางแก้ไม่ใช่ยอมแพ้เรื่อง generate secret แบบ ephemeral แต่คือส่งมันผ่าน write-only pair จริงของ resource แทน value_wo = ephemeral.random_password.db.result คู่กับ value_wo_version ที่เพิ่มขึ้นทุกครั้งที่ควรเปลี่ยนค่า เขียนลง argument value ธรรมดาจะไปไม่ถึง Azure ด้วยซ้ำ — plan เองพังก่อน ซึ่งคือ safety net ที่ทำงานตามที่ออกแบบไว้ ไม่ใช่ bug ที่ต้องหลบด้วย sensitive = true (แบบนั้นจะแค่ redact การแสดงผล ค่า ephemeral ก็ยัง persist ลง attribute ธรรมดาไม่ได้อยู่ดี)