#!/usr/bin/env bash # Keycloak 배포본에 정적으로 들어 있는 취약 jar 를 수정 버전으로 교체한다. # suse.Dockerfile 의 builder 스테이지에서만 실행된다(호스트에서 직접 쓰지 않는다). # # 왜 필요한가 # 차단 CVE 17건 중 12건이 /opt/keycloak/lib/lib/main/ 의 jar 다. 이 버전들은 # Quarkus 3.33.2.1 BOM 이 고정하고 있어 keycloak 상위 태그로도, 베이스 OS 교체로도 # 바뀌지 않는다. etcd 이미지의 `go.work replace` 와 같은 성격의 의존성 override 다. # # 왜 파일명을 유지하는가 # Quarkus fast-jar 배포본은 lib/quarkus-run.jar 의 Class-Path 로 jar 파일명을 그대로 # 참조한다. 이름을 바꾸면 클래스패스가 깨진다. 이름을 유지하고 내용만 바꾸면 # - 런타임: 클래스패스 그대로 동작 # - SBOM: trivy 는 jar 내부 META-INF/maven/**/pom.properties 를 읽으므로 새 버전이 # 정확히 잡힌다 (파일명으로 버전을 위장하는 것이 아니다 — 실제 내용이 새 버전이다) # # 무결성 # Maven Central 의 .sha1 을 함께 받아 검증한다. 실패하면 즉시 종료한다. # # 실패 조건 # OLD 버전 파일을 하나도 못 찾은 spec 이 있으면 실패한다. 업스트림이 의존성 버전을 # 올렸는데 이 스크립트가 조용히 아무것도 안 하는 상태(= CVE 는 그대로인데 빌드는 성공) # 를 막기 위함이다. patch-catalog-tag.py 가 패턴 불일치 시 크게 실패하는 것과 같은 원칙. set -euo pipefail KC_HOME="${1:?사용법: overlay-jars.sh }" LIB="$KC_HOME/lib/lib/main" M2="${M2_BASE:-https://repo1.maven.org/maven2}" [ -d "$LIB" ] || { echo "FAIL: $LIB 가 없다 — 배포본 레이아웃이 바뀌었는지 확인"; exit 1; } : "${NETTY_OLD:?}" "${NETTY_VERSION:?}" : "${JACKSON_OLD:?}" "${JACKSON_VERSION:?}" : "${PGJDBC_OLD:?}" "${PGJDBC_VERSION:?}" # groupId(파일명 프리픽스) | groupPath(Maven 경로) | artifact 필터 | OLD | NEW # artifact 필터가 '*' 이면 해당 groupId + OLD 버전의 모든 jar 가 대상이다. SPECS=( "io.netty|io/netty|*|${NETTY_OLD}|${NETTY_VERSION}" "com.fasterxml.jackson.core|com/fasterxml/jackson/core|jackson-core|${JACKSON_OLD}|${JACKSON_VERSION}" "com.fasterxml.jackson.core|com/fasterxml/jackson/core|jackson-databind|${JACKSON_OLD}|${JACKSON_VERSION}" "org.postgresql|org/postgresql|postgresql|${PGJDBC_OLD}|${PGJDBC_VERSION}" ) fetch() { # $1=url $2=출력경로 local url="$1" out="$2" want got curl -fsSL --retry 3 --retry-delay 2 -o "$out" "$url" want="$(curl -fsSL --retry 3 --retry-delay 2 "$url.sha1" | tr -d '[:space:]')" got="$(sha1sum "$out" | cut -d' ' -f1)" [ "$want" = "$got" ] || { echo "FAIL: sha1 불일치 $url (기대 $want / 실제 $got)"; exit 1; } } total=0 for spec in "${SPECS[@]}"; do IFS='|' read -r gid gpath filter old new <<<"$spec" matched=0 for f in "$LIB/$gid."*"-$old"*.jar; do [ -e "$f" ] || continue base="$(basename "$f")" rest="${base#"$gid."}" # netty-codec-http-4.1.135.Final.jar rest="${rest%.jar}" # netty-codec-http-4.1.135.Final artifact="${rest%%-"$old"*}" # netty-codec-http tail="${rest#*-"$old"}" # '' 또는 -linux-x86_64 (classifier) if [ "$filter" != "*" ] && [ "$artifact" != "$filter" ]; then continue; fi url="$M2/$gpath/$artifact/$new/$artifact-$new$tail.jar" fetch "$url" "$f.new" mv "$f.new" "$f" # 파일명 유지 — 내용만 교체 echo "overlay: $base <= $artifact-$new$tail.jar" matched=$((matched + 1)) done if [ "$matched" -eq 0 ]; then echo "FAIL: spec '$gid/$filter@$old' 에 해당하는 jar 가 없다." echo " 업스트림이 이미 버전을 올렸을 수 있다 — build.env 의 *_OLD 를 재확인하고," echo " 해소됐다면 해당 spec 을 제거한다(그냥 두면 CVE 가 남은 채 빌드가 성공한다)." exit 1 fi total=$((total + matched)) done echo "overlay: 총 ${total}개 jar 교체 완료" # keycloak-admin-cli 는 jackson 을 shade 로 품은 uber-jar 라 jar 교체로 못 고친다 # (SBOM 실측: jackson-databind@2.21.2 의 FilePath 가 bin/client/keycloak-admin-cli-*.jar). # 서버 JVM 이 로드하지 않는 독립 CLI(kcadm.sh/kcreg.sh)이므로 하드닝 이미지에서는 제거한다. # 업스트림 대비 유일한 기능적 차이다 — README.md / CUSTOM-README.md 에 명시. if [ -d "$KC_HOME/bin/client" ]; then rm -rf "$KC_HOME/bin/client" echo "removed: bin/client (kcadm/kcreg — jackson shaded uber-jar, 서버 런타임 미사용)" else echo "FAIL: bin/client 가 없다 — 배포본 레이아웃이 바뀌었다. 제거 전제를 재확인할 것" exit 1 fi