🏠 목록 east-west gateway는 목적지 클러스터를 SNI에 인코딩해, mTLS를 복호화하지 않고 암호화된 채로 원격 워크로드까지 프록시한다 📄 MD 원본 🌓 테마
istiomulticlustereastwest-gatewaysnimtls

east-west gateway는 목적지 클러스터를 SNI에 인코딩해, mTLS를 복호화하지 않고 암호화된 채로 원격 워크로드까지 프록시한다

NOTE

east-west gateway는 mTLS를 풀지 않고 봉투 겉면(ClientHello의 SNI)에 적힌 목적지만 읽어 다음 hop으로 넘기는 L4 SNI 라우터다. 멀티클러스터(network 분리) 메시에서 한 클러스터의 sidecar가 다른 클러스터의 워크로드를 부를 때, sidecar는 목적지 식별자를 SNI 필드에 인코딩해 보내고, 게이트웨이는 그 SNI만 읽어(AUTO_PASSTHROUGH) 암호 바이트를 그대로 흘린다. 종단을 안 하므로 워크로드↔워크로드 mTLS가 관통해 보존된다. 이 문서는 그 메커니즘과 왜를 다룬다(운영 매니페스트는 위임).

대상환경: Istio 1.30, multi-network 멀티클러스터 · 대상독자: 멀티클러스터 mTLS 신원 보존이 왜·어떻게 동작하는지 알고 싶은 SRE · 선행개념: SPIFFE/mTLS 신원, Cluster 해부


1. 배경: 단일 클러스터 mTLS가 멀티클러스터에서 깨지는 두 지점

east-west gateway가 왜 존재하는지는, 그것이 없을 때 무엇이 깨지는지에서 나온다. 먼저 정상 상태부터.

단일 클러스터에서는 sidecar가 목적지 pod IP를 직접 알고, Envoy cluster가 그 endpoint로 mTLS를 맺는다. 워크로드 A의 SPIFFE 신원이 워크로드 B에 그대로 제시되고, B의 AuthorizationPolicy는 "A가 부른 게 맞다"를 그 신원으로 판정한다(SPIFFE/mTLS 신원). 이 그림에서 sidecar↔sidecar는 1-hop이고, 중간에 끼어드는 자가 없다.

멀티클러스터 — 특히 network가 분리된 경우(클러스터 간 pod CIDR이 서로 라우팅 불가) — 에서는 이 그림이 두 군데서 깨진다.

순진한 해법(클러스터 경계에 보통 게이트웨이를 세워 TLS 종단 후 재암호화)은 연결성은 풀지만 신원을 죽인다. 신원을 살리려면 중간 게이트웨이가 절대 TLS를 풀지 않아야 한다. 그런데 풀지 않으면 게이트웨이는 안을 못 보는데, 어떻게 "어디로 보낼지"를 정하나? 이 긴장이 east-west gateway 설계 전체를 결정한다.


2. 핵심 아키텍처: 봉투 겉면(SNI)에 목적지를 적어 라우터에게 읽힌다

머릿속에 담을 한 장면(anchor): 게이트웨이가 편지 을 못 열게 하려면, 받는 사람을 봉투 겉면에 적으면 된다. TLS에서 그 겉면이 평문으로 노출되는 유일한 칸이 ClientHello의 SNI다. 그래서 sidecar는 목적지 식별자를 SNI에 인코딩해 보내고, 게이트웨이는 그 한 줄만 읽어 라우팅한다 — 복호화 0.

이 한 문장에서 나머지 모든 디테일이 따라 나온다. 메커니즘을 세 부품으로 분해하면.

부품 그게 답하는 질문 어떻게
SNI 인코딩 (sidecar 쪽) "게이트웨이가 안을 못 보는데 목적지를 어떻게 전달하나?" 목적지를 outbound_.PORT_.SUBSET_.FQDN로 SNI에 적음
AUTO_PASSTHROUGH (게이트웨이 listener) "게이트웨이가 어떻게 복호화 없이 SNI만 읽나?" tls_inspector로 ClientHello의 SNI만 추출, TLS는 안 풂
sni-dnat (게이트웨이 라우팅) "추출한 SNI를 어디로 보내나?" SNI를 동일 이름 내부 cluster로 역매핑해 tcp_proxy

