diff --git a/.claude/deploy-test-procedure.md b/.claude/deploy-test-procedure.md index 48be6f8..373d5de 100644 --- a/.claude/deploy-test-procedure.md +++ b/.claude/deploy-test-procedure.md @@ -27,8 +27,8 @@ IMAGE_NAME=docker.io/wbsong111/cnpg-postgresql:<태그> bash scripts/deploy-test Operator 는 정리 대상이 아니다. - CI(GitHub Actions) 통합은 아직 없다 — 러너에서 dev 클러스터로의 네트워크 경로가 없다. - 백업/PITR, pooler, 동기 복제 등 스모크 테스트 밖의 항목은 아래 수동 절차를 따른다. - security-catalog 프로젝트에 상세 배포 테스트 기록(`doc/charts/cnpg/deploy-test.md`)이 - 있으나 dip-catalog 에는 아직 이관되지 않았다. + 스모크 테스트가 무엇을 덮는지는 `scripts/deploy-test/deploy-test-cnpg-cluster.sh` 가 + 단일 출처다 — 그 밖은 이 문서의 수동 절차를 따른다. ## etcd (`etcd` 이미지) — 스크립트 있음, 수동 실행 @@ -45,8 +45,8 @@ IMAGE_NAME=quay.io/coreos/etcd:v3.7.1 bash scripts/deploy-test/deploy-test-etcd. health(`etcdctl endpoint health --cluster`), 쓰기/읽기 왕복(`etcdctl put`/`get`). - 정리(uninstall + PVC/PV + 네임스페이스 삭제)는 성공/실패와 무관하게 항상 수행된다. - TLS·백업 등 스모크 테스트 밖의 항목은 `manifests/helm/etcd/1.1.12/CUSTOM-README.md` 를 - 따른다. security-catalog 프로젝트에 상세 배포 테스트 기록(`doc/charts/etcd/deploy-test.md`)이 - 있으나 dip-catalog 에는 아직 이관되지 않았다. + 따른다. 스모크 테스트가 무엇을 덮는지는 `scripts/deploy-test/deploy-test-etcd.sh` 가 + 단일 출처다. ## 카탈로그 앱 → cnpg-cluster 전환 배포 테스트 (`deploy-test-app-with-cnpg.sh`) diff --git a/.claude/image-authoring.md b/.claude/image-authoring.md index c77676c..882a626 100644 --- a/.claude/image-authoring.md +++ b/.claude/image-authoring.md @@ -51,7 +51,7 @@ security-catalog 레포에서 검증한 자체 빌드 프레임워크를 포팅 `keycloak` 은 업스트림이 UBI9 기반이라 "업스트림과 최대한 동일하게" 와 정면으로 충돌했고, **카탈로그 내 일관성을 우선**했다 — 먼저 들어온 이미지들이 전부 BCI 이고 trivy 의 SLES 15.7 커버리지가 양성 대조로 실측 확인돼 있다 -(`doc/analysis/sles-oval-measurement.md`). +(게이트의 `CoverageProbe` — [doc/sbom-pipeline.md](../doc/sbom-pipeline.md)). - 이 정책과 "업스트림과 최대한 동일하게" 가 충돌하면 **무엇을 우선했는지와 왜인지를 해당 이미지 README 에 남긴다.** 정책이 있다고 기록을 생략하지 않는다. diff --git a/CLAUDE.md b/CLAUDE.md index aef5d68..57145b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,6 +41,7 @@ dip-catalog/ ├── cve-exceptions.json # 게이트 승인 예외 목록 ├── catalog-stack-classification.md # 차트 스택 분류·우선순위(P0~P2) ├── define-chart-resources.md # 차트별 Small/Medium/Large 리소스 프로파일 + ├── decisions/ # ADR — 재측정으로 복원되지 않는 이미지·차트 선택 근거 └── migrations/ # 레포 간 이관 핸드오프 문서 ``` @@ -197,6 +198,7 @@ extract-helm-images.sh → generate-sbom.sh → scan-sbom.sh → cve-gate.py |------|--------| | **무엇을 왜 바꿨나** (완료된 작업의 경위) | 커밋 메시지 · PR 설명 · `images//README.md` | | **다시 밟지 말아야 할 함정** | [.claude/pitfalls.md](.claude/pitfalls.md) · [.claude/image-authoring.md](.claude/image-authoring.md) · 해당 Skill | +| **후보를 비교해 하나를 고른 근거** | [doc/decisions/](doc/decisions/) — ADR. 선택지가 하나뿐인 조치는 여기 쓰지 않는다 | | **추적·논의·배정이 필요한 미결** | GitHub 이슈 | | **지금 상태와 다음에 할 일** | [MEMORY.md](MEMORY.md) — 위 셋에 속하면 여기 남기지 않고 링크만 | diff --git a/MEMORY.md b/MEMORY.md index f4ccb4c..2116a24 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -36,7 +36,6 @@ | [#31](https://github.com/paasup/dip-catalog/issues/31) | 자체 빌드: 같은 날 재빌드 시 태그 충돌로 노드 캐시가 반영을 막는다 | 선택지 3안, 미결정 | | [#30](https://github.com/paasup/dip-catalog/issues/30) | `SBOM_PIPELINE_IMAGE` 를 `docker.io/paasup` 으로 이전할지 | 수동 작업 3종, 미결정 | | [#32](https://github.com/paasup/dip-catalog/issues/32) | adc 이미지 배포 검증 미완료 (게이트만 PASS) | apisix-ingress-controller 와 함께 배포 필요 | -| [#33](https://github.com/paasup/dip-catalog/issues/33) | 이미지 문서가 이 레포에 없는 `doc/decisions`·`doc/analysis` 를 인용 | 우선순위 낮음 | | [#19](https://github.com/paasup/dip-catalog/issues/19) | 카탈로그 CVE 트리아지 기준선 | 게이트 강제 전환의 선행 조건 | | [#7](https://github.com/paasup/dip-catalog/issues/7) | CVE 조치 파이프라인 (상위 추적 이슈) | 게이트 강제력 전환 결정이 여기 있다 | | [#3](https://github.com/paasup/dip-catalog/issues/3) · [#9](https://github.com/paasup/dip-catalog/issues/9) | SBOM 파이프라인 도입 · postgresql-ha 대체 | 본문상 완결로 보인다 — **닫을지 확인 필요** | diff --git a/doc/decisions/0001-cnpg-postgresql-image.md b/doc/decisions/0001-cnpg-postgresql-image.md new file mode 100644 index 0000000..5210e97 --- /dev/null +++ b/doc/decisions/0001-cnpg-postgresql-image.md @@ -0,0 +1,63 @@ +# 0001. cnpg-cluster 의 PostgreSQL 이미지를 SUSE BCI 자체 빌드로 한다 + +- 날짜: 2026-07-28 (결정) · 2026-08-03 (`docker.io/paasup` 로 재빌드) +- 상태: 확정 +- 원본: security-catalog ADR 0001 — dip-catalog 맥락으로 다시 썼다([README](README.md) 참고) + +## 결정 + +`manifests/helm/cnpg-cluster/1.0.0` 의 PostgreSQL 이미지를 SUSE BCI 15.7 기반 자체 빌드로 +한다. 현재 태그는 [custom-values.yaml](../../manifests/helm/cnpg-cluster/1.0.0/custom-values.yaml) +의 `postgresql.imageName` 이 단일 출처이고, 빌드 정의는 +[images/cnpg-postgresql/](../../images/cnpg-postgresql/)(`suse.Dockerfile` + `suse.build.env`)다. + +## 배경 + +이전 값 `ghcr.io/cloudnative-pg/postgresql:18.4-system-trixie` 에 두 문제가 있었다. + +1. `system` 은 **업스트림에서 deprecated 된 타입**이다 (in-core barman phase out 예정) +2. 실효 CRITICAL/HIGH **6/26**, 게이트 차단 **32건** (2026-07-28 실측) + +후보 셋을 비교했다. + +| | A `standard-trixie` | B ubuntu:24.04 자체 빌드 | **C SUSE BCI 15.7 자체 빌드** | +| --- | --- | --- | --- | +| 실효 고유 C/H | 23 | 0 | 0 | +| 측정 가능성 | trivy 완전 | trivy 완전 | trivy 완전 | +| 서명·attestation | ✅ | ❌ | ❌ | +| 미조사 사각지대 | 0 | 18건 | 18건 | +| 배포 검증 | — | failover 2초 | failover 3초 | +| gid | `postgres` | ⚠️ `tape` 가 gid 26 선점 | `postgres` | + +## 근거 + +- **A 의 차단 23건은 전부 수정 버전이 없다.** Debian 판정이 `unimportant` 9 / `no-dsa` 7 / + `postponed` 3 / 미분류 4 로, 어떤 조치로도 해소되지 않는다. KEV 교집합은 0건이다. +- **C 는 0건이고, 그 0건이 측정된 0건이다.** 결정 당시에는 "trivy 가 SLES 15.7 을 커버하지 + 않으므로 직접 평가해야 한다" 고 봤는데 **그 전제가 틀렸다**(2026-07-29 재측정). 깨끗한 + 이미지도 findings 0 이라 "0건" 과 "데이터 없음" 이 구분되지 않았던 것이 원인이다. 올바른 + 검사는 양성 대조이고, dip-catalog 는 그것을 `CoverageProbe` 로 **매 스캔마다** 한다 + ([doc/sbom-pipeline.md](../sbom-pipeline.md)). +- **C 는 B 의 gid 위생 문제가 없다.** B 는 gid 26 이 `tape` 그룹에 선점되어 있다. +- **C 는 배포 검증을 통과했다** — CNPG 오퍼레이터 통합, 롤링 전환, 데이터 보존, failover 3초. +- **SUSE 공식 PostgreSQL 차트는 대안이 아니었다** — 확장이 `plpgsql` 하나뿐이다. C 는 + `pgaudit`·`pgvector`·contrib 를 포함한다. + +## 받아들인 비용 + +- **업스트림 서명·provenance·SBOM attestation 을 잃는다.** +- **재빌드 책임을 진다.** PostgreSQL 마이너 릴리스마다, 베이스 보안 업데이트마다. 후보 B 에서 + 이미 실증됐다 — 빌드 후 하루도 안 되어 glibc 업데이트로 findings 38 → 46. +- **업스트림이 테스트하지 않는 구성이다.** CNPG bake 매트릭스는 Debian 3종뿐이다. +- **`pg-failover-slots` 확장이 없다.** PGDG zypp 저장소에 패키지가 없다. +- **미조사 사각지대 18건.** SUSE 는 "미평가" 를 문서 부재로 표현하므로 **무엇이 미평가인지 + 열거할 수 없다.** 이것은 어떤 도구를 쓰든 같다(이 이미지 기준 벤더 데이터가 언급하지 않는 + 패키지 61개 / 176개). `0/0` 의 근거가 그만큼 불완전하다. + +## 재검토 조건 + +- **A 의 차단 23건에 수정 버전이 도착하면** — 업스트림 이미지로 돌아가는 것이 유리해진다. + 게이트 리포트의 `status: fixed` 여부로 감지된다. +- **사각지대 18건 조사 결과가 C 에 불리하면.** +- **재빌드 부담이 실제로 문제가 되면** — 분기 1회 이상 재빌드가 필요해지는 경우. +- **SUSE 가 BCI 기반 CNPG 호환 이미지를 직접 발행하면** — 자체 빌드가 불필요해진다. diff --git a/doc/decisions/0002-cloudnative-pg-operator-self-build.md b/doc/decisions/0002-cloudnative-pg-operator-self-build.md new file mode 100644 index 0000000..866e4b1 --- /dev/null +++ b/doc/decisions/0002-cloudnative-pg-operator-self-build.md @@ -0,0 +1,72 @@ +# 0002. cloudnative-pg 오퍼레이터를 소스 컴파일 자체 빌드로 대체하고, 자체 빌드 오케스트레이션을 하나로 통일한다 + +- 날짜: 2026-07-30 (결정) · 2026-08-04 (`docker.io/paasup` 로 재빌드) +- 상태: 확정 +- 원본: security-catalog ADR 0005 — dip-catalog 맥락으로 다시 썼다([README](README.md) 참고) + +## 결정 + +`manifests/helm/cloudnative-pg/0.29.0` 의 오퍼레이터 이미지를 자체 빌드로 한다. 현재 태그는 +[custom-values.yaml](../../manifests/helm/cloudnative-pg/0.29.0/custom-values.yaml) 의 +`image.repository`/`image.tag` 가 단일 출처이고, 빌드 정의는 +[images/cloudnative-pg/](../../images/cloudnative-pg/)다 — 업스트림 `release-1.30` 브랜치의 +pinned commit 을 직접 `go build` 로 컴파일하고, 최종 런타임 베이스는 SUSE BCI(`bci-micro`)다. + +**추가로, 이 작업에서 `scripts/build/build-hardened-image.sh` 를 일반화해 OS 패키지 +재설치형(`cnpg-postgresql`)과 소스 컴파일형(`cloudnative-pg`)이 오케스트레이션 스크립트 +하나를 공유하게 했다.** 자체 빌드 이미지가 늘어날 때마다 방법론이 갈라지지 않게 하기 +위함이다. 계약은 [.claude/image-authoring.md](../../.claude/image-authoring.md) 가 갖는다. + +## 배경 + +업스트림 `cloudnative-pg:1.30.0` 이 게이트에서 실효 HIGH 3건으로 차단됐다 — `CVE-2026-39822` +(stdlib), `CVE-2026-56852`(`golang.org/x/text`), `GHSA-hrxh-6v49-42gf`(grpc). + +셋 다 Go 바이너리에 **정적 링크된 모듈 버전**이 원인이라 앞선 두 레버가 통하지 않는다. + +- 상위 태그 교체 — 상위 태그가 없다(2026-07-30 확인, 최신 릴리스가 여전히 `v1.30.0`) +- 베이스 OS 교체 — 배포 이미지 베이스가 distroless 라 OS 패키지가 사실상 0개다 + +자체 빌드만 유효한 대응이었다. 세 CVE 모두 업스트림 `release-1.30` 브랜치에 이미 백포트돼 +있어, Go 의존성을 직접 올릴 필요 없이 그 커밋을 그대로 컴파일하면 됐다. + +## 근거 + +- **`release-1.30` HEAD 는 실측으로 확인된 안전한 픽업 지점이다.** `go.mod` 비교로 세 CVE 의 + 수정 버전이 전부 포함됨을 확인했고(stdlib 1.26.5, x/text 0.39.0, grpc 1.82.1), 재스캔으로 + 실효 C/H 0/0 을 확인했다. +- **게이트 통과가 "동작함" 을 증명하지 않는다는 원칙이 실제로 작동했다.** 1차 빌드는 게이트를 + 0/0 으로 통과했지만 배포 검증에서 `Cluster` 리컨실이 `invalid architecture: amd64` 로 + 실패했다. 오퍼레이터가 자신의 가용 아키텍처를 `operator/manager_` **파일 존재**로 + 판단하는데, 업스트림의 멀티아치 심볼릭 링크를 "부수 장치" 로 오판해 제거했기 때문이다. + 같은 바이너리를 `/manager` 와 `/operator/manager_amd64` 양쪽에 COPY 해 해결했다. +- **베이스 OS 는 BCI 로 통일한다는 결정([0001](0001-cnpg-postgresql-image.md))이 이 이미지에도 + 적용된다.** 최초 설계는 "업스트림과 최대한 동일하게" 를 따라 distroless 를 그대로 썼으나 + 카탈로그 정책과 어긋나 `bci-micro` 로 교체했고, 교체 후에도 게이트·배포 검증 PASS 를 + 유지하는 것을 확인했다. +- **오케스트레이션 통일이 회귀를 만들지 않는 것을 확인했다** — 스크립트를 일반화한 직후 기존 + `cnpg-postgresql` 을 다시 빌드해 태그·게이트 결과가 동일함을 확인했다. + +## 받아들인 비용 + +- **업스트림 서명·provenance·SBOM attestation 을 잃는다.** ADR [0001](0001-cnpg-postgresql-image.md) + 과 동일한 비용이다. +- **`SOURCE_COMMIT` 갱신을 사람이 한다.** 자동 추적하지 않는다 — 다음 CVE 발생 시 그 시점의 + 유지보수 브랜치 최신 커밋을 사람이 골라 `source.build.env` 를 고쳐 PR 을 여는 것 자체가 + 트리거다. +- **`release-1.30` 은 순수 패치 백포트가 아니다.** 기능 커밋 2개(`feat(pooler)` auth_user, + `feat` check_empty_wal_archive)가 섞여 있다. 59 commits 전체를 라인 단위로 감사하지 않았고, + 배포 검증 스모크 수준으로만 회귀 여부를 봤다. +- **업스트림이 테스트하지 않는 구성이다.** BCI 기반 오퍼레이터 빌드는 CNPG 의 CI 매트릭스에 없다. +- **`bci-micro` 는 `bci-base` 와 달리 nonroot 계정이 없어 직접 만들어야 한다** — BCI + micro/minimal 계열을 쓰는 다른 자체 빌드 이미지도 같은 작업이 필요하다. + +## 재검토 조건 + +- **업스트림이 `v1.30.1` 이상을 정식 릴리스하면** — 상위 태그 교체가 다시 가능해지므로 자체 + 빌드보다 우선한다. +- **`release-1.30` 의 기능 커밋 2개가 실제 회귀를 일으키면** — 기능 커밋 이전 시점 등 다른 + pinned commit 으로 되돌린다. +- **SUSE 가 CNPG 호환 공식 오퍼레이터 이미지를 발행하면** — 자체 빌드가 불필요해진다. +- **`bci-micro` 에서 예기치 못한 런타임 문제(CA 신뢰 체인, TLS 등)가 나오면** — `bci-base` 로 + 승격하거나 다른 BCI 변종을 재검토한다. diff --git a/doc/decisions/0003-etcd-chart-selection.md b/doc/decisions/0003-etcd-chart-selection.md new file mode 100644 index 0000000..b429603 --- /dev/null +++ b/doc/decisions/0003-etcd-chart-selection.md @@ -0,0 +1,64 @@ +# 0003. etcd 배포 방식 — `groundhog2k/etcd` 단일 차트 채택, replicas=1(SPOF 수용) + +- 날짜: 2026-07-31 +- 상태: 확정 +- 원본: security-catalog ADR 0006 — dip-catalog 맥락으로 다시 썼다([README](README.md) 참고) + +## 결정 + +카탈로그의 etcd 배포 방식으로 `groundhog2k/etcd` 차트(오퍼레이터 없는 단일 StatefulSet +차트)를 채택한다. 용도는 **단일 APISIX 인스턴스의 설정 저장소**이며, 쿼럼 HA 대신 +`replicas: 1` 로 배포한다 — **단일 장애점을 의도적으로 수용한다.** 기존 `bitnami/etcd` 기반 +배포는 이 차트로 교체한다. + +카탈로그 위치는 [manifests/helm/etcd/1.1.12/](../../manifests/helm/etcd/1.1.12/)다. +`replicas: 1` 은 `custom-values.yaml`(APISIX 용도), 쿼럼이 필요한 다른 소비자를 위한 +`replicas: 3` 프로파일은 `dip-values.yaml` 에 있다. + +## 배경 + +dev 클러스터 `apisix` 네임스페이스에 이미 `data-apisix-etcd-0` PVC 가 있었다 — `bitnami/etcd` +기반 APISIX 배포가 떠 있었다는 뜻이다. 이를 카탈로그 방식으로 정식 편입하며 이미지 출처를 +재검토했다. 후보는 `groundhog2k/etcd`(채택), `bitnami/etcd`(기존), `aenix-io/etcd-operator` +류의 오퍼레이터 방식이다. + +## 근거 + +**Bitnami 이미지 카탈로그 정책 변경(2025-08)이 결정적이었다.** `docker.io/bitnami/etcd` 는 +2025-08-28 부로 `bitnamilegacy/etcd` 로 이전되어 **동결**됐고(이후 보안 패치 없음), +2025-09-29 공개 카탈로그 삭제 이후 신규 무료 티어는 44개 "development-only" 이미지로 +한정되며 그마저도 **버전 고정 없이 `latest` 태그만 제공**한다. 계속 패치를 받으려면 유료 +구독이 필요하다. + +이는 카탈로그 목표와 두 지점에서 정면 충돌한다. + +1. 패치가 멈춘 이미지는 시간이 지날수록 미수정 CVE 가 구조적으로 누적된다 — + "CRITICAL/HIGH 0건" 을 유지 가능한 상태로 지속할 수 없다. +2. 버전 고정 없는 `latest`-only 배포는 **롤링 태그 금지** 규칙과 애초에 양립하지 않는다 + ([.claude/image-authoring.md](../../.claude/image-authoring.md)). + +`groundhog2k/etcd` 는 업스트림 etcd 프로젝트의 원본 이미지(`quay.io/coreos/etcd`)를 그대로 +쓰므로 이 문제가 없다 — 다른 카탈로그 항목과 동일한 "업스트림 원본 이미지 + 버전 고정 태그" +모델이다. + +**오퍼레이터 방식은 측정 없이 정성적으로 배제했다.** 단일 APISIX 인스턴스에 쿼럼 HA 가 +불필요하다고 판단해, CRD·컨트롤러·RBAC 을 추가로 들이는 비용이 이득보다 크다고 봤다. 실측 +비교는 하지 않았다 — 이 판단은 근거가 정성적이라는 것을 명시해 둔다. + +## 받아들인 비용 + +- **단일 장애점(SPOF)** — `replicas: 1` 이라 etcd Pod/노드 장애 시 APISIX 설정 조회·변경이 + 즉시 불가능해진다. 쿼럼 기반 자동 복구가 없다. +- **자동 백업이 없다** — 차트에 백업 기능이 없고, SPOF 상태에서 장애가 데이터 손실로 이어질 + 수 있다. 스냅샷은 수동(`etcdctl snapshot save`)이다. +- **Bitnami 가 기본 제공하던 부가 기능**(메트릭 익스포터 등 번들 구성)을 잃는다. 필요하면 + `serviceMonitor` 설정으로 별도 구성한다. +- **쿼럼 HA 로 전환하려면 데이터 마이그레이션을 동반한 재배포가 필요하다** — `replicas` 는 + 최초 배포 후 변경 불가다(차트 제약). + +## 재검토 조건 + +- APISIX 를 다중 인스턴스로 확장해 etcd 쿼럼 HA 가 실제로 필요해지면. +- etcd Pod 장애로 인한 SPOF 위험이 운영상 수용 불가능한 빈도로 발생하면. +- `groundhog2k/etcd` 유지보수가 중단되거나, 이 결정의 근거였던 문제(패치 중단·롤링 태그 + 강제)가 이 차트에서도 재현되면. diff --git a/doc/decisions/0004-etcd-image-self-build.md b/doc/decisions/0004-etcd-image-self-build.md new file mode 100644 index 0000000..0caecc2 --- /dev/null +++ b/doc/decisions/0004-etcd-image-self-build.md @@ -0,0 +1,66 @@ +# 0004. etcd 이미지를 소스 컴파일 자체 빌드로 대체한다 + +- 날짜: 2026-07-31 (결정) · 2026-08-04 (`docker.io/paasup` 로 재빌드) +- 상태: 확정 +- 원본: security-catalog ADR 0007 — dip-catalog 맥락으로 다시 썼다([README](README.md) 참고) + +## 결정 + +[manifests/helm/etcd/1.1.12/](../../manifests/helm/etcd/1.1.12/) 의 이미지를 자체 빌드로 +한다. 현재 태그는 `custom-values.yaml` 의 `image.registry`/`image.repository`/`image.tag` 가 +단일 출처이고, 빌드 정의는 [images/etcd/](../../images/etcd/)다 — 업스트림 `v3.7.1` 태그가 +가리키는 commit 을 그대로 컴파일하되, `go.work` 워크스페이스 전역 `replace` 로 +`golang.org/x/text` 만 강제 업그레이드한다(버전은 `source.build.env` 의 `XTEXT_FIX_VERSION`). +최종 런타임 베이스는 SUSE BCI(`bci-micro`) — [0002](0002-cloudnative-pg-operator-self-build.md) +와 같은 패턴이다. + +**추가로, 이 작업에서 `build-image.yml` CI 를 이미지 종류 무관하게 일반화했다** +(`images//catalog.env` + `scripts/build/patch-catalog-tag.py`) — [0002](0002-cloudnative-pg-operator-self-build.md) +의 백로그였다. 자체 빌드 이미지가 둘일 때는 하드코딩으로 버틸 수 있었지만 세 번째가 생기며 +실제로 한계에 부딪혔다. + +## 배경 + +업스트림 `quay.io/coreos/etcd:v3.7.1` 이 게이트에서 실효 HIGH 1건으로 차단됐다 — +`CVE-2026-56852`, `golang.org/x/text` 의 깨진 UTF-8 입력에 대한 `norm.Iter` 무한루프 DoS. + +Go 바이너리에 정적 링크된 모듈 버전이 원인이라 베이스 OS 교체가 통하지 않고, 상위 태그도 +없다(2026-07-31 확인, 최신 릴리스가 여전히 `v3.7.1`). 게다가 [0002](0002-cloudnative-pg-operator-self-build.md) +와 달리 **`release-3.7` 브랜치에 이 수정이 백포트조차 안 돼 있다**(브랜치 HEAD 가 태그와 +완전히 동일). 자체 빌드 외에는 즉시 대응할 방법이 없었다. + +## 근거 + +- **워크스페이스 전역 `replace` 한 줄로 게이트가 PASS 로 바뀌는 것을 실측했다** — 실효 C/H + 0/1 → **0/0**. 이미 백포트된 커밋을 그대로 가져다 쓸 수 없었지만, 취약 모듈이 이 CVE + 하나뿐이라 직접 강제 업그레이드로도 충분했다. +- **기능 검증(단일 노드 기동 + `etcdctl` put/get 왕복)이 통과했다.** 게이트 PASS 가 "동작함" + 을 증명하지 않으므로 `images/etcd/verify.sh` 로 확인한다. 최초 시도는 최종 베이스에 `sed` + 가 없어 실패했다(`sh: sed: command not found`) — `bci-base` 에는 있지만 `bci-micro` 에는 + 없다는 것을 이때 처음 확인했다. 순수 셸 루프로 교체해 해결하고 그 제약을 + [.claude/image-authoring.md](../../.claude/image-authoring.md) 에 기록했다. +- **CI 일반화가 기존 이미지에 회귀를 만들지 않는 것을 확인했다** — `catalog.env` 를 세 + 이미지에 추가한 뒤 매트릭스로 동시에 검증 빌드했고 기존 두 이미지도 동일하게 PASS 했다. +- **태그 치환 로직을 실제 카탈로그 파일 사본에 돌려 검증했다** — `imageName` 단일 필드 + (cnpg-cluster)와 `image:` 분리 필드(cloudnative-pg, etcd) 두 스타일 모두 기존 주석·포매팅을 + 보존한 채 정확히 치환됨을 diff 로 확인했다. + +## 받아들인 비용 + +- **업스트림 서명·provenance·SBOM attestation 을 잃는다.** [0001](0001-cnpg-postgresql-image.md) + ·[0002](0002-cloudnative-pg-operator-self-build.md) 와 동일한 비용이다. +- **`SOURCE_COMMIT`·`XTEXT_FIX_VERSION` 갱신을 사람이 한다.** 자동 추적하지 않는다. +- **매 patch 릴리스마다 이 자체 빌드를 다시 판단해야 한다** — 업스트림이 이 CVE 를 + `release-3.7` 에 백포트하기 전까지는 "이미 백포트된 안전한 픽업 지점" 이 없어 재검토 부담이 + [0002](0002-cloudnative-pg-operator-self-build.md) 보다 크다. +- **업스트림이 테스트하지 않는 구성이다.** BCI 기반 etcd 빌드는 etcd 의 CI 매트릭스에 없다. +- **강제 업그레이드가 `server`/`etcdctl`/`etcdutl` 각각에 반영됐는지는 SBOM 스캔으로만 간접 + 확인했다** — `go version -m` 직접 대조는 하지 않았다. + +## 재검토 조건 + +- **etcd 가 `release-3.7` 에 이 CVE 를 백포트하고 `v3.7.2` 이상을 릴리스하면** — 상위 태그 + 교체가 다시 가능해지므로 자체 빌드보다 우선한다. +- **`bci-micro` 에서 예기치 못한 런타임 문제가 나오면** — `bci-base` 로 승격하거나 다른 BCI + 변종을 재검토한다([0002](0002-cloudnative-pg-operator-self-build.md) 와 동일한 조건). +- **다중 노드(쿼럼) 배포 검증에서 이 자체 빌드 이미지에 문제가 발견되면.** diff --git a/doc/decisions/README.md b/doc/decisions/README.md new file mode 100644 index 0000000..b9d6482 --- /dev/null +++ b/doc/decisions/README.md @@ -0,0 +1,47 @@ +# 결정 기록 (ADR) + +카탈로그의 이미지·차트 선택 중 **재측정으로 복원되지 않는 것**만 여기 둔다. + +CVE 건수·커버리지·패키지 버전 같은 수치는 게이트가 매 실행마다 다시 재므로 문서로 남기지 +않는다([doc/sbom-pipeline.md](../sbom-pipeline.md)). 반면 "왜 이 후보를 골랐고 무엇을 비용으로 +받아들였나" 는 어디에서도 재생성되지 않는다 — 그것이 ADR 의 범위다. + +| # | 결정 | 날짜 | 상태 | +|---|---|---|---| +| [0001](0001-cnpg-postgresql-image.md) | cnpg-cluster 의 PostgreSQL 이미지를 SUSE BCI 자체 빌드로 한다 | 2026-07-28 | 확정 | +| [0002](0002-cloudnative-pg-operator-self-build.md) | cloudnative-pg 오퍼레이터를 소스 컴파일 자체 빌드로 대체한다 | 2026-07-30 | 확정 | +| [0003](0003-etcd-chart-selection.md) | etcd 는 `groundhog2k/etcd` 단일 차트 · replicas=1 로 배포한다 | 2026-07-31 | 확정 | +| [0004](0004-etcd-image-self-build.md) | etcd 이미지를 소스 컴파일 자체 빌드로 대체한다 | 2026-07-31 | 확정 | + +## 이 문서들의 출처 + +네 결정은 **security-catalog 프로젝트에서 내려졌고**, 그 레포의 ADR 0001·0005·0006·0007 이 +원본이다. dip-catalog 의 이미지·차트가 그 결정의 산물이므로 이 레포에도 근거가 있어야 한다. + +**원문을 복사하지 않고 다시 썼다.** 원본은 같은 레포의 다른 문서(`doc/analysis/*`, +`doc/plans/*`)를 근거로 인용하는데, 그것들까지 연쇄로 가져오면 dip-catalog 에 이중 기록이 +생기고 그러고도 링크가 또 밖으로 나간다. 그래서 **결론과 근거만 추려 자립적으로** 옮겼다 — +이 디렉토리의 문서는 레포 밖을 가리키는 링크가 없다. + +이관하지 않은 것과 그 이유. 아래 경로는 **security-catalog 안의 경로이고 이 레포에는 없다** — +링크로 걸지 않은 이유가 그것이다. + +| security-catalog 의 원본 | 왜 안 가져왔나 | +|---|---| +| `analysis/sles-oval-measurement.md` | 스캐너 커버리지 실측. 원문 스스로 "재측정하면 갱신된다" 고 밝히는 스냅샷이고, dip-catalog 는 이것을 `CoverageProbe` 로 **매 스캔마다 자동 재측정**한다 | +| `analysis/*-cve.md` (3종) | 착수 시점의 CVE 스냅샷. 결론은 각 ADR 과 `images//README.md` 에 들어 있고, 현재 수치는 게이트가 낸다 | +| `cve-zero-pipeline.md` · `architecture/build-pipeline.md` | [doc/sbom-pipeline.md](../sbom-pipeline.md) 와 [.claude/image-authoring.md](../../.claude/image-authoring.md) 가 dip-catalog 의 단일 출처다 | +| `image-selection.md` | 이미지 선정 규칙은 [.claude/image-authoring.md](../../.claude/image-authoring.md) 가 갖는다 | +| `charts/*/deploy-test.md` | 배포 검증은 `scripts/deploy-test/*.sh` 가 실행하고 절차는 [.claude/deploy-test-procedure.md](../../.claude/deploy-test-procedure.md) 가 갖는다. 기록은 실행 산출물이라 문서로 고정하지 않는다 | + +## 새 ADR 을 언제 쓰나 + +- 이미지·차트 후보를 비교해 하나를 골랐고, **탈락한 후보가 있다** +- 자체 빌드로 가면서 **업스트림 서명·provenance 를 잃는 비용을 받아들였다** +- 기본값을 의도적으로 어겼다 (예: HA 대신 SPOF 수용) + +반대로 "게이트가 차단해서 태그를 올렸다" 처럼 선택지가 하나뿐인 조치는 ADR 이 아니다 — +커밋 메시지와 PR 설명이 기록이다([CLAUDE.md](../../CLAUDE.md) "작업 기록을 어디에 남기는가"). + +파일명은 `NNNN-<주제>.md`, 번호는 이어서 붙인다. 결정이 뒤집히면 문서를 지우지 않고 상태를 +`대체됨 → NNNN` 으로 바꾼다. diff --git a/images/cloudnative-pg/README.md b/images/cloudnative-pg/README.md index 2db11e8..dac53bf 100644 --- a/images/cloudnative-pg/README.md +++ b/images/cloudnative-pg/README.md @@ -4,11 +4,11 @@ CNPG 오퍼레이터(컨트롤러) 이미지를 업스트림 소스에서 직접 `manifests/helm/cloudnative-pg/0.29.0/custom-values.yaml`·`dip-values.yaml` 의 `image.repository`/`image.tag` 가 이 산출물을 가리킨다. -security-catalog 프로젝트에서 포팅했다. 목표 정의·CVE 조사 실측·이미지 채택 결정 -근거(각각 `doc/cve-zero-pipeline.md`, `doc/analysis/cloudnative-pg-operator-cve.md`, -`doc/decisions/0005-cloudnative-pg-operator-self-build.md`)는 security-catalog 프로젝트에 -있고 dip-catalog 에는 아직 이관되지 않았다 — 신규 자체 빌드 이미지 추가 절차 전반은 -[.claude/image-authoring.md](../../.claude/image-authoring.md) 참고. +security-catalog 프로젝트에서 포팅했다. 채택 결정과 받아들인 비용은 +[ADR 0002](../../doc/decisions/0002-cloudnative-pg-operator-self-build.md), 게이트·SBOM +파이프라인은 [doc/sbom-pipeline.md](../../doc/sbom-pipeline.md), 신규 자체 빌드 이미지 추가 +절차 전반은 [.claude/image-authoring.md](../../.claude/image-authoring.md) 가 갖는다. +아래 CVE 번호는 자체 빌드에 착수한 시점의 근거다 — **현재 수치는 게이트가 낸다.** > **자체 빌드는 대응 우선순위 3번이다.** 상위 태그 교체·베이스 OS 교체로 목표를 > 만족할 수 있으면 그 쪽을 쓴다. 자체 빌드는 업스트림 서명·provenance·SBOM attestation 을 diff --git a/images/cloudnative-pg/source.Dockerfile b/images/cloudnative-pg/source.Dockerfile index 2b88c78..4fe88e9 100644 --- a/images/cloudnative-pg/source.Dockerfile +++ b/images/cloudnative-pg/source.Dockerfile @@ -11,7 +11,7 @@ # 은 OS 패키지가 아니라 바이너리에 정적 링크된 Go 모듈 버전이 원인이다. 배포 이미지 베이스가 # distroless(OS 패키지 사실상 0개)라 베이스 OS 교체로는 고칠 수 없다 — 소스를 다시 컴파일해야 # 한다. 세 CVE 모두 업스트림 release-1.30 브랜치에 이미 백포트돼 있다(SOURCE_COMMIT 이 그 -# 커밋을 가리킨다) — 근거: doc/analysis/cloudnative-pg-operator-cve.md +# 커밋을 가리킨다) — 근거: doc/decisions/0002-cloudnative-pg-operator-self-build.md # # 업스트림과의 대응 관계 (release-1.30 브랜치 Dockerfile 기준) # go build (Makefile build-manager) → 동일 (ldflags 까지 그대로) diff --git a/images/cloudnative-pg/source.build.env b/images/cloudnative-pg/source.build.env index 57664dc..ee3be0d 100644 --- a/images/cloudnative-pg/source.build.env +++ b/images/cloudnative-pg/source.build.env @@ -4,8 +4,8 @@ # 이 이미지는 소스를 직접 컴파일하므로 "베이스 OS" 선택지가 없다 — BASE_OS=source 로 호출한다: # IMAGE=cloudnative-pg BASE_OS=source bash scripts/build/build-hardened-image.sh # -# 왜 자체 빌드하는가·CVE 근거 → doc/analysis/cloudnative-pg-operator-cve.md -# 결정 → doc/decisions/0004-cloudnative-pg-operator-self-build.md +# 왜 자체 빌드하는가·CVE 근거 → README.md +# 결정 → doc/decisions/0002-cloudnative-pg-operator-self-build.md DOCKERFILE=source.Dockerfile TARGET=final diff --git a/images/cnpg-postgresql/README.md b/images/cnpg-postgresql/README.md index 9803cde..4ded64c 100644 --- a/images/cnpg-postgresql/README.md +++ b/images/cnpg-postgresql/README.md @@ -3,11 +3,11 @@ CNPG 오퍼레이터가 관리하는 PostgreSQL 인스턴스 이미지를 직접 빌드한다. `manifests/helm/cnpg-cluster/1.0.0/custom-values.yaml` 의 `imageName` 이 이 산출물을 가리킨다. -security-catalog 프로젝트에서 포팅했다. 목표 정의·이미지 선택 규칙·후보 비교 실측·이미지 -채택 결정·빌드 자동화(CI) 문서(`doc/cve-zero-pipeline.md`, `doc/image-selection.md`, -`doc/analysis/cnpg-image-vuln-comparison.md`, `doc/decisions/0001-cnpg-postgresql-image.md`, -`doc/architecture/build-pipeline.md`)는 security-catalog 프로젝트에 있고 dip-catalog 에는 -아직 이관되지 않았다. +security-catalog 프로젝트에서 포팅했다. 채택 결정·후보 비교표·받아들인 비용은 +[ADR 0001](../../doc/decisions/0001-cnpg-postgresql-image.md), 게이트·SBOM 파이프라인은 +[doc/sbom-pipeline.md](../../doc/sbom-pipeline.md), 이미지 선정 규칙과 빌드 프레임워크는 +[.claude/image-authoring.md](../../.claude/image-authoring.md) 가 갖는다. +아래 CVE 수치는 자체 빌드에 착수한 시점의 근거다 — **현재 수치는 게이트가 낸다.** > **자체 빌드는 대응 우선순위 3번이다.** 상위 태그 교체·베이스 OS 교체로 목표를 > 만족할 수 있으면 그 쪽을 쓴다. 자체 빌드는 업스트림 서명·provenance·SBOM attestation 을 diff --git a/images/cnpg-postgresql/suse.Dockerfile b/images/cnpg-postgresql/suse.Dockerfile index 8b88f9d..16c64b7 100644 --- a/images/cnpg-postgresql/suse.Dockerfile +++ b/images/cnpg-postgresql/suse.Dockerfile @@ -22,7 +22,7 @@ # 양성 대조(SBOM 사본에 취약 버전 주입 후 재스캔)로 재측정한 결과 오판이었다 — trivy 는 # 13건을 잡았다(SUSE-SU ID 포함). 0건은 이 이미지가 실제로 최신이라서다. # scan-sbom.sh 의 커버리지 자가진단이 매 스캔마다 이를 확인한다. -# 경위: doc/analysis/sles-oval-measurement.md +# 판정 메커니즘: doc/sbom-pipeline.md (CoverageProbe) ARG BASE=registry.suse.com/bci/bci-base:15.7 FROM $BASE AS minimal diff --git a/images/cnpg-postgresql/suse.build.env b/images/cnpg-postgresql/suse.build.env index 4feb45e..79773c2 100644 --- a/images/cnpg-postgresql/suse.build.env +++ b/images/cnpg-postgresql/suse.build.env @@ -4,7 +4,7 @@ # BASE_OS 기본값이 이 파일(suse)이다 — 카탈로그의 imageName 이 실제로 이 정의로 빌드된다. # # 측정 가능성 — trivy 는 SLES 15.7 을 정상 커버한다 (2026-07-29 재측정, 이전 판단 정정). -# 상세: doc/analysis/sles-oval-measurement.md +# 매 스캔마다 커버리지 자가진단(CoverageProbe)이 재확인한다 — doc/sbom-pipeline.md DOCKERFILE=suse.Dockerfile TARGET=patched diff --git a/images/etcd/README.md b/images/etcd/README.md index c1cf9c9..5f796ab 100644 --- a/images/etcd/README.md +++ b/images/etcd/README.md @@ -4,11 +4,12 @@ etcd 서버/`etcdctl`/`etcdutl` 바이너리를 업스트림 소스에서 직접 `manifests/helm/etcd/1.1.12/custom-values.yaml` 의 `image.repository`/`image.tag` 가 이 산출물을 가리킨다. -security-catalog 프로젝트에서 포팅했다. 목표 정의·CVE 조사 실측·이미지 채택 결정 -근거(각각 `doc/cve-zero-pipeline.md`, `doc/analysis/etcd-cve.md`, -`doc/decisions/0007-etcd-image-self-build.md`)는 security-catalog 프로젝트에 있고 -dip-catalog 에는 아직 이관되지 않았다 — 신규 자체 빌드 이미지 추가 절차 전반은 -[.claude/image-authoring.md](../../.claude/image-authoring.md) 참고. +security-catalog 프로젝트에서 포팅했다. 채택 결정과 받아들인 비용은 +[ADR 0004](../../doc/decisions/0004-etcd-image-self-build.md), 이 차트를 고른 이유는 +[ADR 0003](../../doc/decisions/0003-etcd-chart-selection.md), 게이트·SBOM 파이프라인은 +[doc/sbom-pipeline.md](../../doc/sbom-pipeline.md), 신규 자체 빌드 이미지 추가 절차 전반은 +[.claude/image-authoring.md](../../.claude/image-authoring.md) 가 갖는다. +아래 CVE 번호는 자체 빌드에 착수한 시점의 근거다 — **현재 수치는 게이트가 낸다.** > **자체 빌드는 대응 우선순위 3번이다.** 상위 태그 교체·베이스 OS 교체로 목표를 > 만족할 수 있으면 그 쪽을 쓴다. 자체 빌드는 업스트림 서명·provenance·SBOM attestation 을 diff --git a/images/etcd/source.Dockerfile b/images/etcd/source.Dockerfile index 5e152b9..13bc790 100644 --- a/images/etcd/source.Dockerfile +++ b/images/etcd/source.Dockerfile @@ -10,7 +10,7 @@ # (CVE-2026-56852, golang.org/x/text)은 OS 패키지가 아니라 바이너리에 정적 링크된 Go # 모듈 버전이 원인이다. etcd v3.7.1/release-3.7 브랜치는 go.mod 에 x/text v0.37.0(취약)을 # 고정하고 있고 상위 패치 릴리스가 아직 없다(2026-07-31 확인) — 베이스 OS 교체로는 -# 고쳐지지 않으므로 자체 빌드가 유일한 대응이다. 근거: doc/analysis/etcd-cve.md +# 고쳐지지 않으므로 자체 빌드가 유일한 대응이다. 근거: doc/decisions/0004-etcd-image-self-build.md # # 업스트림과의 대응 관계 (v3.7.1 태그, scripts/build_lib.sh 의 etcd_build() 기준) # cd server && go build ... → 동일 (ldflags 는 GitSHA 대신 pinned commit 을 직접 주입 — 아래 참고) @@ -23,7 +23,7 @@ # etcd 는 Go 워크스페이스(go.work)로 서버/etcdctl/etcdutl 모듈을 함께 관리한다. # 각 go.mod 를 개별로 고치는 대신 go.work 에 워크스페이스 전역 replace 한 줄만 추가해 # golang.org/x/text 를 강제 업그레이드한다 — 업스트림과의 차이를 최소로 유지하기 위함 -# (doc/image-selection.md 7절 "빌드 원칙: 업스트림과의 차이를 최소로 유지한다"). +# (.claude/image-authoring.md — 업스트림과의 차이를 최소로 유지한다). # # ldflags — GitSHA 대신 pinned commit 을 직접 주입하는 이유 # 업스트림은 `git rev-parse --short HEAD` 로 얻은 값을 버전 문자열에 심는다. 우리는 diff --git a/images/etcd/source.build.env b/images/etcd/source.build.env index d102d18..9161dac 100644 --- a/images/etcd/source.build.env +++ b/images/etcd/source.build.env @@ -4,8 +4,8 @@ # 이 이미지는 소스를 직접 컴파일하므로 "베이스 OS" 선택지가 없다 — BASE_OS=source 로 호출한다: # IMAGE=etcd BASE_OS=source bash scripts/build/build-hardened-image.sh # -# 왜 자체 빌드하는가·CVE 근거 → doc/analysis/etcd-cve.md -# 결정 → doc/decisions/0007-etcd-image-self-build.md +# 왜 자체 빌드하는가·CVE 근거 → README.md +# 결정 → doc/decisions/0004-etcd-image-self-build.md DOCKERFILE=source.Dockerfile TARGET=final diff --git a/images/keycloak/README.md b/images/keycloak/README.md index 57182c0..827ec2f 100644 --- a/images/keycloak/README.md +++ b/images/keycloak/README.md @@ -71,7 +71,7 @@ Security Advisory 실측). 스캔 시점 trivy DB 에 아직 없어 게이트에 업스트림은 `ubi9` 빌더 + `ubi9-micro` 최종이다. 카탈로그의 먼저 들어온 자체 빌드 이미지들이 전부 SUSE BCI 이고 trivy 의 SLES 15.7 커버리지가 양성 대조로 실측 확인돼 -있어(`doc/analysis/sles-oval-measurement.md`), **"업스트림과 최대한 동일하게" 보다 +있어(게이트의 `CoverageProbe` — [doc/sbom-pipeline.md](../../doc/sbom-pipeline.md)), **"업스트림과 최대한 동일하게" 보다 카탈로그 내 일관성을 우선**했다. **BCI 16.0 이 나와 있지만 15.7 을 쓴다 — 최신이 이 이미지에는 더 낡았다**(2026-08-07 diff --git a/images/keycloak/suse.Dockerfile b/images/keycloak/suse.Dockerfile index 2355e92..3ae8ad8 100644 --- a/images/keycloak/suse.Dockerfile +++ b/images/keycloak/suse.Dockerfile @@ -20,7 +20,7 @@ # 왜 SUSE BCI 인가 # 기존 자체 빌드 3종(cloudnative-pg·cnpg-postgresql·etcd)이 전부 SUSE BCI 이고, # trivy 의 SLES 15.7 커버리지는 양성 대조로 실측 확인돼 있다 -# (doc/analysis/sles-oval-measurement.md). "업스트림과 최대한 동일하게" 원칙과 +# (게이트의 CoverageProbe — doc/sbom-pipeline.md). "업스트림과 최대한 동일하게" 원칙과 # 충돌하지만 카탈로그 내 일관성을 우선했다 — 근거는 README.md. # # ARG 는 반드시 첫 FROM 이전(전역 스코프)에 선언한다. 스테이지 내부에 두면 지역 변수가 diff --git a/manifests/helm/cloudnative-pg/0.29.0/BUILD-README.md b/manifests/helm/cloudnative-pg/0.29.0/BUILD-README.md index 5a447e7..0b5ca5a 100644 --- a/manifests/helm/cloudnative-pg/0.29.0/BUILD-README.md +++ b/manifests/helm/cloudnative-pg/0.29.0/BUILD-README.md @@ -77,12 +77,13 @@ RBAC 규칙 추가/삭제, webhook 경로 변경, CRD 필드 변경을 특히 | --- | --- | | `CUSTOM-README.md` | 차트/operator 버전 번호, 업그레이드 명령의 CRD URL, 변경된 values 키 | | `BUILD-README.md` | 위 버전 대응 표에 신규 행 추가 | -| `doc/charts/cnpg/deploy-test.md` | 신규 버전으로 배포 테스트 재실행 후 결과 갱신 | +| `scripts/deploy-test/deploy-test-cnpg-cluster.sh` | 신규 버전으로 배포 검증 재실행 | | `manifests/helm/cnpg-cluster/*/CUSTOM-README.md` | operator 버전 호환성 명시 부분 | ## 4. 배포 검증 -`doc/charts/cnpg/deploy-test.md` 의 검증 절차를 신규 버전으로 재실행한다. +`scripts/deploy-test/deploy-test-cnpg-cluster.sh` 를 신규 버전으로 재실행한다 +(절차: `.claude/deploy-test-procedure.md`). 최소한 다음 4개는 통과해야 한다. 1. operator Pod `Running`, CRD 11개 생성 diff --git a/manifests/helm/cloudnative-pg/0.29.0/CUSTOM-README.md b/manifests/helm/cloudnative-pg/0.29.0/CUSTOM-README.md index 5fe8ca1..8367947 100644 --- a/manifests/helm/cloudnative-pg/0.29.0/CUSTOM-README.md +++ b/manifests/helm/cloudnative-pg/0.29.0/CUSTOM-README.md @@ -165,5 +165,6 @@ kubectl get crd -o name | grep cnpg.io | xargs -r kubectl delete ## 5. 검증 이력 -`doc/charts/cnpg/deploy-test.md` 참고. RKE2 v1.34.1 / Longhorn 환경에서 3-instance 구성, +`scripts/deploy-test/deploy-test-cnpg-cluster.sh` 로 검증한다(절차: `.claude/deploy-test-procedure.md`). +RKE2 v1.34.1 / Longhorn 환경에서 3-instance 구성, failover 3초, 데이터 정합성 유지를 확인했다. diff --git a/manifests/helm/cloudnative-pg/0.29.0/custom-values.yaml b/manifests/helm/cloudnative-pg/0.29.0/custom-values.yaml index 70cb84f..97d4cae 100644 --- a/manifests/helm/cloudnative-pg/0.29.0/custom-values.yaml +++ b/manifests/helm/cloudnative-pg/0.29.0/custom-values.yaml @@ -4,7 +4,7 @@ image: # 자체 빌드(대응 우선순위 c) — 업스트림 1.30.0 이 게이트 차단 HIGH 3건(stdlib·x/text·grpc, # Go 모듈 정적 링크라 베이스 OS 교체로 해소 불가)으로 막혀 release-1.30 브랜치를 직접 - # 컴파일했다. 근거: doc/analysis/cloudnative-pg-operator-cve.md, 결정: doc/decisions/0005. + # 컴파일했다. 근거·결정: doc/decisions/0002-cloudnative-pg-operator-self-build.md. # 빌드 정의: images/cloudnative-pg/. 상위 태그가 나오면(대응 우선순위 a) 되돌리는 것이 우선. repository: docker.io/paasup/cloudnative-pg tag: "1.30.0-security-hardened-20260804" diff --git a/manifests/helm/cnpg-cluster/1.0.0/BUILD-README.md b/manifests/helm/cnpg-cluster/1.0.0/BUILD-README.md index cab477b..92c4415 100644 --- a/manifests/helm/cnpg-cluster/1.0.0/BUILD-README.md +++ b/manifests/helm/cnpg-cluster/1.0.0/BUILD-README.md @@ -91,7 +91,8 @@ diff -u /tmp/old.yaml /tmp/new.yaml ## 6. 배포 검증 -개발 클러스터에 실제 배포해 `doc/charts/cnpg/deploy-test.md` 의 검증 항목을 재실행한다. +개발 클러스터에 실제 배포해 `scripts/deploy-test/deploy-test-cnpg-cluster.sh` 를 재실행한다 +(절차: `.claude/deploy-test-procedure.md`). 최소 통과 기준: 1. `readyInstances = instances`, `phase = Cluster in healthy state` @@ -115,7 +116,7 @@ diff -u /tmp/old.yaml /tmp/new.yaml | `CUSTOM-README.md` | 차트/PostgreSQL 버전, 변경된 values 키, operator 호환 버전 | | `BUILD-README.md` | 이 문서의 절차에 변경이 있으면 반영 | | `dip-values.yaml` / `dip-*-quotas.yaml` | 파라미터 이름이 바뀌었으면 반영 | -| `doc/charts/cnpg/deploy-test.md` | 신규 버전 검증 결과 추가 | +| `scripts/deploy-test/deploy-test-cnpg-cluster.sh` | 신규 버전으로 배포 검증 재실행 | ## 8. PR diff --git a/manifests/helm/cnpg-cluster/1.0.0/CUSTOM-README.md b/manifests/helm/cnpg-cluster/1.0.0/CUSTOM-README.md index a783608..c1b04b3 100644 --- a/manifests/helm/cnpg-cluster/1.0.0/CUSTOM-README.md +++ b/manifests/helm/cnpg-cluster/1.0.0/CUSTOM-README.md @@ -18,8 +18,7 @@ Helm 으로 감싼 PaaSup 자체 제작 차트다. 업스트림 차트가 아니 - **`backup.enabled: false` 면 PITR 이 불가능하다.** 또한 WAL 이 오브젝트 스토리지로 아카이브되지 않아 `walStorage` 볼륨에 계속 쌓인다. 운영 배포는 반드시 백업을 켠다. - **확장(extension)은 `databases` 로 선언한다.** `bootstrap.initdb.postInitApplicationSQL` 에 - `CREATE EXTENSION` 을 넣으면 **오류 없이 무시된다** (operator 1.30.0 에서 확인, - `doc/charts/cnpg/deploy-test.md` 검증 기록 참고). + `CREATE EXTENSION` 을 넣으면 **오류 없이 무시된다** (operator 1.30.0 배포 검증에서 확인). - **`bootstrap` 은 최초 1회만 적용된다.** 이미 생성된 클러스터의 `bootstrap.initdb.database` 를 바꿔도 아무 일도 일어나지 않는다. DB 추가는 `databases` 로 한다. @@ -266,4 +265,5 @@ kubectl -n get pvc -l cnpg.io/cluster= ## 5. 검증 이력 -`doc/charts/cnpg/deploy-test.md` 참고. +`scripts/deploy-test/deploy-test-cnpg-cluster.sh` 로 검증한다 — 절차와 통과 기준은 +`.claude/deploy-test-procedure.md`. diff --git a/manifests/helm/cnpg-cluster/1.0.0/custom-values.yaml b/manifests/helm/cnpg-cluster/1.0.0/custom-values.yaml index b3cae23..f0730ef 100644 --- a/manifests/helm/cnpg-cluster/1.0.0/custom-values.yaml +++ b/manifests/helm/cnpg-cluster/1.0.0/custom-values.yaml @@ -9,13 +9,13 @@ postgresql: # 빌드 정의 → images/cnpg-postgresql/suse.Dockerfile + suse.build.env # # 태그에 빌드일을 포함한다. 같은 앱 버전이라도 베이스 업데이트 결과가 시점마다 다르므로 - # 롤링 태그를 쓰지 않는다 (doc/image-selection.md 2번). + # 롤링 태그를 쓰지 않는다 (.claude/image-authoring.md). imageName: "docker.io/paasup/cnpg-postgresql:18.4-bci15.7-hardened-20260803" # - # trivy 는 SLES 15.7 을 정상 커버한다(2026-07-29 재측정, 양성 대조로 13건 실측 — - # doc/analysis/sles-oval-measurement.md). 2026-07-28 시점에는 "trivy 가 SLES 15.7 - # 데이터를 커버하지 않아 0건이 측정 불가다"로 판단해 OVAL 직접 평가로 우회했으나 - # 이는 오판이었던 것으로 정정됐다 — 실효 C/H 0/0 은 실제 결과다. 게이트 PASS. + # trivy 는 SLES 15.7 을 정상 커버한다 — 실효 C/H 0/0 은 측정된 결과이며 게이트 PASS 다. + # 초기에는 "커버하지 않아 0건이 측정 불가"로 오판했다. 깨끗한 이미지도 0건이라 + # "0건"과 "데이터 없음"이 구분되지 않았던 것이 원인이다 — 게이트의 커버리지 + # 자가진단(CoverageProbe)이 매 스캔마다 그것을 구분한다. doc/sbom-pipeline.md # # ⚠️ pg-failover-slots 확장이 없다 — PGDG zypp 저장소에 패키지가 없다. # 이 차트는 쓰지 않지만 상위 구성에서 요구하면 확인해야 한다. diff --git a/manifests/helm/cnpg-cluster/1.0.0/dip-values.yaml b/manifests/helm/cnpg-cluster/1.0.0/dip-values.yaml index 02672d0..bc55b5c 100644 --- a/manifests/helm/cnpg-cluster/1.0.0/dip-values.yaml +++ b/manifests/helm/cnpg-cluster/1.0.0/dip-values.yaml @@ -5,7 +5,8 @@ instances: 3 postgresql: # custom-values.yaml 과 동일하게 SUSE BCI 15.7 자체 빌드를 쓴다. # 근거·비용 → doc/decisions/0001-cnpg-postgresql-image.md - # trivy 는 SLES 15.7 을 정상 커버한다(2026-07-29 재측정 — doc/analysis/sles-oval-measurement.md). + # trivy 는 SLES 15.7 을 정상 커버한다(게이트의 CoverageProbe 가 매 스캔마다 확인 — + # doc/sbom-pipeline.md). # 실효 C/H 0/0, 게이트 PASS. imageName: "docker.io/paasup/cnpg-postgresql:18.4-bci15.7-hardened-20260803" diff --git a/manifests/helm/cnpg-cluster/1.0.0/templates/database.yaml b/manifests/helm/cnpg-cluster/1.0.0/templates/database.yaml index 76274df..876571b 100644 --- a/manifests/helm/cnpg-cluster/1.0.0/templates/database.yaml +++ b/manifests/helm/cnpg-cluster/1.0.0/templates/database.yaml @@ -2,7 +2,7 @@ 선언적 데이터베이스/확장 관리 (CNPG 1.26+ Database CRD). 중요: bootstrap.initdb.postInitApplicationSQL 안의 CREATE EXTENSION 은 CNPG 1.30.0 에서 -반영되지 않는다(오류도 나지 않고 조용히 무시됨 — doc/deploy-test-cnpg.md 검증 기록 참고). +반영되지 않는다(오류도 나지 않고 조용히 무시됨 — 배포 검증에서 확인). 확장은 반드시 이 Database CRD 로 선언해야 한다. */}} {{- range .Values.databases }} diff --git a/manifests/helm/cnpg-cluster/1.0.0/values.yaml b/manifests/helm/cnpg-cluster/1.0.0/values.yaml index 8e25731..828f09c 100644 --- a/manifests/helm/cnpg-cluster/1.0.0/values.yaml +++ b/manifests/helm/cnpg-cluster/1.0.0/values.yaml @@ -74,7 +74,7 @@ bootstrap: localeCType: "" # 초기화 SQL. 최초 bootstrap 시 1회만 superuser 로 실행된다. # 주의: CREATE EXTENSION 은 여기에 넣으면 안 된다. CNPG 1.30.0 에서 오류 없이 - # 무시된다(검증 기록: doc/deploy-test-cnpg.md). 확장은 아래 databases 로 선언한다. + # 무시된다(배포 검증에서 확인). 확장은 아래 databases 로 선언한다. postInitApplicationSQL: [] # - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly; diff --git a/manifests/helm/etcd/1.1.12/BUILD-README.md b/manifests/helm/etcd/1.1.12/BUILD-README.md index 6045066..ba92c30 100644 --- a/manifests/helm/etcd/1.1.12/BUILD-README.md +++ b/manifests/helm/etcd/1.1.12/BUILD-README.md @@ -82,8 +82,7 @@ diff -u /tmp/old.yaml /tmp/new.yaml | --- | --- | | `CUSTOM-README.md` | 차트/etcd 버전 번호, 이미지 레지스트리 판단 결과 | | `BUILD-README.md` | 위 버전 대응 표에 신규 행 추가 | -| `doc/charts/etcd/deploy-test.md` | 신규 버전으로 배포 테스트 재실행 후 결과 갱신 | -| `doc/image-selection.md` | 현재 카탈로그 상태 표의 etcd 행 갱신 | +| `scripts/deploy-test/deploy-test-etcd.sh` | 신규 버전으로 배포 검증 재실행 | ## 4. 배포 검증 diff --git a/manifests/helm/etcd/1.1.12/CUSTOM-README.md b/manifests/helm/etcd/1.1.12/CUSTOM-README.md index f024648..d9c2ac7 100644 --- a/manifests/helm/etcd/1.1.12/CUSTOM-README.md +++ b/manifests/helm/etcd/1.1.12/CUSTOM-README.md @@ -28,8 +28,8 @@ etcd 는 분산 key-value 저장소다. 이 차트는 오퍼레이터 없이 Sta (차트 `values.yaml` 의 `settings.https` 주석 참고). - init 컨테이너(`busybox`)가 데이터 디렉토리(`/data/etcd`)를 `chmod 700` 으로 생성한다. `securityContext`(uid/gid 999, non-root, RO rootfs)와 함께 이 권한이 맞물려야 정상 - 기동한다 — 배포 테스트에서 이미지가 실제로 uid 999 로 뜨는지 확인 완료 - (`doc/charts/etcd/deploy-test.md`). + 기동한다 — 배포 검증에서 이미지가 실제로 uid 999 로 뜨는지 확인 완료 + (`scripts/deploy-test/deploy-test-etcd.sh`). ### 2) 배포 @@ -103,4 +103,5 @@ PV 자체가 남을 수 있다 — `.claude/pitfalls.md` 참고. ## 5. 검증 이력 -`doc/charts/etcd/deploy-test.md` 참고. +`scripts/deploy-test/deploy-test-etcd.sh` 로 검증한다 — 절차와 통과 기준은 +`.claude/deploy-test-procedure.md`. diff --git a/manifests/helm/etcd/1.1.12/custom-values.yaml b/manifests/helm/etcd/1.1.12/custom-values.yaml index 6510a4a..0d0e128 100644 --- a/manifests/helm/etcd/1.1.12/custom-values.yaml +++ b/manifests/helm/etcd/1.1.12/custom-values.yaml @@ -5,8 +5,8 @@ image: # 자체 빌드(대응 우선순위 c) — 업스트림 quay.io/coreos/etcd:v3.7.1 이 게이트 차단 # HIGH 1건(golang.org/x/text, CVE-2026-56852, 바이너리 정적 링크라 베이스 OS 교체로 # 해소 불가)으로 막혀 v3.7.1 태그가 가리키는 commit 을 그대로 컴파일했다. x/text 만 - # go.work 워크스페이스 전역 replace 로 0.39.0 이상으로 강제. 근거: doc/analysis/etcd-cve.md, - # 결정: doc/decisions/0007-etcd-image-self-build.md. 빌드 정의: images/etcd/. + # go.work 워크스페이스 전역 replace 로 0.39.0 이상으로 강제. + # 근거·결정: doc/decisions/0004-etcd-image-self-build.md. 빌드 정의: images/etcd/. # 상위 태그가 나오거나 release-3.7 에 백포트되면(대응 우선순위 a) 되돌리는 것이 우선. # # 이전 값: quay.io/coreos/etcd:v3.7.1 (업스트림). etcd 프로젝트는 3.8부터 @@ -17,7 +17,7 @@ image: tag: "3.7.1-security-hardened-20260804" initImage: - # 업스트림 기본값 "stable" 은 롤링 태그다 (doc/image-selection.md 2번 규칙 위반). + # 업스트림 기본값 "stable" 은 롤링 태그다 (롤링 태그 금지 — .claude/image-authoring.md). # 이 init 컨테이너도 extract-helm-images.sh 에 잡혀 게이트 대상이 되므로 고정한다. registry: "docker.io" repository: "busybox" @@ -28,7 +28,7 @@ initImage: # # 이 카탈로그 항목의 용도는 단일 APISIX 인스턴스의 설정 저장소다 — APISIX 자체가 # 단일 인스턴스라 etcd 쪽에서 쿼럼 HA를 가져갈 이유가 없고, 오퍼레이터 없이 가벼운 -# 차트 하나로 충분하다는 것이 이 결정의 핵심이다(doc/decisions/0006-etcd-chart-selection.md). +# 차트 하나로 충분하다는 것이 이 결정의 핵심이다(doc/decisions/0003-etcd-chart-selection.md). # 노드 장애 시 단일 장애점이 된다는 것을 받아들인 트레이드오프다. 쿼럼 HA가 필요한 # 다른 소비자가 생기면 dip-values.yaml(replicas: 3)을 쓰거나 이 값을 재검토한다. replicas: 1