- Published on
EKS Pod간 TLS 실패? 인증서·SNI·mTLS 10분 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서로 잘 통신하던 Pod 간 호출이 갑자기 x509: certificate signed by unknown authority, tls: handshake failure, handshake alert: unrecognized_name, EOF 같은 TLS 에러로 터지면 대부분은 인증서/이름(SNI)/클라이언트 인증(mTLS)/시간/경로 중 하나가 어긋난 겁니다. 문제는 로그 한 줄만 보고는 원인을 단정하기 어렵다는 점이죠.
이 글은 EKS(쿠버네티스) 환경에서 Pod-to-Pod TLS 실패를 10분 안에 분류하고, 재현/검증/수정까지 빠르게 이어지도록 현장에서 바로 쓰는 커맨드 중심으로 정리합니다.
> 참고: TLS가 갑자기 전반적으로 실패하고 STS까지 같이 흔들린다면 시간 문제일 가능성이 큽니다. 아래 글도 함께 확인하세요.
> - EKS Pod 시간 드리프트로 STS·TLS 실패 해결하기
0) 10분 진단 로드맵(결론부터)
아래 순서대로 보면 “원인 범주”가 거의 확정됩니다.
- 서버 인증서가 무엇을 내주는지 확인(체인/만료/SubjectAltName)
- SNI가 올바른지 확인(서비스 DNS로 접속하는지, IP로 찍고 있지 않은지)
- mTLS 여부 확인(서버가 클라이언트 인증서를 요구하는지)
- 시간 드리프트 확인(특히
not yet valid,expired류) - 경로/프록시/사이드카 확인(Envoy/Istio/NGINX에서 TLS 종료 위치)
- 네트워크는 마지막(대부분은 TLS 이전 단계에서 이미 힌트가 나옴)
이제 각 단계를 커맨드로 쪼개서 보겠습니다.
1) 증상 문자열로 1차 분류
TLS 실패 메시지는 꽤 정직합니다. 아래 표처럼 먼저 분류해두면 불필요한 삽질이 줄어듭니다.
x509: certificate signed by unknown authority- CA 번들 누락, 잘못된 체인(중간 인증서 누락), 사설 CA인데 trust store에 없음
x509: certificate is valid for ..., not <host>- SAN 불일치(인증서에 들어있는 DNS 이름과 접속 호스트가 다름)
remote error: tls: bad certificate- (mTLS에서 흔함) 클라이언트 인증서가 잘못됨 또는 서버가 신뢰하지 않음
handshake alert: unrecognized_name- SNI 불일치(서버가 SNI 기반으로 cert 선택, 클라이언트가 다른 이름으로 요청)
certificate has expired/not yet valid- 시간 드리프트 또는 cert 갱신 실패
EOF,connection reset by peer- L7 프록시/사이드카가 중간에서 끊음, 포트 프로토콜 불일치(HTTP로 TLS 포트 접근 등)
2) “서버가 실제로 내주는 인증서”부터 뜯어보기
클라이언트 컨테이너의 trust store가 어떻든, 먼저 서버가 무엇을 내주는지를 봐야 합니다.
2-1) Pod 내부에서 openssl로 서버 인증서/체인 확인
아래는 가장 강력한 1줄입니다. 핵심은 -servername(SNI)입니다.
# 디버그용 임시 Pod에서 실행 권장
# 대상: my-svc.my-ns.svc.cluster.local:443
openssl s_client \
-connect my-svc.my-ns.svc.cluster.local:443 \
-servername my-svc.my-ns.svc.cluster.local \
-showcerts </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
여기서 확인할 것:
notBefore/notAfter: 만료/미래 유효 여부subjectAltName: DNS 이름이 들어있는지issuer: 사설 CA인지, 공인 CA인지
체인까지 보고 싶다면:
openssl s_client \
-connect my-svc.my-ns.svc.cluster.local:443 \
-servername my-svc.my-ns.svc.cluster.local \
-showcerts </dev/null
출력에 중간 인증서가 누락되어 있으면(서버가 leaf만 주는 경우) 클라이언트에서 unknown authority가 나기 쉽습니다.
2-2) curl로 “검증 실패 이유”를 더 읽기
curl -vk https://my-svc.my-ns.svc.cluster.local/healthz
* SSL certificate problem: unable to get local issuer certificate→ 중간/루트 CA 문제* subjectAltName does not match→ SAN 불일치
3) SNI 문제: IP로 때리면 거의 망합니다
EKS 내부 통신에서 흔한 실수는 Service의 ClusterIP로 직접 호출하거나, 환경변수/설정에 IP가 박혀서 호출되는 케이스입니다. TLS는 기본적으로 이름 기반 검증이고, 서버도 SNI로 인증서를 고릅니다.
3-1) SNI 미지정/불일치 재현
아래처럼 -servername 없이 붙으면(또는 다른 이름을 주면) 서버가 기본 인증서를 내줄 수 있습니다.
# SNI 없이 접속(문제 재현용)
openssl s_client -connect my-svc.my-ns.svc.cluster.local:443 </dev/null
# 틀린 SNI로 접속
openssl s_client \
-connect my-svc.my-ns.svc.cluster.local:443 \
-servername wrong.host.example \
</dev/null
unrecognized_name 또는 전혀 다른 인증서가 나오면 SNI 라우팅/가상호스트 구성이 있는 것입니다(Envoy/Ingress/NGINX 등).
3-2) 해결 체크
- 애플리케이션이 DNS 이름으로 호출하도록 강제
- gRPC라면
:authority/SNI가 무엇으로 잡히는지 확인 - Istio/Envoy를 쓰면
DestinationRule/ServiceEntry/Gateway의 TLS 모드 확인
4) mTLS 문제: 서버가 클라이언트 인증서를 요구하는지 확인
서버가 mTLS를 요구하면, 클라이언트가 인증서를 안 주는 순간 바로 실패합니다. openssl s_client로 확인 가능합니다.
openssl s_client \
-connect my-svc.my-ns.svc.cluster.local:443 \
-servername my-svc.my-ns.svc.cluster.local \
-state -tlsextdebug </dev/null
출력 중에 아래 같은 힌트가 보이면 mTLS 의심:
CertificateRequest가 온다(서버가 클라이언트 인증서 요구)
4-1) 클라이언트 인증서로 붙어보기
# client.crt/client.key, 그리고 서버가 신뢰하는 client CA가 맞아야 함
openssl s_client \
-connect my-svc.my-ns.svc.cluster.local:443 \
-servername my-svc.my-ns.svc.cluster.local \
-cert /etc/mtls/client.crt \
-key /etc/mtls/client.key \
-CAfile /etc/mtls/ca.crt \
</dev/null
여기서 성공하면 “네트워크”가 아니라 “클라이언트 인증서 배포/로테이션/권한” 문제로 범위가 확 줄어듭니다.
4-2) 흔한 원인
- Secret이 갱신됐는데 Pod가 재시작되지 않아 구 인증서를 들고 있음
ca.crt가 바뀌었는데 클라이언트 trust store가 그대로- cert-manager 로테이션 타이밍에 앱이 reload를 못 해서 간헐 실패
5) 시간 드리프트: not yet valid는 거의 확정 신호
클러스터 노드 시간은 정상인데 특정 Pod에서만 TLS가 실패하는 경우도 있습니다(컨테이너 이미지의 tz/시간 설정, 노드 문제, 일시적 드리프트 등). 인증서 검증은 시간에 민감합니다.
5-1) Pod에서 시간 확인
# 대상 Pod 내부
date -u
# 노드 시간도 비교하고 싶다면(권한이 있다면)
kubectl get node -o wide
증상이 STS/외부 HTTPS까지 같이 흔들린다면 시간 문제일 확률이 올라갑니다. 자세한 해결 흐름은 아래 글을 참고하세요.
6) “어디서 TLS가 종료되는가”를 먼저 확정하기
Pod-to-Pod TLS라고 해도 실제로는 다음 중 하나일 수 있습니다.
- 앱 ↔ 앱이 직접 TLS
- 앱 ↔ 사이드카(Envoy)에서 TLS 종료 후 평문으로 전달
- Ingress/Service Mesh Gateway에서 TLS 종료
종료 지점이 다르면 봐야 할 설정 파일이 달라집니다.
6-1) 서비스 포트/타겟포트 확인
kubectl -n my-ns get svc my-svc -o yaml
kubectl -n my-ns get endpointslices -l kubernetes.io/service-name=my-svc -o yaml
port: 443인데targetPort: 8080이면, 서비스는 443으로 받지만 백엔드는 8080(평문)일 수 있습니다.- 클라이언트가 TLS로 8080을 때리면
EOF/reset가 날 수 있습니다.
6-2) Pod에 사이드카가 있는지 확인
kubectl -n my-ns get pod my-pod -o jsonpath='{.spec.containers[*].name}'
istio-proxy, envoy 같은 컨테이너가 보이면, TLS 정책은 앱보다 메시 설정이 우선일 수 있습니다.
7) DNS/네트워크는 “TLS 단서 확보 후”에 확인
TLS는 L4 연결이 된 다음에야 시작됩니다. 따라서 먼저 connect가 되는지 확인하고, 그 다음 TLS를 봐야 합니다.
7-1) 연결성 체크
# TCP 레벨
nc -vz my-svc.my-ns.svc.cluster.local 443
# DNS
getent hosts my-svc.my-ns.svc.cluster.local
연결이 불안정하거나 특정 노드/서브넷에서만 실패하면 egress/NAT/SNAT 경로도 의심해야 합니다. 특히 외부로 나가는 TLS가 간헐 실패라면 아래 글이 도움이 됩니다.
8) 실전: 디버그용 ephemeral Pod 템플릿
EKS에서 문제를 빨리 좁히려면 “도구가 들어있는” 디버그 Pod를 하나 띄우는 게 가장 빠릅니다.
kubectl run -n my-ns tls-debug --rm -it \
--image=nicolaka/netshoot \
-- bash
# 들어가서
# openssl, curl, dig, tcpdump 등을 사용
Istio 환경이라면 sidecar 주입을 끄고(또는 켜고) 비교하는 것도 유용합니다.
kubectl run -n my-ns tls-debug-no-sidecar --rm -it \
--image=nicolaka/netshoot \
--overrides='{"metadata":{"annotations":{"sidecar.istio.io/inject":"false"}}}' \
-- bash
9) 자주 나오는 원인별 “바로 고치는” 체크리스트
9-1) unknown authority
- 서버가 fullchain을 제공하는지 확인(중간 인증서 포함)
- 클라이언트 컨테이너에 사설 CA를 넣었는지 확인
- Debian/Ubuntu:
/usr/local/share/ca-certificates/*.crt+update-ca-certificates - Alpine:
/usr/local/share/ca-certificates+update-ca-certificates
- Debian/Ubuntu:
9-2) SAN 불일치
- 인증서를
my-svc.my-ns.svc.cluster.local(또는 실제 호출 호스트)로 재발급 - 호출 호스트를 인증서 SAN에 맞게 변경
9-3) SNI 문제
- IP 호출 금지, DNS 호출 강제
- gRPC/HTTP 클라이언트에서 SNI/Host 헤더를 명시
9-4) mTLS 실패
- 서버가 요구하는 client cert 체인 확인
- Secret 로테이션 시 앱 reload/Pod restart 전략 점검
- 메시 정책(PeerAuthentication/DestinationRule 등)과 앱 설정 충돌 여부 확인
9-5) 시간
- 노드 NTP/chrony 상태 점검
- 특정 노드에서만 발생하면 노드 교체/드레인 고려
10) 마무리: “openssl 한 번”으로 절반은 끝납니다
Pod-to-Pod TLS 장애는 복잡해 보이지만, 실제로는 다음 3가지만 빨리 확인하면 절반 이상이 정리됩니다.
- 서버가 내주는 인증서(SAN/체인/만료)
- SNI가 맞는지(이름으로 접속하는지)
- mTLS로 클라이언트 인증서를 요구하는지
그 다음에야 시간/프록시/네트워크를 보세요. 특히 -servername을 넣은 openssl s_client는 “지금 이 경로에서 실제로 어떤 인증서가 오가는지”를 가장 빠르게 보여주는 도구입니다.
문제 상황의 에러 로그(클라이언트/서버), 대상 서비스 구성(svc/ingress/gateway), 그리고 위 openssl s_client -showcerts 출력 일부를 함께 보면 원인 좁히는 속도가 훨씬 빨라집니다.