Files
service-catalog/MEMORY.md
T
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

19 KiB

현재 상태 · 미결 결정

작업을 이어받을 때 여기서 시작한다. 지금 시점의 상태와 다음에 할 일만 담는다.

최종 갱신: 2026-08-19


다음 작업

argo-cd@10.4.0 이 게이트 차단 0건으로 완결됐다(2026-08-19). ArgoCD 가 k8s 1.35 를 지원하지 못해 시작한 업그레이드가 CVE 조치까지 이어진 건이다.

발단 — k8s 1.33 신규 필드로 ArgoCD 가 전멸했다. .status.terminatingReplicas(k8s 1.33 도입)를 ArgoCD v2.14.5(k8s 라이브러리 0.31/0.32)가 몰라서, ServerSideApply=true 인 Application 이 전부 ComparisonError: field not declared in schema 로 죽었다. 차트 7.8.11 → 10.4.0(ArgoCD v3.5.1, k8s 라이브러리 0.36.1)으로 해소했고 dev 클러스터 (1.35.7+rke2r1)에서 발생·해소를 모두 실측했다.

CVE 조치 — 레버를 순서대로 적용해 574 → 239건.

조치 차단
시작(7.7.0·7.8.11·10.4.0 전부) 574
argo-cd/7.7.0 삭제(구버전, EOSL 이미지 2종 포함) 337
dex.enabled=false — 쓰지도 않는데 떠 있었다 280
images/argocd/ 자체 빌드 239

남은 239건은 전부 동결된 7.8.11(직전 버전) 몫이고, 운영에 쓰는 10.4.0 은 0건이다. 7.8.11 을 언제 지울지가 미결이다 — 지우면 게이트가 PASS 로 떨어진다.

dex 를 끈 것이 가장 값싼 레버였다. ArgoCD 는 oidc.config 가 있으면 dex 를 우회하는데 차트 기본값이 enabled: true 라 유휴 파드가 떠서 차단의 절반 이상을 만들고 있었다. dipup 도 항상 configs.cm.oidc.config 로 Keycloak 에 직접 붙으므로 dex 를 쓰지 않는다. 대응 레버를 보기 전에 "이 컴포넌트가 필요하긴 한가" 를 먼저 묻는다 는 교훈이다.

images/argocd/ 는 기존 자체 빌드 중 가장 크다 — Go 프로젝트 4개(argocd·helm· kustomize·git-lfs) + C 2개(tini·connect-proxy) + Node UI. 업스트림이 번들 도구를 미리 빌드된 릴리스 바이너리로 내려받기 때문에 그 바이너리의 Go 버전을 우리가 통제할 수 없고, 같은 CVE 가 여러 바이너리에 공유돼 부분 조치가 무효였다(본체만 고치면 거의 줄지 않았다).

실측 함정 셋:

  • cp --update=none 이 베이스 OS 를 갈랐다. 차트의 repo-server init 컨테이너가 쓰는 이 형식은 GNU coreutils 9.3+ 에서만 된다. BCI 15.7 로 빌드한 이미지가 게이트도 verify.sh 도 통과하고 배포 시점에 Init:CrashLoopBackOff 로 죽었다. BCI 16.0 (coreutils 9.6)으로 올려 해소했고, verify.sh 가 이 명령을 직접 실행해 다음엔 빌드 단계에서 걸리게 했다. 게이트 PASS 는 동작을 증명하지 않는다 의 실제 사례다.
  • 같은 날 재빌드는 태그가 겹친다. 태그에 빌드일을 넣어도 같은 날 다시 빌드하면 태그가 같아, 노드가 캐시한 옛 digest 가 그대로 쓰인다(imagePullPolicy: IfNotPresent). 이번엔 검증용으로 Always 를 넣어 우회했다 — 프레임워크 차원의 미결 사항이다.
  • 모듈 업그레이드 제안값은 최소치다. 모듈 간 제약으로 더 올려야 할 수 있다 (requires <module>@vX, not @vY 로 빌드가 실패한다).

