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>
apisix — 자체 빌드
apisix(APISIX-Runtime + APISIX 애플리케이션 + keycloak-authz 커스텀 플러그인) 전체를
업스트림 소스에서 SUSE BCI 위에 직접 컴파일한다.
manifests/helm/apisix/2.16.0/custom-values.yaml의 image.repository/tag가 이
산출물을 가리킨다.
자체 빌드는 대응 우선순위 3번이다. 상위 태그 교체·베이스 OS 교체로 목표를 만족할 수 있으면 그 쪽을 쓴다. 자체 빌드는 업스트림 서명·provenance·SBOM attestation 을 잃고 재빌드 책임을 지는 선택이다.
왜 자체 빌드하나
apisix 카탈로그 CVE 조치(2026-08-11~12) 중 발견 — 기존엔 별도 프로젝트
(paasup/dataup 레포 experiment/apisix/apisix-plugin)에서 apache/apisix:3.17.0-debian
위에 keycloak-authz 커스텀 플러그인만 얹어 빌드했다. 이 Debian 베이스 자체가 게이트
차단 26건(2026-08-11 실측) — apache/apisix:3.17.0-debian은 확인 시점 기준 이미
최신 업스트림 태그라 상위 태그 교체로는 해소가 안 되고, 전부 Debian 베이스 OS
패키지(libc, perl, pcre 등) CVE라 베이스 OS를 통째로 바꿔야 했다.
업스트림과 다르게 하는 부분 — SUSE 에 이 이미지가 없는 이유
업스트림(apache/apisix:3.17.0-debian)은 vanilla OpenResty 가 아니라 "APISIX-Runtime"
이라는 커스텀 컴파일 nginx다 — OpenSSL 3.4.1·zlib·PCRE 를 직접 빌드해 넣고,
apisix-nginx-module·wasm-nginx-module(WASM, wasmtime)·lua-var-nginx-module·
lua-resty-events·mod_dubbo·ngx_multi_upstream_module을 --add-module로
컴파일 타임에 정적으로 얹는다(2026-08-11 openresty -V 로 실측 — 아래 "소스·버전
관리" 표에 정확한 버전 나열). 이 조합을 배포하는 apiseven 의 빌드 파이프라인
(api7/apisix-build-tools)은 Debian/RHEL(UBI9) 용 사전 빌드 패키지만 만든다 — SUSE
용은 없다.
- vanilla OpenResty로 대체 불가: openresty.org 는 SLES 15.x 를 공식 지원해 zypper
로 설치할 수 있지만, 그건 위 커스텀 모듈(WASM·mod_dubbo 등)이 하나도 없는 순정
빌드다.
--add-module은 컴파일 타임에만 바이너리에 정적으로 박히는 옵션이라 이미 컴파일된 vanilla 바이너리에 나중에 모듈을 추가할 수 없다 — nginx의 "동적 모듈" 메커니즘도 이 모듈들이 그 형태로 배포되지 않아 적용 안 된다. 우리 apisix 배포가 WASM 플러그인과http-dubbo관련 기능을 공식적으로 지원해야 한다는 요구사항이 있어 (custom-values.yaml의apisix.plugins에http-dubbo포함), 모듈을 뺀 축소판으로 타협하지 않고 업스트림과 동일한 모듈 구성을 그대로 재현했다. - Debian/RHEL 사전 빌드 RPM/DEB 를 SUSE 에 강제 설치도 불가: 같은 이유로 시도하지 않았다 — glibc/openssl 심볼 버전이 안 맞아 ABI 가 깨질 위험(cnpg-postgresql 때와 달리 이번엔 SUSE 자체 저장소에 대응 패키지가 없어 "같은 배포판 계열 재설치"가 성립 하지 않는다).
- 그래서 apiseven 의 공식 빌드 스크립트(
api7/apisix-build-tools, 태그apisix-runtime/1.3.6—openresty -V의APISIX_RUNTIME_VER=1.3.6과 실측 일치)를 그대로 재현해 SUSE BCI 위에서 소스 컴파일했다. 애플리케이션 코드·모듈 버전은 업스트림과 100% 동일하고, 베이스 OS만 바뀐다(diff 최소화 원칙).
3단계 구성
| 단계 | 목표 |
|---|---|
runtime |
OpenSSL 3.4.1·zlib·PCRE 를 $OR_PREFIX/{openssl3,zlib,pcre}에 직접 빌드하고, OpenResty 소스에 위 커스텀 모듈 6종을 --add-module로 붙여 컴파일한다. openresty.org 의 SLES 전용 -devel 패키지가 없어 이 경로들을 소스 빌드로 채운다. |
apisix-app |
runtime이 만든 OpenResty/LuaJIT 위에 APISIX 애플리케이션(Lua 코드 + 일부 C 확장 rock)을 luarocks make로 설치한다. |
final |
위 두 스테이지 산출물만 깨끗한 SUSE BCI 이미지로 옮기고, 런타임에 필요한 공유 라이브러리(libxml2·libxslt·libyaml·pcre·pcre2)만 추가, non-root(apisix, uid 636) 설정, keycloak-authz 플러그인 오버레이까지 마친다. |
실측 함정 (2026-08-11/12)
- 빌드 순서 — zlib 을 OpenSSL 보다 먼저 빌드한다. OpenSSL 의
zlibconfig 옵션이 컴파일 타임에zlib.h를 요구한다 — 반대 순서로 뒀다가zlib.h: No such file로 실패했다. lua-resty-saml(luarocks 가 자동으로 받는 의존 rock)이 SUSE 표준libxml2-devel(2.12.10)의xmlSetStructuredErrorFunc시그니처(콜백 인자에const추가됨)와 안 맞아-Werror=incompatible-pointer-types로 빌드 실패한다. rock 자체의 Makefile 이-Werror를 하드코딩해 환경변수CFLAGS로는 못 끈다 —apisix-app스테이지 안에서만 쓰는gcc래퍼(-Wno-error=incompatible-pointer-types추가)로 그 진단 하나만 경고로 낮추고,luarocks make끝나면 즉시 원복한다(다른 빌드에 영향 안 줌). source.Dockerfile 의 해당 RUN 스텝 주석 참고.ui/(Admin 대시보드 프론트엔드)는apache/apisix저장소 자체엔 없다. apiseven 의 패키징 파이프라인이 별도로(Node.js/yarn 빌드) 끼워 넣는 단계라 git clone 만으로는 재현 안 된다 — 우리 배포는 이 UI 를 쓰지 않아(custom-values.yaml에 관련 설정 없음) 없으면 빈 디렉터리로 대체하고 건너뛴다./usr/bin/apisix래퍼 스크립트가 내부적으로awk를 쓴다. OpenResty 버전 파싱에 쓰는데,final스테이지에gawk를 안 깔면 "awk: command not found" 로apisix version부터 실패한다.- SUSE 공유 라이브러리 패키지명은 soname 이 붙는다.
libxml2/libxslt/pcre같은 이름 그대로는 없고libxml2-2·libxslt1·libpcre1·libpcre2-8-0·libyaml-0-2다 —libxml2-2/libpcre2-8-0/libldap-2_4-2는bci-base에 기본 포함이라 명시 안 해도 되지만, 명확성을 위해 실측한 이름 그대로 적었다. docker build --pull은 베이스 이미지 digest 가 바뀌면 캐시를 전부 무효화한다.build-hardened-image.sh가 항상--pull을 쓰므로(스케줄 재빌드가 최신 베이스를 전제하기 때문), Dockerfile 을 안 고쳤어도 SUSE BCI 태그가 그 사이 갱신되면 전체 재컴파일(약 35분)이 발생할 수 있다 — Dockerfile 반복 수정·테스트는--pull없는 순수docker build로 하고, 최종 검증에서만build-hardened-image.sh를 쓴다.
소스·버전 관리
| 항목 | 값 |
|---|---|
| APISIX 소스 | https://github.com/apache/apisix.git (태그 3.17.0) |
| OpenResty | 1.29.2.4 |
| OpenSSL | 3.4.1 |
| zlib / PCRE | 1.3.1 / 8.45 |
| 커스텀 모듈 | apisix-nginx-module 1.19.5, wasm-nginx-module 0.7.0, lua-var-nginx-module v0.5.3, lua-resty-events 0.2.0, ngx_multi_upstream_module 1.3.3, mod_dubbo 1.0.2 |
| APISIX_RUNTIME_VER | 1.3.6 (apiseven 빌드 스크립트 버전 태그) |
| 최종 베이스 | registry.suse.com/bci/bci-base:15.7 |
전부 source.build.env 에서 관리하며 자동 추적하지 않는다. 다른 자체 빌드 이미지와
동일하게, 사람이 업스트림 새 릴리스(또는 위 CVE 세트를 이미 해소한 버전)를 보고
source.build.env 를 고쳐 PR 을 여는 것 자체가 갱신 트리거다.
권장 점검 주기: 카탈로그 게이트가 이 이미지의 차단 CVE 를 다시 보고할 때, 또는
apache/apisix가 3.17.1(혹은 그 이상)을 내놓았을 때 — 릴리스가 나오면 그쪽으로
갈아타는 것(대응 우선순위 a)이 이 자체 빌드를 유지하는 것보다 항상 우선이다.
빌드
# 로컬 빌드 (push 없음)
IMAGE=apisix BASE_OS=source bash scripts/build/build-hardened-image.sh /tmp/out
# 레지스트리에 push 까지
IMAGE=apisix BASE_OS=source REGISTRY=docker.io/paasup \
bash scripts/build/build-hardened-image.sh /tmp/out
수행 순서: 빌드 → 기능 검증(verify.sh) → SBOM → 전 심각도 스캔 → 게이트 판정.
verify.sh는 apisix version 출력·nginx 설정 문법(nginx -t)뿐 아니라, standalone
YAML 설정으로 실제 nginx worker 를 기동해 APISIX 라우터가 HTTP 요청에 응답하는지
(라우트 없음 → 404)까지 확인한다 — 15개 커스텀 모듈 + ~90개 Lua 플러그인(lua-resty-saml
같은 C 확장 포함) 로딩까지 실제로 거치는 유일한 방법이다. keycloak-authz 커스텀
플러그인은 실제 Keycloak 연동이 필요해 이 스모크 범위 밖이다 — dev 클러스터 배포
검증(.claude/deploy-test-procedure.md)이 담당한다.
결과(2026-08-12 실측): 게이트 PASS, 커버리지 ok, 실효 CRITICAL/HIGH 0/0
(업스트림 26건에서 완전 해소). docker.io/paasup/apisix:3.17.0-security-hardened-20260811
로 push 완료.
파일 구성
| 파일 | 역할 |
|---|---|
source.Dockerfile |
빌드 정의 — 3단계(runtime/apisix-app/final) |
source.build.env |
pinned 버전 전체(BUILD_ARGS에 나열한 이름만 --build-arg 로 전달됨) |
verify.sh |
기능 검증 — 실제 nginx 기동 + HTTP 요청까지 |
keycloak-authz.lua |
paasup/dataup 레포(experiment/apisix/apisix-plugin/keycloak-authz/)와 동일한 커스텀 플러그인 — 원본 레포는 그대로 두고 이 카탈로그가 더 이상 참조만 안 한다 |
docker-entrypoint.sh |
업스트림 apache/apisix-docker(utils/docker-entrypoint.sh)와 동일 |
베이스 변종이 하나뿐이라 파일명이 source.* 로 고정돼 있다.
태그
docker.io/paasup/apisix:3.17.0-security-hardened-20260811
└ app ┘└ 슬러그 ┘└ 하드닝 ┘└ 빌드일 ┘
아직 안 한 것 — 배포 검증
게이트 PASS·기능 스모크테스트(nginx 기동+HTTP 응답)까지만 확인했다. 실제 Keycloak
연동(keycloak-authz 플러그인 동작), etcd 연동(externalEtcd), ingress-controller 와의
연동을 포함한 실제 클러스터 배포 검증은 아직 하지 않았다 —
.claude/deploy-test-procedure.md 절차로 카탈로그 반영 전 반드시 수행할 것.