- Published on
EKS Pod에서 x509 unknown authority 오류 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 아니라 Pod 내부에서만 x509: certificate signed by unknown authority가 터지면, 대부분은 “네트워크는 되는데 TLS 신뢰 사슬이 Pod의 trust store에 없다”는 뜻입니다. 문제는 단순히 인증서가 만료된 경우도 있지만, EKS에서는 사내 프록시(MITM), 사설 CA, 베이스 이미지 CA 번들 부재, 잘못된 CA 마운트, IRSA/STS 호출 경로에서의 TLS 검증 실패처럼 다양한 경로로 나타납니다.
이 글은 EKS 운영 중 자주 만나는 패턴을 기준으로, 10분 내 원인 분류 → 재현 → 영구 해결까지 한 번에 정리합니다.
증상 패턴: 어디로 붙을 때 x509가 나는가
먼저 “무엇을 호출하다가” x509가 나는지 로그에서 확인해야 합니다.
- 앱이 외부 API 호출 시
- 예:
https://api.example.com,https://s3.<region>.amazonaws.com
- 예:
- Kubernetes API 호출 시
- 예:
https://kubernetes.default.svc
- 예:
- AWS STS / ECR / CloudWatch 등 AWS 엔드포인트 호출 시
- IRSA, SDK, awscli가 내부적으로 STS를 호출하다가 실패
로그 예시는 보통 다음 형태입니다.
Get "https://sts.amazonaws.com/": x509: certificate signed by unknown authority
또는
x509: certificate signed by unknown authority
crypto/x509: certificate signed by unknown authority
이 단계에서 대상 URL이 무엇인지가 해결의 80%입니다.
1) Pod 내부에서 빠르게 재현/진단하는 체크리스트
(1) 동일 네임스페이스에서 디버그 Pod 띄우기
애플리케이션 이미지가 너무 미니멀(distroless 등)이라면 진단 도구가 없을 수 있습니다. 디버그용 Pod를 별도로 띄우는 게 빠릅니다.
kubectl -n <ns> run netdebug --rm -it --image=curlimages/curl:8.5.0 -- sh
(2) 문제 URL로 TLS 체인 확인
# 1) 단순 연결
curl -Iv https://sts.amazonaws.com/
# 2) CA 번들을 명시해보기(컨테이너에 있으면)
curl -Iv --cacert /etc/ssl/certs/ca-certificates.crt https://sts.amazonaws.com/
curl: (60) SSL certificate problem: unable to get local issuer certificate면 CA 번들이 없거나, 중간 인증서/사설 CA가 누락된 겁니다.
(3) openssl로 서버가 내주는 체인 보기
curl 이미지에는 openssl이 없을 수 있으니 alpine을 사용합니다.
kubectl -n <ns> run ssldebug --rm -it --image=alpine:3.19 -- sh
apk add --no-cache openssl ca-certificates
# SNI 포함해서 확인
openssl s_client -connect sts.amazonaws.com:443 -servername sts.amazonaws.com -showcerts </dev/null
여기서 “서버 인증서가 정상인데도 verify error”가 뜨면, 대개 Pod의 trust store가 빈약하거나 프록시가 중간에서 다른 인증서를 끼워 넣는(MITM) 상황입니다.
2) 가장 흔한 원인 5가지와 해결
원인 A: 베이스 이미지에 CA 번들이 없거나 오래됨
distroless, scratch, slim 계열 이미지에서 흔합니다. 특히 Go 앱은 시스템 trust store를 보는데, 그 파일이 없으면 바로 x509가 납니다.
해결 1) 이미지에 ca-certificates 설치
Debian/Ubuntu 계열
FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& update-ca-certificates \
&& rm -rf /var/lib/apt/lists/*
Alpine 계열
FROM alpine:3.19
RUN apk add --no-cache ca-certificates && update-ca-certificates
해결 2) distroless를 유지하면서 CA만 복사
# CA를 준비하는 스테이지
FROM debian:bookworm-slim AS certs
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates
# distroless 런타임
FROM gcr.io/distroless/base-debian12
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY app /app
USER 65532:65532
ENTRYPOINT ["/app"]
이걸로도 해결이 안 되면, 다음 원인(B/C)을 의심합니다.
원인 B: 사내 프록시/보안장비가 TLS를 가로채고(SSL Inspection) 사설 CA로 재서명
EKS에서 외부로 나갈 때 회사 프록시를 타거나, VPC egress에 보안 장비가 있는 경우가 대표적입니다. 이때 서버가 내주는 인증서가 AWS/공인 CA가 아니라 사내 CA로 서명된 인증서로 바뀌고, Pod는 그 CA를 모르니 x509가 납니다.
진단 포인트
- openssl 출력에서 Issuer가 회사/사내 CA로 보임
- 동일 URL을 로컬 PC(회사 인증서가 설치된 환경)에서는 정상
- 특정 서브넷/노드에서만 재현
해결: 사내 Root CA를 컨테이너 trust store에 추가
- Root CA PEM을 ConfigMap/Secret로 배포
kubectl -n <ns> create configmap corp-root-ca --from-file=corp-root-ca.crt=./corp-root-ca.pem
- Pod에 마운트하고 update-ca-certificates 수행
(권장) initContainer로 시스템 CA 업데이트
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
volumes:
- name: corp-ca
configMap:
name: corp-root-ca
- name: ssl-certs
emptyDir: {}
initContainers:
- name: init-ca
image: alpine:3.19
command: ["sh", "-c"]
args:
- |
apk add --no-cache ca-certificates;
cp /etc/ssl/certs/ca-certificates.crt /work/ca-certificates.crt;
cp /corp/ca.crt /usr/local/share/ca-certificates/corp-root-ca.crt;
update-ca-certificates;
cp /etc/ssl/certs/ca-certificates.crt /work/ca-certificates.crt;
volumeMounts:
- name: corp-ca
mountPath: /corp
- name: ssl-certs
mountPath: /work
containers:
- name: app
image: your-image
env:
- name: SSL_CERT_FILE
value: /etc/ssl/certs/ca-certificates.crt
volumeMounts:
- name: ssl-certs
mountPath: /etc/ssl/certs
핵심은 앱 컨테이너가 참조하는 CA 번들 경로를 확실히 맞추는 겁니다. 언어/런타임별로 참조 위치가 다를 수 있으니, 가능하면 SSL_CERT_FILE(OpenSSL 계열) 또는 언어별 옵션을 함께 지정하세요.
원인 C: Java/Node/Python 등 런타임이 OS trust store가 아닌 자체 trust store를 사용
- Java는 기본적으로
cacerts(JKS/PKCS12)를 사용 - Node.js는 빌드 옵션/배포판에 따라 번들이 다를 수 있고,
NODE_EXTRA_CA_CERTS가 필요할 수 있음 - Python
requests는certifi번들을 쓰는 경우가 많아 OS CA 추가가 즉시 반영되지 않을 수 있음
Java: 사내 CA를 cacerts에 import
FROM eclipse-temurin:17-jre
COPY corp-root-ca.pem /tmp/corp-root-ca.pem
RUN keytool -importcert -noprompt \
-alias corp-root-ca \
-file /tmp/corp-root-ca.pem \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit
Node.js: NODE_EXTRA_CA_CERTS
env:
- name: NODE_EXTRA_CA_CERTS
value: /etc/ssl/certs/corp-root-ca.crt
Python requests: REQUESTS_CA_BUNDLE 또는 SSL_CERT_FILE
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
# 또는
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
이 케이스는 “OS에 CA를 넣었는데도 앱만 실패”라는 형태로 나타납니다.
원인 D: Kubernetes API(클러스터 내부) 호출에서 x509 발생
https://kubernetes.default.svc 호출에서 x509가 나면 보통 아래 중 하나입니다.
- Pod에 서비스어카운트 토큰/CA 마운트가 비활성화됨
- kubeconfig를 수동으로 넣었는데 CA가 잘못됨
- in-cluster config가 아닌 외부 클러스터로 잘못 붙음
점검: 서비스어카운트 CA 마운트 확인
Pod spec에 다음이 들어가 있으면 in-cluster 인증 정보가 마운트되지 않을 수 있습니다.
automountServiceAccountToken: false
또는, 특정 컨테이너에서 /var/run/secrets/kubernetes.io/serviceaccount/ca.crt가 없는지 확인합니다.
kubectl -n <ns> exec -it <pod> -- ls -l /var/run/secrets/kubernetes.io/serviceaccount/
정상이라면 ca.crt, token, namespace가 보여야 합니다.
원인 E: IRSA/STS 경로에서 x509 → AWS SDK가 STS를 못 믿음
IRSA를 쓰는 Pod가 AssumeRoleWithWebIdentity를 호출하는 과정에서 STS에 TLS 연결을 합니다. 이때 x509가 나면 **(1) CA 문제(B/C)**가 가장 흔하지만, 운영에서는 다음과 같이 연쇄 장애로 커지기도 합니다.
- STS 호출 실패 → 자격증명 획득 실패 → S3/ECR/CloudWatch 모두 실패
- 앱 로그에는 단순 x509만 남고 “IRSA 문제”처럼 보이지 않음
IRSA 자체가 403으로 실패하는 케이스는 별도 원인(예: OIDC provider/Thumbprint/Trust policy)일 수 있으니, x509가 아니라 403이라면 아래 글도 함께 확인하는 게 좋습니다.
다만 **이번 주제(x509)**에서는 IRSA trust policy보다 먼저 **TLS 신뢰(프록시/사설 CA/런타임 trust store)**를 해결해야 합니다.
3) “임시 회피”가 왜 위험한가: TLS 검증 끄기
긴급 상황에서 아래처럼 TLS 검증을 꺼서 우회하는 경우가 있습니다.
- curl:
-k/--insecure - Node:
NODE_TLS_REJECT_UNAUTHORIZED=0 - Git:
http.sslVerify=false
이건 중간자 공격을 허용하고, 프록시/장비가 인증서를 바꿔치기해도 탐지 못합니다. 특히 EKS Pod가 STS/ECR 같은 핵심 엔드포인트에 접근하는 상황에서 TLS 검증을 끄면 보안 사고로 직결될 수 있어 권장하지 않습니다.
4) 운영에서 재발 방지: 표준화 전략 3가지
(1) 조직 표준 CA 번들 레이어를 이미지에 공통 적용
- 모든 베이스 이미지에
ca-certificates포함 - 사내 Root CA가 있다면 멀티스테이지로 공통 복사
- 언어별(Java/Node/Python) 추가 설정을 템플릿화
(2) 프록시 환경을 명시적으로 관리
프록시를 써야 한다면 다음을 명확히 합니다.
HTTP_PROXY/HTTPS_PROXY/NO_PROXY를 Deployment에 표준화NO_PROXY에kubernetes.default.svc, 클러스터 도메인, VPC 엔드포인트 도메인 등을 포함
예시:
env:
- name: HTTPS_PROXY
value: http://proxy.corp.local:3128
- name: HTTP_PROXY
value: http://proxy.corp.local:3128
- name: NO_PROXY
value: ".svc,.cluster.local,10.0.0.0/8,169.254.170.2,169.254.169.254"
169.254.169.254(IMDS), 169.254.170.2(ECS/EKS 일부 자격증명 경로) 등은 환경에 따라 필요합니다. IRSA는 IMDS가 아니라 토큰 파일+STS지만, 프록시를 잘못 타면 STS 호출이 꼬일 수 있습니다.
(3) EKS 장애 진단 런북에 “x509 분기” 추가
x509는 네트워크 장애(타임아웃)와 달리 연결은 되는데 신뢰가 안 되는 케이스가 많아서, 런북에서 별도 분기하는 게 효과적입니다. EKS에서 다른 유형의 장애(예: 503/504)와 섞여 보일 때도 많으니, 트러블슈팅 체계를 함께 갖추면 좋습니다.
5) 실전 예시: “Pod에서만 STS x509”를 끝내는 최소 절차
아래 순서대로 하면 대부분의 케이스를 빠르게 끝낼 수 있습니다.
- Pod에서 대상 URL 확인(로그)
- 디버그 Pod에서
openssl s_client로 Issuer 확인 - Issuer가 사내 CA면 → 사내 Root CA를 ConfigMap으로 배포하고 trust store에 추가
- Issuer가 공인 CA인데도 실패면 → 이미지에
ca-certificates설치/업데이트 - OS에서 되는데 앱만 실패면 → Java/Node/Python 런타임 trust store 설정 반영
마무리
x509: certificate signed by unknown authority는 메시지는 짧지만, 원인은 크게 (1) CA 번들 부재/노후, (2) 사내 프록시/보안장비의 TLS 가로채기, (3) 런타임별 독자 trust store, (4) K8s in-cluster 인증 정보 누락, (5) IRSA/STS 경로에서의 TLS 실패로 정리됩니다.
특히 EKS에서는 IRSA, AWS SDK, 외부 API 호출이 한 Pod 안에서 동시에 일어나기 때문에 “어디에서 x509가 났는지”를 먼저 특정하고, 그 호출 경로의 trust store를 정확히 맞추는 것이 가장 빠른 해결책입니다.