Go 모듈 CVE 조치를 자동화했다scripts/build/suggest-go-upgrades.py 가 스캔 리포트의 FixedVersion 에서 GO_MODULE_UPGRADES / GO_BUILDER_TAG 를 산출한다. 사람이 CVE 를 훑어 최대값을 고르지 않는다. 다음 조치에서 바뀌는 것은 build.env 뿐이고 Dockerfile 은 그대로다(모듈 목록·패키지 목록을 전부 값으로 뺐다).

남은 것 (argo-cd)

  • argo-cd/7.8.11 삭제 여부 — 지우면 게이트 PASS
  • 자체 빌드 이미지의 정식 반영 경로: CI build-image.yml(workflow_dispatch) 로 다시 태워 볼지, 로컬 push 로 끝낼지. 이번엔 로컬에서 docker.io/paasup 에 push 하고 운영 argocd 를 교체해 동작 확인까지 마쳤다.

별건으로 발견한 airflow 문제 2건

  • 매니페스트가 비결정적이다. webserver-secret-key-secret.yamlrandAlphaNum 으로 매 렌더마다 새 값을 만들어, repo-server 의 매니페스트 캐시가 날아갈 때마다(ArgoCD 재시작· hard refresh) webserver 가 롤링되고 웹 세션이 끊긴다. reconcile 만으로는 재렌더가 일어나지 않아 상시 루프는 아니다(4시간 19분 동안 롤아웃 0회로 확인). webserverSecretKey 또는 webserverSecretKeySecretName 을 지정하면 해소된다. 카탈로그 기본값에 없어 모든 테넌트가 해당된다.
  • migrations job 이 5분마다 재실행된다. migrateDatabaseJob.useHelmHooks: false 로 hook 이 아닌 추적 리소스가 됐는데 ttlSecondsAfterFinished: 300 이 그대로라, TTL 삭제 → ArgoCD selfHeal 재생성이 반복된다. ttlSecondsAfterFinished 를 끄거나 hook 으로 되돌린다.

apisix@2.16.0 전체가 게이트 PASS 로 완결됐다(2026-08-12). images/adc/ 신규 추가로 마지막 남은 이미지까지 해소 — 차단 55건(2026-08-12 초 기준) → 0건. adc· apisix-ingress-controller·apisix(paasup/apisix) 세 자체 빌드 이미지 전부 게이트 실효 CRITICAL/HIGH 0/0. apisix-ingress-controller·apisix 두 이미지는 사용자가 별도 테스트 클러스터에서 배포 검증 완료(2026-08-12) — adc 는 아직 미완료 (.claude/deploy-test-procedure.md 로 apisix-ingress-controller 와 함께 배포해 dump/diff/sync 백엔드 연동까지 확인 필요).

adc — "태그 교체로 끝난 줄 알았는데 게이트 재검증하니 아니었다". 0.27.1→0.29.0 태그 교체 후 custom-values.yaml에 "게이트 PASS 확인"이라고 적어뒀는데, 이건 벤더 등급만 본 결과였다 — 실제 cve-gate.py(max(벤더,NVD))로 재검증하니 glibc regex/ collating 스택오버플로 4건(CVE-2019-1010022/23, CVE-2018-20796, CVE-2019-9192, 전부 libc6, Debian "affected, 수정 없음" 영구 고정)이 실효 CRITICAL/HIGH 로 여전히 차단(FAIL). images/adc/ 자체 빌드로 해소 — 업스트림 Dockerfile (libs/tools/src/docker/Dockerfile)이 node:lts-bookworm-slim 빌더 + gcr.io/distroless/nodejs24-debian13 최종인데, 빌더 스테이지는 그대로 두고 최종 베이스 한 줄만 SUSE BCI + nodejs24(zypper)로 교체 — apisix/ apisix-ingress-controller 자체 빌드보다 훨씬 단순한 유형(cnpg-postgresql 과 같은 "OS 패키지 재설치형", 빌드 시간 2분 이내). SUSE 글리브C 에 이 4건이 없음을 사전에 직접 SBOM·스캔으로 검증한 뒤 진행했다. 게이트 PASS(실효 C/H 0/0). docker.io/paasup/adc:0.29.0-security-hardened-20260812 로 push·카탈로그 반영 완료. 이 사례는 일반화할 교훈이 있다 — "벤더 릴리스 노트가 CVE 를 줄였다고 명시해도, 게이트 로 직접 재검증하기 전엔 PASS 로 단정하지 않는다"(sbom-cve-gate/cve-remediation skill 이 이미 강조하는 원칙이지만 이번에 실제로 걸렸다).

