LabHub
배우기 러닝패스 코스

정책을 코드로 · 클러스터에 닿기 전에 막는다 · 실습

종료 코드가 0 이라 파이프라인이 몇 주 동안 초록불이었다

LabHub 에서 이어서 보기

목표

진짜 OpenTofu 계획을 JSON 으로 뽑아 구조를 읽고, 그 JSON 에 정책을 걸어 클러스터와 클라우드에 닿기 전에 파괴·교체·빠진 태그를 잡습니다. 그리고 위반을 찾고도 0 으로 끝나는 도구 위에서 믿을 수 있는 게이트를 만듭니다.

왜 중요한가

어드미션 컨트롤은 이미 만들어진 요청을 봅니다. 그런데 데이터베이스 교체나 버킷 공개 같은 위험한 변경은 쿠버네티스 API 를 지나지 않고 클라우드로 곧장 갑니다. 그 변경들에는 공통점이 하나 있습니다 — 적용 전에 계획 단계가 있고, 그 계획은 JSON 으로 뽑을 수 있는 구조화된 문서라는 것입니다. 여기에 정책을 걸면 되돌릴 것이 아직 없을 때 막을 수 있습니다. 다만 계획에는 적용해 봐야 정해지는 값이 섞여 있어서, 그 자리를 모르고 규칙을 쓰면 오탐이 쏟아지고 게이트는 며칠 만에 꺼집니다. 그리고 판정을 도구의 종료 코드에 맡기면, 위반을 찾고도 0 으로 끝나는 도구 하나 때문에 파이프라인이 몇 주 동안 조용히 초록불일 수 있습니다. 그래서 이 실습은 규칙을 쓰는 법만큼이나 '무엇을 근거로 판정하는가'를 다룹니다.

단계

