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

Secrets and Credential Management

provider authentication ควรมาจาก role หรือกลไก identity แทนที่จะเป็น key ตัวจริงที่พิมพ์ลงในไฟล์ และถึงจะดึง application secret จาก secrets manager จริง ๆ ตอน apply time ก็ยังไม่ได้กันค่าไม่ให้ไปอยู่ใน Terraform state แบบเต็มร้อย

authenticate AWS provider ด้วย access_key กับ secret_key ตัวจริงที่เขียนตรง ๆ ใน provider "aws" block หรือในไฟล์ .tfvars ทำได้จริง และใช้งานได้ในความหมายแคบ ๆ คือ terraform plan รันผ่าน แต่ก็ยังผิดอยู่ดี

# Anti-pattern — never do this
provider "aws" {
region = "us-east-1"
access_key = "AKIAIOSFODNN7EXAMPLE"
secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
}

key ที่เขียนไว้ในไฟล์ .tf จะโดน commit เข้า git ทันทีที่มีคนลืมว่าอยู่ตรงนั้น แล้วค่านั้นจะอยู่ต่อไปในทุก clone และทุก commit ใน history ตั้งแต่จุดนั้นเป็นต้นไป ไม่ว่าทีหลังจะลบออกจาก revision ปัจจุบันหรือเปล่าก็ตาม ทางแก้คือไม่ให้ provider มี credential เลยใน configuration แล้วปล่อยให้ resolve จาก environment ที่รันอยู่แทน

# The provider block declares no credentials.
# Authentication comes from the environment: local env vars,
# an EC2/CI instance role, or OIDC federation in CI.
provider "aws" {
region = "us-east-1"
}

ตอนรัน local ปกติแล้วนี่หมายถึง environment variable หรือ named profile จาก aws configure ส่วนใน CI หมายถึงการ assume role แบบ OIDC-federated ที่พูดถึงในบท CI/CD — aws-actions/configure-aws-credentials เติม environment variable มาตรฐานของ AWS ให้ก่อนที่ Terraform จะรันด้วยซ้ำ เลยทำให้ provider block หา credential เจอได้โดยไม่ต้องมี key สักตัวถูกเขียนไว้ที่ไหนเลย

คำถามที่แยกออกไปจาก provider authentication คือ application secret — password ของ database, API key ของ third-party — ที่ resource ตัวหนึ่งต้องใช้เป็น argument นิยาม secret นั้นเป็น Terraform variable ทำให้ตัว variable declaration กลายเป็นสิ่งที่ต้องปฏิบัติเหมือน source of truth ซึ่งแปลว่าต้องมีคนพิมพ์ค่าจริงลงในไฟล์ tfvars, CI secret หรือตอน prompt ที่ apply time อยู่ดี

pattern ที่ดีกว่าคืออ่านค่าปัจจุบันตรงจาก secrets manager จริง ๆ ผ่าน data source ตอน apply time

data "aws_secretsmanager_secret_version" "api_key" {
secret_id = "prod/third-party/api-key"
}
resource "aws_lambda_function" "webhook_handler" {
function_name = "webhook-handler"
runtime = "nodejs20.x"
handler = "index.handler"
environment {
variables = {
API_KEY = data.aws_secretsmanager_secret_version.api_key.secret_string
}
}
}

นี่ไม่ใช่บัตรผ่านฟรี และควรพูดให้ชัดว่าทำไม module State Management เคยพูดไว้แล้วว่าการ set sensitive = true บน variable แค่ redact ออกจาก CLI output — ค่าจริงยังไปอยู่ใน terraform.tfstate เป็น plaintext อยู่ดี การอ่าน secret ผ่าน data source ก็ไม่ได้กันตรงนี้แบบเต็มร้อยเช่นกัน ผลลัพธ์ของ data source ก็ถูก cache ไว้ใน state เหมือนกัน ดังนั้น secret_string ที่ resolve ได้ก็ยังไปอยู่ใน state เหมือนกับที่ sensitive variable จะเป็น สิ่งที่ pattern แบบ data source ปรับปรุงจริง ๆ คือ lifecycle ของ secret ในที่อื่น ๆ — ค่าจริงไม่เคยถูกพิมพ์ลงใน variable เลย ไม่เคยไปนั่งอยู่ใน .tfvars ที่ใครสัก commit พลาด และ rotate ใน Secrets Manager ก็ไม่ต้องแก้ Terraform เลยสักบรรทัด — apply รอบถัดไปก็แค่อ่านค่าปัจจุบันที่มีอยู่ Secrets Manager คือ source of truth ส่วน Terraform แค่อ่านค่ามา การปกป้อง state backend เอง ที่พูดไว้ใน module State Management ยังเป็นส่วนที่ข้ามไม่ได้อยู่ดี

