LabHub
배우기 러닝패스 코스

Infrastructure as Code

Marked sensitive, yet the password sat in the state file

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

도구가 만든 비밀번호가 상태·JSON 출력·저장한 계획의 어디에 평문으로 남는지 직접 찾고, OpenTofu 의 상태·계획 암호화를 기존 평문 상태에서 이주해 강제한 뒤, 비밀을 상태 밖에 두는 구성을 만듭니다.

왜 중요한가

sensitive 는 사람이 보는 화면에서 값을 가리는 표시일 뿐, 도구가 저장하는 기록에는 값이 그대로 들어갑니다. 상태 파일과 CI 가 올리는 계획 파일이 곧 비밀 저장소가 되는 셈이라, 이것을 모르면 '가렸으니 안전하다' 고 착각한 채 상태를 아무 곳에나 둡니다. 막는 방법은 세 층입니다: 저장 시 암호화(키가 곧 상태의 열쇠가 됨), 평문으로 되돌아가지 못하게 강제, 그리고 애초에 값을 도구에 넘기지 않고 참조만 넘기는 설계입니다.

단계

  1. /root/iac-secret/main.tfrandom_password.db(길이 20, 특수문자 없음), 그 값을 out/db.confpassword=<값> 으로 쓰는 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.txtplanleak=<수> 로 적으세요.
  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.keyopenssl rand -hex 16 으로 비밀을 만들고 권한을 600 으로 두세요(도구 밖에서 만든 비밀). /root/iac-secret/ref/main.tf 에는 random_password 없이 out/db.confpassword_file=<그 경로> 한 줄을 쓰는 local_file.dbconf 만 두고 init·apply 합니다. 채점기는 ref 의 상태 어디에도 db.key 의 값이 없고 경로만 있는지 봅니다.

참고

비밀번호를 만들고 출력은 가린다

/root/iac-secret/main.tfrandom_password.db(길이 20, 특수문자 없음), 그 값을 out/db.confpassword=<값> 으로 쓰는 local_sensitive_file.dbconf, 그리고 sensitive = true 인 출력 db_password 를 두세요. init·apply 한 뒤 tofu output 에 값이 가려져 보이는지 확인합니다.

random_password 의 result 는 프로바이더가 민감한 값으로 표시합니다. 그 값을 출력으로 내보내려면 출력에도 sensitive 를 붙여야 계획이 통과합니다. local_sensitive_file 은 내용을 화면에 보이지 않게 할 뿐 파일은 똑같이 씁니다.

가린 비밀이 상태 파일의 어디에 있는가

상태 파일에서 비밀번호 값이 들어 있는 모든 자리를 찾아 /root/iac-secret/leak.txt 에 JSON 경로를 한 줄에 하나씩 적으세요. 경로는 키와 배열 번호를 점으로 이은 형식입니다(예: resources.0.instances.0.attributes.x). 채점기는 tofu state pull 로 받은 상태에서 같은 계산을 해 비교합니다.

tofu output -raw db_password 로 값을 얻을 수 있습니다(가림은 표시 방식일 뿐이라는 증거이기도 합니다). jq 의 paths(scalars)getpath 로 값이 같은 자리를 모두 찾을 수 있습니다. 한 자리만 있지 않습니다.

가림은 화면에서만 — JSON 출력과 저장한 계획

tofu output 의 출력을 /root/iac-secret/masked.txt 에 저장하세요. 그다음 tofu plan -out=/root/iac-secret/leak.tfplan 으로 계획을 저장하고, 계획 파일(zip) 안의 tfstate 항목에 비밀번호가 몇 번 나오는지 세어 /root/iac-secret/planleak.txtplanleak=<수> 로 적으세요.

저장한 계획 파일은 zip 이고, 그 안에 계획을 세울 때의 상태 사본이 들어 있습니다. unzip -l 로 항목을 보고 unzip -p <파일> tfstate 로 꺼내 셉니다. CI 가 계획 파일을 산출물로 올리면 그 산출물도 비밀이 됩니다.

평문 상태를 암호화 상태로 옮긴다

/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 가 있음).

암호화를 켜기만 하면 도구가 기존 평문 상태를 읽기를 거부합니다(조작된 평문일 수 있어서). 그래서 한 번은 평문을 읽을 수 있게 fallback 을 두고 적용해 새 방법으로 다시 쓰게 합니다. 패스프레이즈는 16자 이상이어야 합니다.

이주가 끝나면 평문 대체를 빼고 강제한다

encryption.tf 에서 unencrypted 방법과 두 fallback 을 지우고 state·plan 에 enforced = true 를 두세요. 올바른 패스프레이즈로는 계획이 깨끗해야 합니다. 틀린 패스프레이즈(TF_VAR_state_passphrase=wrong-passphrase-000000)로 tofu plan 을 돌린 출력을 /root/iac-secret/wrongkey.txt 에 저장하세요. 채점기는 사본에서 틀린 키로 실제로 읽기가 실패하는지 확인합니다.

fallback 을 남겨 두면 누군가 평문 상태를 끼워 넣어도 읽힙니다. enforced 는 평문으로 쓰는 것 자체를 막습니다. 키를 잃으면 상태를 영영 못 읽으니 키 보관이 곧 상태 보관입니다.

암호화된 계획 파일은 키 없이는 읽을 수 없다

올바른 패스프레이즈로 tofu plan -out=/root/iac-secret/safe.tfplan 을 저장하세요. 이 파일에는 비밀번호가 평문으로 없어야 하고 zip 도 아니어야 합니다. 키가 있으면 tofu show -json safe.tfplan 이 되고, 키가 없으면 실패하는지 확인하세요.

3단계의 leak.tfplan 과 나란히 head -c 2 로 앞 두 바이트를 비교해 보세요. zip 은 PK 로 시작합니다. 계획 암호화는 state 와 별도로 plan 블록이 정합니다.

비밀은 상태 밖에 두고, 상태에는 참조만 넣는다

/root/iac-secret/ref/secret/db.keyopenssl rand -hex 16 으로 비밀을 만들고 권한을 600 으로 두세요(도구 밖에서 만든 비밀). /root/iac-secret/ref/main.tf 에는 random_password 없이 out/db.confpassword_file=<그 경로> 한 줄을 쓰는 local_file.dbconf 만 두고 init·apply 합니다. 채점기는 ref 의 상태 어디에도 db.key 의 값이 없고 경로만 있는지 봅니다.

도구가 값을 만들거나 읽으면 그 값은 상태로 들어갑니다. 값을 쓰는 쪽(애플리케이션)이 파일이나 비밀 저장소에서 직접 읽게 하고, 도구는 위치만 알려 주는 구성이 가장 단순한 회피입니다. 데이터 소스로 비밀 파일을 읽어도 상태에 남는다는 점에 주의하세요.