1. /root/tfpolicy/main.tf 에 local·random·null 프로바이더를 요구하고 리소스 넷을 선언하세요. null_resource.api 의 triggers 는 owner = "platform"·env = "dev", null_resource.worker 의 triggers 는 owner = "data"·env = "dev", local_file.config${path.module}/out/config.txtv1 한 줄(끝에 줄바꿈)을 쓰고, terraform_data.release 는 input 이 v1 입니다. tofu inittofu apply -auto-approve 로 적용한 다음, 변경이 하나도 남지 않은 계획을 /root/tfpolicy/base.tfplan 으로 저장하고 tofu show -json 으로 /root/tfpolicy/base.json 을 만드세요.
2. main.tf 를 이렇게 고치세요. null_resource.worker 블록을 지우고, random_pet.suffix(length = 2)를 더하고, null_resource.cache 를 더합니다(triggers 는 owner = ""·env = "dev"·name = random_pet.suffix.id). local_file.config 의 내용은 v2 한 줄로, terraform_data.release 의 input 은 v2 로 바꿉니다. 적용하지 마세요. 계획을 /root/tfpolicy/change.tfplan 으로 저장하고 /root/tfpolicy/change.json 을 만든 뒤, /root/tfpolicy/changes.txtno-op 이 아닌 자원마다 <주소> <create|update|replace|delete> 를 한 줄씩 적으세요. 삭제와 생성이 한 자원에 함께 들어 있으면 replace 한 줄로 적습니다.
3. /root/tfpolicy/change.jsonresource_changes[].change.after_unknown 을 읽어 /root/tfpolicy/unknown.txt 를 만드세요. 모르는 잎(값이 true 인 자리)이 하나라도 있는 자원마다 <주소> <모르는 잎 개수> 를 한 줄씩 적습니다. triggers.name 처럼 중첩된 자리도 잎 하나로 셉니다. 모르는 잎이 없는 자원은 적지 않습니다. 줄 순서는 보지 않습니다.
4. /root/tfpolicy/policies/block.yamlapiVersion: json.kyverno.io/v1alpha1·kind: ValidatingPolicy 인 정책을 쓰세요. 규칙 하나를 두고, change.actionsdelete 가 들어 있는 자원이 하나도 없어야 통과하게 합니다(순수 삭제와 교체가 모두 걸립니다). 그다음 KYVERNO_EXPERIMENTAL=true kyverno json scan --payload /root/tfpolicy/change.json --policy /root/tfpolicy/policies/block.yaml 을 돌리고, 그 출력 전체와 종료 코드를 /root/tfpolicy/scan-exit.txt 에 저장하세요. 종료 코드는 exit=<코드> 꼴로 마지막에 한 줄 덧붙입니다. /root/tfpolicy/base.json 에도 같은 스캔을 돌려 통과하는 것을 눈으로 확인하세요.
5. /root/tfpolicy/gate.sh <계획JSON> 을 만드세요. policies/block.yaml 로 스캔을 돌려 보고서를 파싱합니다. 위반 줄(FAILED 또는 ERROR: 가 든 줄)마다 BLOCK 을 앞에 붙여 한 줄씩 출력하고, 마지막 줄RESULT block=<위반 줄 수> 를 출력하세요(뒤에 낱말이 더 붙어도 됩니다). 위반이 없으면 0, 하나라도 있으면 0 이 아닌 값으로 끝냅니다. 보고서에 판정 줄(PASSED·FAILED·ERROR:)이 하나도 없으면 도구 출력이 바뀐 것이므로 2 로 끝내고, 인자로 받은 파일이 없을 때도 2 로 끝냅니다. 채점기는 자기가 만든 깨끗한 계획과 위반 계획으로 이 스크립트를 돌립니다.
6. /root/tfpolicy/policies/block.yaml 에 두 번째 규칙을 더하세요. change.after.triggers 가 있는 자원은 change.after.triggers.owner있고 빈 문자열이 아니어야 통과합니다. 다만 그 값이 계획 단계에 아직 알려지지 않은 자원(change.after_unknown.triggers.ownertrue)은 위반으로 세지 않습니다. 규칙을 더한 뒤 ./gate.sh /root/tfpolicy/change.json 을 다시 돌려 위반이 두 줄이 되는지 확인하세요. 채점기는 owner 가 빈 계획·owner 가 아직 모르는 값인 계획·깨끗한 계획 셋으로 이 규칙을 시험합니다.
7. /root/tfpolicy/policies/warn.yaml 에 두 번째 정책 파일을 만드세요. 규칙 하나를 두고, 계획 JSON 의 최상위 resource_drift 가 비어 있어야 통과하게 합니다(키 자체가 없는 계획도 통과해야 합니다). 그리고 /root/tfpolicy/gate.sh 를 고쳐 두 정책을 따로 돌리게 하세요. block.yaml 의 위반은 BLOCK 을 붙여 출력하고 종료 코드를 0 이 아니게 만들고, warn.yaml 의 위반은 WARN 을 붙여 출력하되 종료 코드를 바꾸지 않습니다. 마지막 줄은 RESULT block=<차단 위반 수> warn=<경고 위반 수> 로 바꿉니다. 경고 정책 보고서에도 판정 줄이 하나도 없으면 2 로 끝냅니다.
8. /root/tfpolicy/out/config.txt 를 테라폼 밖에서 직접 고치세요(예: printf 'hand-edited\n' > /root/tfpolicy/out/config.txt). 그다음 계획을 /root/tfpolicy/drift.tfplan 으로 저장하고 /root/tfpolicy/drift.json 을 만드세요 — 최상위에 resource_drift 가 생깁니다. 마지막으로 base.json·change.json·drift.json 세 계획에 ./gate.sh 를 돌려 출력을 각각 /root/tfpolicy/reports/base.txt·/root/tfpolicy/reports/change.txt·/root/tfpolicy/reports/drift.txt 에 저장하고, 각 파일 마지막 줄EXIT <게이트 종료 코드> 를 덧붙이세요. 채점기는 세 계획에 게이트를 다시 돌려 보고서와 대조합니다.

참고

단계 8개

  1. 계획을 JSON 으로 뽑아 놓는다
  2. 한 계획에 네 가지 동작이 한꺼번에 들어온다
  3. 계획이 아직 모르는 값을 세어 둔다
  4. 규칙을 데이터로 적고 종료 코드를 찍어 본다
  5. 종료 코드를 버리고 보고서를 센다
  6. 태그를 요구했더니 아직 모르는 값까지 걸렸다
  7. 어떤 규칙은 막고 어떤 규칙은 알리기만 한다
  8. 손으로 고친 파일 하나가 드리프트로 잡힌다