From 5e1422e0e416ab587da3d51f48999e5faeef4cff Mon Sep 17 00:00:00 2001 From: wbsong111 Date: Mon, 3 Aug 2026 10:06:48 +0900 Subject: [PATCH] =?UTF-8?q?CVE/SBOM=20=EA=B2=8C=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=ED=99=94:=20CLAUDE.md=203=EB=B2=88=EC=A7=B8?= =?UTF-8?q?=20=EC=84=9C=EB=B8=8C=EC=8B=9C=EC=8A=A4=ED=85=9C=20+=20MEMORY.m?= =?UTF-8?q?d=20=EC=8B=A0=EC=84=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLAUDE.md 에 CVE/SBOM 게이트 서브시스템 절 추가(기존 7개 설계 원칙·구현 현황은 변경 없음). doc/sbom-pipeline.md 에 게이트 판정 절 + 커버리지 자가진단 미이식 제약 명시. MEMORY.md 신설 — warn-only 상태, 45+ 차트 미검증, 자체 빌드 프레임워크 미사용, SBOM_PIPELINE_IMAGE 재빌드 보류 등 후속 결정 사항 기록. --- CLAUDE.md | 32 +++++++++++++++++++++-- MEMORY.md | 62 ++++++++++++++++++++++++++++++++++++++++++++ doc/sbom-pipeline.md | 40 +++++++++++++++++++++------- 3 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 MEMORY.md diff --git a/CLAUDE.md b/CLAUDE.md index 69a9081..0b210c8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,12 +2,13 @@ ## 프로젝트 개요 -DIP Catalog는 두 개의 독립 서브시스템으로 구성된다. +DIP Catalog는 세 개의 독립 서브시스템으로 구성된다. | 서브시스템 | 설명 | 경로 | |-----------|------|------| | **정적 카탈로그** | 45+ 엔터프라이즈 Helm 차트 버전 보관소 (Kafka, Airflow, MLflow, KServe, OpenMetadata 등) | `manifests/helm/` | | **자동화 에이전트** | Helm 차트 신규 버전 감지 → Diff 분석 → 문서 생성 → GitHub PR 자동화 | `agent/update_catalog/` | +| **CVE/SBOM 게이트** | 카탈로그 이미지 SBOM·취약점 스캔 + 게이트 판정 (현재 warn-only) | `scripts/pipeline/`, `scripts/build/`, `doc/sbom-pipeline.md` | 카탈로그는 운영 클러스터 직접 변경과 무관하다. **모든 변경은 Git PR을 통해서만 이루어진다.** @@ -29,7 +30,12 @@ dip-catalog/ │ ├── design/ # 컴포넌트별 상세 설계 (00~05) │ ├── decisions/ # ADR (아키텍처 결정 기록) │ └── status.md # 구현 현황 -└── doc/ # 차트 리소스 프로파일 (CPU/Memory/Storage 정의) +├── scripts/ +│ ├── pipeline/ # SBOM 생성 + CVE 스캔 + 게이트 판정 (sbom.yml/cve-edge-post.yml 이 쓴다) +│ └── build/ # 자체 빌드 이미지 프레임워크 (build-image.yml 이 쓴다 — 아직 실사용 이미지 없음) +└── doc/ # 차트 리소스 프로파일(CPU/Memory/Storage) + CVE/SBOM 파이프라인 문서 + ├── sbom-pipeline.md # SBOM 생성·스캔·게이트 메커니즘 + └── cve-exceptions.json # 게이트 승인 예외 목록 ``` --- @@ -103,6 +109,27 @@ 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 이 실행) +``` + +- **현재 warn-only**: 게이트가 실패해도 CI/PR 을 막지 않는다. 45+ 개 카탈로그 차트가 + 이 게이트로 트리아지된 적이 없다. +- 자체 빌드 프레임워크(`scripts/build/`, `.github/workflows/build-image.yml`)는 + 도입만 됐고 실사용 이미지가 없다(`images/` 디렉토리 자체가 없음). 게이트가 상위 + 태그·베이스 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) + +--- + ## 설계 원칙 아래 원칙을 위반하는 코드를 제안하거나 작성하지 않는다. @@ -145,3 +172,4 @@ python3 agent/update_catalog/skills/helm_diff/scripts/run.py \ | [agent/update_catalog/docs/design/05-on-cluster-agent.md](agent/update_catalog/docs/design/05-on-cluster-agent.md) | On-Cluster Agent 설계 (Step 2~3) | | [agent/update_catalog/docs/decisions/001-agentic-first.md](agent/update_catalog/docs/decisions/001-agentic-first.md) | 파이프라인 오케스트레이션 건너뛰기 결정 배경 | | [agent/update_catalog/docs/status.md](agent/update_catalog/docs/status.md) | 구현 현황 상세 | +| [doc/sbom-pipeline.md](doc/sbom-pipeline.md) | SBOM 생성·CVE 스캔·게이트 파이프라인 상세 | diff --git a/MEMORY.md b/MEMORY.md new file mode 100644 index 0000000..ed9f6b5 --- /dev/null +++ b/MEMORY.md @@ -0,0 +1,62 @@ +# 현재 상태 · 미결 결정 + +작업을 이어받을 때 여기서 시작한다. **지금 시점의 상태와 다음에 할 일**만 담는다. + +- 프로젝트 개요·설계 원칙 → [CLAUDE.md](CLAUDE.md) +- SBOM·CVE 게이트 메커니즘 → [doc/sbom-pipeline.md](doc/sbom-pipeline.md) + +최종 갱신: 2026-08-03 + +--- + +## 다음 작업 + +**CVE 게이트(`scripts/pipeline/cve-gate.py`)를 도입했다 — 아직 warn-only다.** `sbom.yml` +에 게이트 판정 스텝을 추가했지만 `--warn-only` 로 실행돼 실패해도 워크플로/PR 을 막지 +않는다. **45+ 개 카탈로그 차트가 이 게이트로 한 번도 트리아지된 적이 없다** — 강제 +게이트(`--warn-only` 제거)로 전환하려면 먼저 전체 카탈로그 스캔 1회로 몇 개 이미지가 +차단되는지 파악해야 한다. `gh workflow run helm-catalog-sbom --ref main`(또는 대상 +브랜치)으로 전체 스캔 후 `cve-gate.md` 아티팩트를 확인한다. + +**`doc/cve-exceptions.json` 이 비어 있다.** 첫 전체 스캔에서 차단 항목이 나오면 대응 +우선순위(상위 태그 교체 → 베이스 OS 교체 → 자체 빌드 → 예외 승인)를 검토하고, 예외는 +근거·만료일을 명시해 추가한다. + +**커버리지 자가진단(`CoverageProbe`)이 이식되지 않았다.** security-catalog 의 +`scan-sbom.sh` 는 센티널 패키지 주입 재스캔으로 "0건"과 "측정되지 않음"을 구분하지만, +dip-catalog 의 `scan-sbom.sh` 는 이 로직이 없다 — 게이트는 findings 총계 0건을 보수적으로 +차단 처리하는 구버전 경로로만 동작한다. 45+ 차트 규모에서 이 판정이 오탐을 얼마나 +내는지 실측 후 이식 여부를 결정한다. + +**자체 빌드 프레임워크(`scripts/build/`, `.github/workflows/build-image.yml`)는 도입만 +했고 실사용 이미지가 없다.** `images/` 디렉토리 자체가 아직 없다. 실제 자체 빌드가 +필요해지면(게이트가 상위 태그/베이스 OS 교체로 해소 안 되는 차단 CVE 를 찾을 때) +[.claude/image-authoring.md](.claude/image-authoring.md) 절차로 `images//` 를 +신설한다. 이때 최종 런타임 베이스 OS 정책(security-catalog 는 SUSE BCI 로 고정했으나 +dip-catalog 는 아직 미결)을 처음으로 정해야 한다. + +**`SBOM_PIPELINE_IMAGE` 재빌드가 보류돼 있다.** 이 마이그레이션으로 빌드 컨텍스트 경로가 +`doc/scripts/Dockerfile` → `scripts/pipeline/Dockerfile` 로 바뀌었다. Dockerfile 내용 +자체는 안 바뀌었으므로 기존 `docker.io/wbsong111/sbom-pipeline:latest` 는 당장 깨지지 +않지만, `paasup` 네임스페이스로 이전할지는 별도 결정이 필요하다. **재빌드 + push + +Repo Variable `SBOM_PIPELINE_IMAGE` 갱신은 git 커밋으로 되지 않는 수동 작업**이다. + +**`build-image.yml` 의 `REGISTRY_HOST` 를 `docker.io/paasup` 로 설정했다.** 실제 이미지를 +push 하려면 `DOCKERHUB_USER`/`DOCKERHUB_TOKEN` 시크릿이 `paasup` 조직 네임스페이스에 +push 권한을 가져야 한다 — **미확인**, 실제 첫 자체 빌드 시도 전에 확인 필요. + +--- + +## 미결 결정 + +### 게이트 강제력 전환 시점 +"45+ 차트 미검증" 문제 해소 후 결정한다. branch protection(필수 상태 체크)도 게이트 +강제와 짝을 이뤄야 의미가 있다 — 지금은 미설정. + +### scan-sbom.sh 커버리지 자가진단 이식 여부 +위 "다음 작업" 참고. 이식하면 dip-catalog scan-sbom.sh 와 security-catalog 버전의 +동작 차이가 사라지지만, 스캔 시간이 늘어난다(대상별 추가 재스캔). + +### SBOM_PIPELINE_IMAGE 재빌드/네임스페이스 전환 +결정 나면 `docker.io/paasup/sbom-pipeline:...` 로 빌드·push 하고 Repo Variable +`SBOM_PIPELINE_IMAGE` 를 갱신한다(수동, out-of-band). diff --git a/doc/sbom-pipeline.md b/doc/sbom-pipeline.md index 3a93350..9dfb0f0 100644 --- a/doc/sbom-pipeline.md +++ b/doc/sbom-pipeline.md @@ -24,7 +24,7 @@ manifests/helm/** ──helm template──▶ images_final.tsv ──trivy ima (CVE DB 갱신 시 재pull 없이 재스캔). → 파이프라인 비용의 대부분은 SBOM 생성(이미지 pull)이고 스캔은 초 단위. - 스캔 대상 이미지는 trivy 가 원격에서 받아 분석 후 폐기하므로 호스트/이미지 스토어에 남지 않는다. -## 스크립트 (`doc/scripts/`) +## 스크립트 (`scripts/pipeline/`) 바이너리(`helm`/`trivy`/`python3`)를 **컨테이너 내부에서 직접 호출**한다(도커 소켓·docker CLI 불필요). @@ -45,16 +45,16 @@ manifests/helm/** ──helm template──▶ images_final.tsv ──trivy ima 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 ' - bash doc/scripts/extract-helm-images.sh manifests/helm sbom-out - bash doc/scripts/generate-sbom.sh sbom-out/images_final.tsv sbom-out # 인증: DOCKER_CONFIG/TRIVY_USERNAME - bash doc/scripts/scan-sbom.sh sbom-out + 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 ' # 빠른 검증: generate 단계에 -e LIMIT=3 ``` -## 실행 이미지 (`doc/scripts/Dockerfile`) +## 실행 이미지 (`scripts/pipeline/Dockerfile`) -파이프라인은 **도구가 설치된 컨테이너 안에서** 돈다. 그 이미지는 [doc/scripts/Dockerfile](scripts/Dockerfile)로 빌드한다. +파이프라인은 **도구가 설치된 컨테이너 안에서** 돈다. 그 이미지는 [scripts/pipeline/Dockerfile](../scripts/pipeline/Dockerfile)로 빌드한다. | 항목 | 값 | |------|------| @@ -63,6 +63,9 @@ docker run --rm -v "$PWD:/repo" -w /repo \ | 아키텍처 | **linux/amd64** (GitHub 러너와 일치) | | 현재 이미지 | `docker.io/wbsong111/sbom-pipeline:latest` (public) | +> 재빌드 후 `paasup` 네임스페이스로 이전 예정 — 미완료(`MEMORY.md` 참고). Dockerfile 내용은 +> 안 바뀌었으므로 위 이미지는 이 마이그레이션으로 당장 깨지지 않는다. + > **glibc(debian) 필수**: GitHub Actions 의 `container:` 안에서 `actions/checkout`·`upload-artifact` > (node 기반)가 동작하려면 glibc 이거나 node 가 있어야 한다. `aquasec/trivy` 같은 **alpine(musl) > 이미지는 node 실행 실패** → debian 사용. **amd64 필수**(러너 아키텍처). @@ -71,8 +74,8 @@ docker run --rm -v "$PWD:/repo" -w /repo \ ```bash docker buildx build --platform linux/amd64 \ - -t docker.io//sbom-pipeline:latest \ - -f doc/scripts/Dockerfile --push doc/scripts + -t docker.io/paasup/sbom-pipeline:latest \ + -f scripts/pipeline/Dockerfile --push scripts/pipeline ``` > credsStore(Docker Desktop) 환경에서 `docker-container` 빌더로 `--push` 시 인증 실패하면, @@ -89,6 +92,7 @@ docker buildx build --platform linux/amd64 \ - **아티팩트**: 스테이징 디렉토리(`report/`)로 모아 **상대경로**로 업로드(절대경로면 v4 가 전체 경로트리를 보존하므로). - **스캔 주기 원칙**: 인벤토리·SBOM 은 차트 변경 시에만 바뀌지만 **취약점은 새 CVE 로 계속 변하므로 스케줄 전체 스캔이 필수**. PR 은 변경 차트만, 스케줄은 전체. +- **게이트**: 스캔 뒤 `scripts/pipeline/cve-gate.py` 로 판정(현재 `--warn-only`). ### 실행 @@ -98,6 +102,22 @@ gh workflow run helm-catalog-sbom --repo /dip-catalog # 전체 gh run watch --repo /dip-catalog ``` +## CVE 게이트 (`scripts/pipeline/cve-gate.py`) + +스캔이 끝나면 `cve-gate.py` 가 `trivy-reports/*.json` 을 판정한다: 고유 CVE 단위 집계, +`max(벤더 등급, NVD 등급)` 실효 등급, `doc/cve-exceptions.json` 승인 예외 처리. 상세 판정 +로직·근거는 스크립트 자체의 모듈 docstring을 우선 참고한다. + +현재 `sbom.yml` 은 `--warn-only` 로 호출한다 — 게이트가 실패해도 워크플로/PR 을 막지 +않는다. 45+ 개 카탈로그 차트가 아직 이 게이트로 트리아지된 적이 없어, 강제 전환 전에 +먼저 전체 스캔 1회로 현황을 파악해야 한다(`MEMORY.md`). + +> **커버리지 자가진단 미이식 — 알려진 제약.** security-catalog 의 `scan-sbom.sh` 는 SBOM +> 사본에 센티널 패키지를 주입해 재스캔하는 `CoverageProbe` 자가진단으로 "0건"과 +> "측정되지 않음"을 구분한다. dip-catalog 의 `scan-sbom.sh` 는 이 로직이 없다 — 게이트는 +> 항상 "프로브 이전 리포트" 경로로 판정한다(findings 총계 0건이면 보수적으로 실패, +> os-pkgs만 0건이면 경고만). 이식 여부는 미결(`MEMORY.md`). + ## GitHub 설정 (워크플로 활성화에 필요) `Settings → Secrets and variables → Actions` @@ -158,8 +178,8 @@ trivy 병렬 실행 시 공유 캐시 bolt 잠금 충돌을 피하려 워커는 1. **우선순위**: fix 가능(`--ignore-unfixed`) → KEV/EPSS(실제 악용) → 노출도(인터넷 vs 내부) → 심각도. 2. **대응 레버(모두 Git PR)**: 차트/이미지 **버전 업**(최고 레버리지) · **베이스 이미지 교체**(특히 미유지 `bitnamilegacy/*`) · **소유 이미지(`paasup/*`, `wbsong111/*`) 재빌드** · 좋은 버전 선정 후 **digest 고정**. -3. **게이트·억제**: PR 에 "새로 유입되는 fix-available CRITICAL" 차단 게이트(워크플로에 주석으로 준비됨), - 수용 CVE 는 `.trivyignore`(사유·만료일 명시). +3. **게이트·억제**: `scripts/pipeline/cve-gate.py` 가 고유 CVE·실효 등급 기준으로 판정한다(현재 + warn-only — 위 "CVE 게이트" 절 참고). 수용 CVE 는 `doc/cve-exceptions.json`(근거·만료일 명시). 4. 새 CVE 는 계속 나오므로 스케줄 스캔으로 **추세 추적**. 향후 개선(요약을 행동 가능하게): 이미지별 **fixable(패치 존재) 집계** 컬럼, `.trivyignore` 지원,