ทุกอย่างข้างบนเป็นจริงมาหลายปี และข้อแม้ที่ว่า “ผลลัพธ์ของ data source ก็ถูก cache ใน state เหมือนกัน” ก็เป็นสิ่งที่ต้องอยู่กับมันไป Terraform 1.10 กับ 1.11 ปิด gap ตรงนี้ด้วยสองฟีเจอร์ที่ทำงานร่วมกัน

ephemeral resource ถูกอ่านใหม่ทุกครั้งที่รัน operation และผลลัพธ์ไม่เคยถูกเขียนลง state หรือ plan file เลย ประกาศด้วย block type ชื่อ ephemeral แทน data

# Read at plan and apply time, never persisted to state or the plan file
ephemeral "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/database/password"
}

ค่าที่ไม่เคยถูกเก็บก็คือค่าที่ Terraform เอาไปเทียบในรอบถัดไปไม่ได้ ผลลัพธ์จาก ephemeral จึงส่งเข้า argument ปกติของ resource ไม่ได้ ตรงนี้คือหน้าที่ของ write-only argument provider จะ mark argument บางตัวเป็น write-only ตั้งชื่อลงท้ายด้วย _wo และมาคู่กับ argument _wo_version Terraform ส่งค่าให้ provider ตอน apply แล้วก็ลืมมันไป

resource "aws_db_instance" "main" {
identifier = "app-prod"
engine = "postgres"
instance_class = "db.t3.micro"
allocated_storage = 20
username = "app"
# The password reaches AWS but is never recorded in state
password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string
password_wo_version = 1
}

เพราะตัวค่าไม่ได้อยู่ใน state Terraform จึงไม่มีทางรู้ว่ามันเปลี่ยนไปแล้ว นั่นคืองานของ password_wo_version เมื่อ rotate secret ก็ขยับเลขจำนวนเต็มนี้ แล้ว provider จะส่งค่าใหม่ให้ในรอบ apply ถัดไป ถ้าไม่ขยับ Terraform ก็ไม่ไปยุ่งกับ password เดิม

มีข้อจำกัดสองข้อที่ควรรู้ก่อนหยิบมาใช้ write-only argument ต้องใช้ Terraform 1.11 ขึ้นไปและ provider ต้องรองรับ — argument ตัวนั้นต้องมีอยู่จริงบน resource type นั้น ๆ ให้เช็ค documentation ของ provider ไม่ใช่เหมาว่า _wo ใช้ได้ทุกที่ และค่า ephemeral ไหลไปได้แค่ในที่จำกัดเท่านั้น คือ write-only argument, ephemeral resource หรือ variable ตัวอื่น, provider configuration, provisioner block และ output ที่ mark เป็น ephemeral เอง การเอาไป assign ให้ argument ปกติหรือ output ปกติจะ error ซึ่งคือภาษาบังคับการรับประกันนี้ให้ ไม่ใช่มาขวางทาง

ในที่ที่ resource รองรับ การใช้คู่นี้ดีกว่า pattern แบบ data source แบบชัดเจน เพราะ secret ถูกอ่านจาก source of truth จริงตอน apply และ ไม่เคยถูกบันทึกลง state เลย ส่วนที่ไหนยังไม่รองรับ pattern แบบ data source บวกกับการปกป้อง backend ก็ยังเป็นคำตอบที่ถูกอยู่

พิสูจน์ว่า write-only argument ทั้งสองตัวมีอยู่จริง เทียบกับ schema จริงของ aws provider 6.66.0

หัวข้อที่มีชื่อว่า “พิสูจน์ว่า write-only argument ทั้งสองตัวมีอยู่จริง เทียบกับ schema จริงของ aws provider 6.66.0”

password_wo/password_wo_version บน aws_db_instance และ secret_string_wo/secret_string_wo_version บน aws_secretsmanager_secret_version มีอยู่จริงทั้งคู่ใน schema จริงของ aws provider — พิสูจน์ด้วย terraform validate จริงกับ provider 6.66.0 (mock credentials ไม่ต้องเรียก AWS จริงสำหรับ validate):