전체 흐름을 한 장에 담으면.

cluster1 / network1workload Asidecareast-west GWAUTO_PASSTHROUGHcluster2 / network2east-west GWAUTO_PASSTHROUGHworkload BsidecarmTLS, SNI=outbound_...Bby SNIsymmetric path C2→C1
그림 1. cluster1 workload A → cluster2 east-west GW(AUTO_PASSTHROUGH). GW는 SNI(outbound_.port._.B.ns.svc)만 읽어 같은 암호 바이트를 B로 전달. 경로는 양방향 대칭.
NOTE

A의 sidecar는 cluster2의 east-west GW(EW2)로 곧장 mTLS를 맺는다 — 자기 클러스터 EW1을 거치지 않는다. EW1은 반대 방향(cluster2 → cluster1의 워크로드) 트래픽의 진입점이다. 게이트웨이는 network마다 하나 있는 양방향 관문이고, 트래픽은 항상 목적지 network의 게이트웨이로 들어간다.

2.1 부품 ①: SNI 인코딩 — sidecar가 목적지를 봉투에 적는다

평범한 Envoy cluster의 transport socket은 TLS를 맺을 때 SNI를 목적지 hostname(또는 비움)으로 채운다 — "어떤 server 인증서를 줄까"용이다. east-west 경로용 cluster는 이 SNI를 목적지 식별자 문자열로 덮어쓴다. istiod가 멀티클러스터 sidecar에 내려주는 cluster의 모양은 이렇다.

# istioctl proxy-config cluster deploy/workload-a --fqdn B.ns.svc.cluster.local -o json 의 발췌
{
  "name": "outbound|8080||B.ns.svc.cluster.local",
  "transportSocket": {
    "name": "envoy.transport_sockets.tls",
    "typedConfig": {
      "sni": "outbound_.8080_._.B.ns.svc.cluster.local"   # ← 목적지를 SNI에 인코딩
    }
  },
  "loadAssignment": { "endpoints": [
    { "lbEndpoints": [ { "endpoint": { "address": {
        "socketAddress": { "address": "<EW2 gateway IP>", "portValue": 15443 }
    }}}]}
  ]}
}

이 한 객체에 멀티클러스터의 본질 두 개가 다 들어 있다.

  1. endpoint가 원격 pod가 아니라 east-west gateway IP:15443이다. network 분리로 pod에 직접 못 가니, istiod가 endpoint를 게이트웨이로 치환해 내려준다. (cluster 구조 일반론은 Cluster 해부.)
  2. SNI가 hostname이 아니라 outbound_.{port}_.{subset}_.{fqdn} 인코딩 문자열이다. 일반 TLS의 SNI는 인증서 선택용이지만, 여기 SNI는 "원격 게이트웨이가 어느 내부 cluster로 보낼까"의 라우팅 키다. cluster 이름 outbound|8080||B..._로 평탄화한 게 SNI라는 점을 보면, 둘이 같은 식별자임을 알 수 있다 — 이게 뒤의 역매핑을 공짜로 만든다.
구분 일반 cluster의 SNI east-west용 cluster의 SNI
목적지 hostname 또는 없음 outbound_.PORT_.SUBSET_.FQDN
용도 server 인증서 선택(SAN 매칭) 원격 게이트웨이의 라우팅 키
endpoint 실제 목적지 IP east-west gateway IP:15443
누가 읽나 목적지 워크로드 east-west gateway(passthrough)
KEY

포트 15443은 east-west gateway의 TLS auto-passthrough listener다. 메시 내부 inbound(15006)·outbound(15001)와 별개의, 멀티클러스터 전용 포트다.

