9e5b6633db
실제 레포명은 hardened-containers인데 이관 커밋 이후 문서·워크플로·차트 주석에 잘못된 이름 security-images가 남아 있었다. 텍스트 참조 전체를 정정하고 doc/migrations/의 이관 핸드오프 문서도 파일명까지 리네임했다. env var/secret 이름(SECURITY_IMAGES_REPO, SECURITY_IMAGES_DISPATCH_TOKEN)은 GitHub Secret 재등록이 필요한 별도 운영 작업이라 이번 텍스트 정정 범위에서 제외했다. 덧붙여 scripts/build/patch-catalog-tag.py의 docstring이 삭제된 build-image.yml을 호출자로 여전히 가리키고 있던 것도 실제 호출 경로(check-rebuild-needed.py / apply-published-tags.py → catalog-tag-update.yml)로 고쳤다. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
96 lines
6.7 KiB
Markdown
96 lines
6.7 KiB
Markdown
# 자체 빌드 하드닝 이미지 → `hardened-containers` 레포 이관
|
|
|
|
이 문서는 자체 빌드 이미지 프레임워크·이미지 정의를 별도 public 레포
|
|
`hardened-containers` 로 분리한 작업의 기록이다. 무엇이 어디로 갔고, 두 레포가 지금
|
|
무엇으로 계약하는지, 아직 안 된 것이 무엇인지를 담는다.
|
|
|
|
## 배경
|
|
|
|
이 카탈로그는 두 서로 다른 성격의 축을 한 레포에 담고 있었다.
|
|
|
|
- **차트 카탈로그 축** — "무엇을 배포 중인가" (`manifests/helm/`, warn-only 게이트)
|
|
- **자체 빌드 축** — "그 이미지를 어떻게 만드는가" (`images/`, 강제 게이트)
|
|
|
|
`hardened-containers` 가 public 이 될 예정이라 세 조건이 붙었다: **외부 의존성 없이
|
|
단독 동작**(클론 + `docker` + `trivy` 만으로 빌드·게이트 완결), **슬림 게이트**(이미지
|
|
판정에 불필요한 차트 축 로직 제거), **산업화**(내부 호스트명·개인 계정·이슈/PR 번호
|
|
제거). 이 조건들이 원래 `.claude/image-authoring.md` 가 적어 두었던 "레포 분리 후
|
|
무엇이 끊기는가" 계획을 여러 지점에서 다시 설계하게 만들었다 — 아래 "원래 계획과
|
|
달라진 점"이 그 차이다.
|
|
|
|
## 무엇이 옮겨갔는가
|
|
|
|
| 카탈로그(이 레포)에 있던 것 | `hardened-containers` 로 |
|
|
|---|---|
|
|
| `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 파트)은 제거했다 — `hardened-containers` 의 `suggest-go-upgrades.py` 가 갖는다 |
|
|
| `scripts/build/apply-published-tags.py` | `hardened-containers` 의 `published.json` 을 카탈로그 values 에 반영 |
|
|
| `scripts/build/patch-catalog-tag.py` | 변경 없음(원래도 카탈로그 파일만 다루는 순수 도구였다) |
|
|
| `.github/workflows/self-build-drift-check.yml` | 주간, 배포 중인 이미지 스캔 → 재빌드 필요하면 `hardened-containers` 의 `build-image.yml` 을 `workflow_dispatch` 로 트리거만 |
|
|
| `.github/workflows/catalog-tag-update.yml` | 매일, `published.json` 조회 → 카탈로그 values 패치 → 브랜치 push |
|
|
|
|
## 계약 — `published.json` 하나뿐
|
|
|
|
`hardened-containers` 는 **이 카탈로그를 모른다.** 게이트 PASS + 레지스트리 push 가 실제로
|
|
일어났을 때만 자기 레포의 `published.json` 을 갱신한다:
|
|
|
|
```json
|
|
{"schemaVersion": 1, "images": {"<image>": {"ref": "...", "tag": "...", "digest": "...", "gate": "pass"}}}
|
|
```
|
|
|
|
`catalog-tag-update.yml` 이 이 파일을 **public raw URL** 로 읽어간다 — 인증도, 그
|
|
레포의 시크릿도 필요 없다. 의존 방향은 **카탈로그 → 이미지 단방향**이다.
|
|
`repository_dispatch` 같은 역방향 알림은 쓰지 않는다 — 그러려면 `hardened-containers`
|
|
시크릿에 이 카탈로그 쓰기 권한 PAT 을 둬야 하는데, 그건 그 레포의 "외부 의존성 없이
|
|
단독 동작" 원칙과 충돌한다.
|
|
|
|
## 원래 계획과 달라진 점
|
|
|
|
`.claude/image-authoring.md`(삭제됨, 원문은 `hardened-containers` 의 git 히스토리에)의
|
|
"레포 분리 후 무엇이 끊기는가" 는 게이트를 **composite action** 으로 공개해 두 축이
|
|
`uses:` 로 공유하는 안을 제시했다. 실제로는 그렇게 하지 않았다 — 두 가지 이유다.
|
|
|
|
1. **`suggest-go-upgrades.py` 가 `cve-gate.py` 를 `importlib` 로 모듈 로드했다.**
|
|
composite action 은 워크플로 스텝만 공유하고 파이썬 모듈 임포트를 공유하지 못한다.
|
|
`effective_severity` 함수를 게이트의 "정식 소유"로 옮기고(이 레포의 `cve-gate.py` ·
|
|
`hardened-containers` 의 `image-gate.py` 양쪽에 독립적으로), 두 게이트가 완전히 갈라지는
|
|
쪽을 택했다.
|
|
2. **게이트 규칙 분기를 허용하기로 했다.** 차트 축은 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` 이
|
|
`hardened-containers` 의 `build-image.yml` 을 `workflow_dispatch` 로 부르려면
|
|
그 레포에 `workflow_dispatch` 권한이 있는 PAT 이 필요하다. 등록 전까지 트리거 스텝은
|
|
명시적으로 실패한다.
|
|
- **`hardened-containers` 레포 자체가 아직 GitHub 에 없다.** 로컬 레포만 있는 상태에서
|
|
이 이관을 진행했다 — GitHub 레포 생성·push, `DOCKERHUB_USER`/`DOCKERHUB_TOKEN` 시크릿
|
|
등록이 선행돼야 위 두 워크플로가 실제로 동작한다.
|
|
- **`published.json` 의 `digest` 필드 대부분 공란.** `hardened-containers` 가 아직 실제로
|
|
이미지를 재빌드·push 하지 않아서다. 다음 빌드가 채운다 — MEMORY.md 의 "카탈로그 태그가
|
|
클러스터보다 앞서 있다" 항목이 이 필드를 쓸 계획이다.
|
|
- **`manifests/applicationset/**` 는 `catalog/image-map/` 대상이 아니다.** 옛
|
|
`catalog.env` 도 그랬다 — MEMORY.md #42(고착된 낡은 차단 태그)와 같은 공백이다.
|