variables.tf
variable "db_password" {
type = string
ephemeral = true
sensitive = true
}
variable "api_key_value" {
type = string
ephemeral = true
sensitive = true
}
# terraform.tfvars — harness fixture only, so this lesson's proof can run
# offline; a real repo never commits secret values this way (see the
# .gitignore checklist below) — supply real values via TF_VAR_* instead,
# proven later in this section.
db_password = "mock-secret-value"
api_key_value = "mock-api-key-value"
main.tf
resource "aws_db_instance" "main" {
identifier = "app-prod"
engine = "postgres"
instance_class = "db.t3.micro"
allocated_storage = 20
username = "app"
password_wo = var.db_password
password_wo_version = 1
skip_final_snapshot = true
}
resource "aws_secretsmanager_secret" "api_key" {
name = "prod/third-party/api-key"
}
resource "aws_secretsmanager_secret_version" "api_key" {
secret_id = aws_secretsmanager_secret.api_key.id
secret_string_wo = var.api_key_value
secret_string_wo_version = 1
}
$ terraform validate
Success! The configuration is valid.

output จริงของ terraform plan (mock credentials, สร้าง resource ล้วน ๆ) แสดงให้เห็นเลยว่า provider แสดง write-only argument ยังไง — ไม่ถึงกับไปโผล่เป็นค่าใน diff แบบอ่านได้ด้วยซ้ำ:

# aws_secretsmanager_secret_version.api_key will be created
+ resource "aws_secretsmanager_secret_version" "api_key" {
+ has_secret_string_wo = (known after apply)
+ secret_string_wo = (write-only attribute)
+ secret_string_wo_version = 1
}

และใน terraform show -json ของ plan ที่ save ไว้ resource_changes[].change.after.secret_string_wo เป็น null พร้อม after_sensitive.secret_string_wo เป็น true — Terraform ตัดค่าออกจริง ๆ จากรูปแบบ machine-readable ของ plan ด้วย ไม่ใช่แค่ output ของ CLI ที่สวยงาม:

"after": { "secret_string_wo": null, "secret_string_wo_version": 1 },
"after_sensitive": { "secret_string_wo": true }

has_secret_string_wo (boolean ที่ provider คำนวณจริง) คือวิธีที่ plan รอบถัดไปรู้ได้ว่า “มีการตั้ง write-only value ไว้” โดยไม่ต้องเก็บว่าค่านั้นคืออะไรเลย provider เก็บแค่ bit เดียวนี้ ไม่ใช่ตัว secret

gotcha ที่คอร์สนี้เจอ: default ของ ephemeral variable ยังรั่วอยู่ดี

หัวข้อที่มีชื่อว่า “gotcha ที่คอร์สนี้เจอ: default ของ ephemeral variable ยังรั่วอยู่ดี”

การ mark variable เป็น ephemeral = true (Terraform 1.10) กันไม่ให้มันไปโผล่ที่ argument หรือ output ปกติ แต่ ไม่ได้ แปลว่าเขียนค่าเป็น literal default = ใน source .tf จะปลอดภัย — พิสูจน์จริง:

variable "db_password" {
type = string
ephemeral = true
sensitive = true
default = "mock-secret-value" # do not do this
}
$ terraform show -json tfplan | grep -o "mock-secret-value"
mock-secret-value

ค้นหา literal string นี้ใน plan.json ที่ได้เจอสองที่: .configuration.root_module.variables.db_password.default กับ .variables.db_password.value ไม่ใช่ bug ของ ephemeral/write-only เลยสักนิด — block configuration ของ show -json มี document ไว้ว่าเป็น echo ของ configuration ตามที่เขียนไว้จริง ๆ และ default ที่ hardcode ไว้ก็คือสิ่งที่เขียนไว้จริง ephemeral = true หยุดค่าจาก การถูก persist ลง state หรือไหลเข้า output ปกติเท่านั้น มันไม่ได้พูดอะไรเลยเกี่ยวกับ literal ที่คุณพิมพ์ตรง ๆ ลงใน HCL ที่ version-control ไว้ ซึ่งเป็น rule เดียวกับ “ห้าม hardcode secret ในไฟล์ .tf” จากต้นบทนี้เอง แค่มาโผล่ที่นี่อีกที การเอา default ออกแล้วส่งค่าผ่าน TF_VAR_db_password (environment variable ที่ไม่เคย commit) ตอน plan เท่านั้น ปิด gap นี้ได้สมบูรณ์ — พิสูจน์ด้วยการค้นหาแบบเดียวกันกับ plan JSON ที่ได้ ไม่เจออะไรเลย:

