SBOM 파이프라인 문서 정비 + 이미지 목록 열거 제거
## doc/sbom-pipeline.md — 중복·모순 정리 (328 → 286줄) 같은 사실이 여러 절에 흩어져 있었고, 일부는 문서가 아니라 변경 이력이었다. - `SEVERITY` 를 전 심각도로 덮어써야 하는 이유가 환경변수 절·CI 절·요약 절 3곳에 있었다. 스크립트 절의 blockquote 하나로 합쳤다 — "게이트를 돌릴 거라면 전 심각도로 스캔해야 한다"가 핵심이고 나머지는 그 결과다. - Job Summary 1MB 제한이 CI 절과 결과 확인 절에 중복됐다. CI 절 하나로 합쳤다. - `--warn-only` 서술이 mermaid 라벨·CI 절·게이트 절·트리아지 절 4곳에 있었다. 게이트 절 하나로 합치고, `build-image.yml` 쪽은 이미 강제라는 대비를 함께 적었다. - 자체 빌드 트리거 표가 "PR 은 push 안 함"을 말하는데 바로 아래 불릿이 같은 말을 반복했다. 표는 그대로 두고 불릿은 **왜** 그런지(REGISTRY 미전달 → localhost 태그라 push 를 시도할 수조차 없다)만 남겼다. - "오해를 주던 단일 '총 소요'는 제거" 같은 변경 이력 서술을 걷어냈다. 문서는 현재 상태를 적는 곳이다. - "첫 전체 실행 결과(2026-07-08)" 절은 수치를 싣고 바로 아래에서 "현재 수치가 아니다"로 무효화하는 구조였다. 절 자체를 없애고, 거기서 유일하게 쓸모 있던 사실 (SBOM 생성 실패는 대부분 사설/미인증 레지스트리이거나 대용량 timeout)만 스크립트 절로 옮겼다. - "실행 이력(2026-08-04)" 절은 MEMORY.md 와 중복이라 제거했다. 거기서만 알 수 있던 사실(Actions 시크릿의 push 권한 확인)은 GitHub 설정 표에 반영했다. - `CVE_API_KEY` 가 본문에만 있고 GitHub 설정 표에 빠져 있어 추가했다. - 아키텍처 절 불릿이 mermaid 서브그래프 라벨과 같은 말을 하고 있어, "pull 을 ② 한 곳에 몰아둔 것이 핵심"이라는 결론 한 문장으로 줄였다. ## 이미지 목록을 문서에 박아두지 않는다 이미지는 계속 추가되므로 열거하면 추가할 때마다 낡는다. `images/` 디렉토리를 단일 출처로 삼고 CLAUDE.md·image-authoring.md·build-image.yml·sbom-pipeline.md 의 열거를 걷어냈다. keycloak README 의 베이스 OS 결정 근거도 "기존 3종" 대신 "먼저 들어온 이미지들"로 바꿨다 — 근거의 내용은 그대로다. ## 현황 서술 정정 - build-image.yml 주석이 "아직 도입된 자체 빌드 이미지가 없다(images/ 가 비어 있음)" 로 남아 있었다. 이 레포 CI 에서 빌드→검증→게이트→push→카탈로그 브랜치 push 까지 실제로 검증된 상태다. - MEMORY.md: cve-exceptions.json 첫 예외 등록, 베이스 OS 정책 확정, PR 자동 생성이 조직 정책으로 불가하다는 실측(run 30882785612)을 반영했다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,10 +3,10 @@
|
|||||||
[CLAUDE.md](../CLAUDE.md) 에서 분리했다. 새 자체 빌드 이미지를 추가하거나(CVE 게이트
|
[CLAUDE.md](../CLAUDE.md) 에서 분리했다. 새 자체 빌드 이미지를 추가하거나(CVE 게이트
|
||||||
대응 우선순위 중 "자체 빌드") 기존 이미지의 빌드 정의를 바꿀 때만 참고한다.
|
대응 우선순위 중 "자체 빌드") 기존 이미지의 빌드 정의를 바꿀 때만 참고한다.
|
||||||
|
|
||||||
security-catalog 레포에서 검증한 자체 빌드 프레임워크를 포팅했다. 현재
|
security-catalog 레포에서 검증한 자체 빌드 프레임워크를 포팅했다. 자체 빌드 이미지는
|
||||||
`images/` 에 이미지 3종(`cloudnative-pg`, `cnpg-postgresql`, `etcd`)이 있고
|
`images/<image>/` 에 있고(목록은 그 디렉토리가 단일 출처) `docker.io/paasup` 에 push 되어
|
||||||
`docker.io/paasup` 에 push 되어 카탈로그 values 가 이 태그를 참조한다. 아래는
|
카탈로그 values 가 그 태그를 참조한다. 아래는 프레임워크가 어떻게 동작하는지와,
|
||||||
프레임워크가 어떻게 동작하는지와, 이미지를 추가·변경할 때 지켜야 할 규칙이다.
|
이미지를 추가·변경할 때 지켜야 할 규칙이다.
|
||||||
|
|
||||||
## 원칙 1 — 오케스트레이션은 항상 하나, 이미지 종류는 몰라도 된다
|
## 원칙 1 — 오케스트레이션은 항상 하나, 이미지 종류는 몰라도 된다
|
||||||
|
|
||||||
@@ -49,8 +49,8 @@ security-catalog 레포에서 검증한 자체 빌드 프레임워크를 포팅
|
|||||||
**배포판은 결정됐다(2026-08-07, `keycloak` 이미지 추가 PR).** 그전까지는
|
**배포판은 결정됐다(2026-08-07, `keycloak` 이미지 추가 PR).** 그전까지는
|
||||||
"security-catalog 의 SUSE BCI 단일화 결정을 이식하지 않았다" 는 미결 상태였다.
|
"security-catalog 의 SUSE BCI 단일화 결정을 이식하지 않았다" 는 미결 상태였다.
|
||||||
`keycloak` 은 업스트림이 UBI9 기반이라 "업스트림과 최대한 동일하게" 와 정면으로
|
`keycloak` 은 업스트림이 UBI9 기반이라 "업스트림과 최대한 동일하게" 와 정면으로
|
||||||
충돌했고, **카탈로그 내 일관성을 우선**했다 — 기존 3종이 전부 BCI 이고 trivy 의
|
충돌했고, **카탈로그 내 일관성을 우선**했다 — 먼저 들어온 이미지들이 전부 BCI 이고
|
||||||
SLES 15.7 커버리지가 양성 대조로 실측 확인돼 있다
|
trivy 의 SLES 15.7 커버리지가 양성 대조로 실측 확인돼 있다
|
||||||
(`doc/analysis/sles-oval-measurement.md`).
|
(`doc/analysis/sles-oval-measurement.md`).
|
||||||
|
|
||||||
- 이 정책과 "업스트림과 최대한 동일하게" 가 충돌하면 **무엇을 우선했는지와 왜인지를
|
- 이 정책과 "업스트림과 최대한 동일하게" 가 충돌하면 **무엇을 우선했는지와 왜인지를
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ name: self-build-image
|
|||||||
|
|
||||||
# 자체 빌드 이미지(images/<image>, 실행기 scripts/build/build-hardened-image.sh)의
|
# 자체 빌드 이미지(images/<image>, 실행기 scripts/build/build-hardened-image.sh)의
|
||||||
# 빌드·검증·스캔·게이트·카탈로그 반영을 자동화한다. security-catalog 의 동일 워크플로에서
|
# 빌드·검증·스캔·게이트·카탈로그 반영을 자동화한다. security-catalog 의 동일 워크플로에서
|
||||||
# 프레임워크만 포팅했다 — 아직 이 레포에 도입된 자체 빌드 이미지는 없다(images/ 디렉토리
|
# 프레임워크를 포팅했고 이 레포 CI 에서 빌드→검증→게이트→push→카탈로그 브랜치 push 까지
|
||||||
# 자체가 비어 있음). 게이트(scripts/pipeline/cve-gate.py)가 상위 태그·베이스 OS 교체로
|
# 실제로 검증됐다(2026-08 기준). 대상 이미지 목록은 images/ 디렉토리가 단일 출처다.
|
||||||
# 해소되지 않는 차단 CVE 를 찾으면 이 워크플로로 자체 빌드를 검토한다 —
|
# 게이트(scripts/pipeline/cve-gate.py)가 상위 태그·베이스
|
||||||
# 절차: .claude/image-authoring.md
|
# OS 교체로 해소되지 않는 차단 CVE 를 찾으면 이 워크플로로 자체 빌드를 검토한다 —
|
||||||
|
# 절차: .claude/image-authoring.md, 트리거·제약 요약: doc/sbom-pipeline.md 의 "자체 빌드" 절
|
||||||
#
|
#
|
||||||
# "이미지가 어느 차트의 어느 필드를 가리키는가" 는 images/<image>/catalog.env 가 선언한다
|
# "이미지가 어느 차트의 어느 필드를 가리키는가" 는 images/<image>/catalog.env 가 선언한다
|
||||||
# (CHART_DIRS·TAG_STYLE·TAG_BLOCK·DEFAULT_BASE_OS). 태그 표기 스타일이 두 가지다:
|
# (CHART_DIRS·TAG_STYLE·TAG_BLOCK·DEFAULT_BASE_OS). 태그 표기 스타일이 두 가지다:
|
||||||
|
|||||||
@@ -150,14 +150,20 @@ extract-helm-images.sh → generate-sbom.sh → scan-sbom.sh → cve-gate.py
|
|||||||
(scripts/pipeline/, .github/workflows/sbom.yml·cve-edge-post.yml 이 실행)
|
(scripts/pipeline/, .github/workflows/sbom.yml·cve-edge-post.yml 이 실행)
|
||||||
```
|
```
|
||||||
|
|
||||||
- **현재 warn-only**: 게이트가 실패해도 CI/PR 을 막지 않는다. 45+ 개 카탈로그 차트가
|
- **현재 warn-only**: 게이트가 실패해도 CI/PR 을 막지 않는다. 카탈로그 차트 전체가
|
||||||
이 게이트로 트리아지된 적이 없다.
|
이 게이트로 트리아지된 적이 없다.
|
||||||
|
- **CI 는 전 심각도로 스캔한다** — 게이트가 벤더 하향 등급·NVD 재평가·사각지대 판정에
|
||||||
|
MEDIUM/LOW 까지 필요로 하기 때문이다. 스크립트 기본값(`HIGH,CRITICAL`)으로 만든
|
||||||
|
리포트로 게이트를 돌리면 판정이 달라진다.
|
||||||
- 자체 빌드 프레임워크(`scripts/build/`, `.github/workflows/build-image.yml`)로
|
- 자체 빌드 프레임워크(`scripts/build/`, `.github/workflows/build-image.yml`)로
|
||||||
`images/`에 이미지 3종(`cloudnative-pg`, `cnpg-postgresql`, `etcd`, 전부
|
`images/<image>/` 아래에 자체 빌드 이미지를 둔다(목록은 그 디렉토리가 단일 출처).
|
||||||
security-catalog 프로젝트에서 포팅)이 있으나 dip-catalog 자체 CI 로는 한 번도
|
최종 런타임 베이스 OS 는 SUSE BCI 로 통일하되 버전은 이미지마다 실측해서 고른다.
|
||||||
실행된 적이 없다(빌드·게이트·push 모두 미검증 — 현재 참조 태그는 security-catalog
|
**push·카탈로그 반영을 동반하는 빌드는
|
||||||
쪽에서 이미 빌드된 것). 게이트가 상위 태그·베이스 OS 교체로 해소 안 되는 차단 CVE 를
|
`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).
|
[.claude/image-authoring.md](.claude/image-authoring.md).
|
||||||
- 상세: [doc/sbom-pipeline.md](doc/sbom-pipeline.md) · 승인 예외: `doc/cve-exceptions.json`
|
- 상세: [doc/sbom-pipeline.md](doc/sbom-pipeline.md) · 승인 예외: `doc/cve-exceptions.json`
|
||||||
· 현재 미결 사항: [MEMORY.md](MEMORY.md)
|
· 현재 미결 사항: [MEMORY.md](MEMORY.md)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
- 프로젝트 개요·설계 원칙 → [CLAUDE.md](CLAUDE.md)
|
- 프로젝트 개요·설계 원칙 → [CLAUDE.md](CLAUDE.md)
|
||||||
- SBOM·CVE 게이트 메커니즘 → [doc/sbom-pipeline.md](doc/sbom-pipeline.md)
|
- SBOM·CVE 게이트 메커니즘 → [doc/sbom-pipeline.md](doc/sbom-pipeline.md)
|
||||||
|
|
||||||
최종 갱신: 2026-08-03
|
최종 갱신: 2026-08-07
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -18,9 +18,17 @@
|
|||||||
차단되는지 파악해야 한다. `gh workflow run helm-catalog-sbom --ref main`(또는 대상
|
차단되는지 파악해야 한다. `gh workflow run helm-catalog-sbom --ref main`(또는 대상
|
||||||
브랜치)으로 전체 스캔 후 `cve-gate.md` 아티팩트를 확인한다.
|
브랜치)으로 전체 스캔 후 `cve-gate.md` 아티팩트를 확인한다.
|
||||||
|
|
||||||
**`doc/cve-exceptions.json` 이 비어 있다.** 첫 전체 스캔에서 차단 항목이 나오면 대응
|
**`doc/cve-exceptions.json` 에 첫 예외가 들어갔다(2026-08-07, keycloak 자체 빌드).**
|
||||||
우선순위(상위 태그 교체 → 베이스 OS 교체 → 자체 빌드 → 예외 승인)를 검토하고, 예외는
|
`CVE-2025-59250` — 트리비가 같은 mssql-jdbc jar 하나로 컴포넌트를 두 개 만들어
|
||||||
근거·만료일을 명시해 추가한다.
|
(`13.2.1.jre11` from pom.properties / `13.2.1` from 파일명) 접미사가 잘린 쪽이 취약
|
||||||
|
범위에 매칭된 **파싱 오탐**이다. 만료 2026-11-07. 이후 차단 항목이 나오면 같은 대응
|
||||||
|
우선순위(상위 태그 교체 → 베이스 OS 교체 → 자체 빌드 → 예외 승인)를 지킨다.
|
||||||
|
|
||||||
|
**keycloak 자체 빌드로 베이스 OS 정책이 확정됐다(2026-08-07).** SUSE BCI 로 통일하되
|
||||||
|
**버전은 이미지마다 실측해서 고른다** — BCI 16.0 이 나와 있지만 SLE_BCI 의
|
||||||
|
`java-21-openjdk-headless` 가 15.7 은 `21.0.12`, 16.0 은 `21.0.11` 이라 최신 베이스가
|
||||||
|
오히려 CVE 를 남긴다. 상세: `.claude/image-authoring.md` 원칙 2,
|
||||||
|
`images/keycloak/README.md`.
|
||||||
|
|
||||||
**커버리지 자가진단(`CoverageProbe`)을 security-catalog 에서 이식했다(2026-08-03).**
|
**커버리지 자가진단(`CoverageProbe`)을 security-catalog 에서 이식했다(2026-08-03).**
|
||||||
`scan-sbom.sh` 가 os-pkgs findings 0건인 이미지의 SBOM 사본에 배포판별 센티널 패키지
|
`scan-sbom.sh` 가 os-pkgs findings 0건인 이미지의 SBOM 사본에 배포판별 센티널 패키지
|
||||||
@@ -48,11 +56,12 @@ etcd/1.1.12}/{custom-values,dip-values}.yaml` — etcd 는 `dip-values.yaml`에
|
|||||||
`helm template`·`extract-helm-images.sh` 로 렌더링 결과까지 재확인했다.
|
`helm template`·`extract-helm-images.sh` 로 렌더링 결과까지 재확인했다.
|
||||||
`DOCKERHUB_USER`/`DOCKERHUB_TOKEN` 은 `paasup` 조직에 push 권한이 있음을 이번에
|
`DOCKERHUB_USER`/`DOCKERHUB_TOKEN` 은 `paasup` 조직에 push 권한이 있음을 이번에
|
||||||
확인했다(기존 "미확인" 상태 해소). 최종 런타임 베이스 OS 정책은 security-catalog 의
|
확인했다(기존 "미확인" 상태 해소). 최종 런타임 베이스 OS 정책은 security-catalog 의
|
||||||
SUSE BCI 고정 결정을 그대로 따랐다(ADR 자체는 미이관, 아래 참고).
|
SUSE BCI 고정 결정을 그대로 따랐고(ADR 자체는 미이관), 2026-08-07 keycloak 작업에서
|
||||||
|
이 레포의 정책으로 확정됐다(위 참고).
|
||||||
|
|
||||||
**남은 것: `SBOM_PIPELINE_IMAGE`(sbom.yml 이 쓰는 실행 컨테이너)는 이번 마이그레이션
|
**남은 것: `SBOM_PIPELINE_IMAGE`(sbom.yml 이 쓰는 실행 컨테이너)는 이번 마이그레이션
|
||||||
대상이 아니다** — 여전히 `docker.io/wbsong111/sbom-pipeline:latest` 를 가리킨다.
|
대상이 아니다** — 여전히 `docker.io/wbsong111/sbom-pipeline:latest` 를 가리킨다.
|
||||||
이건 이미지 3종과 무관한 별개 결정(아래 미결 결정 참고).
|
이건 자체 빌드 이미지와 무관한 별개 결정(아래 미결 결정 참고).
|
||||||
|
|
||||||
**`doc/decisions/`·`doc/analysis/` 디렉토리 자체가 dip-catalog 에 없다.** 포팅된 3개
|
**`doc/decisions/`·`doc/analysis/` 디렉토리 자체가 dip-catalog 에 없다.** 포팅된 3개
|
||||||
이미지의 README·values 코멘트가 `doc/decisions/000X-*.md`, `doc/analysis/*.md`,
|
이미지의 README·values 코멘트가 `doc/decisions/000X-*.md`, `doc/analysis/*.md`,
|
||||||
@@ -73,11 +82,19 @@ requests/limits/storage 값을 근거로 표를 추가하는 별도 작업으로
|
|||||||
않지만, `paasup` 네임스페이스로 이전할지는 별도 결정이 필요하다. **재빌드 + push +
|
않지만, `paasup` 네임스페이스로 이전할지는 별도 결정이 필요하다. **재빌드 + push +
|
||||||
Repo Variable `SBOM_PIPELINE_IMAGE` 갱신은 git 커밋으로 되지 않는 수동 작업**이다.
|
Repo Variable `SBOM_PIPELINE_IMAGE` 갱신은 git 커밋으로 되지 않는 수동 작업**이다.
|
||||||
|
|
||||||
**`build-image.yml` 의 `REGISTRY_HOST` 를 `docker.io/paasup` 로 설정했다.** 로컬에서
|
**`build-image.yml` 의 `REGISTRY_HOST` 를 `docker.io/paasup` 로 설정했고, CI 에서
|
||||||
`docker login docker.io` 로 로그인한 계정은 `docker.io/paasup` 에 push 권한이 있음을
|
동작을 확인했다(2026-08-04).** `workflow_dispatch` 로 `etcd`·`cloudnative-pg` 를 돌려
|
||||||
위 3종 이미지 push 로 확인했다. **단, 이건 로컬 자격증명 확인일 뿐이다** — GitHub
|
레지스트리 로그인 → 빌드 → `verify.sh` → SBOM → 스캔 → 게이트 PASS → push → 카탈로그
|
||||||
Actions 시크릿(`DOCKERHUB_USER`/`DOCKERHUB_TOKEN`)이 같은 계정/권한인지는 별도 확인이
|
브랜치 push 까지 전부 성공했다(`build/etcd-20260804060806`,
|
||||||
필요하다(`build-image.yml` workflow_dispatch 를 실제로 한 번 돌려봐야 안다).
|
`build/cloudnative-pg-20260804063006` 브랜치 생성 + `helm-catalog-sbom` PR 스캔 통과).
|
||||||
|
**GitHub Actions 시크릿(`DOCKERHUB_USER`/`DOCKERHUB_TOKEN`)이 `docker.io/paasup` push
|
||||||
|
권한을 갖는다는 것도 이때 확인됐다** — 기존 "로컬 자격증명만 확인" 상태 해소.
|
||||||
|
|
||||||
|
**단, PR 자동 생성은 조직 정책으로 불가하다(실측 확정).** `GITHUB_TOKEN` 으로는 PR 을
|
||||||
|
만들 수 없다(run 30882785612, GraphQL: "GitHub Actions is not permitted to create or
|
||||||
|
approve pull requests"). 리포 설정으로 못 바꾸는 제약이라 `gh pr create` 호출을
|
||||||
|
워크플로에서 제거했고(`d449504`), 브랜치 push + Job Summary 의 compare 링크까지만
|
||||||
|
자동화한다. **PR 오픈·병합 전 게이트 재확인·배포 검증은 사람의 몫이다.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+142
-55
@@ -14,15 +14,39 @@ GitHub Dependency-Graph SBOM(예: `paasup_dip-catalog_199087.json`)은 pypi 등
|
|||||||
|
|
||||||
이미지를 **한 번만 pull** 해서 SBOM 을 만들고, 취약점은 그 SBOM 에서 오프라인으로 뽑는다.
|
이미지를 **한 번만 pull** 해서 SBOM 을 만들고, 취약점은 그 SBOM 에서 오프라인으로 뽑는다.
|
||||||
|
|
||||||
```
|
```mermaid
|
||||||
extract-helm-images.sh generate-sbom.sh scan-sbom.sh
|
flowchart TB
|
||||||
manifests/helm/** ──helm template──▶ images_final.tsv ──trivy image──▶ sbom/*.cdx.json ──trivy sbom──▶ trivy-summary.md
|
CAT[/"manifests/helm/**<br/>차트 카탈로그"/]
|
||||||
(오프라인·결정론) (이미지 1회 pull, 인증 필요) (CycloneDX) (오프라인·인증 불필요) + trivy-reports/
|
|
||||||
|
subgraph S1["① 인벤토리 추출 · 오프라인 · 결정론"]
|
||||||
|
direction LR
|
||||||
|
EX["extract-helm-images.sh"] --> INV[/"images_final.tsv<br/>chart ⇥ version ⇥ image"/]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph S2["② SBOM 생성 · 네트워크 · 레지스트리 인증 필요 · 비용의 대부분"]
|
||||||
|
direction LR
|
||||||
|
GN["generate-sbom.sh"] --> SB[/"sbom/*.cdx.json<br/>CycloneDX"/]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph S3["③ 스캔 · 게이트 · 오프라인 · 인증 불필요 · 초 단위 반복 가능"]
|
||||||
|
direction LR
|
||||||
|
SC["scan-sbom.sh<br/>+ CoverageProbe"] --> RP[/"trivy-reports/*.json<br/>trivy-summary.md"/]
|
||||||
|
RP --> GT["cve-gate.py"]
|
||||||
|
GT --> VD[/"cve-gate.md<br/>cve-gate.json"/]
|
||||||
|
end
|
||||||
|
|
||||||
|
CAT -- "helm template" --> EX
|
||||||
|
INV -- "trivy image · 이미지 1회 pull" --> GN
|
||||||
|
SB -- "trivy sbom" --> SC
|
||||||
|
|
||||||
|
classDef step fill:#dbeafe,stroke:#3b82f6,stroke-width:1.5px,color:#12305c
|
||||||
|
classDef art fill:#f1f3f5,stroke:#adb5bd,color:#212529
|
||||||
|
class EX,GN,SC,GT step
|
||||||
|
class CAT,INV,SB,RP,VD art
|
||||||
```
|
```
|
||||||
|
|
||||||
- **레지스트리 pull·인증은 SBOM 생성 단계 1곳으로 집중.** 취약점 스캔은 네트워크·인증 없이 빠르게 반복 가능
|
**레지스트리 pull·인증을 ② 한 곳에 몰아둔 것이 이 구조의 핵심이다.** CVE DB 가 갱신돼도 재pull 없이
|
||||||
(CVE DB 갱신 시 재pull 없이 재스캔). → 파이프라인 비용의 대부분은 SBOM 생성(이미지 pull)이고 스캔은 초 단위.
|
③ 만 다시 돌리면 된다. 스캔 대상 이미지는 trivy 가 원격에서 받아 분석 후 폐기하므로 호스트에 남지 않는다.
|
||||||
- 스캔 대상 이미지는 trivy 가 원격에서 받아 분석 후 폐기하므로 호스트/이미지 스토어에 남지 않는다.
|
|
||||||
|
|
||||||
## 스크립트 (`scripts/pipeline/`)
|
## 스크립트 (`scripts/pipeline/`)
|
||||||
|
|
||||||
@@ -34,10 +58,18 @@ manifests/helm/** ──helm template──▶ images_final.tsv ──trivy ima
|
|||||||
| `generate-sbom.sh` | `images_final.tsv` | `sbom/<img>.cdx.json`, `sbom-index.tsv`, `sbom-gen.log` |
|
| `generate-sbom.sh` | `images_final.tsv` | `sbom/<img>.cdx.json`, `sbom-index.tsv`, `sbom-gen.log` |
|
||||||
| `scan-sbom.sh` | `sbom/` + `sbom-index.tsv` | `trivy-summary.{md,tsv}`, `trivy-reports/`, `trivy-run.log` |
|
| `scan-sbom.sh` | `sbom/` + `sbom-index.tsv` | `trivy-summary.{md,tsv}`, `trivy-reports/`, `trivy-run.log` |
|
||||||
|
|
||||||
주요 환경변수: `PARALLEL`(동시 처리, 기본 3), `LIMIT`(대상 상한, 0=전체 — 테스트용),
|
SBOM 생성 실패는 대부분 **사설/미인증 레지스트리이거나 대용량 이미지 timeout** 이다 —
|
||||||
`SEVERITY`(기본 `HIGH,CRITICAL`), `TRIVY_CACHE_DIR`(캐시 디렉토리), `MERGE=1`(generate 시
|
`sbom-gen.log` 와 `render_status.tsv` 로 확인한다.
|
||||||
`catalog.cdx.json` 병합, cyclonedx CLI 필요). 사설 레지스트리 인증은 trivy 네이티브:
|
|
||||||
`TRIVY_USERNAME`/`TRIVY_PASSWORD`(단일) 또는 `DOCKER_CONFIG`(다중).
|
주요 환경변수: `PARALLEL`(동시 처리 — generate 기본 3/이미지 pull 이라 보수적, scan 기본 4/오프라인이라
|
||||||
|
높여도 안전), `LIMIT`(대상 상한, 0=전체 — 테스트용), `SEVERITY`(스크립트 기본 `HIGH,CRITICAL`),
|
||||||
|
`TRIVY_CACHE_DIR`(캐시 디렉토리), `MERGE=1`(generate 시 `catalog.cdx.json` 병합, cyclonedx CLI 필요).
|
||||||
|
사설 레지스트리 인증은 trivy 네이티브: `TRIVY_USERNAME`/`TRIVY_PASSWORD`(단일) 또는 `DOCKER_CONFIG`(다중).
|
||||||
|
|
||||||
|
> **게이트를 돌릴 거라면 전 심각도로 스캔해야 한다.** `cve-gate.py` 는 벤더 하향 등급·NVD 재평가·
|
||||||
|
> 사각지대 판정에 MEDIUM/LOW 데이터까지 쓴다. `HIGH,CRITICAL` 만 스캔한 리포트로 게이트를 돌리면
|
||||||
|
> 판정 자체가 달라진다. 그래서 CI 는 스크립트 기본값을 덮어쓴다 — `sbom.yml` 은
|
||||||
|
> `UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL`, `cve-edge-post.yml` 은 `LOW,MEDIUM,HIGH,CRITICAL`.
|
||||||
|
|
||||||
### 로컬/컨테이너 실행
|
### 로컬/컨테이너 실행
|
||||||
|
|
||||||
@@ -54,24 +86,17 @@ docker run --rm -v "$PWD:/repo" -w /repo \
|
|||||||
|
|
||||||
## 실행 이미지 (`scripts/pipeline/Dockerfile`)
|
## 실행 이미지 (`scripts/pipeline/Dockerfile`)
|
||||||
|
|
||||||
파이프라인은 **도구가 설치된 컨테이너 안에서** 돈다. 그 이미지는 [scripts/pipeline/Dockerfile](../scripts/pipeline/Dockerfile)로 빌드한다.
|
|
||||||
|
|
||||||
| 항목 | 값 |
|
| 항목 | 값 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 베이스 | `debian:stable-slim` (**glibc**) |
|
| 베이스 | `debian:stable-slim` (**glibc**) |
|
||||||
| 포함 도구 | `helm`(v3) · `trivy` · `python3` · `bash` · `git` |
|
| 포함 도구 | `helm`(v3) · `trivy` · `python3` · `bash` · `git` |
|
||||||
| 아키텍처 | **linux/amd64** (GitHub 러너와 일치) |
|
| 아키텍처 | **linux/amd64** (GitHub 러너와 일치) |
|
||||||
| 현재 이미지 | `docker.io/wbsong111/sbom-pipeline:latest` (public) |
|
| 현재 이미지 | `docker.io/wbsong111/sbom-pipeline:latest` (public) — `paasup` 네임스페이스 이전 미완료(`MEMORY.md`) |
|
||||||
|
|
||||||
> 재빌드 후 `paasup` 네임스페이스로 이전 예정 — 미완료(`MEMORY.md` 참고). Dockerfile 내용은
|
|
||||||
> 안 바뀌었으므로 위 이미지는 이 마이그레이션으로 당장 깨지지 않는다.
|
|
||||||
|
|
||||||
> **glibc(debian) 필수**: GitHub Actions 의 `container:` 안에서 `actions/checkout`·`upload-artifact`
|
> **glibc(debian) 필수**: GitHub Actions 의 `container:` 안에서 `actions/checkout`·`upload-artifact`
|
||||||
> (node 기반)가 동작하려면 glibc 이거나 node 가 있어야 한다. `aquasec/trivy` 같은 **alpine(musl)
|
> (node 기반)가 동작하려면 glibc 이거나 node 가 있어야 한다. `aquasec/trivy` 같은 **alpine(musl)
|
||||||
> 이미지는 node 실행 실패** → debian 사용. **amd64 필수**(러너 아키텍처).
|
> 이미지는 node 실행 실패** → debian 사용. **amd64 필수**(러너 아키텍처).
|
||||||
|
|
||||||
### 빌드 & 푸시
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker buildx build --platform linux/amd64 \
|
docker buildx build --platform linux/amd64 \
|
||||||
-t docker.io/paasup/sbom-pipeline:latest \
|
-t docker.io/paasup/sbom-pipeline:latest \
|
||||||
@@ -83,18 +108,24 @@ docker buildx build --platform linux/amd64 \
|
|||||||
|
|
||||||
## CI (`.github/workflows/sbom.yml`)
|
## CI (`.github/workflows/sbom.yml`)
|
||||||
|
|
||||||
- **트리거**
|
| 트리거 | 스캔 범위 |
|
||||||
- `workflow_dispatch` — 수동. 입력 `limit`(기본 0=전체, 테스트 시 예 `3`).
|
|--------|----------|
|
||||||
- `pull_request`(`manifests/helm/**`) — **변경된 차트의 이미지만** 증분 스캔(빠른 게이트).
|
| `workflow_dispatch` | 입력 `limit`(기본 0=전체) |
|
||||||
- `schedule`(주 1회) — **전체** 스캔.
|
| `pull_request` (`manifests/helm/**`) | **변경된 차트의 이미지만** (빠른 게이트) |
|
||||||
- **컨테이너**: `vars.SBOM_PIPELINE_IMAGE` 이미지에서 실행.
|
| `schedule` (`0 18 * * 0`, 매주 일요일 18:00 UTC) | **전체** |
|
||||||
- **인증 구성 스텝**: 시크릿으로 `~/.docker/config.json`(인라인 토큰)을 만들어 `DOCKER_CONFIG` 로 trivy 에 전달.
|
|
||||||
- **아티팩트**: 스테이징 디렉토리(`report/`)로 모아 **상대경로**로 업로드(절대경로면 v4 가 전체 경로트리를 보존하므로).
|
|
||||||
- **스캔 주기 원칙**: 인벤토리·SBOM 은 차트 변경 시에만 바뀌지만 **취약점은 새 CVE 로 계속 변하므로 스케줄 전체
|
|
||||||
스캔이 필수**. PR 은 변경 차트만, 스케줄은 전체.
|
|
||||||
- **게이트**: 스캔 뒤 `scripts/pipeline/cve-gate.py` 로 판정(현재 `--warn-only`).
|
|
||||||
|
|
||||||
### 실행
|
인벤토리·SBOM 은 차트 변경 시에만 바뀌지만 **취약점은 새 CVE 로 계속 변하므로 스케줄 전체 스캔이 필수**다.
|
||||||
|
|
||||||
|
- **컨테이너**: `vars.SBOM_PIPELINE_IMAGE` 이미지에서 실행.
|
||||||
|
- **인증**: 시크릿으로 `~/.docker/config.json`(인라인 토큰)을 만들어 `DOCKER_CONFIG` 로 trivy 에 전달.
|
||||||
|
- **아티팩트**: 스테이징 디렉토리(`report/`)로 모아 **상대경로**로 업로드(절대경로면 v4 가 전체 경로트리를 보존).
|
||||||
|
- **게이트 판정 범위는 `--inventory` 가 정한다** — PR 증분에서는 `images_scan.tsv`(변경 차트 이미지만)라
|
||||||
|
**그 PR 이 건드리지 않은 차트는 게이트가 보지 않는다.** 전체 판정은 스케줄/수동 실행에서만 나온다.
|
||||||
|
|
||||||
|
> **Job Summary 는 축약본만 싣는다.** `$GITHUB_STEP_SUMMARY` 는 1MB 를 넘으면 잘리는데 카탈로그 전체
|
||||||
|
> 스캔의 `cve-gate.md` 는 실측 2.2MB 라 실제로 잘렸다. 그래서 Summary 에는 `trivy-summary.md`(건수 표)와
|
||||||
|
> `cve-gate-brief.md`(판정 요약)만 넣고, **CVE 상세는 아티팩트의 `cve-gate.md`** 에서 본다.
|
||||||
|
> `--artifact-name` 은 축약 리포트가 "상세는 이 아티팩트를 보라"고 안내할 때 쓸 이름이다.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gh workflow run helm-catalog-sbom --repo <org>/dip-catalog -f limit=3 # 빠른 검증
|
gh workflow run helm-catalog-sbom --repo <org>/dip-catalog -f limit=3 # 빠른 검증
|
||||||
@@ -102,15 +133,26 @@ gh workflow run helm-catalog-sbom --repo <org>/dip-catalog # 전체
|
|||||||
gh run watch --repo <org>/dip-catalog
|
gh run watch --repo <org>/dip-catalog
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 다른 워크플로 — `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`).
|
||||||
|
**게이트 판정은 하지 않는다.**
|
||||||
|
|
||||||
|
- 트리거는 `workflow_dispatch` 뿐 — `chart`(빈 값=전체) · `limit` 입력. `schedule` 은 주석 처리 상태다.
|
||||||
|
- 이미지 하나가 여러 `manifests/helm/<name>/<version>/` 에서 재사용되면 그 조합 수만큼 항목이 중복된다
|
||||||
|
(의도된 동작 — 소비 측이 카탈로그 단위로 집계한다).
|
||||||
|
|
||||||
## CVE 게이트 (`scripts/pipeline/cve-gate.py`)
|
## CVE 게이트 (`scripts/pipeline/cve-gate.py`)
|
||||||
|
|
||||||
스캔이 끝나면 `cve-gate.py` 가 `trivy-reports/*.json` 을 판정한다: 고유 CVE 단위 집계,
|
스캔이 끝나면 `cve-gate.py` 가 `trivy-reports/*.json` 을 판정한다: 고유 CVE 단위 집계,
|
||||||
`max(벤더 등급, NVD 등급)` 실효 등급, `doc/cve-exceptions.json` 승인 예외 처리. 상세 판정
|
`max(벤더 등급, NVD 등급)` 실효 등급, `doc/cve-exceptions.json` 승인 예외 처리. 상세 판정
|
||||||
로직·근거는 스크립트 자체의 모듈 docstring 을 우선 참고한다.
|
로직·근거는 스크립트 자체의 모듈 docstring 을 우선 참고한다.
|
||||||
|
|
||||||
현재 `sbom.yml` 은 `--warn-only` 로 호출한다 — 게이트가 실패해도 워크플로/PR 을 막지
|
**`sbom.yml` 에서는 아직 `--warn-only` 다** — 게이트가 실패해도 워크플로/PR 을 막지 않는다. 카탈로그
|
||||||
않는다. 45+ 개 카탈로그 차트가 아직 이 게이트로 트리아지된 적이 없어, 강제 전환 전에
|
차트 전체가 아직 이 게이트로 트리아지된 적이 없어, 강제 전환 전에 먼저 전체 스캔 1회로 현황을
|
||||||
먼저 전체 스캔 1회로 현황을 파악해야 한다(`MEMORY.md`).
|
파악해야 한다(`MEMORY.md`). `build-image.yml` 의 게이트는 이미 강제다.
|
||||||
|
|
||||||
> **커버리지 자가진단(`CoverageProbe`).** `scan-sbom.sh` 는 os-pkgs findings 가 0건인
|
> **커버리지 자가진단(`CoverageProbe`).** `scan-sbom.sh` 는 os-pkgs findings 가 0건인
|
||||||
> 이미지의 SBOM 사본에 배포판별(deb/rpm/apk) 센티널 패키지를 주입해 재스캔하고, 발화
|
> 이미지의 SBOM 사본에 배포판별(deb/rpm/apk) 센티널 패키지를 주입해 재스캔하고, 발화
|
||||||
@@ -119,6 +161,49 @@ gh run watch --repo <org>/dip-catalog
|
|||||||
> 유일한 수단이다(security-catalog 프로젝트에서 이식, 2026-08-03). 이 키가 없는 구버전
|
> 유일한 수단이다(security-catalog 프로젝트에서 이식, 2026-08-03). 이 키가 없는 구버전
|
||||||
> 리포트는 findings 총계 0건이면 보수적으로 실패 처리하는 예전 경로로 판정한다.
|
> 리포트는 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).
|
||||||
|
|
||||||
|
> `sbom.yml` 과 별도 파일인 이유: `sbom.yml` 은 `vars.SBOM_PIPELINE_IMAGE` 컨테이너 안에서 도는데
|
||||||
|
> 거기엔 docker/buildx 가 없다. 빌드는 호스트 러너여야 한다.
|
||||||
|
|
||||||
|
| 트리거 | 대상 결정 | 빌드·검증·게이트 | 레지스트리 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).
|
||||||
|
|
||||||
## GitHub 설정 (워크플로 활성화에 필요)
|
## GitHub 설정 (워크플로 활성화에 필요)
|
||||||
|
|
||||||
`Settings → Secrets and variables → Actions`
|
`Settings → Secrets and variables → Actions`
|
||||||
@@ -126,41 +211,46 @@ gh run watch --repo <org>/dip-catalog
|
|||||||
| 종류 | 이름 | 용도 |
|
| 종류 | 이름 | 용도 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| **Variable** | `SBOM_PIPELINE_IMAGE` | 실행 이미지 태그 (예: `docker.io/wbsong111/sbom-pipeline:latest`) |
|
| **Variable** | `SBOM_PIPELINE_IMAGE` | 실행 이미지 태그 (예: `docker.io/wbsong111/sbom-pipeline:latest`) |
|
||||||
| Secret | `DOCKERHUB_USER` / `DOCKERHUB_TOKEN` | **docker.io + docker.getcollate.io rate limit 회피**. getcollate(openmetadata)는 Docker Hub 프록시라 익명 pull 시 rate limit(TOOMANYREQUESTS)에 걸림 → Docker Hub 자격증명으로 인증. pull 만 하므로 read-only PAT 권장 |
|
| 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 | `NGC_API_KEY` | **nvcr.io 인증**(NVIDIA nemo/nim — 없으면 pull 불가) |
|
||||||
| Secret | (필요 시) paasup 사설 레지스트리 자격증명 | `paasup/*` 이미지가 사설일 때 |
|
| Secret | `CVE_API_KEY` | `cve-edge-post.yml` 의 외부 엔드포인트 인증 |
|
||||||
|
|
||||||
추가 고려:
|
추가 고려:
|
||||||
|
|
||||||
- `SBOM_PIPELINE_IMAGE` 가 **사설**이면 워크플로 `container:` 에 `credentials:` 추가 필요.
|
- `SBOM_PIPELINE_IMAGE` 가 **사설**이면 워크플로 `container:` 에 `credentials:` 추가 필요.
|
||||||
- 저장소 Actions 활성화(org 정책). 서드파티 액션은 GitHub 공식(`checkout`·`upload-artifact`)만 사용.
|
- 저장소 Actions 활성화(org 정책). 서드파티 액션은 GitHub 공식(`checkout`·`upload-artifact`)만 사용.
|
||||||
- `DOCKERHUB_TOKEN` 이 Docker Desktop 세션 토큰이면 만료 가능 → 장기적으로 **스코프 지정 PAT** 권장.
|
- `DOCKERHUB_TOKEN` 이 Docker Desktop 세션 토큰이면 만료 가능 → 장기적으로 **스코프 지정 PAT** 권장.
|
||||||
|
|
||||||
## 결과 확인
|
## 결과 확인
|
||||||
|
|
||||||
1. **Job Summary** (권장) — Actions run 페이지 → Summary. `trivy-summary.md` 표가 렌더링된다.
|
`gh run download <run-id> -n sbom-and-vuln-report -D ./out`
|
||||||
2. **아티팩트** `sbom-and-vuln-report` — 아래 구조. `gh run download <run-id> -n sbom-and-vuln-report -D ./out`.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
sbom-and-vuln-report/ ← 아티팩트 루트
|
sbom-and-vuln-report/ ← 아티팩트 루트
|
||||||
├── trivy-summary.md ← 취약점 요약(표)
|
├── trivy-summary.md ← 취약점 요약(차트별 표)
|
||||||
├── trivy-summary.tsv
|
├── trivy-summary.tsv
|
||||||
|
├── cve-gate.md ← 게이트 판정 전문 (CVE 상세 전부 — 실질적인 트리아지 입력)
|
||||||
|
├── cve-gate.json ← 게이트 판정 결과(기계 판독용)
|
||||||
├── sbom-index.tsv ← 차트↔이미지↔SBOM 인덱스
|
├── sbom-index.tsv ← 차트↔이미지↔SBOM 인덱스
|
||||||
|
├── catalog.cdx.json ← MERGE=1 로 병합한 경우에만
|
||||||
├── sbom-gen.log / trivy-run.log
|
├── sbom-gen.log / trivy-run.log
|
||||||
└── sbom/ ← 이미지별 상세 CycloneDX SBOM
|
└── sbom/ ← 이미지별 상세 CycloneDX SBOM
|
||||||
├── <img>.cdx.json
|
├── <img>.cdx.json
|
||||||
└── ...
|
└── ...
|
||||||
```
|
```
|
||||||
|
|
||||||
### 요약(`trivy-summary.md`) 내용
|
### 요약(`trivy-summary.md`) 읽는 법
|
||||||
|
|
||||||
- **단계별 소요시간**: `SBOM 생성 소요`(이미지 pull, 주 비용) / `취약점 스캔 소요`(오프라인). 오해를 주던 단일 "총 소요"는 제거.
|
- **단계별 소요시간**: `SBOM 생성 소요`(이미지 pull, 주 비용) / `취약점 스캔 소요`(오프라인).
|
||||||
- **동적 심각도 컬럼**: 실제 스캔한 `SEVERITY` 만 표에 표시(기본 `CRITICAL`·`HIGH`). MEDIUM/LOW 는 스캔 안 하면 컬럼 자체가 없다.
|
- **심각도 컬럼은 실제 스캔한 `SEVERITY` 만** 나온다. CI 는 전 심각도라 컬럼도 전부 나오고, 로컬에서
|
||||||
- 차트·이미지별 집계 + 심각도별 총계. TSV 는 `chart⇥image⇥<심각도들>⇥status`(CRITICAL 이 첫 카운트 열).
|
스크립트 기본값으로 돌리면 `CRITICAL`·`HIGH` 두 컬럼만 나온다.
|
||||||
|
- **차트명 기준으로 묶고 차트마다 합계 행**을 붙인다. 이미지 하나가 여러 차트에 쓰이면(예: `busybox`)
|
||||||
|
차트마다 한 행씩 나온다 — 그래야 차트 합계가 그 차트의 실제 노출을 뜻한다.
|
||||||
|
- TSV 는 `chart⇥image⇥<심각도들>⇥status`(CRITICAL 이 첫 카운트 열), `.md` 표와 같은 범위를 담는다.
|
||||||
|
|
||||||
### 첫 전체 실행 결과(참고, 2026-07-08)
|
> **표에는 차트별 최신 버전만 나온다.** 카탈로그는 한 차트의 여러 버전을 동시에 보관하는 "버전 보관소"라
|
||||||
|
> 전 버전을 표에 실으면 읽을 수 없게 된다. **구버전도 스캔·게이트 판정은 전부 받는다** — 다만 그 결과는
|
||||||
- SBOM 생성 **159/178**(19 실패 — 대부분 사설/미인증 레지스트리 또는 대용량 timeout), 스캔 **145 전부 성공**.
|
> `cve-gate.md`/`cve-gate.json` 에만 있다. 요약 표에 안 보인다고 판정 대상에서 빠진 것이 아니다.
|
||||||
- 취약점 합계 **CRITICAL 1,511 · HIGH 15,724**, 총 소요 약 20분(생성 19분 / 스캔 23초).
|
|
||||||
|
|
||||||
## 렌더 함정 (extract 단계, 스크립트에 내장)
|
## 렌더 함정 (extract 단계, 스크립트에 내장)
|
||||||
|
|
||||||
@@ -177,23 +267,20 @@ trivy 병렬 실행 시 공유 캐시 bolt 잠금 충돌을 피하려 워커는
|
|||||||
원시 CVE 수를 0으로 만드는 것이 목표가 아니다. **고칠 수 있고, 악용 가능하고, 노출된** 것부터 대응한다.
|
원시 CVE 수를 0으로 만드는 것이 목표가 아니다. **고칠 수 있고, 악용 가능하고, 노출된** 것부터 대응한다.
|
||||||
|
|
||||||
1. **우선순위**: fix 가능(`--ignore-unfixed`) → KEV/EPSS(실제 악용) → 노출도(인터넷 vs 내부) → 심각도.
|
1. **우선순위**: fix 가능(`--ignore-unfixed`) → KEV/EPSS(실제 악용) → 노출도(인터넷 vs 내부) → 심각도.
|
||||||
2. **대응 레버(모두 Git PR)**: 차트/이미지 **버전 업**(최고 레버리지) · **베이스 이미지 교체**(특히 미유지 `bitnamilegacy/*`) ·
|
2. **대응 레버(모두 Git PR)**: 차트/이미지 **버전 업**(최고 레버리지) → **베이스 이미지 교체**(특히 미유지
|
||||||
**소유 이미지(`paasup/*`, `wbsong111/*`) 재빌드** · 좋은 버전 선정 후 **digest 고정**.
|
`bitnamilegacy/*`) → **자체 빌드**(위 절) → 수용. 앞 레버로 안 되는 것만 다음으로 넘긴다.
|
||||||
3. **게이트·억제**: `scripts/pipeline/cve-gate.py` 가 고유 CVE·실효 등급 기준으로 판정한다(현재
|
3. 수용하는 CVE 는 `doc/cve-exceptions.json` 에 **근거·만료일을 명시**해 등록한다.
|
||||||
warn-only — 위 "CVE 게이트" 절 참고). 수용 CVE 는 `doc/cve-exceptions.json`(근거·만료일 명시).
|
|
||||||
4. 새 CVE 는 계속 나오므로 스케줄 스캔으로 **추세 추적**.
|
4. 새 CVE 는 계속 나오므로 스케줄 스캔으로 **추세 추적**.
|
||||||
|
|
||||||
향후 개선(요약을 행동 가능하게): 이미지별 **fixable(패치 존재) 집계** 컬럼, `.trivyignore` 지원,
|
|
||||||
소유/legacy/3rd-party **분류 컬럼**.
|
|
||||||
|
|
||||||
## 운영 노트 — `:latest`/무태그 이미지 digest 고정
|
## 운영 노트 — `:latest`/무태그 이미지 digest 고정
|
||||||
|
|
||||||
태그가 고정돼 있어도 내용물이 바뀌는 `:latest`/무태그 이미지는 재현성 저해 + 증분 스캔의 사각지대다.
|
태그가 고정돼 있어도 내용물이 바뀌는 `:latest`/무태그 이미지는 재현성 저해 + 증분 스캔의 사각지대다.
|
||||||
정기 전체 스캔 대상에 항상 포함하고, 가능하면 **구체 버전 또는 digest(`@sha256:...`)로 고정**한다.
|
정기 전체 스캔 대상에 항상 포함하고, 가능하면 **구체 버전 또는 digest(`@sha256:...`)로 고정**한다.
|
||||||
|
|
||||||
적용된 고정(2026-07-08 기준):
|
적용된 고정(2026-07-08 기준):
|
||||||
|
|
||||||
- `mlflow@1.9.0` postgresql → `bitnamilegacy/postgresql@sha256:42a8200d...` (legacy 에 v18 버전 태그 부재, latest 내용 digest 고정)
|
- `mlflow@1.9.0` postgresql → `bitnamilegacy/postgresql@sha256:42a8200d...` (legacy 에 v18 버전 태그 부재, latest 내용 digest 고정)
|
||||||
- `vllm@0.0.11` vllm-openai → `vllm/vllm-openai:v0.24.0`
|
- `vllm@0.0.11` vllm-openai → `vllm/vllm-openai:v0.24.0`
|
||||||
|
|
||||||
digest 고정은 재현성을 보장하지만 자동 보안 패치는 멈춘다. → 정기 스캔으로 새 CVE 를 감지하고 필요 시 digest 를 갱신한다.
|
digest 고정은 재현성을 보장하지만 **자동 보안 패치는 멈춘다** — 정기 스캔으로 새 CVE 를 감지하고 필요 시
|
||||||
`bitnamilegacy/*` 는 미유지 이미지이므로 중기적으로 legacy 탈피(대체 이미지)를 검토한다.
|
digest 를 갱신한다. `bitnamilegacy/*` 는 미유지 이미지이므로 중기적으로 legacy 탈피를 검토한다.
|
||||||
|
|||||||
@@ -69,10 +69,10 @@ Security Advisory 실측). 스캔 시점 trivy DB 에 아직 없어 게이트에
|
|||||||
|
|
||||||
### 베이스 OS 결정 배경 (image-authoring.md 원칙 2)
|
### 베이스 OS 결정 배경 (image-authoring.md 원칙 2)
|
||||||
|
|
||||||
업스트림은 `ubi9` 빌더 + `ubi9-micro` 최종이다. 카탈로그의 기존 자체 빌드 3종
|
업스트림은 `ubi9` 빌더 + `ubi9-micro` 최종이다. 카탈로그의 먼저 들어온 자체 빌드
|
||||||
(`cloudnative-pg`·`cnpg-postgresql`·`etcd`)이 전부 SUSE BCI 이고 trivy 의 SLES 15.7
|
이미지들이 전부 SUSE BCI 이고 trivy 의 SLES 15.7 커버리지가 양성 대조로 실측 확인돼
|
||||||
커버리지가 양성 대조로 실측 확인돼 있어(`doc/analysis/sles-oval-measurement.md`),
|
있어(`doc/analysis/sles-oval-measurement.md`), **"업스트림과 최대한 동일하게" 보다
|
||||||
**"업스트림과 최대한 동일하게" 보다 카탈로그 내 일관성을 우선**했다.
|
카탈로그 내 일관성을 우선**했다.
|
||||||
|
|
||||||
**BCI 16.0 이 나와 있지만 15.7 을 쓴다 — 최신이 이 이미지에는 더 낡았다**(2026-08-07
|
**BCI 16.0 이 나와 있지만 15.7 을 쓴다 — 최신이 이 이미지에는 더 낡았다**(2026-08-07
|
||||||
실측). 필요한 나머지 패키지는 16.0 에도 전부 있지만 JDK 가 뒤처져 있다.
|
실측). 필요한 나머지 패키지는 16.0 에도 전부 있지만 JDK 가 뒤처져 있다.
|
||||||
|
|||||||
Reference in New Issue
Block a user