2.2 부품 ②③: AUTO_PASSTHROUGH로 SNI만 읽고, sni-dnat으로 내부 cluster에 꽂는다

원격 east-west gateway의 listener는 tls.mode: AUTO_PASSTHROUGH로 떠 있다. 동작은 egress passthroughprotocol: TLS + tls_inspector와 같은 계열 — TLS를 풀지 않고 ClientHello의 SNI만 추출한다.

protocol: TLS + AUTO_PASSTHROUGH  →  listener:[ tls_inspector → tcp_proxy ]
   ClientHello의 SNI 추출 → SNI 문자열을 cluster 이름으로 해석 → 그 cluster로 tcp_proxy
   복호화 0, L7 0 (암호 바이트 그대로 통과)

게이트웨이가 하는 일은 SNI 디코딩 → 매칭되는 내부 cluster 선택뿐이다.

받은 SNI:  outbound_.8080_._.B.ns.svc.cluster.local
             │        │    │   └── fqdn      → 어느 서비스
             │        │    └────── subset    → 어느 DestinationRule subset
             │        └─────────── port      → 어느 포트
             └──────────────────── direction(outbound)
                  ▼ 이 인코딩을 cluster "outbound|8080||B.ns.svc.cluster.local" 로 역매핑
       → 그 cluster의 endpoint(= cluster2 내부 B의 실제 pod들)로 tcp_proxy

이 "SNI를 보고 동일 이름의 내부 cluster로 dnat"이 sni-dnat 라우터 모드다. istiod가 멀티클러스터 게이트웨이에 ROUTER_MODE=sni-dnat을 켜면, 게이트웨이는 각 서비스마다 outbound_.* SNI에 매칭되는 passthrough cluster를 자동 생성한다. 게이트웨이는 받은 암호 바이트를 건드리지 않고 그 내부 cluster의 실제 pod endpoint로 tcp_proxy할 뿐이다. SNI 문자열과 cluster 이름이 같은 식별자(§2.1)이므로 이 역매핑은 문자열 변환 한 번이다 — L7 파싱도, 세션 키도 필요 없다.

workload A sidecar (c1)east-west GW (c2)workload B (c2)ClientHello, SNI=outbound_.8080._.B...tls_inspector: SNI만 읽음, 복호화 Xsni-dnat: SNI → cluster outbound|8080||Bforward SAME bytes (tcp_proxy)mTLS handshake A↔B end-to-end, A의 SPIFFE 제시encrypted response (via GW)
그림 2. east-west GW는 tls_inspector로 SNI만 읽고 sni-dnat으로 cluster를 정해 암호 바이트를 그대로 tcp_proxy. mTLS는 A↔B 종단에서 성립하고 A의 SPIFFE 신원이 B에 전달됨.

결정적 결과: TLS 핸드셰이크는 A와 B 사이에서 완성된다. 게이트웨이는 그 핸드셰이크의 ClientHello만 엿보고 바이트를 옮겼을 뿐, 세션 키를 모른다. 따라서 B는 A의 진짜 SPIFFE 신원을 받고, AuthorizationPolicy가 그 신원으로 정상 평가된다 — 게이트웨이가 신원을 "삼키지" 않는다.

2.3 왜 종단하면 안 되나 — 대안과의 대조로 설계를 못 박기

AUTO_PASSTHROUGH의 당위는 그 반대를 그려보면 확정된다. 만약 east-west gateway가 ISTIO_MUTUAL/SIMPLE로 TLS를 종단한다면.

AUTO_PASSTHROUGH는 이 모두를 피한다 — L4 SNI 라우터로만 동작하므로 신원이 게이트웨이를 투명하게 관통한다. 이것이 ingress gateway와 갈리는 본질이다. ingress는 외부→메시 진입이라 종단·L7 라우팅이 목적이지만, east-west는 메시 내부 mTLS를 그대로 다른 network로 운반하는 게 목적이다.

