Files
service-catalog/doc/architecture.md
T
wbsong111 f5b8e4b91f 자체 빌드 드리프트 스캔·재빌드 트리거를 제거하고 CI 파이프라인을 정리한다
hardened-containers가 이미 rescan.yml로 매일 자율 재스캔·재빌드하고, sbom.yml이
custom-values.yaml 기준으로 자체 빌드 이미지를 다른 카탈로그 이미지와 동일하게
스캔하고 있어 self-build-drift-check.yml의 트리거·전용 스캔이 순수 중복이었다
(SECURITY_IMAGES_DISPATCH_TOKEN도 등록된 적 없어 트리거 스텝은 항상 실패하던
죽은 코드). check-rebuild-needed.py가 더하던 fixable/no-fix 구분도 cve-gate.py
리포트에 이미 있어 흡수할 필요 없이 삭제했다. 근거는 ADR 0005.

곁들여 CI 위생 문제(trivy DB 캐시 없음, concurrency 없음, catalog-tag-update.yml의
브랜치 누적)를 함께 고치고, hardened-containers의 docs/image-authoring.md가
docs/image-authoring/ 로 분할된 것을 반영해 관련 링크를 정정했다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 09:48:48 +09:00

156 lines
9.0 KiB
Markdown

# CI 파이프라인 아키텍처
이 문서는 `.github/workflows/`에 있는 3개 워크플로가 **무엇을 트리거로 도는지**, **잡 흐름이
어떻게 이어지는지**를 한눈에 보여준다. 각 워크플로 내부 메커니즘(스캔 방식, 게이트 판정
로직 등)의 단일 출처는 별도 문서다 — 여기서 복제하지 않는다. 스타일은 별도 레포
`hardened-containers``docs/architecture.md`를 참고했다.
## 1. 두 축, 세 워크플로
카탈로그는 "무엇을 배포 중인가"(차트 카탈로그 축)와 "그 이미지를 어떻게 만드는가"(자체
빌드 축)를 분리해서 다룬다 — 축·소유 레포·게이트 강제력 비교는
[CLAUDE.md](../CLAUDE.md)의 "CVE/SBOM 게이트 작업" 표 참고. 이 레포에 있는 워크플로 3개는
그 두 축 위에 이렇게 나뉜다.
| 워크플로 | 축 | 트리거 | 게이트 |
|---|---|---|---|
| `sbom.yml` | 차트 카탈로그(자체 빌드 이미지 포함) | `pull_request`(`manifests/helm/**`) · `workflow_dispatch` · 매주 일 18:00 UTC | warn-only |
| `cve-edge-post.yml` | 차트 카탈로그 | `workflow_dispatch`만 (스케줄 주석 처리됨) | 없음 — 원시 집계 POST |
| `catalog-tag-update.yml` | 자체 빌드 | `workflow_dispatch` · 매일 03:00 UTC | 없음 — 반영만 |
**PR 이벤트에 반응하는 워크플로는 `sbom.yml` 하나뿐이다.** 나머지 두 개는 사람이 Actions
탭에서 수동 실행하거나 스케줄로만 돈다.
> 자체 빌드 이미지의 CVE 드리프트 스캔·재빌드 트리거를 위한 전용 워크플로
> (`self-build-drift-check.yml`)는 없다 — `sbom.yml`이 자체 빌드 이미지도 다른 카탈로그
> 이미지와 동일하게 스캔·보고하고, 재빌드는 전적으로 `hardened-containers`의 `rescan.yml`이
> 자율 수행한다. 근거는 ADR [0005](decisions/0005-self-build-drift-check-removed.md).
## 2. 트리거 조건 → Job 흐름
```mermaid
flowchart TD
PR["pull_request<br/>manifests/helm/** 변경"]
WD1["workflow_dispatch<br/>chart, limit"]
SCH1["schedule<br/>매주 일 18:00 UTC"]
subgraph SBOM["sbom.yml — 차트 카탈로그 SBOM/CVE 스캔 (자체 빌드 이미지 포함)"]
S1[이미지 인벤토리 추출]
S2["PR: diff 증분만 대상<br/>스케줄·수동: 전체 또는 지정 차트"]
S3[SBOM 생성 CycloneDX]
S4[취약점 스캔]
S5["CVE 게이트 판정<br/>warn-only — 실패해도 PR 안 막음"]
S6[아티팩트 업로드 + Job Summary]
S1 --> S2 --> S3 --> S4 --> S5 --> S6
end
PR --> S1
WD1 --> S1
SCH1 --> S1
WD2["workflow_dispatch<br/>chart, limit<br/>(스케줄 없음 — 주석 처리)"]
subgraph EDGE["cve-edge-post.yml — 외부 집계 전송"]
V1[이미지 인벤토리 추출]
V2[SBOM 생성 + 취약점 스캔]
V3[CVE JSON 집계]
V4["POST edge.gke.paasup.io<br/>승인 예외·실효 등급 미적용"]
V1 --> V2 --> V3 --> V4
end
WD2 --> V1
subgraph HC["hardened-containers — 다른 레포, 독립 루프 (이 레포는 트리거하지 않는다)"]
HR1["rescan.yml<br/>매일 자율 재스캔"]
HR2{드리프트 있음?}
HB1[build-image.yml<br/>빌드→검증→SBOM→스캔→게이트]
HB2["PASS → 레지스트리 push + attest<br/>+ published.json 갱신"]
HR1 --> HR2
HR2 -->|예| HB1
HB1 --> HB2
end
WD4["workflow_dispatch"]
SCH4["schedule<br/>매일 03:00 UTC"]
subgraph SYNC["catalog-tag-update.yml — 발행 태그 카탈로그 반영"]
C1[published.json 조회]
C2["반영 대상 계산<br/>apply-published-tags.py"]
C3{변경 있음?}
C4["고정 브랜치로 push<br/>+ compare 링크 Job Summary"]
C5[변경 없음 — 종료]
C1 --> C2 --> C3
C3 -->|예| C4
C3 -->|아니오| C5
end
WD4 --> C1
SCH4 --> C1
HB2 -.->|"public raw URL 폴링<br/>인증 없음, pull 방식"| C1
C4 -.->|"CI는 GITHUB_TOKEN으로 PR 생성 불가<br/>(조직 정책, 실측 확정)"| HumanPR["사람이 compare 링크로<br/>PR 직접 오픈"]
```
세 갈래를 눈여겨볼 것:
1. **`sbom.yml`만 PR에 반응한다.** `cve-edge-post.yml`·`catalog-tag-update.yml`은 스케줄·수동뿐이다.
2. **레포 경계를 넘는 방향은 하나뿐이다 — pull.** `catalog-tag-update.yml`
`published.json`을 폴링해서 가져올 뿐, 자체 빌드가 언제 도는지는 이 레포가 전혀
모른다 — `hardened-containers``rescan.yml`이 전적으로 자율 결정하고, 이 레포는
어느 쪽으로도 그 레포를 트리거하지 않는다.
3. **끝은 항상 사람이다.** `catalog-tag-update.yml`이 브랜치까지는 push하지만 PR은 열지
않는다 — CI가 `GITHUB_TOKEN`으로 PR을 못 여는 게 조직 정책으로 실측 확정된 제약이라서다
(완화 방법은 [MEMORY.md](../MEMORY.md), [.claude/skills/cve-remediation/SKILL.md](../.claude/skills/cve-remediation/SKILL.md) 참고).
## 3. 워크플로별 상세
### `sbom.yml` — 차트 카탈로그 SBOM/CVE 스캔 (warn-only)
메커니즘 상세(스캔 대상 결정, `cve-gate.py` 판정 로직, 아티팩트 구조, trivy DB 캐싱·
concurrency)의 단일 출처는 [doc/sbom-pipeline.md](sbom-pipeline.md)다. 여기서는 트리거·잡
경계만 짚는다.
- PR에서는 `git diff`로 변경된 차트만 골라 스캔 범위를 줄인다(전체 스캔은 무겁다).
- 스케줄·수동 실행은 전체(또는 `chart` 입력으로 지정한 하나)를 스캔한다.
- **자체 빌드 이미지(`docker.io/paasup/*`)도 같은 대상이다** — `extract-helm-images.sh`
`custom-values.yaml`을 적용해 렌더하므로 별도 취급 없이 그대로 스캔된다. 자체 빌드
이미지 CVE 드리프트를 따로 보는 워크플로는 없다 — 이 리포트가 유일한 경로다.
- 실행 컨테이너(`vars.SBOM_PIPELINE_IMAGE`)에 helm/trivy/python3/bash/git이 없으면
preflight 스텝에서 즉시 실패한다.
- 게이트가 실패해도(`--warn-only`) 워크플로 자체는 성공으로 끝난다 — Job Summary와
아티팩트로만 결과를 남긴다.
### `cve-edge-post.yml` — 외부 집계 전송
- `sbom.yml`과 이미지 인벤토리·SBOM·스캔 스텝을 그대로 재사용하지만, 게이트 판정
(`cve-gate.py`)을 부르지 않는다 — 승인 예외·실효 등급(`max(벤더,NVD)`)이 적용 안 된
**원시** 집계라는 뜻이다. `sbom.yml`의 결과와 CRITICAL 건수가 다르게 보일 수 있다.
- 결과는 `(catalog, version, image)` 조합 단위 JSON으로 만들어져
`https://edge.gke.paasup.io/api/v1/cve-scans`로 POST된다(`CVE_API_KEY` 헤더 인증,
TLS 검증은 `--insecure`로 스킵).
- 스케줄이 주석 처리돼 있어 현재는 **수동 실행만** 된다.
### `catalog-tag-update.yml` — 발행 태그 카탈로그 반영
- `hardened-containers`는 이 카탈로그의 존재를 모른다 — 게이트 PASS + push가 실제로
일어났을 때만 자기 레포의 `published.json`을 갱신해둘 뿐이다. 이 워크플로가 그 파일을
**public raw URL로 폴링**해서 가져온다(인증 불필요, 상세 계약은
[doc/migrations/self-build-images-to-hardened-containers.md](migrations/self-build-images-to-hardened-containers.md)).
역방향 알림(`repository_dispatch` 등)은 의도적으로 안 쓴다 — 그러려면
`hardened-containers` 쪽에 이 카탈로그 쓰기 권한 PAT을 둬야 하는데, 그 레포의 "외부
의존성 없이 단독 동작" 원칙과 충돌하기 때문이다.
- `scripts/build/apply-published-tags.py``gate == "pass"`인 이미지만 골라
`custom-values.yaml`/`dip-values.yaml`을 패치한다(`patch-catalog-tag.py`를 내부에서
호출) — 실패한 빌드의 태그가 반영되는 일은 없다.
- 변경이 있으면 고정 브랜치(`build/catalog-tag-update`)로 force push까지만 하고
멈춘다 — 매 실행 새 브랜치를 만들지 않으므로 방치된 동일 내용 브랜치가 쌓이지 않는다.
**PR은 자동으로 열리지 않는다** — Job Summary의 compare 링크를 보고 사람이 직접 연다.
## 4. 참고
- 두 축 개념·소유 레포·문서 단일 출처 매핑: [CLAUDE.md](../CLAUDE.md) "CVE/SBOM 게이트 작업"
- 차트 카탈로그 축 스캔 메커니즘·CI 위생(캐싱·concurrency) 상세: [doc/sbom-pipeline.md](sbom-pipeline.md)
- 자체 빌드 축 이관 배경·계약: [doc/migrations/](migrations/)
- 자체 빌드 드리프트 전용 워크플로를 없앤 근거: ADR [0005](decisions/0005-self-build-drift-check-removed.md)
- 차단 CVE 대응 시 레버 판단: [.claude/skills/cve-remediation/SKILL.md](../.claude/skills/cve-remediation/SKILL.md)
- `hardened-containers` 자체 파이프라인(빌드→검증→SBOM→스캔→게이트→push, 매일 자율
재스캔 `rescan.yml`) 상세는 그 레포의 `docs/architecture.md`가 단일 출처다 — 이 문서는
그 파이프라인의 트리거·출력 지점만 다룬다.