apisix 자체 이미지도 SUSE BCI 자체 빌드로 교체 완료(2026-08-12). images/apisix/ 신규 — 기존엔 별도 프로젝트(paasup/dataup 레포 experiment/apisix/apisix-plugin)에서 apache/apisix:3.17.0-debian(이미 최신 태그) 위에 keycloak-authz 플러그인만 얹어 빌드했는데, 그 Debian 베이스 자체가 차단 26건이었다. 문제는 이게 vanilla OpenResty가 아니라 WASM(wasmtime)·mod_dubbo 등 15개 커스텀 모듈을 정적으로 얹은 "APISIX-Runtime" 이라, apiseven 의 빌드 파이프라인(api7/apisix-build-tools, 태그 apisix-runtime/1.3.6)이 Debian/RHEL 용으로만 배포하고 SUSE 용은 없다 — vanilla OpenResty(openresty.org 가 SLES 공식 지원)로 축소하는 것도 검토했으나, WASM·dubbo 관련 기능이 필요하다는 판단으로 커스텀 모듈 전체를 소스에서 그대로 재현하는 쪽으로 갔다. 3단계(runtime: OpenSSL/zlib/PCRE+OpenResty+6개 모듈 컴파일 → apisix-app: luarocks 로 APISIX 앱 설치 → final: SUSE BCI 로 산출물 이관+keycloak-authz 오버레이) 빌드, 실측 함정 다수 해결(zlib→OpenSSL 순서, lua-resty-saml 의 libxml2 2.12 시그니처 불일치를 gcc 래퍼로 우회, ui/(admin 대시보드) 부재 허용, SUSE 공유 라이브러리 soname 등 — 상세는 images/apisix/README.md). 실제 nginx 기동 + HTTP 요청 응답까지 확인하는 verify.sh 작성(초판에 curl 연결 실패를 "000" 문자열로 오판하는 버그가 있었음 — 수정 완료). 게이트 PASS(실효 C/H 0/0, 업스트림 26건에서 완전 해소). docker.io/paasup/apisix:3.17.0-security-hardened-20260811 로 push·카탈로그 반영 완료. 배포 검증 완료(2026-08-12, 사용자가 별도 테스트 클러스터에서 확인).

