diff --git a/.claude/image-authoring.md b/.claude/image-authoring.md index 8fc07bf..b2c40a5 100644 --- a/.claude/image-authoring.md +++ b/.claude/image-authoring.md @@ -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//` 에 있고(목록은 그 디렉토리가 단일 출처) `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//` 를 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/-<타임스탬프>` 브랜치를 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/-` 브랜치를 카탈로그 레포에 push | 카탈로그 쪽이 자기 브랜치를 만든다 | +| 7 | `images/` 가 이미지 목록의 단일 출처 (여러 문서가 이 경로 참조) | 참조 갱신 | + +**3번이 가장 크다** — 드리프트 탐지는 "카탈로그가 가리키는 이미지" 를 기준으로 재는 것이 설계의 +핵심인데 이미지 레포는 카탈로그를 갖고 있지 않다. 그래서 **탐지는 카탈로그, 빌드는 이미지 레포** +로 갈라야 한다. + +**게이트를 공유할 때 `workflow_call` 은 쓸 수 없다** — 별도 job 으로 돌아 파일시스템을 공유하지 +않는데 게이트는 스캔과 **같은 job 에서 `$OUT_DIR` 를 읽어야** 한다. composite action 이어야 한다. +참고로 `SBOM_PIPELINE_IMAGE` 는 도구만 담고 있어(`Dockerfile` 에 `COPY` 가 없다) 그것만으로는 +`cve-gate.py` 가 따라오지 않는다. + ## 두 가지 유형 (둘 다 같은 스크립트를 쓴다) | 유형 | Dockerfile 이 하는 일 | 셸 유무 | diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index ab25cc3..174bbd8 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -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: diff --git a/CLAUDE.md b/CLAUDE.md index 57145b6..88f87f0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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`
`cve-edge-post.yml` | warn-only
**호출 안 함** | [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//` 아래에 자체 빌드 이미지를 둔다(목록은 그 디렉토리가 단일 출처). - 최종 런타임 베이스 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) --- diff --git a/doc/sbom-pipeline.md b/doc/sbom-pipeline.md index 077cc0e..32d78c4 100644 --- a/doc/sbom-pipeline.md +++ b/doc/sbom-pipeline.md @@ -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 /dip-catalog # 전체 gh run watch --repo /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///` 에서 재사용되면 그 조합 수만큼 항목이 중복된다 (의도된 동작 — 소비 측이 카탈로그 단위로 집계한다). @@ -161,48 +202,17 @@ gh run watch --repo /dip-catalog > 유일한 수단이다(security-catalog 프로젝트에서 이식, 2026-08-03). 이 키가 없는 구버전 > 리포트는 findings 총계 0건이면 보수적으로 실패 처리하는 예전 경로로 판정한다. -## 자체 빌드 (`.github/workflows/build-image.yml`) +## 자체 빌드 축은 이 문서가 다루지 않는다 -게이트 대응 우선순위(상위 태그 교체 → 베이스 OS 교체 → **자체 빌드** → 예외 승인)의 세 번째 레버를 -실행하는 워크플로다. 대상은 `images//` 에 빌드 정의가 있는 이미지이고(목록은 그 디렉토리가 -단일 출처), 실행기는 `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//` 를 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/-<타임스탬프>` 브랜치를 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` 의 외부 엔드포인트 인증 | diff --git a/scripts/pipeline/Dockerfile b/scripts/pipeline/Dockerfile index b232fee..e5825fe 100644 --- a/scripts/pipeline/Dockerfile +++ b/scripts/pipeline/Dockerfile @@ -12,8 +12,12 @@ # # 빌드 & 푸시 (amd64 필수 — GitHub 러너가 amd64): # docker buildx build --platform linux/amd64 \ -# -t docker.io//sbom-pipeline:latest -f scripts/pipeline/Dockerfile --push scripts/pipeline -# # 이후: gh variable set SBOM_PIPELINE_IMAGE --body docker.io//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