Files
service-catalog/images/keycloak/README.md
T
wbsong111 4f67f63f69 SBOM 파이프라인 문서 정비 + 이미지 목록 열거 제거
## doc/sbom-pipeline.md — 중복·모순 정리 (328 → 286줄)

같은 사실이 여러 절에 흩어져 있었고, 일부는 문서가 아니라 변경 이력이었다.

- `SEVERITY` 를 전 심각도로 덮어써야 하는 이유가 환경변수 절·CI 절·요약 절 3곳에
  있었다. 스크립트 절의 blockquote 하나로 합쳤다 — "게이트를 돌릴 거라면 전 심각도로
  스캔해야 한다"가 핵심이고 나머지는 그 결과다.
- Job Summary 1MB 제한이 CI 절과 결과 확인 절에 중복됐다. CI 절 하나로 합쳤다.
- `--warn-only` 서술이 mermaid 라벨·CI 절·게이트 절·트리아지 절 4곳에 있었다.
  게이트 절 하나로 합치고, `build-image.yml` 쪽은 이미 강제라는 대비를 함께 적었다.
- 자체 빌드 트리거 표가 "PR 은 push 안 함"을 말하는데 바로 아래 불릿이 같은 말을
  반복했다. 표는 그대로 두고 불릿은 **왜** 그런지(REGISTRY 미전달 → localhost 태그라
  push 를 시도할 수조차 없다)만 남겼다.
- "오해를 주던 단일 '총 소요'는 제거" 같은 변경 이력 서술을 걷어냈다. 문서는 현재
  상태를 적는 곳이다.
- "첫 전체 실행 결과(2026-07-08)" 절은 수치를 싣고 바로 아래에서 "현재 수치가
  아니다"로 무효화하는 구조였다. 절 자체를 없애고, 거기서 유일하게 쓸모 있던 사실
  (SBOM 생성 실패는 대부분 사설/미인증 레지스트리이거나 대용량 timeout)만 스크립트
  절로 옮겼다.
- "실행 이력(2026-08-04)" 절은 MEMORY.md 와 중복이라 제거했다. 거기서만 알 수 있던
  사실(Actions 시크릿의 push 권한 확인)은 GitHub 설정 표에 반영했다.
- `CVE_API_KEY` 가 본문에만 있고 GitHub 설정 표에 빠져 있어 추가했다.
- 아키텍처 절 불릿이 mermaid 서브그래프 라벨과 같은 말을 하고 있어, "pull 을 ② 한
  곳에 몰아둔 것이 핵심"이라는 결론 한 문장으로 줄였다.

## 이미지 목록을 문서에 박아두지 않는다

이미지는 계속 추가되므로 열거하면 추가할 때마다 낡는다. `images/` 디렉토리를 단일
출처로 삼고 CLAUDE.md·image-authoring.md·build-image.yml·sbom-pipeline.md 의 열거를
걷어냈다. keycloak README 의 베이스 OS 결정 근거도 "기존 3종" 대신 "먼저 들어온
이미지들"로 바꿨다 — 근거의 내용은 그대로다.

## 현황 서술 정정

- build-image.yml 주석이 "아직 도입된 자체 빌드 이미지가 없다(images/ 가 비어 있음)"
  로 남아 있었다. 이 레포 CI 에서 빌드→검증→게이트→push→카탈로그 브랜치 push 까지
  실제로 검증된 상태다.
- MEMORY.md: cve-exceptions.json 첫 예외 등록, 베이스 OS 정책 확정, PR 자동 생성이
  조직 정책으로 불가하다는 실측(run 30882785612)을 반영했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 14:27:03 +09:00

12 KiB
Raw Blame History

keycloak — 자체 빌드

업스트림 Keycloak 배포본(tar.gz)을 SUSE BCI rootfs 위에 재패키징하고, 배포본에 정적으로 들어 있는 취약 jar 를 수정 버전으로 교체한다. manifests/helm/keycloakx/7.2.2/custom-values.yamlimage.repository/image.tag 가 이 산출물을 가리킨다.

신규 자체 빌드 이미지 추가 절차 전반은 .claude/image-authoring.md 참고.

자체 빌드는 대응 우선순위 3번이다. 상위 태그 교체·베이스 OS 교체로 목표를 만족할 수 있으면 그 쪽을 쓴다. 자체 빌드는 업스트림 서명·provenance·SBOM attestation 을 잃고 재빌드 책임을 지는 선택이다.