$ TF_VAR_db_password="mock-secret-value" terraform plan -out=tfplan
$ terraform show -json tfplan | grep -o "mock-secret-value"
# (ไม่มี output เลย — literal ไม่โผล่ใน plan JSON ที่ไหนเลย)

.gitignore สั้น ๆ ที่ตั้งใจเขียนไว้ กันอุบัติเหตุข้างบนส่วนใหญ่ไม่ให้ไปถึง git ตั้งแต่แรก

# Terraform / Terragrunt .gitignore
# Never commit state — it can contain plaintext secrets and is the
# canonical source of truth; committing it invites drift and leaks.
*.tfstate
*.tfstate.backup
# Local provider and module cache, regenerated by `terraform init`.
.terraform/
# May contain sensitive values depending on the file — judge case by
# case rather than blanket-ignoring every *.tfvars in the repo.
*.tfvars
# Terragrunt's local working-directory cache, regenerated automatically.
.terragrunt-cache/

บางบรรทัดในนี้ควรมีเหตุผลกำกับสั้น ๆ

  • *.tfstate กับ *.tfstate.backup ห้าม commit state เด็ดขาด นอกจากปัญหาเรื่องขนาดไฟล์กับ merge conflict แล้ว state ยังมี plaintext secret แฝงอยู่ได้อย่างที่พูดไปข้างบน และซ้ำกับงานที่ remote backend ทำอยู่แล้วอย่างถูกต้อง
  • .terraform/ นี่คือ local cache ของ provider กับ module ที่ init ดาวน์โหลดมา folder นี้ใหญ่ ผูกกับเครื่องนั้น ๆ และ reproduce ได้เต็มร้อยจาก .terraform.lock.hcl เลยไม่มีอะไรใน folder นี้ที่คุ้มค่าจะ version
  • *.tfvars ให้ตัดสินเป็นเคส ๆ ไป ไม่ใช่ ignore ทุกไฟล์แบบเหมารวม บางทีมตั้งใจ commit environment.tfvars ที่ไม่ sensitive ซึ่งแค่ set instance size หรือชื่อ region — rule คือ “ไฟล์นี้มี secret หรือเปล่า” ไม่ใช่ “ไฟล์ .tfvars ทุกไฟล์คือ secret เสมอ”
  • .terragrunt-cache/ เป็น equivalent ของ .terraform/ ฝั่ง Terragrunt มีหนึ่งตัวต่อ unit regenerate ทุกครั้งที่รัน ไม่ต้อง commit เลย และถ้า commit พลาดมักทำให้ repo บวมเร็วมาก
flowchart LR
  bad["Hardcoded credential in a .tf / .tfvars file"] -->|committed by accident| leak["Leaked secret in git history"]
  sm["Secrets Manager: source of truth"] -->|data source at apply time| attr["Resource attribute"]
  attr -->|still recorded| state["terraform.tfstate"]
  sm -->|ephemeral block| eph["Ephemeral value, never persisted"]
  eph -->|write-only argument| provider["Sent to AWS at apply, not stored"]
credential ที่ hardcode ไว้ในไฟล์ที่ commit เทียบกับ secrets manager ที่เป็น source of truth จริง อ่านตอน apply time

ทุกเทคนิคในบทนี้ตอบคำถามเดียวกันด้วยความแรงที่เพิ่มขึ้นเรื่อย ๆ: “secret value นี้อยู่ที่ไหน นานแค่ไหน” credential ที่ hardcode ไว้อยู่ตลอดไป ใน git history เป็นคำตอบที่อ่อนที่สุด variable อยู่นานเท่าที่สำเนาของมันใน .tfvars/CI secret/state ยังอยู่ data source แคบลงแค่ ค่ามาจากไหน (secrets manager จริง ไม่ใช่ค่าที่พิมพ์เอง) แต่ไม่แคบ ไปจบที่ไหน — พิสูจน์ในเนื้อหา baseline ของบทนี้เองว่ามันยังลงไปอยู่ใน state ephemeral + write-only คือคำตอบที่แรงที่สุด ค่ามีอยู่แค่ใน memory แค่ระหว่าง operation เดียว ส่งตรงให้ provider แล้วไม่เคยถูกบันทึกถาวรที่ไหนที่ Terraform คุมได้เลย — พิสูจน์ข้างบนด้วย null จริงใน change.after กับ boolean has_secret_string_wo จริงที่ยืนแทนมัน gotcha ที่พิสูจน์ข้างบน (default แบบ literal บน ephemeral variable ยังรั่วเข้า show -json) คือบทเรียนเดียวกันแบบย่อ: กลไก อาจสมบูรณ์แบบ แต่ literal ที่คุณพิมพ์เอง ยังเอาชนะมันได้

