images/·scripts/build/build-hardened-image.sh·suggest-go-upgrades.py·
build-image.yml·.claude/image-authoring.md·이미지 ADR(0001·0002·0004)을 삭제했다 —
전부 별도 public 레포 security-images 로 이미 이관됐다.
카탈로그 쪽에는 "무엇을 배포 중인가"를 아는 부분만 남긴다:
- catalog/image-map/<image>.env — 옛 catalog.env 의 카탈로그 레이아웃 정보만 뗀 것
- scripts/build/check-rebuild-needed.py — 드리프트 탐지(A 파트)만 남기고 핀 판단
(B 파트: pin_changes/apply_changes/parse_module_specs)은 제거
- scripts/build/apply-published-tags.py(신규) — security-images 의 published.json
을 읽어 카탈로그 values 를 패치
- .github/workflows/{self-build-drift-check,catalog-tag-update}.yml(신규) — 각각
드리프트 스캔+트리거, 발행 태그 반영
effective_severity 를 cve-gate.py 로 옮겼다 — check-rebuild-needed.py 가 핀 도구를
거치지 않고 게이트를 직접 로드하게 하기 위한 선행 작업이다.
두 레포의 계약은 published.json 스키마 하나뿐이다 — security-images 는 이 카탈로그를
모른다(단방향 의존). 이관 배경·결합점 전체는
doc/migrations/self-build-images-to-security-images.md.
부수 수정: 자체 빌드 이미지를 참조하는 차트 values/README 의 죽은 링크(images/**,
doc/decisions/000{1,2,4}, .claude/image-authoring.md)를 security-images 레포를
가리키는 서술로 교체. deploy-test 스크립트·CUSTOM-README 의 개인 Docker Hub 계정
(docker.io/wbsong111) 을 docker.io/paasup 로 교체.
pitfalls.md 의 "스캐너 결과를 그대로 믿지 말 것" 절은 sbom-cve-gate skill 이 차트
축 설명에 실제로 참조하고 있어 남겼다 — "이미지 태그의 베이스 OS" 절만 제거했다
(다른 참조 없음, security-images 문서로 이관 완료).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.6 KiB
자체 빌드 하드닝 이미지 → security-images 레포 이관
이 문서는 자체 빌드 이미지 프레임워크·이미지 정의를 별도 public 레포
security-images 로 분리한 작업의 기록이다. 무엇이 어디로 갔고, 두 레포가 지금
무엇으로 계약하는지, 아직 안 된 것이 무엇인지를 담는다.
배경
이 카탈로그는 두 서로 다른 성격의 축을 한 레포에 담고 있었다.
- 차트 카탈로그 축 — "무엇을 배포 중인가" (
manifests/helm/, warn-only 게이트) - 자체 빌드 축 — "그 이미지를 어떻게 만드는가" (
images/, 강제 게이트)
security-images 가 public 이 될 예정이라 세 조건이 붙었다: 외부 의존성 없이
단독 동작(클론 + docker + trivy 만으로 빌드·게이트 완결), 슬림 게이트(이미지
판정에 불필요한 차트 축 로직 제거), 산업화(내부 호스트명·개인 계정·이슈/PR 번호
제거). 이 조건들이 원래 .claude/image-authoring.md 가 적어 두었던 "레포 분리 후
무엇이 끊기는가" 계획을 여러 지점에서 다시 설계하게 만들었다 — 아래 "원래 계획과
달라진 점"이 그 차이다.
무엇이 옮겨갔는가
| 카탈로그(이 레포)에 있던 것 | security-images 로 |
|---|---|
images/<image>/ 8개 |
그대로(경로 보존) |
scripts/build/build-hardened-image.sh |
그대로(경로 보존) |
scripts/build/suggest-go-upgrades.py |
--apply/--dry-run 신규 추가 |
scripts/pipeline/scan-sbom.sh · cve-gate.py (이미지 판정 부분만) |
scripts/gate/scan-image.sh · image-gate.py (슬림화) |
.github/workflows/build-image.yml |
카탈로그 무의존으로 재구성(catalog.env 의존 제거, drift 모드 제거) |
.claude/image-authoring.md |
docs/image-authoring.md (정책 중심으로 재작성) |
doc/decisions/0001·0002·0004(이미지 ADR) |
docs/decisions/(같은 번호 유지) |
.claude/skills/self-build-image/ |
그대로(문서 경로만 수정) |
무엇이 남았는가 (이 레포)
카탈로그가 "무엇을 배포 중인가"를 알아야 하는 부분만 남았다 — 전부 새로 만든 것이다.
| 경로 | 역할 |
|---|---|
catalog/image-map/<image>.env |
자체 빌드 이미지 → 어느 차트의 어느 필드(CHART_DIRS·TAG_STYLE·TAG_BLOCK). 옛 images/<image>/catalog.env 의 카탈로그 레이아웃 정보만 뗀 것 |
scripts/build/check-rebuild-needed.py |
드리프트 탐지(A 파트만) — 배포 중인 이미지가 새 CVE 로 규정을 벗어났는가. 핀 판단(B 파트)은 제거했다 — security-images 의 suggest-go-upgrades.py 가 갖는다 |
scripts/build/apply-published-tags.py |
security-images 의 published.json 을 카탈로그 values 에 반영 |
scripts/build/patch-catalog-tag.py |
변경 없음(원래도 카탈로그 파일만 다루는 순수 도구였다) |
.github/workflows/self-build-drift-check.yml |
주간, 배포 중인 이미지 스캔 → 재빌드 필요하면 security-images 의 build-image.yml 을 workflow_dispatch 로 트리거만 |
.github/workflows/catalog-tag-update.yml |
매일, published.json 조회 → 카탈로그 values 패치 → 브랜치 push |
계약 — published.json 하나뿐
security-images 는 이 카탈로그를 모른다. 게이트 PASS + 레지스트리 push 가 실제로
일어났을 때만 자기 레포의 published.json 을 갱신한다:
{"schemaVersion": 1, "images": {"<image>": {"ref": "...", "tag": "...", "digest": "...", "gate": "pass"}}}
catalog-tag-update.yml 이 이 파일을 public raw URL 로 읽어간다 — 인증도, 그
레포의 시크릿도 필요 없다. 의존 방향은 카탈로그 → 이미지 단방향이다.
repository_dispatch 같은 역방향 알림은 쓰지 않는다 — 그러려면 security-images
시크릿에 이 카탈로그 쓰기 권한 PAT 을 둬야 하는데, 그건 그 레포의 "외부 의존성 없이
단독 동작" 원칙과 충돌한다.
원래 계획과 달라진 점
.claude/image-authoring.md(삭제됨, 원문은 security-images 의 git 히스토리에)의
"레포 분리 후 무엇이 끊기는가" 는 게이트를 composite action 으로 공개해 두 축이
uses: 로 공유하는 안을 제시했다. 실제로는 그렇게 하지 않았다 — 두 가지 이유다.
suggest-go-upgrades.py가cve-gate.py를importlib로 모듈 로드했다. composite action 은 워크플로 스텝만 공유하고 파이썬 모듈 임포트를 공유하지 못한다.effective_severity함수를 게이트의 "정식 소유"로 옮기고(이 레포의cve-gate.py·security-images의image-gate.py양쪽에 독립적으로), 두 게이트가 완전히 갈라지는 쪽을 택했다.- 게이트 규칙 분기를 허용하기로 했다. 차트 축은 warn-only, 자체 빌드 축은 강제라
강제력부터 다르다. 공유 action 대신 각자 자기 게이트를 소유하고,
max(벤더, NVD)계산과 예외 파일 스키마만 같게 유지하기로 했다(승인 예외는 카탈로그 자체 빌드 이미지에도 별도로 등록해야 한다 —doc/cve-exceptions.json의_readme참고).
카탈로그 반영도 원래 계획(patch-catalog-tag.py 를 카탈로그 쪽에서 그대로 재사용)은
맞았지만, 트리거 방식이 달라졌다 — repository_dispatch 가 아니라 published.json
을 카탈로그가 주기적으로 읽어가는 pull 방식이다. 위 "계약" 절 참고.
아직 안 된 것
SECURITY_IMAGES_DISPATCH_TOKEN시크릿 미등록.self-build-drift-check.yml이security-images의build-image.yml을workflow_dispatch로 부르려면 그 레포에workflow_dispatch권한이 있는 PAT 이 필요하다. 등록 전까지 트리거 스텝은 명시적으로 실패한다.security-images레포 자체가 아직 GitHub 에 없다. 로컬 레포만 있는 상태에서 이 이관을 진행했다 — GitHub 레포 생성·push,DOCKERHUB_USER/DOCKERHUB_TOKEN시크릿 등록이 선행돼야 위 두 워크플로가 실제로 동작한다.published.json의digest필드 대부분 공란.security-images가 아직 실제로 이미지를 재빌드·push 하지 않아서다. 다음 빌드가 채운다 — MEMORY.md 의 "카탈로그 태그가 클러스터보다 앞서 있다" 항목이 이 필드를 쓸 계획이다.manifests/applicationset/**는catalog/image-map/대상이 아니다. 옛catalog.env도 그랬다 — MEMORY.md #42(고착된 낡은 차단 태그)와 같은 공백이다.