Published on

EKS Pod에서 x509 unknown authority 오류 해결

Authors

서버가 아니라 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 certificateCA 번들이 없거나, 중간 인증서/사설 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 --from=certs /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에 추가

  1. Root CA PEM을 ConfigMap/Secret로 배포
kubectl -n <ns> create configmap corp-root-ca --from-file=corp-root-ca.crt=./corp-root-ca.pem
  1. 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 requestscertifi 번들을 쓰는 경우가 많아 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_PROXYkubernetes.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”를 끝내는 최소 절차

아래 순서대로 하면 대부분의 케이스를 빠르게 끝낼 수 있습니다.

  1. Pod에서 대상 URL 확인(로그)
  2. 디버그 Pod에서 openssl s_client로 Issuer 확인
  3. Issuer가 사내 CA면 → 사내 Root CA를 ConfigMap으로 배포하고 trust store에 추가
  4. Issuer가 공인 CA인데도 실패면 → 이미지에 ca-certificates 설치/업데이트
  5. 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를 정확히 맞추는 것이 가장 빠른 해결책입니다.