Commit Graph

453 Commits

Author SHA1 Message Date
wbsong111 f3e3b9697e Merge pull request #41 from paasup/docs/pipeline-axis-boundary
파이프라인을 두 축으로 갈라 소유 문서를 확정 + SBOM_PIPELINE_IMAGE paasup 이전 (#30)
2026-08-20 16:58:29 +09:00
wbsong111 7d36f4fddb SBOM_PIPELINE_IMAGE 를 docker.io/paasup 으로 옮긴다 (#30)
파이프라인 실행 이미지가 개인 네임스페이스(docker.io/wbsong111)를 가리키고 있었다. 카탈로그의
다른 자체 빌드 이미지는 2026-08-03 에 전부 docker.io/paasup 으로 이전됐는데 이것만 남아 있었다 —
"카탈로그가 배포하는 이미지가 아니라 CI 도구" 라 이전 대상에서 빠졌던 것이다.

  before  docker.io/wbsong111/sbom-pipeline:latest
  after   docker.io/paasup/sbom-pipeline:20260820

롤링 태그를 쓰지 않는다
---------------------
:latest 를 유지하지 않고 날짜 태그로 갔다. 이 Dockerfile 은 helm/trivy 를 **빌드 시점 최신으로
설치**하므로 같은 파일이 매번 다른 이미지를 낸다 — 태그가 "무엇으로 스캔했는지" 의 기록이어야
한다. Repo Variable 이 전체 ref 를 담으므로 고정 태그를 써도 워크플로 수정이 필요 없다.

이번 이미지가 담은 것: helm v3.21.4 · trivy 0.74.0 · python 3.13.5 · git 2.47.3

검증
----
  push        docker.io/paasup/sbom-pipeline:20260820 (linux/amd64)
  변수 갱신    gh variable set SBOM_PIPELINE_IMAGE
  CI 실제 동작 helm-catalog-sbom limit=3 실행 → 전체 success
              (Preflight 도구 확인 · 인벤토리 추출 · SBOM · 스캔 · 게이트 판정 전부 통과)

문서의 낡은 참조 4곳(doc/sbom-pipeline.md)과 Dockerfile 헤더 절차, sbom.yml 주석 예시를 고쳤다.
Dockerfile 헤더에는 왜 :latest 를 쓰지 않는지도 적었다.

Closes #30

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 16:55:55 +09:00
wbsong111 bce6255468 applicationset 미스캔은 결함이 아니라 의도 — 이유와 함께 적는다
"manifests/applicationset/** 는 어떤 워크플로도 보지 않는다" 를 사각지대로 적었는데 잘못된
평가였다. 그 아래 dip-values.yaml 은 dip-console 이 배포 values 를 만들 때 쓰는 참조 파일이고,
같은 이미지를 manifests/helm/ 차트에서 이미 스캔·게이트한다. 같은 이미지를 두 번 스캔할 이유가
없어 인벤토리 추출 대상을 manifests/helm/ 으로 한정한 것이다.

이유를 적어두지 않으면 다음 사람이 또 "미검사 결함" 으로 보고한다 — 실제로 내가 그렇게 했다.

다만 그 가정이 무엇에 의존하는지도 함께 적었다: **두 곳이 같은 이미지를 가리킨다는 것**.
참조 파일이 차트와 다른 태그를 들고 있으면 스캔한 것과 배포되는 것이 갈린다. 태그 동기화는
스캔 커버리지와 별개 문제이고 patch-catalog-tag.py 의 CHART_DIRS 범위가 그것을 결정한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 16:41:40 +09:00
wbsong111 79555215a0 파이프라인을 두 축으로 갈라 소유 문서를 확정한다
"파이프라인이 chart CVE 조치와 커스텀 이미지 빌드 2개로 나뉘어 있는가" 를 확인하다가 실제
구성이 그 모델과 다른 것이 드러났다.

  1. 워크플로는 3개다. cve-edge-post.yml 이 CLAUDE.md 에서 디렉토리 트리와 괄호 안에만
     등장해 파이프라인으로 읽히지 않았다 — "2개" 인식의 근원이다.
  2. doc/sbom-pipeline.md 가 두 축을 한 파일에 담고 있었다(자체 빌드 절 43줄).
     커스텀 이미지가 별도 레포로 분리될 예정인데 이 상태로는 분리 때 파일을 찢어야 한다.
  3. 그 문서가 이미 뒤집힌 결정을 담고 있었다 — "schedule 트리거는 없다. 블라인드 정기
     재빌드는 제거했다" 인데 PR #40 이 schedule 을 추가했다.

축을 이렇게 갈랐다
-----------------
  차트 카탈로그 축   sbom.yml · cve-edge-post.yml   →  doc/sbom-pipeline.md
  자체 빌드 축       build-image.yml                →  .claude/image-authoring.md

doc/sbom-pipeline.md — 차트 축만 남긴다
  - 상단에 "이 문서가 다루는 축" 을 두고 자체 빌드는 링크로 넘긴다
  - 자체 빌드 절(43줄)을 image-authoring.md 로 이관
  - sbom.yml ↔ cve-edge-post.yml 비교 표 신설. **판정기가 두 벌**이라는 사실을 명시했다 —
    cve-edge-post.yml 은 집계를 워크플로 YAML 안의 인라인 python 으로 갖고 있어 승인 예외도
    실효 등급도 적용하지 않는다. 같은 스캔 데이터에서 다른 숫자가 나올 수 있다
  - PR 을 실제로 막는 게이트는 images/** PR 뿐이고 manifests/applicationset/** 는 아무
    워크플로도 보지 않는다는 사각지대를 적었다

.claude/image-authoring.md — 자체 빌드 축의 단일 출처가 된다
  - 이관받은 워크플로 서술 + "이 워크플로의 게이트는 강제다"(차트 축 warn-only 와 다르다는
    사실이 지금까지 한 곳에만 있었다)
  - schedule 결정 정정 — 지금 것은 블라인드가 아니라 CVE 트리거다. 수정 버전이 있는 차단
    CVE 가 있을 때만 빌드하고 없으면 아무것도 하지 않는다. 거부된 것과 조건이 다르다
  - "레포 분리 후 무엇이 끊기는가" 결합점 7개 표. 3번(탐지가 카탈로그를 읽는다)이 가장 크고,
    게이트 공유는 workflow_call 이 아니라 composite action 이어야 한다는 것도 적었다
    (workflow_call 은 별도 job 이라 $OUT_DIR 를 공유하지 못한다)
  - 파일 상단에 "레포 분리 시 images/·scripts/build/·build-image.yml 과 함께 이동한다"
  - #35 에서 실측한 매핑 함정 추가 — CHART_DIRS 에 없는 파일은 patch-catalog-tag.py 가
    검사조차 하지 않아 cnpg-cluster/1.1.0 이 조용히 빠졌다

CLAUDE.md — 지도만 남긴다
  두 축 비교 표(질문·워크플로·게이트 강도·소유 문서)로 바꾸고 메커니즘 서술을 걷어냈다.
  cve-edge-post.yml 을 파이프라인으로 처음 등재했다.

검증
----
  표의 사실 대조   각 워크플로의 cve-gate 호출·warn-only·활성 schedule 을 파일에서 확인
  축 분리          sbom-pipeline.md 에 남은 build-image.yml 언급은 전부 링크·대조·시크릿
                   공유 문장(의도된 것)
  뒤집힌 결정      "schedule 트리거는 없다" 잔존 0건
  링크             3개 문서의 로컬 링크 전부 실재

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 16:26:14 +09:00
wbsong111 981d36daa4 차단 CVE 가 나오면 자체 빌드 이미지를 스스로 재빌드한다 + Dockerfile 작성 규칙 (#40)
* 차단 CVE 가 나오면 자체 빌드 이미지를 스스로 재빌드한다 + Dockerfile 작성 규칙

두 가지다. (1) 이전 이미지에서 CVE 가 발견되면 재빌드가 자동으로 일어나게 한다.
(2) Dockerfile 을 OS·언어 축으로 일관되게 쓰도록 규칙을 정한다.

(1) 재빌드 자동화 — 트리거를 로컬 실측으로 고쳤다
------------------------------------------------
처음에는 트리거를 "핀이 뒤처졌는가" 로 잡았다. **배포 중인 이미지 8개를 실제로 스캔해 보니
그게 틀렸다** — 차단 CVE 가 있는 4개 중 핀 변경이 필요한 것은 하나도 없었고, 그 판정은
"0건" 을 냈다. 재빌드로 고쳐지는 경우가 셋이기 때문이다.

  핀이 뒤처졌다                          → 핀 올려서 빌드
  핀은 맞는데 그 핀으로 아직 안 빌드됐다  → 그냥 빌드   ← 핀만 고친 PR 직후 상태
  베이스 OS 패키지가 뒤처졌다             → 그냥 빌드   ← 재빌드하면 zypper 가 최신을 깐다

그래서 트리거는 **"수정 버전이 있는 차단 CVE 가 있는가"** 다. 핀 변경은 필요하면 함께 하는
부수 작업이고 트리거가 아니다. 수정 버전 없는 차단(affected·will_not_fix)은 재빌드해도
그대로이므로 no-fix 로 따로 보고해 사람이 다른 레버를 판단하게 한다.

scripts/build/check-rebuild-needed.py 신규
  카탈로그가 실제로 가리키는 이미지를 기준으로 잰다 — 재빌드해 보지 않고도 "지금 배포 중인
  것이 규정을 벗어났는가" 를 답한다.
    catalog.env → 카탈로그 values 의 ref → 그 ref 의 스캔 리포트 ↕ build.env 의 핀
  판정 규칙은 게이트와 같은 출처를 쓴다(max(벤더,NVD)·승인 예외·만료 예외 무효) —
  cve-gate.py 의 함수를 그대로 가져다 쓴다. 핀 산출은 suggest-go-upgrades.py 재사용.
  --list-refs 로 "무엇을 스캔해야 하나" 도 스크립트가 낸다 — ref 해석 규칙이 워크플로로
  새면 두 곳이 어긋난다.

suggest-go-upgrades.py 는 산출 로직이 main() 안에 있어 재사용할 수 없었다. collect_modules/
collect_stdlib/builder_candidates/effective_severity 로 빼고 main() 은 호출해 출력만 한다 —
CLI 출력은 리팩터 전후 동일하다(실측 대조).

build-image.yml 에 schedule(월요일 02:00 UTC)과 mode=drift 를 추가했다. 판정 → (핀이
뒤처졌으면) 브랜치 push → 빌드·verify.sh·게이트까지 자동이고 **레지스트리 push 와 카탈로그
반영은 하지 않는다.** 게시 여부 로직에서 catch-all 을 true → false 로 바꿨다 — 원래는
트리거를 추가하면 자동으로 push 가 켜지는 구조였고 schedule 을 넣는 순간 사고가 났다.
이제 push 는 workflow_dispatch + mode=image 에서만 켜진다.

(2) Dockerfile 작성 규칙 — 실제 8개에서 추출했다
----------------------------------------------
image-authoring.md 가 규칙 본문의 단일 출처이고 skill 은 "어느 절을 읽어야 하는가" 선택표만
갖는다(CLAUDE.md 의 "Skill 은 절차 본문을 복제하지 않는다").

두 축은 독립이다 — 같은 bci-micro 최종 위에 Go 빌더도 Node 빌더도 온다.

  축 1 최종 베이스   bci-base / bci-micro / scratch+micro rootfs — 셋뿐이다.
                     네 번째를 만들기 전에 왜 셋으로 안 되는지 먼저 적는다.
                     micro·scratch 는 nonroot 계정을 직접 만들어야 한다(실측 형태 첨부).
  축 2 빌더          Go(BUILDPLATFORM/TARGETARCH, ldflags 에 SOURCE_COMMIT) /
                     Node(런타임은 OS 패키지) / JVM(재컴파일 없이 jar 만 OLD/NEW 쌍으로
                     교체 — OLD 를 받는 이유는 못 찾으면 실패시키기 위함) /
                     C·Lua(정적 링크 금지 — SLE_BCI 에 static glibc 없음)

"업스트림 런타임 계약은 보존한다" 절을 새로 넣었다(USER·ENTRYPOINT·파일 레이아웃).
cloudnative-pg 가 멀티아치 심볼릭 링크를 부수 장치로 오판해 지웠다가 리컨실이 실패한 것이
근거다. 스캐너는 이걸 전혀 보지 못한다.

로컬 검증 — 판정만이 아니라 빌드까지 돌렸다
-----------------------------------------
판정이 지목한 4건을 실제로 빌드해 전제를 확인했다. 문서에 단정만 해두고 넘어가지 않았다.

  이미지                     배포 중   재빌드 후                      핀 변경
  apisix-ingress-controller  차단 8    0/0 PASS · VERIFY-OK · cov=ok  없음
  cloudnative-pg             차단 8    0/0 PASS · VERIFY-OK · cov=ok  없음
  etcd                       차단 8    0/0 PASS · VERIFY-OK · cov=ok  없음
  cnpg-postgresql            차단 2    0/0 PASS · VERIFY-OK · cov=ok  없음

  - cnpg-postgresql 이 "베이스 OS 패키지 갱신으로 해소" 사례다(perl·rpm-ndb). 핀을 하나도
    안 바꾸고 재빌드만으로 0건이 됐다 — 검증 없이 단정했던 부분이라 이게 핵심 확인이다.
  - 빌드된 etcd 바이너리가 go1.26.6 + pinned commit 을 보고한다 — 핀이 실제로 반영됐다.
  - 4건 다 CoverageProbe=ok 이므로 0건이 "측정되지 않음" 이 아니다.
  - 빌드 후에도 git status images/ 가 깨끗하다 = 트리거를 핀 기준으로 뒀다면 4건 전부
    놓쳤을 것이라는 확인.

그 외: 예외 적용(keycloak CVE-2025-59250 → 0건), --fail-on-drift rc=1, 워크플로 셸 재현
(GITHUB_OUTPUT 4개 JSON·fromJson OK·핀 변경 0 → 브랜치 미생성 → 기본 ref 로 빌드),
push 정책 전 조합 확인.

CI 에서 미검증인 것은 환경 특성뿐이다 — trivy 설치 스텝, GITHUB_STEP_SUMMARY 출력,
matrix 팬아웃, contents:write 로 브랜치 push. 빌드 자체는 로컬과 같은 스크립트를 쓴다.

Refs #35

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* self-build-image SKILL 의 자동 재빌드 절을 실행 계약만 남긴다

같은 커밋에서 image-authoring.md 와 SKILL 양쪽에 자동 재빌드 절차를 거의 동일하게 썼다.
SKILL 이 "작성 규칙 본문은 image-authoring.md 가 단일 출처다 — 여기 복제하지 않는다" 고
두 번 선언한 파일에서 내가 그 규칙을 어겼다.

SKILL 에는 실행 계약(수동 실행 명령 · 로컬 판정 명령 · push 안 한다는 사실)만 남기고
트리거 조건과 그 근거는 image-authoring.md 를 가리킨다.

frontmatter 도 함께 고친다 — "7종" 에 argocd 가 빠져 있어 실제 images/ 8개와 어긋났다.
description 은 Skill 로딩 판단에 쓰이는 텍스트이므로 "목록은 images/ 가 단일 출처" 라는
회피 조항으로 덮을 수 없다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* 레포 분리 시 갈라지는 이음선을 코드와 문서에 명시한다

커스텀 이미지가 별도 레포로 분리될 예정인데, check-rebuild-needed.py 는 카탈로그(무엇을
배포 중인가)와 build.env(어떻게 만드는가)를 **양쪽 다** 읽는다. 분리 시 이 파일이 갈라진다.

절단면은 이미 함수 경계에 있다. 6개월 뒤에 "이 함수가 왜 카탈로그를 읽지" 를 다시 파지
않도록 어느 함수가 어느 쪽인지 docstring 에 적었다.

  A (카탈로그 레포)  resolve_current_ref + blocking_cves  → "차단됐는가"
  B (이미지 레포)    pin_changes + apply_changes          → "핀을 올려야 하는가"
  check_image        둘을 엮는 오케스트레이션 — 여기가 갈라진다

image-authoring.md 의 드리프트 절에도 같은 사실을 한 문단으로 적어 분리 작업 때 이 주석을
먼저 읽게 했다. 결합점 전체 목록은 경계 문서화 작업에서 표로 정리한다.

코드 동작은 바뀌지 않았다 — 주석과 문서만 추가했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 16:19:26 +09:00
wbsong111 bd8615f82f Merge pull request #39 from paasup/chore/self-build-tags-20260820
자체 빌드 이미지 4종 태그 갱신 — 차단 CVE 26건 해소 (#35)
2026-08-20 15:38:28 +09:00
wbsong111 a9290c9650 cnpg-cluster 1.1.0 이 태그 갱신에서 빠져 있었다 — catalog.env 매핑을 고친다
위 4개 커밋은 build-image.yml 이 자동 생성한 것이고, cnpg-postgresql 은 1.0.0 만 갱신됐다.
1.1.0 도 같은 이미지를 가리키는데 낡은 태그(20260803)로 남아 있었다.

원인은 매핑이다. images/cnpg-postgresql/catalog.env 의 CHART_DIRS 가 1.0.0 하나만 선언해
워크플로가 1.1.0 을 볼 방법이 없었다. 카탈로그는 여러 버전을 동시에 보관하는 "버전 보관소"
이므로 이미지 하나가 여러 버전 디렉토리에 걸리는 것이 정상이다 — 매핑이 그것을 표현해야 한다.

  CHART_DIRS="manifests/helm/cnpg-cluster/1.0.0"
  → CHART_DIRS="manifests/helm/cnpg-cluster/1.0.0 manifests/helm/cnpg-cluster/1.1.0"

다른 세 이미지는 전수 확인 결과 CHART_DIRS 가 참조를 전부 덮는다(etcd 1.1.12 ·
cloudnative-pg 0.29.0 · apisix 2.16.0). cnpg-postgresql 만 누락이었다.

이 누락은 조용히 지나간다는 점이 문제다 — patch-catalog-tag.py 는 "예상 패턴을 못 찾으면
실패" 하지만, 애초에 대상 목록에 없는 파일은 검사하지 않는다. 매핑이 불완전하면 게이트만
계속 그 이미지를 차단으로 잡고 이유를 알기 어렵다.

Refs #35

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 15:12:43 +09:00
github-actions[bot] 7d43d0915c apisix-ingress-controller 이미지 태그 갱신: 2.1.0-security-hardened-20260811 → 2.1.0-security-hardened-20260820
게이트 PASS(build-image.yml, workflow_dispatch 트리거)로 확인된 태그로 교체한다.

Co-Authored-By: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-20 15:09:53 +09:00
github-actions[bot] 96dd16a59b cloudnative-pg 이미지 태그 갱신: 1.30.0-security-hardened-20260804 → 1.30.0-security-hardened-20260820
게이트 PASS(build-image.yml, workflow_dispatch 트리거)로 확인된 태그로 교체한다.

Co-Authored-By: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-20 15:09:53 +09:00
github-actions[bot] 66e42c669a etcd 이미지 태그 갱신: 3.7.1-security-hardened-20260804 → 3.7.1-security-hardened-20260820
게이트 PASS(build-image.yml, workflow_dispatch 트리거)로 확인된 태그로 교체한다.

Co-Authored-By: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-20 15:09:53 +09:00
github-actions[bot] 64f049a00a cnpg-postgresql 이미지 태그 갱신: 18.4-bci15.7-hardened-20260803 → 18.4-bci15.7-hardened-20260820
게이트 PASS(build-image.yml, workflow_dispatch 트리거)로 확인된 태그로 교체한다.

Co-Authored-By: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-20 15:09:41 +09:00
wbsong111 a9720af69b 자체 빌드 Go 이미지 3종의 툴체인을 1.26.6 으로 올린다 (#35) (#36)
etcd·cloudnative-pg·apisix-ingress-controller 가 GO_BUILDER_TAG=1.26.5-trixie 에 핀돼
있어 새로 공개된 stdlib 차단 CVE 8건에 걸린다. 전부 Go 1.26.6 에서 고쳐졌다.

소스는 하나도 안 바꿨다 — CVE 데이터가 갱신됐을 뿐이다. 자체 빌드 이미지는 이렇게
가만히 있어도 게이트를 벗어난다는 것이 이번 사례의 요지다.

발견 경위
--------
PR #34(문서 전용)가 images/** 를 건드려 검증 빌드가 돌면서 드러났다. 오늘 날짜로
재빌드하니 etcd·cloudnative-pg 가 각각 실효 0/8 로 FAIL 했고, 기능 검증(verify.sh)은
둘 다 VERIFY-OK 였다 — 게이트만 실패했다. 대조군으로 어제 1.26.6 으로 올린 argocd 는
같은 실행에서 PASS 했다.

값은 손으로 고르지 않았다
------------------------
  python3 scripts/build/suggest-go-upgrades.py --reports <trivy-reports>
  → GO_BUILDER_TAG=1.26.6-trixie / 모듈 업그레이드 제안 없음(stdlib 전용)

FixedVersion 의 `1.25.13, 1.26.6, 1.27.0-rc.3` 은 브랜치별 대안이라 최대값을 고르면
프리릴리스를 정식 버전으로 오독한다. 스크립트가 그 규칙을 처리한다.

apisix-ingress-controller 는 이번 검증 빌드 대상이 아니었다
--------------------------------------------------------
PR #34 가 이 이미지 파일을 건드리지 않아 매트릭스에서 빠졌을 뿐, 같은 툴체인 핀이다.
trivy 는 바이너리에 박힌 Go 버전으로 stdlib 을 판정하므로 1.26.5 로 빌드한 Go 바이너리는
예외 없이 해당된다. 이 PR 의 검증 빌드가 셋 다 돌려 확인한다.

각 build.env 주석에 "이 값은 go.mod 최소 요구가 아니라 stdlib 차단 CVE 를 해소하는
지점으로 정한다" 를 적어뒀다 — 다음 사람이 go.mod 만 보고 되돌리지 않게 하기 위함이다.

Refs #35

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 11:37:25 +09:00
wbsong111 f23efcfea2 Merge pull request #28 from paasup/chore/cve-gate-sort-and-drop-argocd-7.7.0
argo-cd CVE 조치: 7.7.0 제거 · dex 비활성 · 자체 빌드 이미지 (차단 574 → 239, 10.4.0 은 0건)
2026-08-20 11:04:33 +09:00
wbsong111 1a747f61a8 자체 빌드 이미지 문서가 이 레포에 없는 경로를 인용하던 것을 없앤다 (#33) (#34)
images/·manifests/helm/·.claude/ 의 20개 파일이 doc/decisions·doc/analysis 등 **이 레포에
존재한 적 없는 경로 15종을 48곳에서** 인용하고 있었다. security-catalog 에서 포팅할 때
따라온 것인데, 그 레포는 개인 레포(github.com/wbsong111/security-catalog)라 팀 구성원은
접근조차 못 한다 — "security-catalog 에 있으나 이관되지 않았다" 는 안내가 아무 역할을
하지 못했다.

원문을 통째로 복사하지 않았다
----------------------------
원본 문서들이 서로를 근거로 인용한다. decisions/0001 하나만 봐도 analysis/cnpg-image-baseline.md
· analysis/vendor-unassessed-data-sources.md 처럼 **인용 목록에 없던 또 다른 미이관 문서**를
가리킨다. 복사는 문제를 옮기는 것이지 없애는 게 아니다.

그리고 대부분은 애초에 dip-catalog 가 더 나은 것을 갖고 있다. 7곳에서 인용되던
analysis/sles-oval-measurement.md 는 원문 스스로 "이 문서는 결정하지 않는다. 재측정하면
갱신된다" 고 밝히는 스냅샷인데, dip-catalog 는 같은 측정을 CoverageProbe 로 매 스캔마다
자동으로 한다. 문서를 복사하는 것보다 게이트를 가리키는 것이 정확하다.

그래서 성격별로 나눴다
---------------------
  재측정으로 복원 안 되는 것  →  doc/decisions/ 에 자립적 ADR 로 다시 씀 (4건)
  이미 단일 출처가 있는 것    →  그쪽으로 인용 교체 (11종 경로)

ADR 4건은 security-catalog 0001·0005·0006·0007 이 원본이고, 결론과 근거만 추려
dip-catalog 맥락으로 새로 썼다 — **레포 밖을 가리키는 링크가 0이다.** 번호는 이 레포에서
0001~0004 로 다시 붙였고 원본 대응은 각 문서와 README 에 적었다. 왜 안 가져온 것은 안
가져왔는지도 README 표에 남겼다.

인용 교체는 카테고리별로:
  analysis/*-cve.md, cnpg-image-vuln-comparison.md  →  해당 ADR · images/<image>/README.md
  analysis/sles-oval-measurement.md                 →  게이트 CoverageProbe (doc/sbom-pipeline.md)
  cve-zero-pipeline.md, architecture/build-pipeline.md → doc/sbom-pipeline.md
  image-selection.md                                →  .claude/image-authoring.md
  charts/*/deploy-test.md                           →  scripts/deploy-test/*.sh + 절차 문서

찾은 오류 2건
-------------
- images/cloudnative-pg/source.build.env 가 인용한 decisions/0004-cloudnative-pg-operator-self-build.md
  는 **번호 오기**다. 원본 0004 는 postgresql-chart-selection 이고 이 결정은 0005 다.
- cnpg-cluster values.yaml·templates/database.yaml 이 인용한 doc/deploy-test-cnpg.md 는
  **원본 레포에도 없다.** CREATE EXTENSION 함정 설명은 주석 자체에 이미 있어 인용만 뺐다.

검증
----
  우리 파일의 깨진 doc/ 인용        0건 (전수 스캔)
  새 문서·수정 문서의 로컬 링크     전부 실재 확인
  helm template                     cnpg-cluster · etcd · cloudnative-pg 정상 렌더

남은 doc/health-checking.md(144곳)·doc/integration/*(2곳)은 업스트림 CRD·차트 안의 문자열로
우리가 쓴 인용이 아니다 — 건드리지 않았다.

Closes #33

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 10:20:40 +09:00
wbsong111 63d25221df MEMORY.md 를 상태 파일로 되돌린다 (264줄 → 64줄)
MEMORY.md 는 3번째 줄에서 "지금 시점의 상태와 다음에 할 일만 담는다" 고 스스로 선언하는데
실제로는 완료 기록이 쌓여 264줄이 됐고, 이슈 참조가 한 건도 없었다. 그 결과:

- SBOM_PIPELINE_IMAGE 재빌드가 파일 안에서 3번 반복된다 (215·234·262줄)
- 게이트 강제력 전환이 2번 반복되고, 그 내용은 이미 이슈 #7 체크리스트와 #19 에 있다
- argo-cd 절 55줄은 커밋 33e91c0 메시지의 요약본이다
- 베이스 OS 정책·"벤더 릴리스 노트 믿지 말 것" 은 본문이 스스로 "이미 image-authoring.md /
  skill 에 있다" 고 밝히면서 중복 서술한다

같은 사실이 여러 곳에 있으면 나중에 어느 쪽이 맞는지가 새 문제가 된다.

성격별로 목적지를 정하고 내보냈다
---------------------------------
  추적이 필요한 미결   →  GitHub 이슈 (#29~#33 발행, MEMORY.md 엔 한 줄 링크)
  재발 방지 교훈       →  .claude/ 문서·skill
  단순 완료 기록       →  삭제 (커밋·PR·images/<image>/README.md 가 권위 있는 기록)

교훈 이관 — 아직 어디에도 없던 것만 옮겼다:

- image-authoring.md: 베이스 OS 를 CVE 수치만으로 고르면 배포에서 죽는다.
  cp --update=none(coreutils 9.3+) 실측과, 차트가 실행하는 명령을 verify.sh 에서
  재현하라는 규칙. 원칙 2 의 "BCI 버전은 실측해서 정한다" 바로 뒤에 붙였다.
- self-build-image skill: 같은 날 재빌드하면 태그가 겹쳐 노드 캐시가 반영을 막는다.
  근본 해결은 #31, 여기엔 우회법만.
- cve-remediation skill: 레버 표를 보기 전에 "이 컴포넌트가 필요하긴 한가" 를 먼저 묻는다.
  dex 가 한 줄로 차단 57건(58%)이었다. 가장 값싼 레버인데 표에 없어서 새 단계로 넣었다.

유지 규칙을 명시했다
--------------------
트리거는 시간이 아니라 상태다 — "1달 지났으니 정리" 는 아무도 시작하지 않고, 날짜로 자르면
오래됐지만 유효한 미결까지 잘린다. 항목이 "다음에 할 일" 이 아니게 되는 순간 내보낸다.
다만 놓친 것을 걷어내기 위해 월 1회 점검하고 파일 상단의 점검 날짜를 갱신한다.

규칙은 MEMORY.md 자체와 CLAUDE.md("작업 기록을 어디에 남기는가") 양쪽에 뒀다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 16:56:05 +09:00
wbsong111 bfd87644c1 MEMORY.md: 사실과 어긋난 두 항목 정정
argo-cd 절의 수치·상태는 전부 재확인해 그대로 뒀다(게이트 574→337→280→239, 10.4.0 0건,
BCI 16.0 빌드 PASS/CoverageProbe ok, 7.7.0 삭제·7.8.11 잔존).

- airflow 비결정적 렌더: 재렌더 조건을 "ArgoCD 재시작·hard refresh" 로 적어뒀는데, 이후
  실측에서 **일반 refresh 로도** 새 ReplicaSet 이 생겼다. 조건을 정정하고, 정해진 조치
  방향(dip-values placeholder + dip-console-api DeployPreInstall 치환)과 아직 커밋 전이라는
  것, 기존 테넌트는 재배포해야 적용된다는 것을 함께 적는다.

- define-chart-resources: "신규 차트 3종의 리소스 프로파일이 없다 / 규칙을 못 지켰다" 는
  사실이 아니다. 세 차트 모두 dip-resources-quotas.yaml 에 small/medium/large 를 갖고 있고
  차트 도입 커밋(6878b61)에 함께 들어갔다. 남은 건 문서에 전용 절이 없다는 것뿐이라
  그렇게 고쳐 적는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 16:00:10 +09:00
wbsong111 f1d10a1d09 cve-remediation skill 을 레포에 추가 (문서만 가리키고 파일이 없었다)
2026-08-11 apisix 작업에서 만들어 실제로 적용한 skill 인데 커밋되지 않은 채 로컬에만
있었다. MEMORY.md 와 CLAUDE.md 가 .claude/skills/cve-remediation/SKILL.md 를 가리키므로
레포만 보는 사람에게는 깨진 링크였다.

CLAUDE.md 의 skill 표에도 같은 이유로 빠져 있던 줄을 넣는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 16:00:10 +09:00
wbsong111 33e91c0536 argocd: 자체 빌드 하드닝 이미지 추가, 게이트 차단 0건
업스트림 quay.io/argoproj/argocd 의 차단 CVE 는 대부분 OS 패키지가 아니라 바이너리에
정적 링크된 Go 모듈이라, 상위 태그 교체(이미 최신)로도 베이스 OS 교체로도 잡히지 않는다.

부분 조치는 효과가 없었다 — 같은 stdlib·x/crypto 취약점이 다섯 바이너리(argocd·helm·
kustomize·git-lfs·pebble)에 각각 들어있어 본체만 고치면 거의 줄지 않는다. 업스트림
Dockerfile 이 helm/kustomize/git-lfs 를 hack/install.sh 로 **미리 빌드된 릴리스 바이너리**
로 내려받기 때문에 그 바이너리의 Go 버전을 우리가 통제할 수도 없다. 그래서 넷 다 소스에서
다시 컴파일한다.

- images/argocd/ 신규 (Go 4개 + C 2개 + Node UI — 기존 자체 빌드 중 가장 크다)
  - 최종 베이스 ubuntu → SUSE BCI(원칙 2). ubuntu 가 딸려오던 pebble 도 함께 사라진다
  - tini·connect-proxy 는 SLE_BCI 에 없어 소스 빌드 — 기능을 빼지 않기 위해
  - UI 는 업스트림과 동일하게 node 로 빌드(Go embed 라 생략 불가)

- manifests/helm/argo-cd/10.4.0/custom-values.yaml 이 이 이미지를 가리킨다
  global.image 하나로 argocd 바이너리를 쓰는 5개 컴포넌트가 공유한다

재사용 설계 — Dockerfile 에 버전을 박지 않는다
---------------------------------------------
이 이미지는 앞으로도 CVE 조치를 반복해서 받는다. 모듈 목록·패키지 목록을 전부 값으로 빼서
다음 조치에서 바뀌는 것이 source.build.env 한 곳뿐이게 했다. 이전 방식이라면 모듈 하나
추가에 build.env·ARG 선언·go get 목록·BUILD_ARGS 네 곳을 고쳐야 했다.

- scripts/build/suggest-go-upgrades.py 신규 — 스캔 리포트의 FixedVersion 에서
  GO_MODULE_UPGRADES / GO_BUILDER_TAG 를 산출한다. 사람이 CVE 를 훑어 최대값을 고르지
  않는다. 빌드 시점에 최신을 당기는 방식(go get -u)은 재현성을 버리므로 택하지 않았다 —
  값은 제안만 하고 채택은 사람이 커밋한다.
- images/argocd/go-mod-upgrade.sh 신규 — 목록 하나를 네 프로젝트에 재사용한다.
  go get 은 의존성에 없는 모듈도 go.mod 에 추가하므로 그래프에 있는 것만 골라 적용한다.

실측 함정 — verify.sh 에 반영했다
--------------------------------
차트의 repo-server init 컨테이너가 `cp --update=none` 을 쓴다. 이 형식은 GNU coreutils
9.3+ 에서만 되는데, BCI 15.7 로 빌드한 이미지가 게이트도 verify.sh 도 통과하고 **배포
시점에** Init:CrashLoopBackOff 로 죽었다. BCI 16.0(coreutils 9.6)으로 올려 해소했고,
verify.sh 가 이 명령과 copyutil 흐름을 직접 재현하도록 해서 다음엔 빌드 단계에서 걸린다.
16.0 의 패키지 가용성과 trivy 커버리지(CoverageProbe=ok, EOSL 아님)도 확인했다.

검증
----
  게이트     PASS — 실효 CRITICAL/HIGH 0건, CoverageProbe ok
  기능       VERIFY-OK (심볼릭 링크 9개 · 번들 도구 3종 · LFS 필터 · copyutil 흐름)
  배포       docker.io/paasup 에 push 후 운영 argocd 교체 — 파드 8종 Running,
             admin 로그인, Application 2건 Synced/Healthy, repo-server 렌더링 확인
             (서버가 go1.26.6 · helm v4.2.4 로 보고 — 우리 빌드가 맞다)

카탈로그 차단은 574 → 239 건이 됐고(7.7.0 삭제 · dex 비활성 · 이 커밋), 남은 239 건은
전부 동결된 7.8.11 몫이다. 운영에 쓰는 10.4.0 은 0 건이다. 경과·미결은 MEMORY.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 15:04:05 +09:00
wbsong111 7957c6a9eb argo-cd: 쓰이지 않는 dex 를 끄고 차단 CVE 57 건 제거
CVE 게이트 실측에서 ghcr.io/dexidp/dex:v2.45.1 이 혼자 차단 57 건을 만들고 있었다.
argo-cd 10.4.0 전체 98 건의 58% 다. 그런데 이 dex 는 인증 경로에 전혀 관여하지
않는 상태였다.

  차트 기본값        dex.enabled = true
  운영 argocd-cm     oidc.config 있음(Keycloak 직접) / dex.config 없음
  dipup tpl          항상 configs.cm.oidc.config — dex 참조 0건

ArgoCD 는 oidc.config 가 있으면 dex 를 우회하고 dex.config 를 쓸 때만 dex 가
관여한다. 즉 쓰이지도 않는 파드가 떠서 차단 CVE 의 절반 이상을 만들고 있었다.

대응 레버를 순서대로 검토한 결과 이 방법이 먼저였다.

  태그 교체     소진 — dex v2.45.1(2026-03-03) · argocd v3.5.1(2026-08-12) 모두 최신.
                번들 도구도 git-lfs 3.7.1 · kustomize 5.8.1 로 이미 최신이고,
                낡은 Go 는 각 업스트림 프로젝트의 선택이라 버전으로 해결되지 않는다.
  베이스 OS     무의미 — 차단 98 건 중 9 건(9%)만 OS 패키지다. 89 건이 Go 모듈이라
                바이너리에 컴파일돼 들어가 있어 베이스를 바꿔도 남는다.
  미사용 제거   ← 이것. 한 줄로 57 건(58%).
  자체 빌드     남은 41 건 대상. 별건으로 검토한다.

검증:
  게이트 재실행   차단 337 → 280 건 (10.4.0 몫 98 → 41)
  배포 테스트     PASS — 워크로드 7종 → 6종, dex-server 사라짐, /api/version v3.5.1

dex 기반 SSO 로 전환하려면 dex.enabled 를 true 로 되돌리고 configs.cm.dex.config 를
채운다. 그때 dex 이미지의 CVE 가 다시 잡히므로 대응 계획이 함께 필요하다 —
CUSTOM-README 에 근거와 함께 적어뒀다.

7.8.11(구버전)의 dex 는 건드리지 않았다. released 된 버전이라 동결한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 12:51:58 +09:00
wbsong111 5120120ed9 cve-gate: 요약표의 등급 열을 실효 C/H 하나로 정리
벤더 C/H · NVD C/H · 실효 C/H 세 열이 나란히 놓여 어느 것이 판정값인지 헷갈렸다.
실제로 "벤더 5 / NVD 11 인데 실효가 15" 를 어떻게 읽어야 하는지 질문이 나왔다 —
세 열이 같은 CVE 집합을 세지 않기 때문이다. 실효는 CVE 마다 max(벤더, NVD) 를
적용한 결과라 합집합이 된다(5 + 11 − 교집합 1 = 15). 가로로 더하거나 빼서
검산되지 않는 숫자들을 나란히 두는 것이 혼란의 원인이었다.

판정 기준은 실효 C/H 하나뿐이므로 그것만 남기고, 벤더·NVD 의 불일치는
`하향` 열(벤더가 NVD 보다 낮게 매긴 CVE 수)로 대신 드러낸다. 개별 CVE 목록은
이미 `⚠️ 벤더 하향 등급` 절에 있어 집계 열은 중복이었다.

  이전: | 벤더 C/H | NVD C/H | 실효 C/H | 차단 | 예외 |
  이후: | 실효 C/H | 차단 | 예외 | 하향 |

- 범례(SUMMARY_LEGEND) 를 상수로 빼서 render_md 와 render_brief_md 양쪽에 붙였다.
  Job Summary(render_brief_md)에는 범례가 아예 없어서, 새 `하향` 열이 설명 없이
  노출될 뻔했다.

하향 신호를 열로 남긴 이유 — 실측:

  argocd:v2.14.5 (ubuntu 24.04)  하향 43건
  argocd:v3.5.1  (ubuntu 26.04)  하향  1건
  dex:v2.45.1    (alpine)        하향  4건  ← 전부 SeveritySource=ghsa (Go 모듈)
  redis 2개      (alpine)        하향  0건  ← Alpine 은 자체 등급을 제공하지 않는다

os-pkgs 의 등급 출처를 세어보면 ubuntu 332건은 SeveritySource=ubuntu 인데
alpine 150건은 nvd 이거나 출처 없음이다. 즉 Alpine 이미지에서 "벤더" 열은
NVD 를 그대로 되비추는 값이라 비교 자체가 성립하지 않았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 12:38:43 +09:00
wbsong111 f10d50e400 argo-cd 7.7.0 제거 + CVE 게이트 요약표를 버전 최신순으로 정렬
카탈로그가 한 차트의 여러 버전을 들고 있어서, 게이트 요약표에 버전 정렬이 없으면
최신 버전과 구버전이 위험도 순으로 섞여 "지금 쓸 버전이 어느 정도인가" 를 한눈에
볼 수 없었다. 실측 사례 — argo-cd 10.4.0 이 구버전 7.8.11 사이에 끼어 표시됐다.

  변경 전 (chart asc → 실효 C/H desc)
    argo-cd@7.8.11  argocd:v2.14.5   14/101
    argo-cd@7.8.11  redis:7.4.2       5/60
    argo-cd@10.4.0  dex:v2.45.1       5/52     ← 최신이 구버전 사이에
    argo-cd@7.8.11  dex:v2.42.0       4/55
    argo-cd@10.4.0  argocd:v3.5.1     1/40

  변경 후 (chart asc → 버전 desc → 실효 C/H desc)
    argo-cd@10.4.0  dex:v2.45.1       5/52
    argo-cd@10.4.0  argocd:v3.5.1     1/40
    argo-cd@10.4.0  redis:8.6.4       0/0
    argo-cd@7.8.11  argocd:v2.14.5   14/101
    argo-cd@7.8.11  redis:7.4.2       5/60
    argo-cd@7.8.11  dex:v2.42.0       4/55

- scripts/pipeline/cve-gate.py
  - version_sort_key() 추가. 문자열 비교로는 "10.4.0" < "7.8.11" 이 되어 최신이
    아래로 내려간다. 숫자 구간을 정수로 파싱한다.
    프리릴리스는 코어와 분리해 다룬다 — 단순히 구간을 이어붙이면 리스트가 긴 쪽이
    커져 "1.2.0-rc1" 이 "1.2.0" 보다 최신으로 정렬된다(단위 테스트로 실측).
    semver 규칙대로 프리릴리스 없는 정식 릴리스를 더 크게 본다.
  - 정렬은 안정 정렬을 3번 겹쳐 만든다. 버전만 내림차순이라 reverse=True 가
    필요해 한 번에 튜플 하나로 묶을 수 없다.

- manifests/helm/argo-cd/7.7.0 제거
  게이트 차단 574건 중 237건이 이 버전의 이미지 3개였다
  (argocd:v2.13.0 117 / dex:v2.41.1 61 / redis:7.2.4-alpine 59 — 뒤 둘은 EOSL).
  제거 후 차단 337건.
  doc/catalog-stack-classification.md 의 버전 목록도 갱신했다(10.4.0 이 빠져 있어
  이미 stale 이었다). doc/chart-restructure-plan.md 는 과거 이관 기록이라 두었다.

로컬 파이프라인으로 검증했다(extract → generate → scan → gate, argo-cd 범위).
이미지 6개 / SBOM 6개 성공 / 커버리지 전부 ok.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:51:46 +09:00
wbsong111 ae682674eb Merge pull request #27 from paasup/feat/argo-cd-10.4.0
argo-cd: 10.4.0 (ArgoCD v3.5.1) 추가 + 배포 테스트 스크립트
2026-08-19 11:33:47 +09:00
wbsong111 19c42b0bb2 Merge pull request #26 from paasup/fix/airflow-oidc-static-ingress
airflow: OIDC 인증 제외 경로 Ingress 추가 (#24)
2026-08-19 11:33:36 +09:00
wbsong111 3aa69e5eda argo-cd: 10.4.0 (ArgoCD v3.5.1) 추가 + 배포 테스트 스크립트
k8s 1.33 에서 Deployment/ReplicaSet 에 추가된 .status.terminatingReplicas 를
ArgoCD v2.14.5(k8s 라이브러리 0.31/0.32)가 몰라, 클러스터가 1.33 이상이면
ServerSideApply=true 인 Application 이 전부 아래 오류로 죽는다.

  ComparisonError: failed to calculate diff: error calculating structured
  merge diff: .status.terminatingReplicas: field not declared in schema

v3.5.1 은 k8s 라이브러리 0.36.1 을 써서 해소된다. dev 클러스터(1.35.7+rke2r1)
에서 실제 발생·해소를 확인했다.

- manifests/helm/argo-cd/10.4.0/ 추가 (chart_updater 로 생성)
  - custom-values.yaml 을 실제 배포 기준(dipup argo-cd-values.yaml.tpl)에 맞춤:
    kong → apisix, server.extraArgs(--insecure), configs.cm/rbac 추가
  - configs.params.controller.resource.health.persist=true 지정 —
    ArgoCD v3.0 부터 리소스 health 를 CR 에 저장하지 않는 것이 기본값인데,
    dip-console-api 가 .status.resources[].health 를 읽는다
  - CUSTOM-README.md 는 현재 차트 기준 배포 가이드로 재작성.
    승계된 cert-manager 예시가 10.4.0 에 없는 구버전 스키마(hosts/tls 리스트)라
    Ingress 가 생성되지 않는 상태였던 것도 함께 수정
  - breaking_change_check: breaking=false (제거 26건이 전부 미사용 key)

- scripts/deploy-test/deploy-test-argo-cd.sh 신규
  같은 클러스터에 두 번째 argo-cd 를 띄우려면 crds.install=false 가 필수다 —
  CRD 3종이 클러스터 전역이고 운영 릴리스가 소유해 Helm 이 ownership 충돌로
  거부한다. Ingress 도 항상 끈다(운영과 host 충돌).
  검증: 워크로드 롤아웃 / health.persist / api 버전 / admin 로그인

- argo-cd/7.8.11/BUILD-README.md 에 `helm repo add argo ...` 추가
  chart_version_detector 가 이 한 줄을 정규식으로 뽑아 업스트림 레포를 정하는데,
  argo-cd 에는 없어서 신규 버전 자동 감지가 조용히 실패하고 있었다
  (repo: null → latest_version: null). CLAUDE.md 의 "변경 금지" 규정도
  실제 파싱 계약에 맞게 정정했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:28:21 +09:00
wbsong111 dbabf4b551 airflow: 인증 제외 Ingress 이름을 -ingress-static 으로 변경
-ingress-unauth 는 kubectl get ingress 목록에서 "인증 없는 입구"를
그대로 광고한다. 리소스가 무엇인지가 아니라 무엇이 없는지로 이름을
지은 것이기도 해서, 경로가 /static 외로 늘어나면 이름이 뜻을 잃는다.

파일명(webserver-ingress-static.yaml)과 일치시키고 용도를 그대로
드러내는 -ingress-static 으로 바꾼다. 값 키 dip.unauthenticatedPaths
는 values 파일 안에만 있고 의도를 정확히 서술하므로 그대로 둔다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 11:28:16 +09:00
wbsong111 a8530d3968 airflow: OIDC 인증 제외 경로 Ingress 추가 (#24)
세션이 없는 상태로 접속하면 Airflow UI 한 페이지가 /static 자원을 수십 개
동시에 요청하고, 각 요청이 저마다 APISIX openid-connect 로그인 플로우를
시작한다. 세션은 state를 하나만 보관하므로 콜백이 동시에 돌아오면 마지막
하나를 뺀 전부가 state 검증에 실패해 500이 난다.

templates/webserver/webserver-ingress-static.yaml 은 업스트림 Apache Airflow
차트에 없는 PaaSup 추가 템플릿이다. dip.unauthenticatedPaths 가 있을 때만
애노테이션 없는 Ingress를 하나 더 렌더해 해당 경로를 인증 없이 통과시킨다.
애노테이션을 전부 비우는 것은 의도된 것으로, plugin-config-name 을 빼는 게
목적이고 cert-manager.io/* 까지 빼는 이유는 웹 Ingress와 같은 TLS Secret 을
두고 Certificate 를 중복 생성하지 않게 하기 위해서다.

값을 ingress.web 아래가 아니라 dip 아래에 두는 이유는 values.schema.json 의
ingress.web 이 additionalProperties: false 라 그 아래 새 키를 넣으면 스키마
검증에서 배포가 실패하기 때문이다. qdrant·litellm 의 dip.mainPath 와 같은
자리를 쓴다.

webserver-ingress.yaml 과 동일한 조건으로 렌더하므로 Airflow 3.0
(webserver → api-server) 전환 시 함께 재작업이 필요하다. 업스트림 파일은
수정하지 않았고 fork 델타는 신규 템플릿 1개뿐이다.

이슈 #24 는 dip-console 이 차트 밖에서 생성하는 방안(B안)을 권고했으나,
실제로 증상이 재현되는 서비스가 airflow 하나로 좁혀져 차트 템플릿(A안)으로
갔다. dip-console 로 옮길 때는 dip.unauthenticatedPaths 를 비우면 된다.

redirect_uri 고정은 여전히 dip-console 몫으로 남는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 11:19:01 +09:00
wbsong111 acf7a016e7 Merge pull request #25 from paasup/feat/airflow-applicationset-dip-files
airflow: applicationset dip-values/dip-questions 추가 + 문서 현행화
2026-08-14 15:48:06 +09:00
wbsong111 ad9f5927bb docs: CLAUDE.md·Skill 설명을 현재 레포 상태로 현행화
- CLAUDE.md 정적 카탈로그 규모를 실측치로 교체(차트 59 / 버전 디렉토리 70).
  차트 디렉토리 5종 파일이 전부 갖춰지지 않은 곳이 있다는 사실과 dip-* 계열이
  선택적이라는 점을 명시해 "5개 필수"로 오해하지 않게 했다.
- 디렉토리 트리에 scripts/deploy-test/·images/·MEMORY.md·doc/ 하위 문서를 추가.
  scripts/build/ 의 "아직 실사용 이미지 없음" 문구는 실제 빌드 이미지가 생겨
  더 이상 사실이 아니라 제거했다.
- chart-to-cnpg: 남은 전환 대상에서 flowise 제외(2026-08 완료), infisical-standalone 추가.
- self-build-image: 이미지 3종 → 7종. 목록이 또 어긋나지 않도록 images/ 디렉토리가
  단일 출처임을 설명에 박아뒀다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 15:47:02 +09:00
wbsong111 1084713614 airflow applicationset: dip-values / dip-questions 추가
flowise/6.0.0 의 dip-values.yaml + dip-questions.yaml 스키마를 airflow 에 적용했다.
doc1 은 cnpg-cluster(airflow-db), doc2 는 airflow 본체다.

dip-values.yaml
- bootstrap.initdb 의 database/owner 는 airflow-db-values.yaml 과 맞춘 airflow.
- databases[] 도 airflow/airflow. 차트 기본값인 appdb/appuser 를 쓰면 appuser
  롤이 생성되지 않아 Database CR reconcile 이 실패한다.
- bootstrap.initdb 에는 ensure/reclaimPolicy 를 넣지 않았다. cnpg-cluster 의
  cluster.yaml 이 읽지 않는 databases[] 전용 키다.
- postgresql.enabled: false. 내장 bitnami 대신 syncWave 0 의 cnpg 를 쓴다.
- metadataConnection.host 는 "{{ .Name }}-airflow-db-rw". dip 배포는 cnpg
  릴리스명에 테넌트 접두사를 붙여 나간다(관측: instance=demo01-air-airflow-db).
  flowise 의 externalPostgresql.host 와 같은 규칙이다.

dip-questions.yaml
- git-sync 는 GIT_SYNC_*/GITSYNC_* 네 키를 모두 선언한다. 차트가 네 개를 각각
  secretKeyRef 로 잡아서 하나라도 없으면 파드가 기동되지 않는다.
- connection 기본값 호스트는 $CATALOG_NAME-airflow-db-rw.$CATALOG_NAME.
  차트가 data.metadataSecretName 의 connection 키를
  AIRFLOW__DATABASE__SQL_ALCHEMY_CONN 에 그대로 주입하므로 이 값이 유일한 실효 값이다.

두 파일의 토큰 형식 차이는 의도한 것이다 — dip-questions 는 $CATALOG_NAME,
dip-values 는 {{ .Name }} 가 각 파일의 기존 관례다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 15:38:33 +09:00
wbsong111 75b0eb8ef7 Merge pull request #23 from paasup/docs/catalog-stack-priority-cleanup
카탈로그 스택 분류: 우선순위 재정리 (P0 범위를 dipup 기본 배포로 한정)
2026-08-13 15:06:26 +09:00
wbsong111 f0d73a2be3 카탈로그 스택 분류 우선순위 재정리 — P0를 dipup 기본 배포 인프라로 한정
LLM·데이터분석 스택(2,3)에 남아있던 P0(litellm, open-webui, langfuse,
strimzi-kafka-operator, kafka-cluster, airflow, lakekeeper, superset)를 P1/P2로
재분류해, P0는 dipup 기본 배포(argocd·harbor·gitea·infisical·keycloakx)와 그
직접 의존 인프라(cert-manager, apisix, etcd, cloudnative-pg, cnpg-cluster,
rancher, dnsup)에만 남도록 정리했다. 데이터분석 스택 차트 수 헤더(16→18)도
실제 개수에 맞춰 수정.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 15:05:19 +09:00
wbsong111 862ea68e7c Merge pull request #22 from paasup/ci/sbom-chart-filter
sbom.yml: workflow_dispatch에 chart 입력 추가
2026-08-13 14:47:17 +09:00
wbsong111 7a92b53833 sbom.yml에 workflow_dispatch chart 입력 추가 — 차트별 단독 스캔 지원
limit(개수 상한)만으로는 특정 차트를 겨냥해 스캔할 수 없었다(images_final.tsv가
차트 알파벳 순으로 쌓여 앞쪽 무관한 차트까지 같이 스캔됨). chart 입력을 비우면
기존과 동일하게 전체 스캔.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 14:46:16 +09:00
paasup 51ea5074f0 flowise applicationsest dip-values add 2026-08-13 14:42:55 +09:00
wbsong111 46f6748963 Merge pull request #21 from paasup/build/adc-20260813051843
adc 이미지 태그 갱신: 0.29.0-security-hardened-20260812 → 20260813
2026-08-13 14:38:49 +09:00
wbsong111 c93549e59f Merge pull request #20 from paasup/build/apisix-20260813052607
apisix 이미지 태그 갱신: 3.17.0-security-hardened-20260811 → 20260813
2026-08-13 14:38:19 +09:00
github-actions[bot] 7406a881e0 apisix 이미지 태그 갱신: 3.17.0-security-hardened-20260811 → 3.17.0-security-hardened-20260813
게이트 PASS(build-image.yml, workflow_dispatch 트리거)로 확인된 태그로 교체한다.

Co-Authored-By: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-13 05:26:07 +00:00
github-actions[bot] 4a43ef1c20 adc 이미지 태그 갱신: 0.29.0-security-hardened-20260812 → 0.29.0-security-hardened-20260813
게이트 PASS(build-image.yml, workflow_dispatch 트리거)로 확인된 태그로 교체한다.

Co-Authored-By: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-13 05:18:43 +00:00
paasup 5207eef613 flowise applicationsest dip-values add 2026-08-12 17:56:32 +09:00
paasup 988a1e3c36 flowise applicationsest dip-values add 2026-08-12 13:37:53 +09:00
wbsong111 18044210f6 apisix 카탈로그 CVE 게이트 완전 해소 (차단 165건 → 0건)
etcd(bitnamilegacy 동결 미러) → etcd.enabled=false + 카탈로그 자체 etcd 차트를
externalEtcd 기본값으로 연결. adc·apisix-ingress-controller·apisix(paasup/apisix)
세 이미지는 SUSE BCI 자체 빌드로 교체 — 전부 벤더 등급만으로는 안 보이던
벤더 하향 등급 CVE(NVD 재평가 시 드러남)가 원인이었다.

- images/apisix-ingress-controller: 정적 링크 Go 모듈 취약 버전만 강제 업그레이드
- images/apisix: APISIX-Runtime(WASM·dubbo 등 커스텀 모듈 포함) 전체를 SUSE BCI
  위에서 소스로 재현, keycloak-authz 플러그인 오버레이
- images/adc: 업스트림 빌더 스테이지는 그대로 두고 distroless 최종 베이스만
  SUSE BCI+nodejs24 로 교체

scripts/build/patch-catalog-tag.py 의 TAG_BLOCK 이 점 구분 중첩 경로를 지원하도록
확장(apisix 서브차트 alias 때문에 필요).

세 이미지 모두 게이트 PASS(실효 CRITICAL/HIGH 0/0)와 배포 검증(테스트 클러스터)을
마쳤다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 11:37:32 +09:00
wbsong111 bc4002f41c apisix 2.14.0 삭제 — 최신 버전(2.16.0) 하나로 정리
CVE 자체 빌드 이미지가 앱 버전까지 함께 올리며 두 카탈로그 버전이 서로 다른
컴포넌트 버전을 요구하는 복잡도가 생겨, 구버전을 유지하는 대신 최신 버전
하나로 정리한다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 09:55:06 +09:00
wbsong111 2817007950 카탈로그 스택 분류 문서 추가
manifests/helm 차트를 DIP/LLM/데이터분석 3개 스택과 우선순위(P0~P2)로 분류한다.
차트별 CVE 트리아지 상세는 issue #19로 별도 관리한다.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 10:34:13 +09:00
wbsong111 b527547dfa Merge pull request #18 from paasup/add-keycloakx
keycloakx 7.2.2 추가 + keycloak 자체 빌드 하드닝 이미지 (keycloak/18.4.0 제거, 이슈 #1)
2026-08-07 14:31:47 +09:00
wbsong111 89fcef2a6f Merge remote-tracking branch 'origin/main' into add-keycloakx 2026-08-07 14:27:10 +09:00
wbsong111 4f67f63f69 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>
2026-08-07 14:27:03 +09:00
wbsong111 4e12e38293 add-keycloakx: micrometer 오버레이 추가 (CI 에서 새로 잡힌 차단 2건)
CI(31148639764)의 trivy 취약점 DB 가 로컬보다 최신이라 로컬 스캔에는 없던
CVE-2026-40983·CVE-2026-40984(io.micrometer:micrometer-core)가 차단으로 잡혔다.
netty·jackson 과 동일한 성격의 번들 jar CVE 라 같은 오버레이 메커니즘으로 처리한다.

micrometer 1.16.3 → 1.16.6. netty 와 같은 이유로 CVE 가 붙은 micrometer-core 만이
아니라 패밀리 전체 4종(commons·core·observation·registry-prometheus-simpleclient)을
함께 올린다 — 아티팩트 간 버전 혼용이 비지원이다.

Dockerfile 의 ARG 선언을 처음에 빠뜨렸는데 overlay-jars.sh 의 `: "${VAR:?}"` 가드가
설계대로 빌드를 세웠다("parameter null or not set"). 조용히 통과하지 않는다.

재빌드 결과(로컬 trivy DB 캐시를 지우고 CI 와 같은 최신 DB 로 재판정):

  차단 0건 — 게이트 PASS
  docker.io/paasup/keycloak:26.7.1-bci15.7-hardened-20260807 push 완료

태그는 빌드일 기준이라 같은 날 재빌드가 같은 태그를 덮는다(프레임워크 설계대로).
직전에 push 된 것은 micrometer 취약점이 남아 있던 빌드이고 어디에도 소비되지 않았다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 14:05:05 +09:00
wbsong111 a27c2a9423 add-keycloakx: images/keycloak 의 셸 스크립트에 실행 권한 부여
기존 3종의 verify.sh 가 전부 100755 인데 100644 로 들어갔다. 둘 다 `bash <파일>`
로 호출되어 동작에는 영향이 없지만 관례를 맞춘다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 13:50:36 +09:00
wbsong111 f9a2d8400f add-keycloakx: keycloak 자체 빌드 하드닝 이미지 추가 (차단 CVE 17건 → 0건)
PR #18 이 카탈로그에 넣는 quay.io/keycloak/keycloak:26.6.4 가 게이트에서 차단
17건(실효 HIGH 17 / CRITICAL 0)이었다. sbom.yml 이 warn-only 라 PR 은 통과했지만
실제로는 게이트 실패 상태로 카탈로그에 들어간다.

## 상위 태그·베이스 OS 교체를 먼저 검토한 결과

차단 17건 중 12건이 배포본에 함께 실린 jar 다. keycloak 26.6.4 와 최신 26.7.1 의
quarkus.version 이 둘 다 3.33.2.1 이고 그 BOM 이 netty 4.1.135.Final /
jackson-bom 2.21.2 를 고정한다(keycloak pom.xml 두 태그 + quarkus BOM 실측).
필요한 수정 버전은 netty 4.1.136.Final, jackson 2.21.4 라 **상위 태그로도 풀리지
않고**, CVE 가 OS 패키지가 아니라 jar 자체라 **베이스 OS 교체도 통하지 않는다.**
jar 를 직접 교체하는 자체 빌드가 유일한 수단이다 — etcd 이미지의
`go.work replace golang.org/x/text` 와 같은 성격의 의존성 override.

## images/keycloak/

업스트림 quarkus/container/Dockerfile 을 기준으로 하되 셋이 다르다.

1. 런타임 rootfs 가 SUSE BCI. bci-micro 파일시스템을 **씨앗으로 깔고** 그 위에
   zypper --installroot 로 설치한다. 업스트림 ubi-null.sh 처럼 별도 installroot 를
   micro 위에 덮으면 micro 의 rpmdb 가 가려져 micro 자체 패키지가 SBOM 에서 통째로
   사라진다 — CVE 가 주는 게 아니라 스캔 사각지대가 생긴다. 씨앗 방식으로 OS 패키지
   65종이 정상적으로 잡히는 것을 SBOM 으로 확인했다.
2. 취약 jar 오버레이(overlay-jars.sh). netty 17종 → 4.1.136.Final, jackson
   core/databind → 2.21.4, pgjdbc → 42.7.12. Quarkus fast-jar 의 클래스패스가
   파일명을 그대로 참조하므로 **파일명은 유지하고 내용만** 바꾸고 sha1 로 검증한다.
   trivy 는 jar 내부 메타데이터를 읽으므로 SBOM 에 새 버전이 정확히 잡힌다.
3. bin/client 제거. keycloak-admin-cli 가 jackson 을 shade 로 품은 uber-jar 라
   교체가 불가능하다. 서버 JVM 이 로드하지 않는 독립 CLI 라 제거했다 — 업스트림
   대비 유일한 기능적 차이이며 CUSTOM-README 에 대안을 적었다.

버전은 26.7.1 로 올렸다. 26.6.4 는 26.7.1(및 26.6.5)에서만 패치된 keycloak-services
HIGH 5건(CVE-2026-16102/16442/16443/15572/15573)에 취약하다. 차트(keycloakx 7.2.2)는
최신이고 그대로 둔다 — appVersion 26.6.4 는 codecentric 의 릴리스 캐던스 지연이다.

## 베이스 OS 정책 확정 (image-authoring.md 원칙 2 미결 해소)

SUSE BCI 로 통일하되 **버전은 이미지마다 실측해서 고른다.** BCI 16.0 이 나와 있지만
SLE_BCI 의 java-21-openjdk-headless 가 15.7 은 21.0.12, 16.0 은 21.0.11 이라 최신
베이스로 가면 CVE-2026-41254·CVE-2026-47063 이 오히려 남는다. bci-micro 에
sed·grep·find 가 셋 다 없다는 것과 SLE 패키지명 차이(tzdata→timezone 등)도 함께
기록했다.

## 실측 결과

로컬 빌드(linux/amd64) → verify.sh → SBOM → 전 심각도 스캔 → 게이트:

  차단 17건 → **0건** (커버리지 자가진단 ok, OS=sles 15.7)

남은 1건 CVE-2025-59250 은 예외 등록했다 — 트리비가 같은 mssql-jdbc jar 하나로
컴포넌트를 둘 만들어(pom.properties 의 13.2.1.jre11 / 파일명의 13.2.1) 접미사가
잘린 쪽이 매칭된 파싱 오탐이다. 설치본은 FixedVersion 목록에 있는 13.2.1.jre11 이다.

dev 클러스터 격리 네임스페이스(kc-test-build)에 cnpg-cluster + keycloakx 로 실배포
검증: Pod Running, jdbc-postgresql 연결, liquibase 스키마 생성, admin 부트스트랩
(KC-SERVICES0077), apisix ingress 경유 OIDC discovery 200 / admin 토큰 발급 /
realm·client 생성(201) 까지 확인. 정리 시 Longhorn Volume 까지 삭제했다.

## custom-values.yaml ingress 수정

path 가 exact "/" 였다. apisix 에서는 루트만 매치되어 /realms/*, /admin/* 이 전부
404 가 난다 — airflow·superset·mlflow·lakekeeper 에서 이미 실측된 문제로 카탈로그가
regex 방식으로 통일돼 있다. path: /.* + k8s.apisix.apache.org/use-regex 로 맞췄고,
배포 검증에서 이 경로들이 실제로 뜨는 것을 확인했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 13:49:42 +09:00
wbsong111 fce8b62766 Merge pull request #17 from paasup/feat/cnpg-cluster-sqlrefs
cnpg-cluster 1.1.0 — postInitApplicationSQLRefs 노출
2026-08-07 10:02:55 +09:00