IT/IaC

이제 Terraform state에 password 안 넣어도 된다

gfrog 2026. 5. 6. 12:10
반응형

지난주에 사내 보안팀에서 state 파일 감사를 돌렸다. 결과 보고서를 받아보니 우리 팀 모듈 몇 개에 RDS master password가 평문으로 박혀 있었다. random_password로 만들어서 aws_db_instance.password에 넘긴 건데, 그 흐름을 타는 모든 값이 state에 그대로 적힌다. KMS로 백엔드 암호화는 해뒀어도, 누가 terraform show만 치면 그냥 보이는 거라 좀 찜찜했다.

이거 사실 1.11부터 들어온 write-only argument로 우회할 수 있다. 1.10에서 ephemeral resource가 먼저 들어왔고, 1.11에서 write-only가 따라 들어온 건데, 모르고 계신 분이 의외로 많길래 짧게 정리해본다.

동작 방식

1.10의 ephemeral 블록은 plan/apply 사이클 동안에만 메모리에 존재한다. state에도, plan 파일에도 안 남는다. Vault에서 끌어온 시크릿이라도 그 단계에서만 살아있다가 사라지는 거라 감사 측면에서 안전하다.

근데 1.10까지는 그 ephemeral 값을 managed resource(aws_db_instance 같은 것)에 넘기는 순간 다시 state에 적혔다. 의미가 좀 반쪽이었던 것이다. 1.11의 write-only argument는 ephemeral 값을 managed resource까지 그대로 흘려보낼 수 있게 해준다. 결과적으로 password가 state 어디에도 안 남는다.

코드 예시

ephemeral "vault_kv_secret_v2" "rds" {
  mount = "kv"
  name  = "prod/rds/master"
}

resource "aws_db_instance" "main" {
  identifier = "prod-main"
  engine     = "postgres"
  username   = "admin"

  # 일반 password 인자가 아닌 _wo 접미사
  password_wo         = ephemeral.vault_kv_secret_v2.rds.data["password"]
  password_wo_version = 1
}

핵심은 두 가지다. password_wo는 state에 안 남는다. 그리고 password_wo_version은 그냥 정수인데, 이 값을 올리면 Terraform이 "아 시크릿이 바뀌었구나" 하고 재적용한다. drift detection을 의도적으로 끊어놓은 셈이다. 시크릿 로테이션할 때 버전만 +1 해주면 된다.

도입할 때 본 함정

provider 버전이 받쳐줘야 한다. AWS provider는 5.83 이상, Vault는 4.5 이상이 필요했다. terraform init -upgrade 한 번 돌리고 시작하는 걸 권한다. 그리고 모든 인자가 _wo를 지원하는 것도 아니다 — 리소스별로 어떤 인자가 write-only 호환인지 provider docs에 따로 명시돼 있어서, 처음 도입할 때는 그거 한 번씩 확인하는 게 좋다.

또 하나, write-only 값은 plan diff에 안 찍힌다. PR 리뷰에서 "어 이 변경 뭐지?" 싶을 때 password 변경이 숨어있을 수 있다는 뜻이다. 그래서 우리 팀은 *_wo_version 변경이 들어간 PR은 라벨을 달도록 CI에 hook을 걸어뒀다. 별거 아닌데 리뷰어 입장에서 마음이 편하다.

정리

이미 state에 박혀있는 password들을 어떻게 정리할지는 아직 논의 중이다. 한 모듈씩 옮길지, 마이그레이션 스크립트로 한 번에 갈아엎을지. 후자가 깔끔한데 롤백이 부담스러워서 선뜻 못 가고 있다. 신규 모듈부터 _wo 적용하는 건 일단 시작했다.

혹시 비슷한 마이그레이션 해보신 분 있으면 댓글로 경험 공유 부탁드립니다.

반응형