apisix 카탈로그 CVE 조치 완료(2026-08-11). cve-remediation skill(신설, .claude/skills/cve-remediation/SKILL.md)의 결정 절차를 처음 실제 적용한 사례. 조치 전 차단 실효 CRITICAL/HIGH 165건(2.16.0 기준, 구버전 2.14.0 포함 시 331건) → 조치 후 55건.

  • docker.io/bitnamilegacy/etcd:latest(차단 65건) 완전 제거 — bitnami 가 무료 레포를 동결한 레거시 미러라 latest 태그만 제공(bitnami/containers#83267), 태그 교체로 해소 불가. etcd.enabled: false 로 기본값을 바꾸고 카탈로그 자체 etcd 차트 (manifests/helm/etcd/1.1.12)를 externalEtcd 기본 대상으로 연결했다 (manifests/helm/apisix/2.16.0/custom-values.yaml).
  • ghcr.io/api7/adc 를 최신 태그(0.27.1)로 교체 — 완전 해소는 아니고 34→29건으로 부분 완화(같은 debian 12.14 베이스가 계속 쓰여 OS 패키지 CVE 잔존). 상위 태그가 이미 최신이라 추가 레버는 자체 빌드뿐 — 아직 안 함.
  • images/apisix-ingress-controller/ 신규 자체 빌드 — 이미 최신 태그(2.1.0)인데도 차단 25건(21건이 정적 링크 Go 모듈: stdlib/x-net/x-text/grpc/otel — 태그 교체 불가, 나머지 4건은 distroless 베이스 OS 패키지). v2.1.0 태그 코드는 그대로 두고 취약 모듈만 go get+go mod tidy 로 강제 업그레이드, 베이스는 SUSE BCI(bci-micro)로 교체 → 게이트 PASS(실효 C/H 0/0). docker.io/paasup/apisix-ingress-controller: 2.1.0-security-hardened-20260811 로 push·카탈로그 반영 완료. 배포 검증은 아직 안 함(이 세션에 클러스터 없음) — .claude/deploy-test-procedure.md 로 필수 수행.
  • scripts/build/patch-catalog-tag.py 확장TAG_BLOCK 이 점 구분 중첩 경로(예: ingress-controller.deployment.image)를 지원하도록 바꿨다(기존엔 top-level 키만 가능). apisix 서브차트 alias 때문에 필요해짐. 기존 이미지(단일 세그먼트 image) 회귀 테스트로 동작 동일함 확인.
  • manifests/helm/apisix/2.14.0 삭제 — 사용자 지시. 자체 빌드가 앱 버전(2.0.1)까지 같이 올리며 두 카탈로그 버전이 서로 다른 컨트롤러 버전을 요구하는 복잡도가 생겨서, 구버전을 유지하는 대신 최신(2.16.0) 하나로 정리했다. doc/catalog-stack-classification.md 갱신 완료.
  • 남은 것: paasup/apisix:3.17.0-keycloak-authz(Keycloak authz 플러그인 포함 커스텀 빌드, 차단 26건, debian 12.14 OS 패키지) — 빌드 소스가 dip-catalog 밖의 별도 프로젝트 (dataup experiment/apisix/apisix-plugin)에 있어 이 레포에서 재빌드 불가. 그 프로젝트에서 베이스를 갱신해야 한다.

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 에 첫 예외가 들어갔다(2026-08-07, keycloak 자체 빌드). 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). scan-sbom.sh 가 os-pkgs findings 0건인 이미지의 SBOM 사본에 배포판별 센티널 패키지 (deb/rpm/apk)를 주입해 재스캔하고, 발화 여부로 CoverageProbe(ok|none|n/a)를 리포트에 기록한다 — cve-gate.py 는 이미 이 키를 읽도록 구현돼 있었으므로 소비 쪽 변경은 없다. 로컬에서 rpm(SUSE)·deb(Debian)·apk(Alpine) 세 경로 전부 실측 검증했고, 병렬 스캔(여러 SBOM 동시 처리)에서도 회귀 없음을 확인했다. 이 이식으로 아래 "images/ 3종" 항목의 결과가 실제로 바뀌었다 — cloudnative-pg·cnpg-postgresql 이 전에는 "데이터 커버리지 이상"으로 FAIL 했으나(findings 가 전 심각도 0건이라 구버전 로직이 "측정 안 됨"으로 오판) CoverageProbe 이식 후 재게이트하니 셋 다 커버리지 ok, 실효 C/H 0/0, PASS 로 나온다 (양성 대조로 재확인: 같은 SBOM 에 오래된 취약 curl 버전을 주입해 재스캔하면 SUSE-SU 어드바이저리가 정상 검출됨 — trivy 의 SLES 15.7 커버리지 자체는 문제 없었다).

images/의 이미지 3종(cloudnative-pg, cnpg-postgresql, etcd)을 docker.io/paasup 로 재빌드·게이트·push 완료했다(2026-08-03). REGISTRY=docker.io/paasup 로 빌드 → verify.sh 전부 VERIFY-OK → 게이트 셋 다 커버리지 ok, 실효 C/H 0/0, PASS → push ​→ docker manifest inspect 로 레지스트리 존재 재확인:

  • docker.io/paasup/cloudnative-pg:1.30.0-security-hardened-20260803
  • docker.io/paasup/cnpg-postgresql:18.4-bci15.7-hardened-20260803
  • docker.io/paasup/etcd:3.7.1-security-hardened-20260803

