Infrastructure as Code · 상태 관리 · 실습
sensitive 로 가렸는데 상태 파일에 비밀번호가 그대로 있었다
목표
도구가 만든 비밀번호가 상태·JSON 출력·저장한 계획의 어디에 평문으로 남는지 직접 찾고, OpenTofu 의 상태·계획 암호화를 기존 평문 상태에서 이주해 강제한 뒤, 비밀을 상태 밖에 두는 구성을 만듭니다.
왜 중요한가
sensitive 는 사람이 보는 화면에서 값을 가리는 표시일 뿐, 도구가 저장하는 기록에는 값이 그대로 들어갑니다. 상태 파일과 CI 가 올리는 계획 파일이 곧 비밀 저장소가 되는 셈이라, 이것을 모르면 '가렸으니 안전하다' 고 착각한 채 상태를 아무 곳에나 둡니다. 막는 방법은 세 층입니다: 저장 시 암호화(키가 곧 상태의 열쇠가 됨), 평문으로 되돌아가지 못하게 강제, 그리고 애초에 값을 도구에 넘기지 않고 참조만 넘기는 설계입니다.
단계
1. /root/iac-secret/main.tf 에 random_password.db(길이 20, 특수문자 없음), 그 값을 out/db.conf 에 password=<값> 으로 쓰는 local_sensitive_file.dbconf, 그리고 sensitive = true 인 출력 db_password 를 두세요. init·apply 한 뒤 tofu output 에 값이 가려져 보이는지 확인합니다.
2. 상태 파일에서 비밀번호 값이 들어 있는 모든 자리를 찾아 /root/iac-secret/leak.txt 에 JSON 경로를 한 줄에 하나씩 적으세요. 경로는 키와 배열 번호를 점으로 이은 형식입니다(예: resources.0.instances.0.attributes.x). 채점기는 tofu state pull 로 받은 상태에서 같은 계산을 해 비교합니다.
3. tofu output 의 출력을 /root/iac-secret/masked.txt 에 저장하세요. 그다음 tofu plan -out=/root/iac-secret/leak.tfplan 으로 계획을 저장하고, 계획 파일(zip) 안의 tfstate 항목에 비밀번호가 몇 번 나오는지 세어 /root/iac-secret/planleak.txt 에 planleak=<수> 로 적으세요.
4. /root/iac-secret/encryption.tf 에 민감한 변수 state_passphrase 와 상태·계획 암호화를 선언하세요: pbkdf2 키 제공자(패스프레이즈는 그 변수), aes_gcm 방법, 그리고 기존 평문을 읽기 위한 unencrypted 방법을 state·plan 의 fallback 으로 둡니다. 패스프레이즈는 labhub-state-passphrase-01 이고 환경 변수 TF_VAR_state_passphrase 로 넘깁니다. apply 하면 terraform.tfstate 가 암호화돼야 합니다(파일에 비밀번호가 없고 encrypted_data 가 있음).
5. encryption.tf 에서 unencrypted 방법과 두 fallback 을 지우고 state·plan 에 enforced = true 를 두세요. 올바른 패스프레이즈로는 계획이 깨끗해야 합니다. 틀린 패스프레이즈(TF_VAR_state_passphrase=wrong-passphrase-000000)로 tofu plan 을 돌린 출력을 /root/iac-secret/wrongkey.txt 에 저장하세요. 채점기는 사본에서 틀린 키로 실제로 읽기가 실패하는지 확인합니다.
6. 올바른 패스프레이즈로 tofu plan -out=/root/iac-secret/safe.tfplan 을 저장하세요. 이 파일에는 비밀번호가 평문으로 없어야 하고 zip 도 아니어야 합니다. 키가 있으면 tofu show -json safe.tfplan 이 되고, 키가 없으면 실패하는지 확인하세요.
7. /root/iac-secret/ref/secret/db.key 에 openssl rand -hex 16 으로 비밀을 만들고 권한을 600 으로 두세요(도구 밖에서 만든 비밀). /root/iac-secret/ref/main.tf 에는 random_password 없이 out/db.conf 에 password_file=<그 경로> 한 줄을 쓰는 local_file.dbconf 만 두고 init·apply 합니다. 채점기는 ref 의 상태 어디에도 db.key 의 값이 없고 경로만 있는지 봅니다.
참고
- 파드에는 OpenTofu 1.9.0 과 local·random 프로바이더 미러가 있어 인터넷 없이 돕니다. 4단계부터는 셸마다
export TF_VAR_state_passphrase='labhub-state-passphrase-01'를 먼저 실행하세요. - 암호화를 켠 뒤에는
tofu state pull이 복호화된 상태를 보여 줍니다. 파일을 직접 열면 encrypted_data 만 보입니다. - 값을 상태·계획에 아예 남기지 않는 ephemeral 리소스와 write-only 속성은 OpenTofu 1.11 에서 들어왔습니다. 이 파드의 1.9.0 에는 없어서 참조 방식으로 대신합니다.
- 흔한 실수: 4단계에서 fallback 없이 암호화를 켜 기존 평문 상태를 못 읽는 것. 흔한 실수: 패스프레이즈를 main.tf 기본값에 적어 저장소에 커밋하는 것.
- [Sensitive Data in State](https://opentofu.org/docs/language/state/sensitive-data/) · [State and Plan Encryption](https://opentofu.org/docs/language/state/encryption/) · [Ephemerality](https://opentofu.org/docs/language/ephemerality/) · [Terraform: Sensitive data](https://developer.hashicorp.com/terraform/language/state/sensitive-data)
단계 7개
- 비밀번호를 만들고 출력은 가린다
- 가린 비밀이 상태 파일의 어디에 있는가
- 가림은 화면에서만 — JSON 출력과 저장한 계획
- 평문 상태를 암호화 상태로 옮긴다
- 이주가 끝나면 평문 대체를 빼고 강제한다
- 암호화된 계획 파일은 키 없이는 읽을 수 없다
- 비밀은 상태 밖에 두고, 상태에는 참조만 넣는다