증상
회사 보안 정책으로 Zscaler가 설치된 뒤, 어느 날 갑자기 Docker image pull이 실패하기 시작한다.
failed to resolve reference "asia-northeast3-docker.pkg.dev/...":
failed to do request: Head "https://asia-northeast3-docker.pkg.dev/...":
tls: failed to verify certificate: x509: certificate signed by unknown authority
macOS 키체인에 Zscaler Root CA를 신뢰하도록 등록했고, docker context ls로 보면 별 문제 없어 보인다. 그런데도 docker pull은 계속 같은 오류를 낸다.
원인: Docker daemon은 macOS가 아니라 Colima VM 안에 있다
Colima를 사용하는 환경에서 docker info를 실행해 보면 이런 결과가 나온다.
Client: Docker Engine - Community
OS/Arch: darwin/arm64
Context: colima
Server:
Operating System: Ubuntu 24.04.4 LTS
Name: colima
Docker Root Dir: /var/lib/docker
Docker client는 macOS에서 실행되지만, Docker daemon(실제로 이미지를 pull하는 주체)은 Colima가 띄운 Ubuntu VM 내부에서 실행된다.
따라서 macOS 키체인이나 macOS의 /etc/docker/certs.d에 아무리 인증서를 추가해도 Colima VM 내부의 daemon에는 전달되지 않는다.
추가로 Zscaler는 TLS를 중간에서 재서명(SSL inspection)한다. 실제 레지스트리에 연결할 때 서버가 보내는 TLS chain에는 Root CA뿐 아니라 Intermediate CA도 포함된다.
subject=CN = *.domain.com, O = Zscaler Inc., OU = Zscaler Inc.
issuer=CN = "Zscaler Intermediate Root CA (zscalerthree.net) (t) ", ...
macOS 키체인에서 Root CA 하나만 추출하는 방식으로는 부족한 이유가 여기에 있다.
핵심 해결 원칙
- Colima VM 내부에 Zscaler CA를 설치해야 한다.
- macOS 키체인의 Root CA + 레지스트리 TLS chain의 Intermediate CA를 모두 수집한다.
- Docker registry 전용 CA 디렉터리(
/etc/docker/certs.d/)와 시스템 CA 번들(update-ca-certificates) 두 곳 모두에 반영한다. - CA 설치 후 Colima를 재시작해야 Docker daemon에 적용된다.
수정 스크립트
아래 스크립트는 위 원칙을 자동으로 수행한다.REGISTRY 환경변수에 사용 중인 레지스트리 도메인을 지정하면 된다.
#!/usr/bin/env bash
set -euo pipefail
REGISTRY="${REGISTRY:-your-registry.example.com}"
CERT_DIR="$HOME/.certs"
BUNDLE="$CERT_DIR/zscaler-bundle.pem"
mkdir -p "$CERT_DIR"
# 1. macOS 키체인에서 Zscaler CA 추출
extract_from_keychains() {
local tmp
tmp="$(mktemp -d)"
security find-certificate -a -p \
/System/Library/Keychains/SystemRootCertificates.keychain \
/Library/Keychains/System.keychain > "$tmp/all.pem"
awk '/-----BEGIN CERTIFICATE-----/{n++} n{print > ("'"$tmp"'/cert" n ".pem")}' "$tmp/all.pem"
for f in "$tmp"/cert*.pem; do
[[ -s "$f" ]] || continue
if openssl x509 -in "$f" -noout -subject -issuer 2>/dev/null | grep -iq Zscaler; then
cat "$f" >> "$BUNDLE"
printf '\n' >> "$BUNDLE"
fi
done
rm -rf "$tmp"
}
# 2. 실제 TLS chain에서 Intermediate CA 추출 (CA:TRUE 인증서만)
extract_from_tls_chain() {
local tmp
tmp="$(mktemp -d)"
echo | openssl s_client -connect "${REGISTRY}:443" \
-servername "$REGISTRY" -showcerts 2>/dev/null > "$tmp/chain.txt"
awk '/-----BEGIN CERTIFICATE-----/{n++} n{print > ("'"$tmp"'/cert" n ".pem")}' "$tmp/chain.txt"
for f in "$tmp"/cert*.pem; do
[[ -s "$f" ]] || continue
local text
text="$(openssl x509 -in "$f" -noout -text 2>/dev/null || true)"
if printf '%s\n' "$text" | grep -iq Zscaler \
&& printf '%s\n' "$text" | grep -q 'CA:TRUE'; then
cat "$f" >> "$BUNDLE"
printf '\n' >> "$BUNDLE"
fi
done
rm -rf "$tmp"
}
# 3. Colima VM 내부에 설치
install_to_colima() {
colima status >/dev/null 2>&1 || colima start
colima ssh -- bash -lc 'cat > /tmp/zscaler-bundle.pem' < "$BUNDLE"
colima ssh -- bash -s <<EOS
set -euo pipefail
REG_DIR="/etc/docker/certs.d/${REGISTRY}"
SYS_DIR="/usr/local/share/ca-certificates/zscaler"
sudo mkdir -p "\$REG_DIR" "\$SYS_DIR"
sudo cp /tmp/zscaler-bundle.pem "\$REG_DIR/ca.crt"
# 인증서 낱장 분리 후 fingerprint 파일명으로 저장
awk '/-----BEGIN CERTIFICATE-----/{n++} n{print > ("/tmp/zca-" n ".crt")}' /tmp/zscaler-bundle.pem
for f in /tmp/zca-*.crt; do
fp="\$(openssl x509 -in "\$f" -noout -fingerprint -sha256 | sed 's/.*=//' | tr -d ':')"
sudo cp "\$f" "\$SYS_DIR/\${fp}.crt"
done
sudo update-ca-certificates
EOS
colima restart
}
: > "$BUNDLE"
extract_from_keychains
extract_from_tls_chain
install_to_colima
echo "완료: docker pull로 검증하세요."
실행 및 검증
# 스크립트 저장 후 실행
REGISTRY=your-registry.example.com bash fix-zscaler-ca.sh
# 검증
docker pull your-registry.example.com/your-image:latest
성공하면 Colima VM 내부 CA 적용 상태를 이렇게 확인할 수 있다.
colima ssh -- bash -lc \
'ls /etc/docker/certs.d/your-registry.example.com/ && \
grep -c Zscaler /etc/ssl/certs/ca-certificates.crt'
Rancher Desktop / OrbStack 환경
Colima 대신 다른 Docker 런타임을 쓴다면 인증서를 복사하는 방법만 다르다.
| 런타임 | VM 접근 | 재시작 |
|---|---|---|
| Colima | colima ssh -- bash -lc '...' | colima restart |
| Rancher Desktop | rdctl shell bash -lc '...' | rdctl shutdown && open -a "Rancher Desktop" |
| OrbStack | orb ssh docker bash -lc '...' | orb restart docker |
재발 체크리스트
Zscaler CA 인증서는 보안 정책 변경 시 교체되거나 만료될 수 있다. 같은 오류가 다시 나타나면 순서대로 점검한다.
# 1. Docker context 확인
docker context show
docker info | grep -E 'Operating System|Name'
# 2. Colima VM 내부 CA 상태
colima ssh -- bash -lc \
'ls -la /etc/docker/certs.d/your-registry.example.com/ca.crt && \
grep -c Zscaler /etc/ssl/certs/ca-certificates.crt 2>/dev/null || echo 0'
# 3. TLS 검증
colima ssh -- bash -lc \
'echo | openssl s_client -connect your-registry.example.com:443 \
-CAfile /etc/ssl/certs/ca-certificates.crt 2>/dev/null | \
grep "Verify return code"'
Verify return code: 0 (ok) 가 나오지 않으면 스크립트를 다시 실행한다.
핵심 요약
- macOS에서 Colima를 사용하면 Docker daemon은 VM 내부 Linux에서 실행된다.
- Zscaler가 TLS를 재서명하므로 Root CA 하나만으로는 부족하다. Intermediate CA도 수집해야 한다.
- 인증서는
/etc/docker/certs.d/<registry>/ca.crt와 시스템 CA 번들 양쪽에 설치한다. - CA 설치 후에는 반드시 Colima를 재시작한다.