구분 ingress gateway east-west gateway
TLS 처리 종단(복호화), L7 라우팅 AUTO_PASSTHROUGH, SNI만
신원 클라이언트→GW에서 끝, GW가 새 신원으로 mesh 진입 A→B end-to-end 보존
라우팅 키 host/path(L7) SNI 인코딩 문자열(L4)
포트 80/443 등 15443
목적 외부 트래픽 진입 network 간 mTLS 운반

3. 무엇이 이 라우팅을 켜고 끄나 — network 식별자가 방아쇠

위 메커니즘은 istiod가 어떤 endpoint가 어느 network에 있는지 알 때만 성립한다. 그 지식은 세 식별자에서 온다.

식별자 어디서 정의 역할
meshID install values (global.meshID) 여러 클러스터를 하나의 신뢰·메시 단위로 묶음. 같은 meshID + 공유 root CA여야 mTLS 신뢰가 클러스터를 횡단
clusterName install values (global.multiCluster.clusterName) 각 클러스터에 고유 이름 부여. endpoint 출처 식별·메트릭 라벨(source_cluster)에 쓰임
network namespace label topology.istio.io/network + gateway 설정 endpoint가 어느 network에 속하는지. 이게 핵심 스위치

istiod의 endpoint discovery(EDS) 분기는 단순하다 — 이 한 비교가 §2 전체를 켤지 말지 결정한다.

목적지 endpoint의 network == 호출자 sidecar의 network ?
├─ 같다  → endpoint를 pod IP 그대로 내려줌(직접 연결, east-west GW 불필요)
└─ 다르다 → endpoint를 그 network의 east-west GW IP:15443으로 치환 + SNI 인코딩

east-west gateway 경유는 network가 다를 때만 일어난다. 같은 network면(예: flat pod network를 공유하는 멀티클러스터) sidecar가 원격 pod로 직접 mTLS를 맺고 게이트웨이를 안 거친다. 트리거는 클러스터 경계가 아니라 network 경계다. (어떤 endpoint가 sidecar에 보이는가의 일반 메커니즘은 data-plane sync state, 가시성 범위 한정은 sidecar scope.)


4. 떴는지 확인 — SNI 인코딩과 endpoint 치환을 한 번에 본다

클러스터 간 호출이 동작한다면, 호출자 sidecar의 cluster에 §2.1의 두 흔적이 찍혀 있어야 한다. 한 명령으로 둘 다 본다.

istioctl proxy-config cluster deploy/workload-a \
  --fqdn B.ns.svc.cluster.local -o json \
  | jq '.[0] | {name, sni: .transportSocket.typedConfig.sni,
                ep: .loadAssignment.endpoints[0].lbEndpoints[0].endpoint.address.socketAddress}'

기대 출력(멀티클러스터·network 분리가 정상일 때):

{
  "name": "outbound|8080||B.ns.svc.cluster.local",
  "sni": "outbound_.8080_._.B.ns.svc.cluster.local",
  "ep": { "address": "<EW2 gateway IP>", "portValue": 15443 }
}

판정 기준 두 줄: - snioutbound_.* 인코딩이면 ✓ — SNI 라우팅용 cluster가 맞다. 평범한 hostname이면 멀티클러스터 EDS가 안 걸린 것. - ep.address가 원격 GW IP, portValue15443이면 ✓ — endpoint 치환 성공. 여전히 원격 pod IP면 network label(topology.istio.io/network) 미설정이다.

신원이 정말 보존됐는지는 도착지에서 본다: B의 AuthorizationPolicy 로그/메트릭에서 peer principal이 A의 SPIFFE(게이트웨이가 아니라)로 찍히면 end-to-end가 살아있다는 증거다.


정리

한 문장 멘탈 모델: east-west gateway는 편지를 열지 않는다 — sidecar가 봉투 겉면(SNI)에 적은 목적지만 읽어 암호 바이트를 그대로 다음 hop으로 넘기는 L4 라우터이고, 그래서 A↔B mTLS 신원이 게이트웨이를 투명하게 관통한다.

핵심 정리

What you might be missing