#!/usr/bin/env bash # argocd 이미지 기능 검증 — 호스트에서 bash 로 실행된다 # (build-hardened-image.sh 가 `env TAG=... PLATFORM=... bash verify.sh` # 형태로 호출한다). # # 이 이미지의 핵심 위험은 "번들 도구를 릴리스 바이너리 다운로드에서 소스 컴파일로 바꾼 것"과 # "ubuntu → SUSE BCI 베이스 교체" 두 가지다. 그래서 argocd 본체뿐 아니라 helm·kustomize· # git-lfs·tini·connect-proxy 가 전부 실제로 실행되는지, 업스트림이 만들던 런타임 구조 # (심볼릭 링크 9개, /etc/gitconfig 의 LFS 필터, /app/config 권한)가 그대로인지 확인한다. # # 실제 기동(서버·컨트롤러)은 Kubernetes API 가 필요해 이 스모크 범위 밖이다 — # dev 클러스터 배포 검증(.claude/deploy-test-procedure.md, scripts/deploy-test/deploy-test-argo-cd.sh)이 # 담당한다. # # 마지막 줄에 VERIFY-OK 를 출력하면 통과다. build-hardened-image.sh 가 그것으로 판정한다. set -e TAG="${TAG:?TAG 환경변수가 필요하다}" PLATFORM="${PLATFORM:-linux/amd64}" APP_VERSION="${APP_VERSION:?APP_VERSION 환경변수가 필요하다 (build.env 에서 전달)}" SOURCE_COMMIT="${SOURCE_COMMIT:?SOURCE_COMMIT 환경변수가 필요하다}" HELM_VERSION="${HELM_VERSION:?HELM_VERSION 환경변수가 필요하다}" KUSTOMIZE_VERSION="${KUSTOMIZE_VERSION:?KUSTOMIZE_VERSION 환경변수가 필요하다}" GIT_LFS_VERSION="${GIT_LFS_VERSION:?GIT_LFS_VERSION 환경변수가 필요하다}" echo "== 이미지 메타데이터 ==" USER_CFG="$(docker inspect --format '{{.Config.User}}' "$TAG")" [ "$USER_CFG" = "999" ] || { echo "FAIL: Config.User 가 999 가 아니다 (실제: $USER_CFG) — 업스트림 ARGOCD_USER_ID"; exit 1; } ENTRY="$(docker inspect --format '{{json .Config.Entrypoint}}' "$TAG")" case "$ENTRY" in *tini*) ;; *) echo "FAIL: ENTRYPOINT 에 tini 가 없다 (실제: $ENTRY)"; exit 1 ;; esac echo " Config.User=$USER_CFG Entrypoint=$ENTRY" docker run --rm -i --platform "$PLATFORM" \ -e APP_VERSION="$APP_VERSION" \ -e SOURCE_COMMIT="$SOURCE_COMMIT" \ -e HELM_VERSION="$HELM_VERSION" \ -e KUSTOMIZE_VERSION="$KUSTOMIZE_VERSION" \ -e GIT_LFS_VERSION="$GIT_LFS_VERSION" \ --entrypoint sh "$TAG" <<'GUEST' set -e echo "== argocd 본체 ==" OUT="$(argocd version --client --short)" echo " $OUT" case "$OUT" in *"v$APP_VERSION"*) ;; *) echo "FAIL: 버전 출력에 v$APP_VERSION 이 없다 — Makefile ldflags 주입 확인 필요"; exit 1 ;; esac case "$OUT" in *"$SOURCE_COMMIT"*) ;; *) echo "WARN: 버전 출력에 pinned commit 이 안 보인다 (short 출력이라 생략됐을 수 있음)" ;; esac echo "== 업스트림 심볼릭 링크 9개 ==" for n in argocd-server argocd-repo-server argocd-cmp-server argocd-application-controller \ argocd-dex argocd-notifications argocd-applicationset-controller argocd-k8s-auth \ argocd-commit-server; do [ -x "/usr/local/bin/$n" ] || { echo "FAIL: /usr/local/bin/$n 없음"; exit 1; } done echo " 9개 모두 존재·실행 가능" echo "== 번들 도구 (소스 컴파일로 교체한 것들) ==" H="$(helm version --short)" echo " helm $H" case "$H" in *"$HELM_VERSION"*) ;; *) echo "FAIL: helm 버전이 $HELM_VERSION 이 아니다"; exit 1 ;; esac K="$(kustomize version)" echo " kustomize $K" case "$K" in *"$KUSTOMIZE_VERSION"*) ;; *) echo "FAIL: kustomize 버전이 $KUSTOMIZE_VERSION 이 아니다"; exit 1 ;; esac L="$(git-lfs version)" echo " git-lfs $L" case "$L" in *"$GIT_LFS_VERSION"*) ;; *) echo "FAIL: git-lfs 버전이 $GIT_LFS_VERSION 이 아니다"; exit 1 ;; esac echo "== SLE_BCI 에 없어 소스 빌드한 C 도구 ==" [ -x /usr/bin/tini ] || { echo "FAIL: /usr/bin/tini 없음"; exit 1; } /usr/bin/tini --version [ -x /usr/bin/connect-proxy ] || { echo "FAIL: /usr/bin/connect-proxy 없음"; exit 1; } # connect 는 인자 없이 부르면 usage 를 내고 비정상 종료한다 — 실행 가능 여부만 본다. /usr/bin/connect-proxy >/dev/null 2>&1 || true echo " tini · connect-proxy 실행 가능" echo "== 런타임 필수 도구 (업스트림 apt 목록 대응) ==" git --version >/dev/null || { echo "FAIL: git 없음"; exit 1; } gpg --version >/dev/null || { echo "FAIL: gpg 없음"; exit 1; } ssh -V 2>/dev/null || ssh -V || { echo "FAIL: ssh 없음"; exit 1; } echo " git · gpg · ssh 정상" echo "== 차트가 실제로 실행하는 명령 ==" # argo-cd 차트의 repo-server init 컨테이너(copyutil)가 그대로 쓰는 형식이다. # /bin/cp --update=none /usr/local/bin/argocd /var/run/argocd/argocd # `--update=none` 은 GNU coreutils 9.3+ 에서만 된다. 베이스 OS 의 coreutils 가 낮으면 # 이미지는 멀쩡히 빌드·스캔을 통과하고 **배포 시점에** Init:CrashLoopBackOff 로 죽는다 # (실측). 그래서 여기서 직접 확인한다 — 베이스를 바꿀 때 이 검사가 걸러준다. : > /tmp/_vsrc if ! /bin/cp --update=none /tmp/_vsrc /tmp/_vdst 2>/dev/null; then echo "FAIL: 'cp --update=none' 이 동작하지 않는다 — 베이스 OS 의 coreutils 가 9.3 미만이다." echo " argo-cd 차트의 repo-server init 컨테이너가 이 형식을 쓰므로 배포가 실패한다." cp --version 2>/dev/null | head -1 exit 1 fi echo " cp --update=none 동작 ($(cp --version 2>/dev/null | head -1))" # argocd 바이너리를 공유 볼륨에 복사하는 흐름 자체를 그대로 재현해 본다. mkdir -p /tmp/_vrun /bin/cp --update=none /usr/local/bin/argocd /tmp/_vrun/argocd || { echo "FAIL: argocd 복사 실패"; exit 1; } /bin/ln -sf /tmp/_vrun/argocd /tmp/_vrun/argocd-cmp-server || { echo "FAIL: cmp-server 심볼릭 링크 실패"; exit 1; } [ -x /tmp/_vrun/argocd-cmp-server ] || { echo "FAIL: 복사된 argocd 가 실행 가능하지 않다"; exit 1; } echo " copyutil 흐름(복사 + cmp-server 링크) 재현 성공" echo "== git-lfs 시스템 설정 (업스트림 git lfs install --system) ==" git config --system --get filter.lfs.clean >/dev/null || { echo "FAIL: /etc/gitconfig 에 LFS 필터가 없다"; exit 1; } echo " filter.lfs.clean 등록됨" echo "== 업스트림 런타임 디렉토리 구조 ==" [ -L /etc/ssh/ssh_known_hosts ] || { echo "FAIL: /etc/ssh/ssh_known_hosts 심볼릭 링크 없음"; exit 1; } [ -d /app/config/tls ] || { echo "FAIL: /app/config/tls 없음"; exit 1; } [ -d /app/config/gpg/keys ] || { echo "FAIL: /app/config/gpg/keys 없음"; exit 1; } [ -x /usr/local/bin/entrypoint.sh ] || { echo "FAIL: entrypoint.sh 없음"; exit 1; } [ -x /usr/local/bin/uid_entrypoint.sh ] || { echo "FAIL: uid_entrypoint.sh 없음"; exit 1; } [ -x /usr/local/bin/gpg-wrapper.sh ] || { echo "FAIL: gpg-wrapper.sh 없음"; exit 1; } echo " ssh_known_hosts 링크 · /app/config/{tls,gpg/keys} · 래퍼 스크립트 정상" echo "VERIFY-OK" GUEST