# 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 의 `zlib` config 옵션이 컴파일 타임에 `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)이 이 자체 빌드를 유지하는 것보다 항상 우선이다. ## 빌드 ```sh # 로컬 빌드 (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` 절차로 카탈로그 반영 전 반드시 수행할 것.