Merge pull request #41 from paasup/docs/pipeline-axis-boundary

파이프라인을 두 축으로 갈라 소유 문서를 확정 + SBOM_PIPELINE_IMAGE paasup 이전 (#30)
This commit is contained in:
wbsong111
2026-08-20 16:58:29 +09:00
committed by GitHub
5 changed files with 170 additions and 78 deletions
+86 -2
View File
@@ -1,7 +1,15 @@
# 자체 빌드 이미지 작업 규칙
[CLAUDE.md](../CLAUDE.md) 에서 분리했다. 새 자체 빌드 이미지를 추가하거나(CVE 게이트
대응 우선순위 중 "자체 빌드") 기존 이미지의 빌드 정의를 바꿀 때만 참고한다.
**자체 빌드 축의 단일 출처다.** 새 자체 빌드 이미지를 추가하거나 기존 빌드 정의를 바꿀 때,
그리고 `build-image.yml` 이 무엇을 하는지 알아야 할 때 여기를 본다.
카탈로그의 CVE 파이프라인은 두 축이고 이 문서는 **"이미지를 어떻게 만드는가"** 를 갖는다.
**"우리가 배포하는 이미지에 무엇이 있는가"**(`sbom.yml`·`cve-edge-post.yml`, 스캔·게이트
메커니즘)는 [doc/sbom-pipeline.md](../doc/sbom-pipeline.md) 가 단일 출처다 — 여기 복제하지 않는다.
> **레포 분리 시 이 파일은 `images/` · `scripts/build/` · `build-image.yml` 과 함께 이동한다.**
> 커스텀 이미지가 별도 레포로 나가는 것이 계획이고, 그때 끊기는 결합점은 아래
> "레포 분리 후 무엇이 끊기는가" 에 정리돼 있다.
security-catalog 레포에서 검증한 자체 빌드 프레임워크를 포팅했다. 자체 빌드 이미지는
`images/<image>/` 에 있고(목록은 그 디렉토리가 단일 출처) `docker.io/paasup` 에 push 되어
@@ -354,6 +362,82 @@ python3 scripts/build/check-rebuild-needed.py --reports ... --image etcd --apply
docstring 이 어느 함수가 어느 쪽인지 적어둔다 — **탐지는 카탈로그 쪽에 남고, 핀 판단은 이미지
쪽으로 간다.** 분리 작업 때 그 주석을 먼저 읽는다.
## CI — `build-image.yml`
이 축의 워크플로다. 실행기는 `scripts/build/build-hardened-image.sh` 하나이고 빌드 → `verify.sh`
→ SBOM → `scan-sbom.sh``cve-gate.py` 를 순서대로 부른다 — 스캔·게이트는 차트 축의 스크립트를
그대로 재사용한다([doc/sbom-pipeline.md](../doc/sbom-pipeline.md)).
**이 워크플로의 게이트는 강제다.** 차트 축의 `sbom.yml``--warn-only` 인 것과 다르다 —
게이트가 실패하면 push 도 카탈로그 반영도 일어나지 않는다.
| 트리거 | 대상 결정 | 빌드·검증·게이트 | 레지스트리 push | 카탈로그 태그 갱신 |
|--------|----------|------------------|-----------------|-------------------|
| `workflow_dispatch` | `image` 입력(필수) · `base_os`(비우면 `catalog.env``DEFAULT_BASE_OS`) · `push`(기본 `true`) | ✅ | `push=true` 일 때만 | 게이트 PASS + 태그 변경 시 |
| `pull_request` (`images/**`) | 변경된 `images/<image>/` 를 diff 로 자동 탐지(복수면 매트릭스 병렬) | ✅ | ❌ | ❌ |
| `schedule` (`0 2 * * 1`, 매주 월요일 02:00 UTC) · `mode=drift` | 배포 중인 이미지를 스캔해 재빌드가 필요한 것만 | ✅ | ❌ | ❌ |
PR 트리거가 push 하지 않는 것은 `REGISTRY` 를 넘기지 않기 때문이다 — 태그가 `localhost/...` 로 남아
push 를 시도할 수조차 없다(검증 전용 안전장치).
- **`schedule` 이 있지만 블라인드 정기 재빌드는 아니다.** 예전에는 트리거가 없었고 그 이유가
"블라인드 재빌드는 정말 개선인지를 매번 되묻게 만든다" 였다. 지금 것은 **수정 버전이 있는 차단
CVE 가 있을 때만** 빌드하고 없으면 아무것도 하지 않는다 — 거부된 것과 조건이 다르다.
판정은 아래 "핀은 가만히 있어도 뒤처진다" 절이 갖는다.
- **`sbom.yml` 의 게이트가 이 워크플로를 자동 호출하지 않는다.** 차트 축이 차단 CVE 를 찾아도
자체 빌드로 갈지는 사람이 판단한다 — 두 축 사이에 자동 연결은 없다. 이 워크플로의 `schedule`
**자기 이미지만** 본다(배포 중인 자체 빌드 이미지).
- 레지스트리는 `REGISTRY_HOST: docker.io/paasup` 고정. 인증은 `DOCKERHUB_USER`/`DOCKERHUB_TOKEN`.
### 카탈로그 반영 — PR 은 자동 생성되지 않는다
게이트 PASS 이고 새 태그가 현재 카탈로그 태그와 다르면, 워크플로가 `catalog.env``CHART_DIRS` 아래
`custom-values.yaml`/`dip-values.yaml``scripts/build/patch-catalog-tag.py` 로 갱신하고
`build/<image>-<타임스탬프>` 브랜치를 push 한다. **여기까지가 자동이다.**
PR 오픈은 사람이 한다 — `GITHUB_TOKEN` 으로 PR 을 만드는 것 자체를 조직 정책이 막는다("Allow GitHub
Actions to create and approve pull requests" 미허용, 리포 설정으로 변경 불가). 2026-08-04 실측으로
확인했고(run 30882785612, GraphQL: `GitHub Actions is not permitted to create or approve pull requests`),
`gh pr create` 호출은 워크플로에서 제거했다. 대신 Job Summary 에 compare 링크가 남는다.
병합 전에 사람이 해야 하는 것:
1. compare 링크로 PR 오픈.
2. `gh workflow run helm-catalog-sbom --ref <브랜치>` 로 카탈로그 게이트 확인.
3. **배포 검증** — 게이트 PASS 는 "동작한다"를 증명하지 않는다(스캐너는 런타임 요구사항을 보지 못한다).
절차: [deploy-test-procedure.md](deploy-test-procedure.md).
> **매핑이 불완전하면 조용히 지나간다.** `patch-catalog-tag.py` 는 "예상 패턴을 못 찾으면 실패"
> 하도록 만들어져 있지만 **애초에 `CHART_DIRS` 에 없는 파일은 검사조차 하지 않는다.** 실측
> (2026-08-20): `cnpg-postgresql` 의 `CHART_DIRS` 가 `cnpg-cluster/1.0.0` 만 담고 있어 같은
> 이미지를 쓰는 `1.1.0` 이 낡은 태그로 남았고, 게이트만 계속 차단으로 잡았다. 카탈로그는 여러
> 버전을 동시에 보관하므로 **이미지 하나가 여러 버전 디렉토리에 걸리는 것이 정상**이다 —
> 새 버전 디렉토리를 만들 때 `catalog.env` 를 함께 본다.
### 레포 분리 후 무엇이 끊기는가
커스텀 이미지가 별도 레포로 나가면 이 축은 **"어떻게 만드는가"** 만 갖고, **"무엇을 배포 중인가"**
는 카탈로그 레포에 남는다. 지금 한 레포 안이라 보이지 않는 결합이 그때 드러난다.
| # | 결합점 | 분리 후 필요한 것 |
|---|---|---|
| 1 | `patch-catalog-tag.py` 가 카탈로그 values 를 **쓴다** | 카탈로그 쪽이 자기 파일을 쓰게 한다(반영 워크플로) |
| 2 | `catalog.env``CHART_DIRS`·`TAG_STYLE`·`TAG_BLOCK` | **카탈로그 레이아웃 정보다** — 카탈로그 쪽으로 옮긴다 |
| 3 | `check-rebuild-needed.py` 가 카탈로그 values 를 **읽어** 배포 중 ref 를 해석 | **탐지는 카탈로그 쪽에 남는다** (그 파일 docstring 의 절단면 참고) |
| 4 | `build-hardened-image.sh``scan-sbom.sh`·`cve-gate.py` 를 부른다 | 게이트를 composite action 으로 공개해 양쪽이 `uses:` |
| 5 | `doc/cve-exceptions.json` 공유 | 카탈로그가 소유하고 action 입력으로 받는다(예외는 "무엇을 감수하는가") |
| 6 | `build/<image>-<ts>` 브랜치를 카탈로그 레포에 push | 카탈로그 쪽이 자기 브랜치를 만든다 |
| 7 | `images/` 가 이미지 목록의 단일 출처 (여러 문서가 이 경로 참조) | 참조 갱신 |
**3번이 가장 크다** — 드리프트 탐지는 "카탈로그가 가리키는 이미지" 를 기준으로 재는 것이 설계의
핵심인데 이미지 레포는 카탈로그를 갖고 있지 않다. 그래서 **탐지는 카탈로그, 빌드는 이미지 레포**
로 갈라야 한다.
**게이트를 공유할 때 `workflow_call` 은 쓸 수 없다** — 별도 job 으로 돌아 파일시스템을 공유하지
않는데 게이트는 스캔과 **같은 job 에서 `$OUT_DIR` 를 읽어야** 한다. composite action 이어야 한다.
참고로 `SBOM_PIPELINE_IMAGE` 는 도구만 담고 있어(`Dockerfile``COPY` 가 없다) 그것만으로는
`cve-gate.py` 가 따라오지 않는다.
## 두 가지 유형 (둘 다 같은 스크립트를 쓴다)
| 유형 | Dockerfile 이 하는 일 | 셸 유무 |
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
timeout-minutes: 120
container:
# helm+trivy+python3+bash+git 보유 이미지. scripts/pipeline/Dockerfile 로 빌드해 푸시한 뒤
# Repo Variable SBOM_PIPELINE_IMAGE 에 그 태그를 지정한다. (예: docker.io/paasup/sbom-pipeline:latest
# Repo Variable SBOM_PIPELINE_IMAGE 에 그 태그를 지정한다. (예: docker.io/paasup/sbom-pipeline:20260820
# 재빌드/네임스페이스 전환은 별도 수동 작업, MEMORY.md 참고)
image: ${{ vars.SBOM_PIPELINE_IMAGE }}
env:
+17 -23
View File
@@ -162,31 +162,25 @@ python3 agent/update_catalog/skills/helm_diff/scripts/run.py \
## CVE/SBOM 게이트 작업
`manifests/helm/` 카탈로그가 참조하는 컨테이너 이미지의 SBOM·취약점을 스캔하고
게이트로 판정한다. 정적 카탈로그·자동화 에이전트와 독립적으로 동작한다.
카탈로그가 참조하는 컨테이너 이미지의 취약점을 다룬다. **두 축**이고 각 축의 상세는 소유 문서가
갖는다 — 여기 메커니즘을 쓰지 않는다.
```
extract-helm-images.sh → generate-sbom.sh → scan-sbom.sh → cve-gate.py
(scripts/pipeline/, .github/workflows/sbom.yml·cve-edge-post.yml 이 실행)
```
| 축 | 질문 | 워크플로 | 게이트 | 단일 출처 |
|----|------|---------|--------|----------|
| **차트 카탈로그** | 우리가 배포하는 이미지에 무엇이 있는가 | `sbom.yml`<br>`cve-edge-post.yml` | warn-only<br>**호출 안 함** | [doc/sbom-pipeline.md](doc/sbom-pipeline.md) |
| **자체 빌드** | 그 이미지를 어떻게 만드는가 | `build-image.yml` | **강제** | [.claude/image-authoring.md](.claude/image-authoring.md) |
- **현재 warn-only**: 게이트가 실패해도 CI/PR 을 막지 않는다. 카탈로그 차트 전체가
이 게이트로 트리아지된 적이 없다.
- **CI 는 전 심각도로 스캔한다** — 게이트가 벤더 하향 등급·NVD 재평가·사각지대 판정에
MEDIUM/LOW 까지 필요로 하기 때문이다. 스크립트 기본값(`HIGH,CRITICAL`)으로 만든
리포트로 게이트를 돌리면 판정이 달라진다.
- 자체 빌드 프레임워크(`scripts/build/`, `.github/workflows/build-image.yml`)로
`images/<image>/` 아래에 자체 빌드 이미지를 둔다(목록은 그 디렉토리가 단일 출처).
최종 런타임 베이스 OS 는 SUSE BCI 로 통일하되 버전은 이미지마다 실측해서 고른다.
**push·카탈로그 반영을 동반하는 빌드는
`workflow_dispatch` 수동 실행뿐이고**(`images/**` PR 은 검증 전용, `schedule` 없음),
`sbom.yml` 의 게이트가 이 워크플로를 자동 호출하지 않는다 — 자체 빌드로 갈지는 사람이
판단한다. 2026-08-04 에 `etcd`·`cloudnative-pg` 로 빌드→게이트→push→카탈로그 브랜치
push 까지 실제 CI 에서 검증됐다. 게이트가 상위 태그·베이스 OS 교체로 해소 안 되는 차단
CVE 를 찾으면 이 프레임워크로 자체 빌드를 검토한다 — 절차는
[.claude/image-authoring.md](.claude/image-authoring.md).
- 상세: [doc/sbom-pipeline.md](doc/sbom-pipeline.md) · 승인 예외: `doc/cve-exceptions.json`
· 현재 미결 사항: [MEMORY.md](MEMORY.md)
- **차트 축은 warn-only 다** — 게이트가 실패해도 CI/PR 을 막지 않는다. 카탈로그 차트 전체가
이 게이트로 트리아지된 적이 없다. **자체 빌드 축의 게이트는 이미 강제다.**
- **`cve-edge-post.yml` 은 게이트를 부르지 않는다** — 같은 스캔 데이터에 판정기가 두 벌이라는
뜻이다(승인 예외·실효 등급 미적용). 외부 엔드포인트로 집계를 POST 하는 용도다.
- **자체 빌드는 대응 우선순위 3번**(상위 태그 교체 → 베이스 OS 교체 → 자체 빌드 → 예외 승인).
레버 판단은 [cve-remediation](.claude/skills/cve-remediation/SKILL.md) skill 이 갖는다.
**레지스트리 push·카탈로그 반영을 동반하는 빌드는 `workflow_dispatch` 수동 실행뿐이다**
`schedule`·PR 트리거는 검증만 한다.
- 커스텀 이미지는 **별도 레포로 분리 예정**이다. 그때 끊기는 결합점은
[.claude/image-authoring.md](.claude/image-authoring.md) "레포 분리 후 무엇이 끊기는가".
- 승인 예외: `doc/cve-exceptions.json` · 현재 미결: [MEMORY.md](MEMORY.md)
---
+60 -50
View File
@@ -4,6 +4,20 @@
결정론적으로 생성하는 파이프라인. helm/trivy/python3 가 설치된 리눅스 컨테이너 안에서 실행하며,
GitHub Actions([.github/workflows/sbom.yml](../.github/workflows/sbom.yml))로 자동화한다.
## 이 문서가 다루는 축
카탈로그의 CVE 파이프라인은 **두 축**이고 이 문서는 그중 하나만 다룬다.
| | 다룬다 — **차트 카탈로그 축** | 다루지 않는다 — **자체 빌드 축** |
|---|---|---|
| 질문 | 우리가 배포하는 이미지에 무엇이 있는가 | 그 이미지를 어떻게 만드는가 |
| 워크플로 | `sbom.yml` · `cve-edge-post.yml` | `build-image.yml` |
| 단일 출처 | **이 문서** | [.claude/image-authoring.md](../.claude/image-authoring.md) |
**카탈로그 values 가 자체 빌드 이미지(`docker.io/paasup/*`)를 가리키므로 그 이미지도 이 문서의
스캔 대상이다** — 축이 갈린 것은 "누가 만들고 결정하는가" 이고 "누가 스캔되는가" 가 아니다.
자체 빌드 이미지도 카탈로그가 배포하는 한 여기서 판정된다.
## 왜 필요한가
GitHub Dependency-Graph SBOM(예: `paasup_dip-catalog_199087.json`)은 pypi 등 소스 매니페스트만 담고
@@ -76,7 +90,7 @@ SBOM 생성 실패는 대부분 **사설/미인증 레지스트리이거나 대
```bash
docker run --rm -v "$PWD:/repo" -w /repo \
-e TRIVY_CACHE_DIR=/repo/sbom-out/cache -e DOCKER_CONFIG=/repo/sbom-out/.docker \
docker.io/wbsong111/sbom-pipeline:latest bash -c '
docker.io/paasup/sbom-pipeline:20260820 bash -c '
bash scripts/pipeline/extract-helm-images.sh manifests/helm sbom-out
bash scripts/pipeline/generate-sbom.sh sbom-out/images_final.tsv sbom-out # 인증: DOCKER_CONFIG/TRIVY_USERNAME
bash scripts/pipeline/scan-sbom.sh sbom-out
@@ -91,7 +105,7 @@ docker run --rm -v "$PWD:/repo" -w /repo \
| 베이스 | `debian:stable-slim` (**glibc**) |
| 포함 도구 | `helm`(v3) · `trivy` · `python3` · `bash` · `git` |
| 아키텍처 | **linux/amd64** (GitHub 러너와 일치) |
| 현재 이미지 | `docker.io/wbsong111/sbom-pipeline:latest` (public)`paasup` 네임스페이스 이전 미완료(`MEMORY.md`) |
| 현재 이미지 | `docker.io/paasup/sbom-pipeline:20260820` (public) |
> **glibc(debian) 필수**: GitHub Actions 의 `container:` 안에서 `actions/checkout`·`upload-artifact`
> (node 기반)가 동작하려면 glibc 이거나 node 가 있어야 한다. `aquasec/trivy` 같은 **alpine(musl)
@@ -99,14 +113,42 @@ docker run --rm -v "$PWD:/repo" -w /repo \
```bash
docker buildx build --platform linux/amd64 \
-t docker.io/paasup/sbom-pipeline:latest \
-t docker.io/paasup/sbom-pipeline:$(date -u +%Y%m%d) \
-f scripts/pipeline/Dockerfile --push scripts/pipeline
```
> credsStore(Docker Desktop) 환경에서 `docker-container` 빌더로 `--push` 시 인증 실패하면,
> 단일 아키텍처를 로컬 적재(`--load`) 후 `docker push` 하거나 인라인 토큰 config 를 쓴다.
## CI (`.github/workflows/sbom.yml`)
## CI — 이 축의 워크플로 2개
같은 스크립트(`extract``generate-sbom``scan-sbom`)와 같은 실행 이미지를 쓰지만 **뒤가 다르다.**
| | `sbom.yml` | `cve-edge-post.yml` |
|---|---|---|
| 목적 | 게이트 판정 | 외부 엔드포인트로 집계 POST |
| PR 트리거 | `manifests/helm/**` | **없음** |
| schedule | 일 18:00 UTC | **주석 처리** (수동만) |
| **게이트** | `cve-gate.py` **`--warn-only`** | **호출하지 않음** |
| 산출 | 아티팩트 `sbom-and-vuln-report` | `cve-summary.json` → POST |
> **판정기가 두 벌이라는 뜻이다.** `cve-edge-post.yml` 은 집계를 워크플로 YAML 안의 인라인
> python 으로 갖고 있어 **승인 예외(`cve-exceptions.json`)도 실효 등급(`max(벤더,NVD)`)도
> 적용하지 않는다.** 같은 스캔 데이터에서 다른 숫자가 나올 수 있다.
**PR 을 실제로 막는 게이트는 `images/**` 를 건드린 PR 에만 있다** — 이 축의 `sbom.yml` 은
warn-only 다.
> **`manifests/applicationset/**` 를 스캔하지 않는 것은 의도다.** 그 아래 `dip-values.yaml` 은
> dip-console 이 배포 values 를 만들 때 쓰는 **참조 파일**이고, 같은 이미지를 `manifests/helm/`
> 차트에서 이미 스캔·게이트한다. 같은 이미지를 두 번 스캔할 이유가 없어 인벤토리 추출 대상을
> `manifests/helm/` 으로 한정했다 — **미검사 결함이 아니다.**
>
> 단, 그 가정은 **"두 곳이 같은 이미지를 가리킨다"** 에 의존한다. 참조 파일이 차트와 다른
> 태그를 들고 있으면 스캔한 것과 배포되는 것이 갈린다 — 태그 동기화는 스캔 커버리지와 별개
> 문제이고 `scripts/build/patch-catalog-tag.py` 의 `CHART_DIRS` 범위가 그것을 결정한다.
### `sbom.yml` 스캔 범위
| 트리거 | 스캔 범위 |
|--------|----------|
@@ -133,14 +175,13 @@ gh workflow run helm-catalog-sbom --repo <org>/dip-catalog # 전체
gh run watch --repo <org>/dip-catalog
```
### 다른 워크플로 — `cve-edge-post.yml`
### `cve-edge-post.yml` 상세
같은 스크립트·같은 실행 이미지를 쓰지만 **목적이 다르다.** `(catalog, version, image)` 단위 취약점
건수 + CRITICAL 설명을 **단일 JSON 으로 만들어 외부 엔드포인트
(`https://edge.gke.paasup.io/api/v1/cve-scans`)로 POST** 한다(`X-CVE-API-Key`, Repo Secret `CVE_API_KEY`).
**게이트 판정은 하지 않는다.**
`(catalog, version, image)` 단위 취약점 건수 + CRITICAL 설명을 **단일 JSON 으로 만들어 외부
엔드포인트(`https://edge.gke.paasup.io/api/v1/cve-scans`)로 POST** 한다(`X-CVE-API-Key`,
Repo Secret `CVE_API_KEY`).
- 트리거는 `workflow_dispatch` 뿐 — `chart`(빈 값=전체) · `limit` 입력. `schedule` 은 주석 처리 상태다.
- 트리거는 `workflow_dispatch` 뿐 — `chart`(빈 값=전체) · `limit` 입력.
- 이미지 하나가 여러 `manifests/helm/<name>/<version>/` 에서 재사용되면 그 조합 수만큼 항목이 중복된다
(의도된 동작 — 소비 측이 카탈로그 단위로 집계한다).
@@ -161,48 +202,17 @@ gh run watch --repo <org>/dip-catalog
> 유일한 수단이다(security-catalog 프로젝트에서 이식, 2026-08-03). 이 키가 없는 구버전
> 리포트는 findings 총계 0건이면 보수적으로 실패 처리하는 예전 경로로 판정한다.
## 자체 빌드 (`.github/workflows/build-image.yml`)
## 자체 빌드 축은 이 문서가 다루지 않는다
게이트 대응 우선순위(상위 태그 교체베이스 OS 교체**자체 빌드** → 예외 승인)의 세 번째 레버를
실행하는 워크플로다. 대상은 `images/<image>/` 에 빌드 정의가 있는 이미지이고(목록은 그 디렉토리가
단일 출처), 실행기는 `scripts/build/build-hardened-image.sh` 하나다 — 빌드 → `verify.sh` → SBOM →
`scan-sbom.sh``cve-gate.py` 를 순서대로 부른다(이 문서의 파이프라인을 그대로 재사용).
신규 이미지 추가 절차·계약(`build.env`/`catalog.env`)은
[.claude/image-authoring.md](../.claude/image-authoring.md).
게이트 상위 태그 교체·베이스 OS 교체로 해소되지 않는 차단 CVE 를 찾으면 자체 빌드로 간다.
그 축의 단일 출처는 **[.claude/image-authoring.md](../.claude/image-authoring.md)** 다 —
`build-image.yml` 의 트리거·게이트 강도·카탈로그 반영·레포 분리 계획이 전부 거기 있다.
> `sbom.yml` 과 별도 파일인 이유: `sbom.yml` 은 `vars.SBOM_PIPELINE_IMAGE` 컨테이너 안에서 도는데
> 거기엔 docker/buildx 가 없다. 빌드는 호스트 러너여야 한다.
이 문서가 알아야 할 것은 하나뿐이다: **자체 빌드 이미지도 카탈로그가 가리키는 한 위 스캔·게이트
대상이다.** 실제로 `docker.io/paasup/*` 가 게이트 리포트에 등장한다.
| 트리거 | 대상 결정 | 빌드·검증·게이트 | 레지스트리 push | 카탈로그 태그 갱신 |
|--------|----------|------------------|-----------------|-------------------|
| `workflow_dispatch` | `image` 입력(필수) · `base_os`(비우면 `catalog.env``DEFAULT_BASE_OS`) · `push`(기본 `true`) | ✅ | `push=true` 일 때만 | 게이트 PASS + 태그 변경 시 |
| `pull_request` (`images/**`) | 변경된 `images/<image>/` 를 diff 로 자동 탐지(복수면 매트릭스 병렬) | ✅ | ❌ | ❌ |
PR 트리거가 push 하지 않는 것은 `REGISTRY` 를 넘기지 않기 때문이다 — 태그가 `localhost/...` 로 남아
push 를 시도할 수조차 없다(검증 전용 안전장치).
- **`schedule` 트리거는 없다.** 블라인드 정기 재빌드는 "정말 개선인지"를 매번 되묻게 만들어 제거했다.
- **`sbom.yml` 의 게이트가 이 워크플로를 자동 호출하지 않는다.** 차단 CVE 를 찾아도 자체 빌드로 갈지는
사람이 판단해 수동 실행한다 — 두 워크플로 사이에 자동 연결은 없다.
- 레지스트리는 `REGISTRY_HOST: docker.io/paasup` 고정. 인증은 `DOCKERHUB_USER`/`DOCKERHUB_TOKEN`.
### 카탈로그 반영 — PR 은 자동 생성되지 않는다
게이트 PASS 이고 새 태그가 현재 카탈로그 태그와 다르면, 워크플로가 `catalog.env``CHART_DIRS` 아래
`custom-values.yaml`/`dip-values.yaml``scripts/build/patch-catalog-tag.py` 로 갱신하고
`build/<image>-<타임스탬프>` 브랜치를 push 한다. **여기까지가 자동이다.**
PR 오픈은 사람이 한다 — `GITHUB_TOKEN` 으로 PR 을 만드는 것 자체를 조직 정책이 막는다("Allow GitHub
Actions to create and approve pull requests" 미허용, 리포 설정으로 변경 불가). 2026-08-04 실측으로
확인했고(run 30882785612, GraphQL: `GitHub Actions is not permitted to create or approve pull requests`),
`gh pr create` 호출은 워크플로에서 제거했다. 대신 Job Summary 에 compare 링크가 남는다.
병합 전에 사람이 해야 하는 것:
1. compare 링크로 PR 오픈.
2. `gh workflow run helm-catalog-sbom --ref <브랜치>` 로 카탈로그 게이트 확인.
3. **배포 검증** — 게이트 PASS 는 "동작한다"를 증명하지 않는다(스캐너는 런타임 요구사항을 보지 못한다).
절차: [.claude/deploy-test-procedure.md](../.claude/deploy-test-procedure.md).
> `build-image.yml` 이 `sbom.yml` 과 별도 파일인 이유: `sbom.yml` 은 `vars.SBOM_PIPELINE_IMAGE`
> 컨테이너 안에서 도는데 거기엔 docker/buildx 가 없다. 빌드는 호스트 러너여야 한다.
## GitHub 설정 (워크플로 활성화에 필요)
@@ -210,7 +220,7 @@ Actions to create and approve pull requests" 미허용, 리포 설정으로 변
| 종류 | 이름 | 용도 |
|------|------|------|
| **Variable** | `SBOM_PIPELINE_IMAGE` | 실행 이미지 태그 (예: `docker.io/wbsong111/sbom-pipeline:latest`) |
| **Variable** | `SBOM_PIPELINE_IMAGE` | 실행 이미지 태그 (예: `docker.io/paasup/sbom-pipeline:20260820`) |
| Secret | `DOCKERHUB_USER` / `DOCKERHUB_TOKEN` | **docker.io + docker.getcollate.io rate limit 회피**. getcollate(openmetadata)는 Docker Hub 프록시라 익명 pull 시 rate limit(TOOMANYREQUESTS)에 걸림 → Docker Hub 자격증명으로 인증. `build-image.yml` 의 push 에도 같은 시크릿을 쓴다 |
| Secret | `NGC_API_KEY` | **nvcr.io 인증**(NVIDIA nemo/nim — 없으면 pull 불가) |
| Secret | `CVE_API_KEY` | `cve-edge-post.yml` 의 외부 엔드포인트 인증 |
+6 -2
View File
@@ -12,8 +12,12 @@
#
# 빌드 & 푸시 (amd64 필수 — GitHub 러너가 amd64):
# docker buildx build --platform linux/amd64 \
# -t docker.io/<org>/sbom-pipeline:latest -f scripts/pipeline/Dockerfile --push scripts/pipeline
# # 이후: gh variable set SBOM_PIPELINE_IMAGE --body docker.io/<org>/sbom-pipeline:latest
# -t docker.io/paasup/sbom-pipeline:$(date -u +%Y%m%d) -f scripts/pipeline/Dockerfile --push scripts/pipeline
# # 이후: gh variable set SBOM_PIPELINE_IMAGE --body docker.io/paasup/sbom-pipeline:<태그>
#
# 롤링 태그(:latest)를 쓰지 않는다 — helm/trivy 를 빌드 시점 최신으로 설치하므로 같은
# Dockerfile 이 매번 다른 이미지를 낸다. 날짜 태그가 "무엇으로 스캔했는지" 의 기록이다.
# 변수가 전체 ref 를 담으므로 고정 태그를 써도 워크플로 수정이 필요 없다.
# =============================================================================
FROM debian:stable-slim