ทางเลือกได้อะไรเสียอะไร
provider credential ที่ resolve จาก environment (OIDC, instance role)ไม่มี key ค้างอยู่ให้หลุดจากไฟล์ .tf/.tfvarsต้อง setup infrastructure ก่อน (OIDC provider, IAM role) ถึงจะใช้งานได้
data source แทน variable สำหรับ secretรวม lifecycle การ rotate ไว้ที่เดียว ค่าจริงไม่เคยอยู่ใน .tfvarsพิสูจน์ในเนื้อหา baseline ของบทนี้: ค่าที่ resolve แล้วยังลงไปอยู่ใน state — ต้องปกป้อง backend อยู่ดี
ephemeral + write-only (password_wo, secret_string_wo)พิสูจน์ข้างบน: ค่าไม่มีอยู่จริงทั้งใน state และใน change.after ของ planต้องมี Terraform 1.10/1.11+ และ provider รองรับเป็นราย resource — ไม่ใช่ทุก argument ของทุก resource จะมีคู่ _wo
ephemeral variable ที่ไม่มี default ส่งผ่าน TF_VAR_* อย่างเดียวพิสูจน์ข้างบน: ค่าไม่โผล่ใน plan JSON เลยไม่ว่าที่ไหนเผลอทำพังได้ง่าย ๆ ถ้าเพิ่ม default = literal ไว้เพื่อความสะดวกตอน test local แล้วลืมเอาออก — พิสูจน์ข้างบนว่าแค่นั้นก็ทำให้รั่วกลับมาอีกครั้ง
  • “mark variable เป็น sensitive = true กันมันออกจาก state ได้” เนื้อหา baseline ของบทนี้เองกับ module State Management บอกไว้แล้ว: sensitive แค่ redact CLI output ค่ายังคงลงไปอยู่ใน terraform.tfstate แบบ plaintext
  • “data source ที่ดึงจาก secrets manager แทนที่ความจำเป็นของ ephemeral/write-only ได้เต็มร้อย” พิสูจน์ข้างบนจาก design ของ pattern data source เอง: ผลลัพธ์ของ data source ถูก cache ใน state เหมือน attribute ของ resource เป๊ะ — ephemeral มีไว้เพื่อปิด gap นี้โดยเฉพาะเพราะ data แก้ไม่ได้
  • “ephemeral = true บน variable ทำให้ค่าอะไรก็ตามที่ assign เข้าไปปลอดภัย รวมถึง default ที่ hardcode ไว้” พิสูจน์ข้างบนด้วย string จริงที่ค้นหาเจอใน plan.json: default แบบ literal บน ephemeral variable ถูก echo เข้า block configuration และ variables ระดับบนสุดของ show -json ไม่ว่า flag ephemeral จะตั้งไว้หรือไม่ ephemeral ปกป้อง flow ของ state/output ไม่ใช่ literal ที่พิมพ์ตรง ๆ ลงใน HCL ที่ version-control ไว้ — นั่นคือ rule เก่าแยกต่างหากที่บทนี้คุยไปแล้ว
  • “write-only argument ใช้ได้กับ resource ไหนก็ได้ เพราะ feature นี้มีอยู่ใน provider แล้ว” พิสูจน์ข้างบนด้วยการ validate schema ตรง ๆ: password_wo มีอยู่บน aws_db_instance และ secret_string_wo มีอยู่บน aws_secretsmanager_secret_version โดยเฉพาะ เพราะ schema ของแต่ละ resource ต้องเพิ่มมันเข้าไปเอง feature ที่มีอยู่ใน Terraform core ไม่ได้แปลว่าทุก argument ของทุก resource จะมีคู่ _wo