왜 자체 빌드하나

업스트림 quay.io/keycloak/keycloak:26.6.4 는 게이트에서 차단 17건(실효 HIGH 17, CRITICAL 0) 이다(PR #18 의 helm-catalog-sbom run 31085455385 실측). 계층별로 성격이 전혀 다르다.

계층 건수 대표 CVE 상위 태그·베이스 OS 교체로 풀리나
UBI9 rpm 5 java-21-openjdk-headless ×3, libacl, pcre2 일부. 3건은 rpm 최신화로 해소
번들 jar 12 netty ×6, jackson ×3, postgresql-jdbc, mssql-jdbc, keycloak-services 아니다

상위 태그 교체를 먼저 검토한 결과 (image-authoring.md 체크리스트 1)

jar 12건은 keycloak 버전을 올려도 풀리지 않는다. keycloak 26.6.4 와 최신 26.7.1quarkus.version 이 둘 다 3.33.2.1 이고, Quarkus 3.33.2.1 BOM 이 netty 4.1.135.Final / jackson-bom 2.21.2 를 고정한다(실측: keycloak pom.xml 두 태그 비교 + quarkusio/quarkus bom/application/pom.xml@3.33.2.1). 차단 CVE 의 수정 버전은 netty 4.1.136.Final, jackson 2.21.4 다 — 다음 Quarkus BOM 이 올라오기 전까지 업스트림 이미지로는 방법이 없다.

베이스 OS 교체도 jar 에는 통하지 않는다. CVE 가 OS 패키지가 아니라 배포본에 함께 실려 있는 jar 자체이기 때문이다 — cloudnative-pg/etcd 의 정적 링크 Go 모듈과 같은 구조다.

그래서 jar 를 직접 교체하는 자체 빌드가 유일한 수단이다. etcd 이미지가 go.workreplace golang.org/x/text 한 줄을 넣어 해결한 것과 같은 성격의 의존성 override 이며, 오케스트레이션은 동일한 scripts/build/build-hardened-image.sh 하나를 공유한다.

그래도 버전은 26.7.1 로 올린다

26.6.426.7.1(및 26.6.5)에서만 패치된 keycloak-services HIGH 5건에 취약하다 — CVE-2026-16102 / 16442 / 16443 / 15572 / 15573 (+ MEDIUM 2건, GitHub Security Advisory 실측). 스캔 시점 trivy DB 에 아직 없어 게이트에 잡히지 않았을 뿐이다.

차트(keycloakx)는 7.2.2 가 최신이고 그대로 둔다. 차트의 appVersion: 26.6.4image.tag 미지정 시의 기본값일 뿐이고(templates/statefulset.yaml.Values.image.tag | default .Chart.AppVersion), 우리는 custom-values.yaml 에서 태그를 명시한다. appVersion 이 26.7.1 보다 낮은 것은 codecentric 차트의 릴리스 캐던스 지연이지 차트 결함이 아니다.

유형과 베이스 OS

유형: "업스트림 산출물을 다른 배포판에 재설치" (image-authoring.md 두 유형 중 첫 번째). Keycloak 을 소스에서 Maven 빌드하지 않는다 — 업스트림이 릴리스한 tar.gz 를 그대로 쓰고 런타임 rootfs 만 SUSE 로 바꾼다.

항목
배포본 github.com/keycloak/keycloak/releases/download/$KEYCLOAK_VERSION/keycloak-$KEYCLOAK_VERSION.tar.gz
빌더 registry.suse.com/bci/bci-base:15.7 (zypper 필요)
최종 rootfs 씨앗 registry.suse.com/bci/bci-micro:15.7
최종 스테이지 FROM scratch + 위 rootfs

베이스 OS 결정 배경 (image-authoring.md 원칙 2)

업스트림은 ubi9 빌더 + ubi9-micro 최종이다. 카탈로그의 먼저 들어온 자체 빌드 이미지들이 전부 SUSE BCI 이고 trivy 의 SLES 15.7 커버리지가 양성 대조로 실측 확인돼 있어(doc/analysis/sles-oval-measurement.md), "업스트림과 최대한 동일하게" 보다 카탈로그 내 일관성을 우선했다.

BCI 16.0 이 나와 있지만 15.7 을 쓴다 — 최신이 이 이미지에는 더 낡았다(2026-08-07 실측). 필요한 나머지 패키지는 16.0 에도 전부 있지만 JDK 가 뒤처져 있다.

BCI java-21-openjdk-headless
15.7 21.0.12.0-150600.3.29.1
16.0 21.0.11.0-160000.2.1

21.0.12 가 CVE-2026-41254·CVE-2026-47063 의 수정 버전이라, 16.0 으로 갔으면 이 이미지가 없애려는 차단 CVE 2건이 그대로 남았다. 16.0 의 JDK 가 15.7 을 따라잡으면 그때 올린다 — 재측정 방법은 suse.build.env 주석에 있다.

rootfs 를 왜 "씨앗" 방식으로 만드나

업스트림 ubi-null.sh 는 빈 installroot 에 패키지를 깔고 그 rootfs 를 ubi9-micro 위에 덮는다. 이 구조를 SUSE 에 그대로 옮기면 bci-micro 의 rpmdb 가 새 rpmdb 로 가려져 micro 자체 패키지가 SBOM 에서 통째로 사라진다 — CVE 가 줄어드는 게 아니라 스캔 사각지대가 생기는 것이고, 게이트가 경고하는 "베이스 OS 교체로 수치만 낮아진 것"의 전형이다.

그래서 bci-micro 파일시스템을 씨앗으로 깐 뒤 그 위에 zypper --installroot 로 설치한다. rpmdb 가 micro 것 위에 이어 써지므로 최종 이미지의 모든 OS 패키지가 SBOM 에 잡힌다. 빌드 후 이 점을 반드시 확인한다(아래 "빌드" 절).

업스트림과 다른 부분

업스트림 quarkus/container/Dockerfile 대비 차이는 셋뿐이다.

  1. 런타임 rootfs 가 SUSE BCI — 위 참조. 패키지 목록(RUNTIME_PACKAGES)은 업스트림 이미지 SBOM 의 rpm 44종을 근거로 정했다. sed·grep없으면 안 된다bin/kc.sh/bin/sh 스크립트로 esceval() 에서 sed, 인자 파싱에서 grep 을 쓰는데 bci-micro 에는 sed 가 없다.

  2. 취약 jar 오버레이 (overlay-jars.sh) — lib/lib/main/ 의 jar 를 파일명은 유지하고 내용만 수정 버전으로 바꾼다. Quarkus fast-jar 의 클래스패스가 파일명을 그대로 참조하기 때문이다. 대상은 suse.build.env*_OLD/*_VERSION 이 정의한다:

    그룹 대상 버전
    io.netty 패밀리 전체 17종 4.1.135.Final4.1.136.Final
    com.fasterxml.jackson.core jackson-core, jackson-databind 2.21.22.21.4
    org.postgresql postgresql 42.7.1142.7.12
    io.micrometer 패밀리 전체 4종 1.16.31.16.6

    netty·micrometer 는 CVE 가 붙은 아티팩트만이 아니라 패밀리 전체를 함께 올린다 — 둘 다 아티팩트 간 버전 혼용이 비지원이다. jackson 은 2.21.x 안에서 호환이 보장되고 jackson-annotations2.21 로 별도 버저닝돼 있어 CVE 있는 둘만 바꾼다.

    위장이 아니다 — trivy 는 jar 내부 META-INF/maven/**/pom.properties(없으면 sha1↔GAV 인덱스)로 버전을 판정하므로 SBOM 에는 새 버전이 정확히 잡히고, 실제 내용도 새 버전이다.

  3. bin/client/ 제거keycloak-admin-cli-<ver>.jar 는 jackson 을 shade 로 품은 uber-jar 라 jar 교체로 고칠 수 없다(SBOM 실측: jackson-databind@2.21.2 의 FilePath 가 이 파일). 서버 JVM 이 로드하지 않는 독립 CLI(kcadm.sh/kcreg.sh)이므로 하드닝 이미지에서는 제거했다. 업스트림 대비 유일한 기능적 차이다 — 운영에서 kcadm 이 필요하면 업스트림 이미지를 별도 컨테이너로 쓴다.

kc.sh build 를 최종 스테이지에서 한 번 돌리는 것은 차이가 아니다 — 옵션 없는 build 는 릴리스 tar 의 사전 augmentation 상태를 그대로 재현하며, 오버레이한 jar 로 augmentation 이 실제로 통과하는지 빌드 시점에 확인하기 위한 것이다(최적화 이미지로 만드는 것이 아니다).

버전 관리

APP_VERSION/KEYCLOAK_VERSION*_OLD/*_VERSION jar 버전은 자동 추적하지 않는다. 사람이 업스트림 릴리스를 보고 suse.build.env 를 고쳐 PR 을 여는 것 자체가 갱신 트리거다.

overlay-jars.sh*_OLD 버전 jar 를 하나도 못 찾으면 빌드를 실패시킨다. 업스트림이 의존성을 올렸는데 스크립트가 조용히 아무것도 안 해서 "CVE 는 그대로인데 빌드는 성공" 하는 상태를 막기 위함이다.

권장 점검 주기: 게이트가 이 이미지의 차단 CVE 를 다시 보고할 때, 또는 Keycloak 이 Quarkus BOM 을 올린 릴리스를 낼 때 — BOM 이 올라가 오버레이가 불필요해지면 해당 spec 을 제거하는 것이 이 자체 빌드를 유지하는 것보다 항상 우선이다.

빌드

# 로컬 빌드 (push 없음)
IMAGE=keycloak BASE_OS=suse bash scripts/build/build-hardened-image.sh /tmp/kc-out

# 레지스트리 push 까지
IMAGE=keycloak BASE_OS=suse REGISTRY=docker.io/paasup \
  bash scripts/build/build-hardened-image.sh /tmp/kc-out

arm64 호스트(Apple Silicon)에서 돌릴 때: linux/amd64 를 QEMU 로 에뮬레이션하므로 verify.sh 의 실기동이 매우 느리다 — Quarkus augmentation 만 100초, 기동 전체가 300초를 넘는다(2026-08-07 실측). verify.sh 의 기본 대기 시간은 600초이고 VERIFY_BOOT_TIMEOUT 으로 조정한다. 네이티브 amd64 러너에서는 12분이면 끝난다.

수행 순서: 빌드 → 기능 검증(verify.sh) → SBOM → 전 심각도 스캔 → 게이트 판정.

빌드 후 확인할 것:

  1. verify.log 마지막 줄이 VERIFY-OK
  2. cve-gate.md 의 차단 항목 — doc/cve-exceptions.json 에 등록된 것 외에 없는지
  3. 커버리지 자가진단이 ok 인지(none 이면 findings 0 이 진짜 0 이 아니다 — doc/sbom-pipeline.md)
  4. rpmdb 마스킹이 없는지 — SBOM 의 OS 패키지 수가 bci-micro 단독 + 설치분에 해당하는지. 업스트림 UBI9 이미지의 OS 패키지는 44종이었다. 한 자릿수로 떨어졌다면 씨앗 방식이 동작하지 않은 것이므로 설계를 재검토한다.

verify.sh 가 확인하는 것: kc.sh 가 쓰는 셸 도구·java 21·en_US.UTF-8 로케일· Asia/Seoul 타임존, bin/client 제거, 오버레이한 jar 의 내부 pom.properties 버전(파일명이 아니라 내용 — trivy 와 같은 근거), 그리고 실제 start-dev 기동 후 OIDC discovery 응답·admin 토큰 발급·GET /admin/realms 까지. 외부 postgres·ingress·클러스터링은 이 스모크 테스트 범위 밖이며 dev 클러스터 배포 테스트(.claude/deploy-test-procedure.md)가 담당한다.

파일 구성

파일 역할
suse.Dockerfile 빌드 정의 — bci-base 빌더(rootfs 구성 + 배포본 전개 + jar 오버레이) + FROM scratch 최종
suse.build.env keycloak 버전·베이스 이미지·런타임 패키지·jar 오버레이 버전. BUILD_ARGS 에 나열한 이름만 --build-arg 로 전달된다
overlay-jars.sh 취약 jar 교체 + bin/client 제거. builder 스테이지 전용(호스트에서 직접 쓰지 않는다)
verify.sh 기능 검증. 호스트에서 bash 로 실행되며 게스트 셸 주입 + 호스트 python3 jar 검사 + 실기동을 조합한다
catalog.env 이 이미지가 갱신하는 차트 디렉토리와 태그 표기 스타일 (build-image.yml 이 읽는다)

베이스 변종이 하나뿐이라 파일명이 suse.* 로 고정돼 있다.

태그

docker.io/paasup/keycloak:26.7.1-bci15.7-hardened-20260807
                           └ app ┘└ 슬러그 ┘└ 하드닝 ┘└ 빌드일 ┘