# apisix-ingress-controller — 자체 빌드 apisix-ingress-controller 바이너리를 업스트림 소스에서 직접 컴파일한다. `manifests/helm/apisix/{2.14.0,2.16.0}/custom-values.yaml` 의 `ingress-controller.deployment.image.repository`/`tag` 가 이 산출물을 가리킨다. > **자체 빌드는 대응 우선순위 3번이다.** 상위 태그 교체·베이스 OS 교체로 목표를 > 만족할 수 있으면 그 쪽을 쓴다. 자체 빌드는 업스트림 서명·provenance·SBOM attestation 을 > 잃고 재빌드 책임을 지는 선택이다. ## 왜 자체 빌드하나 apisix 카탈로그 CVE 조치(2026-08-11) 중 발견 — `apache/apisix-ingress-controller:2.1.0` (2026-05-29 빌드, **이 시점 기준 최신 태그**)가 게이트에서 차단하는 CRITICAL/HIGH 25건 중 21건이 OS 패키지가 아니라 바이너리에 **정적 링크된 Go 모듈** 버전이 원인이다: | 모듈 | 설치 버전 | 필요 버전 | 관련 CVE(대표) | | --- | --- | --- | --- | | stdlib (Go 툴체인) | go1.24.7 | go1.25.12/1.26.5+ | CVE-2026-39822 외 다수 | | golang.org/x/net | v0.47.0 | v0.56.0 | CVE-2026-25681, 27136, 39821 | | golang.org/x/text | v0.31.0 | v0.39.0 | CVE-2026-56852 | | google.golang.org/grpc | v1.71.1 | v1.82.1 | CVE-2026-33186, GHSA-hrxh-6v49-42gf | | go.opentelemetry.io/otel | v1.40.0 | v1.43.0 | CVE-2026-29181 | | go.opentelemetry.io/otel/sdk | v1.40.0 | v1.43.0 | CVE-2026-39883 | 이미 최신 태그라 **상위 태그 교체가 불가능**하다. 나머지 4건(libc6 3건, libssl3 1건)은 업스트림 베이스(`gcr.io/distroless/cc-debian12`)의 OS 패키지지만, 그 4건만 잡자고 베이스를 바꿔도 위 21건은 그대로 남는다 — 자체 빌드(소스 재컴파일)가 유일한 대응이다. ## 업스트림과 다르게 하는 부분 **애플리케이션 코드는 `v2.1.0` 태그 그대로다.** cloudnative-pg 처럼 "release 브랜치 HEAD 로 옮겨서 백포트된 수정을 받는" 방식을 먼저 검토했지만, 이 프로젝트는 그런 유지보수 브랜치가 없다 — `master` 하나뿐이고, 2026-08-11 확인 시점 `master` HEAD 는 `v2.1.0` 태그 대비 커밋 35개 앞서 있으며 그 사이에 Gateway API 1.6.0 지원·L4RoutePolicy·mTLS 지원 같은 **신규 기능 커밋이 다수 섞여 있다.** 최소 diff 원칙(`.claude/image-authoring.md`)에 맞지 않아 쓰지 않았다. 대신 위 표의 취약 모듈만 `go get`(+`go mod tidy`)로 최소 호환 버전까지 끌어올린다. `go.work` 워크스페이스가 없는 단일 모듈 프로젝트라 etcd 처럼 전역 `replace` 한 줄로 끝나지 않고 여러 모듈을 한 번에 지정해야 하지만(otel/otel-sdk 는 버전이 서로 맞아야 해서 반드시 함께 지정), 로컬에서 `go mod tidy` + `go build`(linux/amd64, `CGO_ENABLED=0`)가 정상 종료하는 것을 실측 확인했다(2026-08-11) — 의존성 그래프가 서로 충돌하지 않는다. `gcr.io/distroless/cc-debian12` → `registry.suse.com/bci/bci-micro:15.7`(카탈로그는 SUSE BCI 하나만 쓴다 — `.claude/image-authoring.md` 원칙 2). 바이너리가 `CGO_ENABLED=0` 정적 링크라 애초에 `cc`(glibc 포함) 변형이 필요 없었다 — 업스트림이 왜 `cc` 를 쓰는지는 불명 (안전 마진으로 추정), `static` 대비 이점이 없어 우리는 `bci-micro` 로 통일한다. ## 소스·버전 관리 | 항목 | 값 | | --- | --- | | 소스 | `https://github.com/apache/apisix-ingress-controller.git` | | pinned commit | `source.build.env` 의 `SOURCE_COMMIT` — `2.1.0` 태그가 가리키는 실제 커밋 | | 빌더 | 공식 `golang` 이미지(`source.build.env` 의 `GO_BUILDER_TAG`) — 의존성 업그레이드 후 `go mod tidy` 가 `go.mod` 를 `go 1.25`/`toolchain go1.26.5` 로 자동 상향했다(2026-08-11 실측) | | 최종 베이스 | `registry.suse.com/bci/bci-micro:15.7` | `SOURCE_COMMIT`·의존성 최소 버전은 **자동 추적하지 않는다.** 다른 자체 빌드 이미지와 동일하게, 사람이 업스트림 새 태그(또는 이 CVE 세트를 이미 해소한 커밋)를 보고 `source.build.env` 를 고쳐 PR 을 여는 것 자체가 갱신 트리거다. **권장 점검 주기**: 카탈로그 게이트가 이 이미지의 차단 CVE 를 다시 보고할 때, 또는 업스트림이 `2.1.1`(혹은 그 이상, 위 표의 CVE 를 이미 해소한 릴리스)을 내놓았을 때 — 릴리스가 나오면 그쪽으로 갈아타는 것(대응 우선순위 a)이 이 자체 빌드를 유지하는 것보다 항상 우선이다. > apisix 카탈로그는 한때 2.14.0/2.16.0 두 버전을 함께 보관해 apisix-ingress-controller > 앱 버전(2.0.1/2.1.0)도 갈렸었다 — 2.14.0 은 2026-08-11 삭제되어(구버전 유지 대신 최신 > 버전 하나로 정리) 지금은 변종이 하나뿐이다. ## 빌드 ```sh # 로컬 빌드 (push 없음) IMAGE=apisix-ingress-controller BASE_OS=source bash scripts/build/build-hardened-image.sh /tmp/out # 레지스트리에 push 까지 IMAGE=apisix-ingress-controller BASE_OS=source REGISTRY=docker.io/paasup \ bash scripts/build/build-hardened-image.sh /tmp/out ``` 수행 순서: **빌드 → 기능 검증(`verify.sh`) → SBOM → 전 심각도 스캔 → 게이트 판정.** `verify.sh` 는 바이너리가 실행 가능한지, `version --long` 출력에 pinned commit 이 실제로 반영됐는지(ldflags 주입 확인), `--help` 가 정상 종료하는지, 이미지가 `65532:65532` (nonroot) 로 실행되는지를 확인한다 — **이 컨트롤러는 Kubernetes API 서버 접속이 있어야 실제로 기동한다**, 그 부분은 이 스모크 테스트 범위 밖이며 dev 클러스터 배포 테스트 (`.claude/deploy-test-procedure.md`)가 담당한다. 이 세션에서는 클러스터가 없어 배포 검증을 아직 하지 못했다 — **카탈로그 반영 전 반드시 수행할 것.** ### 파일 구성 | 파일 | 역할 | | --- | --- | | `source.Dockerfile` | 빌드 정의 — 소스 컴파일(builder 스테이지) + SUSE BCI(`bci-micro`) 패키징(final 스테이지) | | `source.build.env` | pinned commit·버전·빌더 이미지 태그·취약 모듈 최소 버전. `BUILD_ARGS` 에 나열한 이름만 `--build-arg` 로 전달된다 | | `verify.sh` | 기능 검증. 호스트에서 bash 로 실행되며 `docker run --entrypoint sh` 로 게스트 셸 스크립트를 주입한다(`bci-micro` 는 bash·coreutils 가 있다) | 베이스 변종이 하나뿐이라 파일명이 `source.*` 로 고정돼 있다. ### 태그 ``` docker.io/paasup/apisix-ingress-controller:2.1.0-security-hardened-20260811 └ app ┘└ 슬러그 ┘└ 하드닝 ┘└ 빌드일 ┘ ``` ### 카탈로그 반영 `ingress-controller.deployment.image` 가 apisix 서브차트 alias 아래 중첩돼 있어 (`ingress-controller:` → `deployment:` → `image:`), `catalog.env` 의 `TAG_BLOCK` 은 점 구분 경로(`ingress-controller.deployment.image`)를 쓴다. 기존 `scripts/build/patch-catalog-tag.py` 는 top-level 블록만 지원했어서, 이 이미지를 추가하며 중첩 경로까지 지원하도록 확장했다(첫 세그먼트는 여전히 top-level 강제, 그 뒤부터는 부모 블록 범위 안에서만 찾아 다른 형제 블록의 동명 키를 오인하지 않는다) — 기존 이미지(단일 세그먼트 `image`)의 동작은 회귀 테스트로 그대로임을 확인했다.