เวลา review AI-generated Terraform ที่จัดการ secret ให้เช็ค:

  • default = literal บน variable ที่ mark ephemeral = true หรือ sensitive = true — พิสูจน์ข้างบน สำหรับกรณี ephemeral นี่ทำลายจุดประสงค์ทั้งหมด (รั่วเข้า show -json) และเป็นกลิ่นที่ต้อง review ไม่ว่ากรณีไหน
  • argument แบบ password_wo/_wo ที่ใช้โดยไม่เช็ค schema ของ resource นั้นจริง ๆ — พิสูจน์ข้างบนผ่าน terraform validate argument ต้องมีอยู่จริงบน resource ตัวนั้นเป๊ะ ๆ ชื่อ _wo ที่ฟังดูสมเหตุสมผลแต่ไม่มีจริงจะ fail validate ทันที เป็นวิธีเร็วที่สุดในการจับชื่อ argument ที่ AI แต่งขึ้นมาเอง
  • password_wo ที่ไม่มี password_wo_version ขยับตามใน PR ที่ควรจะ rotate credential — เพราะตัวค่าเองไม่ได้ track ใน state เลข version ที่ไม่เปลี่ยนแปลว่า provider ยังใช้ password เก่าแบบเงียบ ๆ แม้ ephemeral source จะ return ค่าใหม่แล้วก็ตาม
  • ephemeral value ที่ assign เข้า argument ปกติ (ไม่ใช่ write-only ไม่ใช่ ephemeral) หรือ output ปกติ — ภาษาปฏิเสธตรง ๆ เลย นี่คือ type error ระดับ Terraform ไม่ใช่การรั่วแบบเงียบ ๆ ตอน runtime แต่ก็ยังควรรู้จักตั้งแต่แรกเห็น ไม่ใช่แปลกใจตอนเจอ error
  • โค้ด application ที่อ่าน secret จาก data source ของ aws_secretsmanager_secret_version (ไม่ใช่ ephemeral) แล้ว treat เหมือนว่าเทียบเท่ากับการจัดการแบบ write-only — พิสูจน์ข้างบน ผลลัพธ์ของ data ถูก cache ใน state มีแค่คู่ ephemeral/write-only เท่านั้นที่หลีกเลี่ยงได้

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

variable "db_password" {
type = string
ephemeral = true
sensitive = true
default = "changeme-in-ci"
}
resource "aws_db_instance" "main" {
# ...
password_wo = var.db_password
password_wo_version = 1
}
เฉลย

default = "changeme-in-ci" ดูเหมือน placeholder ปลอม ๆ ที่ชัดเจนอยู่แล้ว ซึ่งนั่นแหละคือสิ่งที่ทำให้ bug นี้พลาดตอน review ได้ง่าย — แต่มันก็ยังเป็น string literal ที่ commit เข้า version control อยู่ดี และพิสูจน์ข้างบนว่า ephemeral = true ไม่ได้หยุด default แบบ literal จากการถูก echo เข้า block configuration กับ variables ของ terraform show -json ถ้า “placeholder” นี้ถูกปล่อยไว้เป็น default จริงใน environment ที่ไม่มีใครจำได้ว่าต้อง override ด้วย -var/TF_VAR_db_password database จะถูก provision ด้วย password string ตรง ๆ ที่มองเห็นได้สาธารณะ จาก variable ที่ดูเหมือนจะ ephemeral-safe ทางแก้ พิสูจน์ข้างบน คือไม่ต้องมี default เลย ส่งค่าผ่าน TF_VAR_db_password หรือ -var เท่านั้น ซึ่งกันมันออกจาก plan JSON ได้เต็มร้อย

practice ปัจจุบันสำหรับ authenticate AWS provider ใน CI pipeline คืออะไร
ทำไมดึง secret จาก secrets manager ผ่าน data source ถึงมักดีกว่านิยามเป็น Terraform variable
nuance อะไรที่ยังใช้ได้อยู่ แม้จะเปลี่ยน secret จาก Terraform variable มาเป็น data source ของ secrets manager แล้ว
resource ตัวหนึ่งรองรับ write-only argument ชื่อ password_wo ที่รับค่าจาก ephemeral block แล้ว password_wo_version ทำหน้าที่อะไร
literal นี้เจอใน plan.json จริงของ variable ที่ประกาศ ephemeral = true พร้อม default = "mock-secret-value" สิ่งนี้พิสูจน์อะไร?
$ terraform show -json tfplan | grep -o "mock-secret-value"
mock-secret-value
อะไรควรอยู่ใน gitignore ของ repo Terraform/Terragrunt และเพราะอะไร