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

Sensitive Data and Team Workflows

การตั้งค่าเป็น sensitive = true ซ่อนค่านั้นจาก terminal แต่ค่ายังอยู่แบบ plaintext ใน state file เหมือนเดิม ดังนั้นทางแก้ที่แท้จริงคือปกป้อง state backend เอง และไม่เอา secret ระยะยาวเข้ามาไว้ใน Terraform เลยตั้งแต่แรก

ทั้ง variable และ output block รับ argument sensitive ได้

variable "database_password" {
type = string
sensitive = true
}
output "db_connect_string" {
value = "Server=${aws_db_instance.main.address};Pwd=${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 เลื่อนผ่านหน้าจอไปใน CI log ที่แชร์กัน หรือ terminal ที่ screen-share อยู่โดยไม่ตั้งใจ

สิ่งที่ต้องพูดให้ชัดคือความเข้าใจผิดที่พบบ่อย sensitive = true เป็น feature ด้าน display ไม่ใช่ feature ด้านการเข้ารหัส ค่าถูกคำนวณ เก็บ และ diff โดย Terraform เหมือนเดิมทุกอย่างไม่ว่าจะตั้ง sensitive หรือไม่ — ค่ายังไปอยู่ใน terraform.tfstate แบบ plaintext เต็ม ๆ พร้อม tag ทุกตัว ใครก็ตามที่อ่าน state file ดิบได้ ไม่ว่าจะจาก S3 bucket, .tfstate local หรือ terraform state pull ก็เห็น password ตัวนั้นนอนอยู่ใน JSON ตรง ๆ ไม่ว่าจะมี flag sensitive หรือไม่มีก็ตาม

Terminal window
terraform state pull | grep -A3 db_connect_string
# "db_connect_string": {
# "value": "Server=mydb.abc123.us-east-1.rds.amazonaws.com;Pwd=S3cretPassword!",
# "type": "string"

เพราะการตั้งค่าให้เป็น sensitive ไม่ได้เอาค่านั้นออกจาก state การป้องกันจริง ๆ ต้องเกิดที่ layer ของ storage ให้ดูแล terraform.tfstate เองเหมือนที่คุณดูแลไฟล์ .env ที่เต็มไปด้วย credential ของ production

  • เข้ารหัส backend ที่ rest สำหรับ S3 backend ที่พูดถึงในบทก่อน หมายถึงเปิด S3 server-side encryption (SSE) บน state bucket เพื่อให้ทุก object — รวมถึงทุก version ของ state file — ถูกเข้ารหัสบน disk ไม่ว่าใครจะเรียก bucket API
  • จำกัดว่าใครอ่าน bucket ได้ IAM policy ที่จำกัด access ของ state bucket ให้เหลือแค่ role และ user ที่ต้องรัน Terraform จริง ๆ คือสิ่งที่กันไม่ให้คนที่ไม่ได้รับอนุญาตไปถึง plaintext ได้ ไม่ว่าจะเข้ารหัสที่ rest ไว้หรือไม่ก็ตาม — encryption at rest ป้องกันคนที่เข้าถึง disk ดิบ ไม่ใช่ป้องกันคนที่มี AWS credential ถูกต้องแล้วเรียก s3:GetObject
resource "aws_s3_bucket" "state" {
bucket = "my-company-terraform-state"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "state" {
bucket = aws_s3_bucket.state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

ถ้าเป็นไปได้ วิธีที่ดีกว่าคือหลีกเลี่ยงปัญหานี้ไปเลย อย่าเอา secret ระยะยาวใส่เข้าไปใน Terraform variable ตั้งแต่แรกถ้าดึงตอน apply ได้ data source ที่อ่านจาก secrets manager จะดึงค่าปัจจุบันตอน Terraform รัน โดยที่คุณไม่ต้องพิมพ์ secret ลงไฟล์ .tf หรือ .tfvars เลย

data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/app-db/password"
}
resource "aws_db_instance" "main" {
identifier = "app-db"
engine = "postgres"
instance_class = "db.t3.micro"
username = "app_admin"
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}

ต้องเข้าใจให้ชัดว่าวิธีนี้แก้อะไรและไม่แก้อะไร ค่า secret ที่ resolve ออกมายังไปจบอยู่ใน state entry ของ aws_db_instance.main เหมือนเดิม เพราะ Terraform ต้อง record attribute ที่ตั้งไว้ เหมือนความเสี่ยง plaintext จากบทแรกของ module นี้ สิ่งที่แก้ได้จริงคือ lifecycle ของ secret ในที่อื่น ๆ ทั้งหมด — ไม่เคยถูกพิมพ์ลง variable ไม่เคยอยู่ในไฟล์ .tfvars ที่อาจโดน commit พลาด และการ rotate ใน Secrets Manager ก็ไม่ต้องแตะ Terraform configuration เลย การปกป้อง state backend เองยังคงเป็นส่วนที่ต่อรองไม่ได้

บางครั้ง sensitive = true แพร่กระจายไปไกลกว่าที่คุณต้องการ — expression ใด ๆ ที่แตะค่า sensitive จะกลายเป็น sensitive ไปด้วย แม้ผลลัพธ์จริง ๆ จะไม่ใช่ secret เลยก็ตาม (เช่น boolean ที่คำนวณจากความยาวของ password) ฟังก์ชัน 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 มีให้

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
}

ephemeral block ก็ทำงานแบบเดียวกันสำหรับ resource ที่มีอยู่แค่ชั่วคราว — pattern ที่ built-in มาให้คือ credential อายุสั้นหรือ secret ที่ 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 plan
Error: 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-only
attribute and must be persisted to state.

error นั้นระบุกฎจริงตรง ๆ ค่า ephemeral ไหลไปได้แค่ใน attribute ที่ provider ประกาศเป็น write-only ไว้ชัดเจนเท่านั้น เพราะ write-only attribute คือ argument ชนิดเดียวที่ Terraform รับประกันว่าจะไม่ persist เลย ข้อจำกัดจริงอีกข้อ พิสูจน์แบบเดียวกัน output แบบ ephemeral ใช้ใน root module ไม่ได้เลย —

$ terraform plan
Error: Ephemeral outputs are not allowed in context of a root module

— ephemeral output มีไว้ให้ child module ส่งค่า ephemeral กลับขึ้นไปให้ตัวที่เรียกมัน (เพื่อให้ผู้เรียกเอาไปใช้กับ write-only argument ของตัวเองหรือส่งต่อเป็น ephemeral chain) ไม่ใช่ให้ root module print มันออกมา

Terraform 1.11 เพิ่ม write-only argument — resource attribute ที่ตามธรรมเนียมลงท้ายด้วย _wo คู่กับ integer _wo_version ที่ provider สัญญาว่าจะไม่เขียนลง state หรือ saved plan เลย ตามตัวอย่างจริงจาก docs

resource "aws_db_instance" "main" {
instance_class = "db.t3.micro"
# ...
password_wo = ephemeral.random_password.db_password.result
password_wo_version = 1
}

password_wo_version คือสิ่งที่บอก Terraform ว่าค่านั้นเปลี่ยนแล้วต้อง apply ใหม่ — เพราะตัวค่าเองไม่เคยถูกเก็บไว้ Terraform เลยไม่มีอะไรจะ diff ในครั้งถัดไปที่ plan การเพิ่มเลข version จึงเป็น signal เดียวที่บอกว่าควรส่งค่า write-only ใหม่ นี่คือกลไกจริงที่ document ไว้ (quote จาก developer.hashicorp.com/terraform/language/manage-sensitive-data) “Providers define which managed resources support these arguments, and Terraform discards the values immediately after the operation is complete”

write-only argument เป็น opt-in ต่อ resource ต่อ provider — ผู้เขียน provider ต้องประกาศ write_only: true บน attribute เจาะจงใน schema ของ resource นั้นเอง ไม่ใช่การเดา terraform providers schema -json dump schema จริงที่ provider hashicorp/aws 6.66.0 ที่ pin ไว้ ship มา และ aws_db_instance ก็ประกาศคู่นี้จริง ๆ

$ terraform providers schema -json | python3 -c "
import json,sys
d = json.load(sys.stdin)
attrs = d['provider_schemas']['registry.terraform.io/hashicorp/aws']['resource_schemas']['aws_db_instance']['block']['attributes']
for k, v in attrs.items():
if v.get('write_only') or k.endswith('_wo_version'):
print(k, v)
"
password_wo {'type': 'string', 'description_kind': 'plain', 'optional': True, 'sensitive': True, 'write_only': True}
password_wo_version {'type': 'number', 'description_kind': 'plain', 'optional': True}

การ dump schema แบบนี้ไม่ต้องมี AWS credential เลย — terraform providers schema -json แค่อ่านสิ่งที่ init ดาวน์โหลดมาแล้ว สิ่งที่ต้องการ credential จริงคือการ apply มันต่างหาก aws_db_instance สร้าง RDS instance จริง ซึ่งคอร์สนี้ทำไม่ได้ คำถามที่ต้องตอบตรง ๆ แทนคือ การรองรับนี้เป็นสากลทุก resource หรือมีแค่บางตัว เช็คกับ random_password ของ hashicorp/random — resource ที่คอร์สนี้ apply ได้เต็มรูปแบบแบบ offline — ด้วย query เดียวกันเป๊ะ

$ terraform providers schema -json | python3 -c "
import json,sys
d = 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 และ resource ของ hashicorp/local ภาพจริงที่พิสูจน์แล้วทั้งสองด้านข้างบนคือ write-only support ไม่ใช่การรับประกันทั่วทั้ง Terraform ที่ resource ไหนก็ได้ฟรีตั้งแต่ 1.11 — มันคือสิ่งที่ผู้เขียน provider แต่ละคนเพิ่มเข้ามาทีละ resource ทีละ attribute และตามเวอร์ชันที่ pin ไว้นี้ aws_db_instance.password_wo มีจริง ในขณะที่ resource ที่ apply ได้แบบ offline เต็มรูปแบบของคอร์สนี้ไม่มีตัวไหนมีเลย สิ่งที่พิสูจน์ได้จริงแบบ offline ครบวงจร และพิสูจน์ไปแล้วข้างบนคือครึ่งหนึ่งของกลไกที่ทำให้ write-only argument มีประโยชน์ตั้งแต่แรก คือการ enforce ค่า ephemeral ว่าอะไรได้รับอนุญาตให้ไหลเข้า _wo argument บ้าง เมื่อ resource นั้นมี attribute แบบนี้แล้ว

บทก่อนตั้ง S3 backend พร้อม locking ไว้แล้ว ซึ่งแก้ปัญหา “ทุกคนอ่านและเขียน state เดียวกันได้อย่างปลอดภัย” นั่นจำเป็น แต่ไม่พอเมื่อทีมโตเกินสองสามคน ถ้าไม่มีอะไรเพิ่มเติม การที่ “ทุกคนรัน terraform apply จาก laptop ของตัวเองเมื่อไหร่ก็ได้ตามใจ” ยังมีช่องว่างจริง ๆ อยู่

  • run apply ที่เกิดพร้อมกันไม่ทำให้ state เสียหายแล้วเพราะมี locking แต่ก็ยังเข้าคิวรอกัน — engineer คนที่สองแค่รอเฉย ๆ โดยไม่รู้ว่า run แรกกำลังทำอะไรอยู่หรือจะใช้เวลานานแค่ไหน
  • ไม่มีจุดตามธรรมชาติให้คนที่สอง review change ก่อนขึ้นจริง locking กันแค่ apply สองตัวไม่ให้แข่งกัน แต่ไม่ได้กัน engineer คนหนึ่งจาก apply change ที่ไม่มีใครเคยดูมาก่อน
  • engineer แต่ละคนต้องมี AWS credential ที่มีสิทธิ์พอจะรัน apply ได้จาก local นั่นเป็น blast radius ที่กว้างกว่าที่ทีมส่วนใหญ่อยากแจกให้ทุก laptop

ทั้งหมดนี้ไม่ได้แปลว่า shared backend จากบทก่อนผิด เพราะเป็นสิ่งที่ต้องมีก่อน ไม่ใช่ความผิดพลาด แค่ยังไม่ใช่เรื่องทั้งหมด การปิดช่องว่างนี้คือสิ่งที่ CI/CD สำหรับ infrastructure มีไว้ทำ — pipeline รัน plan บนทุก change ที่เสนอมาเพื่อ 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"]
  sm["Secrets Manager"] -->|data source at apply time| res["Resource attribute"]
  res -->|attribute recorded| state
variable แบบ sensitive ถูก redact จาก CLI output แต่ยังไปอยู่ใน state แบบ plaintext เทียบกับ secret ที่ดึงจาก Secrets Manager ตอน apply

สามกลไกในบทนี้ดูคล้ายกันจากภายนอก — 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 มันเลย อ่านตามลำดับนี้ — 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 (พิสูจน์ข้างบน)ใช้ได้แค่บริบทแคบ ๆ (write-only argument, provider config, ephemeral value อื่น) ephemeral output ใน root module ถูกปฏิเสธตรง ๆ (พิสูจน์ข้างบน)
write-only (_wo) argumentค่านั้นไม่แตะ state เลยจริง ๆ แม้จะชั่วคราวก็ตาม (ตามที่ provider สัญญาไว้เอง พิสูจน์ข้างบนว่า aws_db_instance ประกาศ password_wo จริง)opt-in ต่อ resource ต่อ provider ไม่ใช่การรับประกันทั่ว 1.11 — พิสูจน์ข้างบน random_password ไม่มีเลยสักตัว ใช้จริงกับ aws_db_instance.password_wo ต้องมี RDS instance จริงกับ credential
nonsensitive()ให้ค่าที่จริง ๆ ไม่ใช่ secret หลุดจากการ redact ที่ไม่จำเป็น (พิสูจน์ข้างบนผ่าน console)เอาการป้องกันเดียวที่ sensitive = true มีให้ออกไป ถ้าใช้ผิดกับสิ่งที่เป็น secret จริง
  • “sensitive = true กันไม่ให้ค่าไปอยู่ใน state file” พิสูจน์ข้างบนสองครั้ง ทั้งตัวอย่าง state pull | grep เดิม และข้อเท็จจริง baseline — มันเปลี่ยนแค่การแสดงผลบน CLI ไม่เคยเปลี่ยนวิธีเก็บ
  • “ephemeral = true เป็นแค่ sensitive = true เวอร์ชันเข้มกว่า” พิสูจน์ข้างบน คนละกลไกกันเลย sensitive redact การแสดงผลแต่ยังเขียนลง state ephemeral ถูกกันออกจาก state และ plan โดยโครงสร้าง enforce ด้วย error จริง “Ephemeral values are not valid for …” ทันทีที่พยายาม persist
  • “resource ไหนก็รับ ephemeral value ได้ถ้าอ้างถึงตรง ๆ” พิสูจน์ข้างบน terraform plan ปฏิเสธด้วยเหตุผลที่ระบุชัดเจน — attribute เป้าหมาย “ไม่ใช่ write-only attribute และต้อง persist ลง state” มีแค่ _wo attribute ที่ provider สร้างไว้ให้ชัดเจนเท่านั้นที่ผ่านได้
  • “write-only argument เป็น feature ทั่วไปของ Terraform 1.11 ใช้กับ resource ไหนก็ได้” พิสูจน์ข้างบนด้วยการ dump provider schema จริงสองตัว aws_db_instance ประกาศ password_wo/password_wo_version จริง ในขณะที่ random_password บน toolchain เวอร์ชันเดียวกันเป๊ะไม่มี write-only attribute เลย — มันคือ opt-in ต่อ resource ต่อ provider ไม่ใช่ความสามารถที่ทุก resource ได้ฟรีตั้งแต่ 1.11

ตอน 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 แบบ password_wo/secret_string_wo ถูกใช้โดยไม่เพิ่ม _wo_version คู่กัน แล้วมีรายงานว่า “password ใหม่ไม่เคย apply เลย” — พิสูจน์ข้างบน เลข version คือ signal เดียวที่ Terraform มีว่าต้องส่งค่า write-only ใหม่ เพราะมันไม่เคยเก็บค่าเองไว้ diff

🐛 หา bug ในโค้ดนี้

ephemeral "random_password" "db" {
length = 16
}
resource "aws_db_instance" "main" {
identifier = "app-db"
engine = "postgres"
instance_class = "db.t3.micro"
username = "app_admin"
password = ephemeral.random_password.db.result
}
เฉลย

พิสูจน์ข้างบนด้วย error จริงสำหรับความผิดพลาดแบบเดียวกันบน terraform_data.marker — password บน aws_db_instance เป็น 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 password แบบ ephemeral แต่คือส่งมันผ่าน write-only pair จริงของ resource แทน password_wo = ephemeral.random_password.db.result คู่กับ password_wo_version ที่เพิ่มขึ้นทุกครั้งที่ควรเปลี่ยนค่า เขียนลง argument password ธรรมดาจะไปไม่ถึง AWS ด้วยซ้ำ — plan เองพังก่อน ซึ่งคือ safety net ที่ทำงานตามที่ออกแบบไว้ ไม่ใช่ bug ที่ต้องหลบด้วย sensitive = true (แบบนั้นจะแค่ redact การแสดงผล ค่า ephemeral ก็ยัง persist ลง attribute ธรรมดาไม่ได้อยู่ดี)

การตั้ง sensitive = true บน Terraform variable ป้องกันอะไรจริง ๆ
database password ถูกตั้ง sensitive = true บน variable block ของตัวเอง ค่านั้นยังอ่านได้แบบ plaintext ที่ไหนไหม
error จริงนี้พิสูจน์อะไรเกี่ยวกับที่ที่ ephemeral value ไหลไปได้
$ terraform plan
Error: Invalid use of ephemeral value

  with terraform_data.marker,
  ...
  15:   input = ephemeral.random_password.db.result

Ephemeral values are not valid for "input", because it is not a write-only
attribute and must be persisted to state.
schema ของ aws_db_instance ที่ pin ไว้ประกาศ password_wo จริง แต่ schema ของ random_password ไม่มี write-only attribute เลยสักตัว สิ่งนี้พิสูจน์อะไรจริง ๆ เกี่ยวกับ write-only argument
$ terraform providers schema -json | ... aws_db_instance ...
password_wo {'write_only': True} password_wo_version {...}
$ terraform providers schema -json | ... random_password ...
[]
ทำไม root-module output ที่ตั้ง ephemeral = true ถึง fail ด้วย "Ephemeral outputs are not allowed in context of a root module"
วิธีป้องกัน sensitive data ที่เก็บอยู่ใน Terraform state file ที่แท้จริงคืออะไร