카탈로그 values 6개 파일(manifests/helm/{cloudnative-pg/0.29.0,cnpg-cluster/1.0.0, etcd/1.1.12}/{custom-values,dip-values}.yaml — etcd 는 dip-values.yaml에 image 오버라이드 없어 5개만 실제 갱신)도 patch-catalog-tag.py로 새 태그로 교체하고 helm template·extract-helm-images.sh 로 렌더링 결과까지 재확인했다. DOCKERHUB_USER/DOCKERHUB_TOKENpaasup 조직에 push 권한이 있음을 이번에 확인했다(기존 "미확인" 상태 해소). 최종 런타임 베이스 OS 정책은 security-catalog 의 SUSE BCI 고정 결정을 그대로 따랐고(ADR 자체는 미이관), 2026-08-07 keycloak 작업에서 이 레포의 정책으로 확정됐다(위 참고).

남은 것: SBOM_PIPELINE_IMAGE(sbom.yml 이 쓰는 실행 컨테이너)는 이번 마이그레이션 대상이 아니다 — 여전히 docker.io/wbsong111/sbom-pipeline:latest 를 가리킨다. 이건 자체 빌드 이미지와 무관한 별개 결정(아래 미결 결정 참고).

doc/decisions/·doc/analysis/ 디렉토리 자체가 dip-catalog 에 없다. 포팅된 3개 이미지의 README·values 코멘트가 doc/decisions/000X-*.md, doc/analysis/*.md, doc/image-selection.md, doc/cve-zero-pipeline.md, doc/architecture/build-pipeline.md 를 근거로 계속 인용하지만 이 경로들은 dip-catalog 에 하나도 없다(security-catalog 프로젝트에만 있음). 당장 급한 건 아니지만, 이 상태로는 이 레포만 보는 사람이 자체 빌드 결정의 CVE 실측·비교 근거를 확인할 방법이 없다 — 각 README 에 이미 요약된 근거(CVE 번호·후보 비교표)를 압축한 로컬 stub ADR 작성을 검토한다.

doc/define-chart-resources.md 에 신규 차트 3종(cloudnative-pg, cnpg-cluster, etcd) 의 Small/Medium/Large 리소스 프로파일이 없다. CLAUDE.md 의 "신규 차트 추가" 규칙(리소스 프로파일 필수)을 아직 못 지켰다 — custom-values.yaml 의 기존 requests/limits/storage 값을 근거로 표를 추가하는 별도 작업으로 처리한다.

SBOM_PIPELINE_IMAGE 재빌드가 보류돼 있다. 이 마이그레이션으로 빌드 컨텍스트 경로가 doc/scripts/Dockerfilescripts/pipeline/Dockerfile 로 바뀌었다. Dockerfile 내용 자체는 안 바뀌었으므로 기존 docker.io/wbsong111/sbom-pipeline:latest 는 당장 깨지지 않지만, paasup 네임스페이스로 이전할지는 별도 결정이 필요하다. 재빌드 + push + Repo Variable SBOM_PIPELINE_IMAGE 갱신은 git 커밋으로 되지 않는 수동 작업이다.

build-image.ymlREGISTRY_HOSTdocker.io/paasup 로 설정했고, CI 에서 동작을 확인했다(2026-08-04). workflow_dispatchetcd·cloudnative-pg 를 돌려 레지스트리 로그인 → 빌드 → verify.sh → SBOM → 스캔 → 게이트 PASS → push → 카탈로그 브랜치 push 까지 전부 성공했다(build/etcd-20260804060806, 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 오픈·병합 전 게이트 재확인·배포 검증은 사람의 몫이다.


미결 결정

게이트 강제력 전환 시점

"45+ 차트 미검증" 문제 해소 후 결정한다. branch protection(필수 상태 체크)도 게이트 강제와 짝을 이뤄야 의미가 있다 — 지금은 미설정.

SBOM_PIPELINE_IMAGE 재빌드/네임스페이스 전환

결정 나면 docker.io/paasup/sbom-pipeline:... 로 빌드·push 하고 Repo Variable SBOM_PIPELINE_IMAGE 를 갱신한다(수동, out-of-band).