4e12e38293
CI(31148639764)의 trivy 취약점 DB 가 로컬보다 최신이라 로컬 스캔에는 없던
CVE-2026-40983·CVE-2026-40984(io.micrometer:micrometer-core)가 차단으로 잡혔다.
netty·jackson 과 동일한 성격의 번들 jar CVE 라 같은 오버레이 메커니즘으로 처리한다.
micrometer 1.16.3 → 1.16.6. netty 와 같은 이유로 CVE 가 붙은 micrometer-core 만이
아니라 패밀리 전체 4종(commons·core·observation·registry-prometheus-simpleclient)을
함께 올린다 — 아티팩트 간 버전 혼용이 비지원이다.
Dockerfile 의 ARG 선언을 처음에 빠뜨렸는데 overlay-jars.sh 의 `: "${VAR:?}"` 가드가
설계대로 빌드를 세웠다("parameter null or not set"). 조용히 통과하지 않는다.
재빌드 결과(로컬 trivy DB 캐시를 지우고 CI 와 같은 최신 DB 로 재판정):
차단 0건 — 게이트 PASS
docker.io/paasup/keycloak:26.7.1-bci15.7-hardened-20260807 push 완료
태그는 빌드일 기준이라 같은 날 재빌드가 같은 태그를 덮는다(프레임워크 설계대로).
직전에 push 된 것은 micrometer 취약점이 남아 있던 빌드이고 어디에도 소비되지 않았다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
100 lines
4.8 KiB
Bash
Executable File
100 lines
4.8 KiB
Bash
Executable File
#!/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 <KEYCLOAK_HOME>}"
|
|
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:?}"
|
|
: "${MICROMETER_OLD:?}" "${MICROMETER_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}"
|
|
"io.micrometer|io/micrometer|*|${MICROMETER_OLD}|${MICROMETER_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
|