---
title: 이중 egress gateway — passthrough와 mTLS를 별도 ns·별도 pod로 나란히
date: 2026-06-09
type: guide
domain: istio
tags: [egress, multi-gateway, mtls, tls-passthrough, namespace-isolation]
---
> [!abstract]
> 단일 egress gateway를 **멀티-gateway 토폴로지로 일반화**하면서, 두 egress 패턴(① TLS PASSTHROUGH,
> ② outer 메시 mTLS + inner 앱 TLS)을 **서로 다른 namespace의 서로 다른 gateway pod**로 동시에 띄워 직접 비교한
> homelab 검증 랩. 결론: **진짜 격리는 물리 분리(pod/ns/label)와 논리 스코핑(`exportTo`/`sourceLabels`)을 함께
> 갖춰야 성립**한다 — 둘 중 하나라도 빠지면 "분리된 것처럼 보이는" 상태에 그친다. 본 문서는 그 둘을 어떻게
> 맞물리는지에 집중한다(패턴 자체의 구조·운영은 기존 문서로 링크).
**대상 환경:** homelab (kubespray bare-metal, k8s v1.30.6, CNI Calico, 3노드), Istio **1.30.0** (Helm gateway chart)
**대상 독자:** 단일 egress gateway는 띄워봤고, 이제 패턴/tier별로 gateway를 쪼개려는 SRE/DevOps
**범위:** "왜 쪼개나" → 격리의 두 축(물리·논리) → 경로별 Istio 객체 → 트래픽·Envoy 검증 → 실측 함정 2건
**선행 개념:** [egress route 스코핑](/docs/istio/egress/vs-scoping/) (멀티-gateway의 전제) · [egress CRD 멘탈모델](/docs/istio/egress/crd-mental-model/)
---
## 1. 배경 — 왜 egress gateway를 둘로 쪼개나
단일 egress gateway면 메시의 모든 외부 호출이 한 pod를 통과한다. 처음엔 단순해서 좋지만, 운영이 커지면 그 한 pod가
**여러 책임을 한 몸에 떠안는** 구조가 문제가 된다:
- **장애 격리 부재** — 한 tier의 graceful drain·재시작·crashloop이 *모든* 외부 트래픽을 흔든다.
- **SNAT 포트 고갈 공유** — 한 워크로드가 source port를 다 써버리면 무관한 다른 트래픽도 외부 연결을 못 연다.
- **정책 충돌** — passthrough(SNI만 보고 통과)와 mTLS 종단(client cert 검증)은 **listener의 TLS 동작이 정반대**다.
한 pod에 얹으면 포트를 갈라야 하고, 한쪽 정책 변경이 다른 쪽 listener를 건드릴 위험이 상존한다.
그래서 패턴/tier별로 gateway pod를 분리한다. 그런데 "Deployment를 둘로 나눴다"만으로 격리가 끝나지 않는다는 게
이 문서의 핵심 교훈이다. Istio에서 한 메시 안의 리소스는 기본적으로 **전역 가시성**을 갖기 때문에, 물리적으로
떨어진 두 pod라도 잘못된 전역 리소스 하나가 양쪽 config를 동시에 망가뜨릴 수 있다(함정 ②, 단 아래 §5·검증
기록 참고 — 2026-07-05 재현에서는 이 현상 자체가 재현되지 않았다). 진짜 격리에는 **두 번째 축**이 필요하다.
> 선행 개념인 [egress route 스코핑](/docs/istio/egress/vs-scoping/)을 먼저 잡아두면 이 문서의 `exportTo`/
> client-VS·gateway-VS 분리가 자연스럽게 읽힌다. 멀티-gateway는 그 스코핑 규칙을 **gateway 개수만큼 곱한** 것에 가깝다.
## 2. 멘탈모델 — 격리는 두 축의 곱(物理 × 論理)
**머릿속에 담을 한 장면:** 두 gateway는 pod·ns·label로 *물리적으로* 분리돼 있지만 여전히 **하나의 메시**다.
그래서 격리는 다음 두 축이 **둘 다** 채워질 때만 성립한다 — 하나라도 비면 "분리된 척"에 그친다.
```
isolation = PHYSICAL × LOGICAL
(분리) (가시성·적용대상)
PHYSICAL axis LOGICAL axis
+--------------------+ +---------------------------+
| label (selector) | | exportTo (누가 이 리소스를 |
| Gateway가 자기 | | 볼 수 있나) |
| pod만 잡음 | | sourceLabels (어떤 client에 |
| ns (소유권 경계) | | 라우트가 붙나) |
| pod (장애·SNAT 경계)| +---------------------------+
+--------------------+
| |
v v
"딴 pod의 변경이 "전역 리소스 하나가
내 listener를 안 건드림" 모든 gateway를 못 얼리게"
```
- **PHYSICAL** — 각 ns의 `Gateway` 리소스가 **자기 label만** selector로 잡으므로 한쪽 변경이 다른 pod로 새지 않는다.
ns는 소유권/RBAC 경계, pod는 장애·SNAT 경계. `PILOT_SCOPE_GATEWAY_TO_NAMESPACE=true` 환경에서도 Gateway
리소스와 workload가 같은 ns라 안전하다.
- **LOGICAL** — `exportTo: ["."]`로 SE/VS의 가시성을 자기 ns에 닫아, 전역 push에 끼어 남의 config를 얼리지 않게 한다
(함정 ②). `sourceLabels`는 "어떤 client가 어느 gateway로 가나"를 명시한다(현재 랩은 host로 분기, §다음 작업 참고).
이 한 장면에서 나머지가 따라 나온다. 아래 토폴로지가 그 물리 축을 그림으로 보인 것이다 — 같은 client(`sleep`)가
두 외부 host를 호출하지만, host에 따라 **다른 ns의 다른 gateway pod**로 갈린다.
```mermaid
flowchart TB
subgraph mesh["mesh-test ns — client sleep (injection ON)"]
C1["curl https://example.org"]
C2["curl https://www.wikipedia.org"]
end
subgraph pt["ns: egress-pt"]
GPT["pod egw-pt (label istio=egw-pt)
Gateway :443 → :8443
tls PASSTHROUGH
SNI route, no decrypt"]
end
subgraph mt["ns: egress-mtls"]
GMT["pod egw-mtls (label istio=egw-mtls)
Gateway :443 → :8443
tls ISTIO_MUTUAL
terminate OUTER mTLS (verify client cert/SPIFFE)
tcp_proxy INNER app TLS"]
end
EX1["example.org:443"]
EX2["www.wikipedia.org:443"]
C1 -->|"tls/sniHosts route (sidecar no terminate)"| GPT
C2 -->|"DR ISTIO_MUTUAL + sni (sidecar wraps mesh mTLS)"| GMT
GPT --> EX1
GMT --> EX2
```
왼쪽 경로는 gateway가 복호화하지 않는 passthrough, 오른쪽은 gateway가 outer mesh mTLS를 *종단*하고 inner 앱 TLS만
흘려보내는 패턴이다. **inner 앱 TLS는 두 경로 모두 client↔external 간 end-to-end로 유지**된다 — passthrough는 애초에
건드리지 않고, mTLS 경로는 outer mesh 레이어만 벗기고 inner는 `tcp_proxy`로 통과시키므로.
### 두 패턴의 본질 차이 (각 상세는 링크)
| 측면 | PASSTHROUGH | outer mTLS + inner TLS |
|---|---|---|
| in-mesh leg | 평문 TCP(앱 TLS만) | 메시 mTLS(SPIFFE 신원 검증) |
| gateway가 보는 것 | SNI만 | outer 종단 후 inner는 불투명 통과 |
| L7 정책 | 불가 | 불가(inner 미복호) |
| 호출자 식별 | gateway단 불가(→ Calico 등) | SPIFFE로 가능 |
| 상세 구조·실측 | [passthrough 가이드](/docs/istio/egress/gateway-https/), [필드 매뉴얼](/docs/istio/egress/gateway/) | [HTTPS over mTLS 구조](/docs/istio/egress/https-over-mtls/), [mTLS 테스트 리포트](/docs/istio/egress/report-egress-mtls/) |
| 패턴 선택 근거 | — | [이중 TLS 없이(decision)](/docs/istio/egress/identity-without-mtls/) |
| 운영(모니터링·SNAT·graceful) | [egress 운영 가이드](/docs/istio/egress/operations/) | 동일 |
두 패턴 모두 inner는 gateway가 못 보므로 **L7 정책은 어느 쪽도 불가**다. 차이는 *in-mesh leg*에 있다 — mTLS 경로만
SPIFFE 신원으로 호출자를 식별할 수 있고, passthrough는 gateway단에서 호출자를 못 가린다(Calico 등 외부 수단 필요).
## 3. 구성 따라하기 — 두 축을 어떻게 코드로 채우나
### 3-1. 물리 축: gateway 2개를 별도 ns·pod로
```bash
# (repo) scenarios/20-egress/dual-gateway/00-namespaces.yaml — egress-pt / egress-mtls (둘 다 injection ON)
kubectl apply -f scenarios/20-egress/dual-gateway/00-namespaces.yaml
# Helm gateway chart 2 release — label로만 구분(egw-pt / egw-mtls), 둘 다 ClusterIP
helm upgrade --install egw-pt istio/gateway -n egress-pt --version 1.30.0 -f install/helm/values-egw-pt.yaml --wait
helm upgrade --install egw-mtls istio/gateway -n egress-mtls --version 1.30.0 -f install/helm/values-egw-mtls.yaml --wait
```
두 release의 차이는 **label과 ns뿐**이다 — 나머지(svc 포트, ClusterIP)는 의도적으로 동일하게 둬서 "차이 = 격리의
근거"가 label/ns 단 두 곳임을 드러낸다.
```
# egw-pt svc 포트: 443 -> targetPort 8443 (PASSTHROUGH listener)
# egw-mtls svc 포트: 443 -> targetPort 8443 (ISTIO_MUTUAL listener) <- egw-pt와 동일
# 둘 다 443으로 통일: 다른 pod·다른 ns라 충돌 없음. (한 pod에 두 모드를 얹을 때만 포트 분리 필요.)
```
→ 포트를 같이 443으로 둘 수 있는 이유 자체가 물리 격리의 증거다. 단일 pod였다면 두 모드가 같은 listener 포트를
다툴 테니 갈라야 했다. 별도 pod라 충돌이 원천적으로 없다.
### 3-2. 논리 축: 경로별 Istio 객체
**passthrough** (`scenarios/20-egress/dual-gateway/10-passthrough.yaml`): SE(example.org, `protocol: TLS`,
`exportTo: [".", "egress-pt"]`) + Gateway(egress-pt, :443 PASSTHROUGH) + DR(subset만) + client-VS(mesh, `tls`) +
gateway-VS(egress-pt, `tls`). gateway는 복호화 안 함.
**mTLS** (`scenarios/20-egress/dual-gateway/20-mtls.yaml`): SE(www.wikipedia.org) + Gateway(egress-mtls, :443
**ISTIO_MUTUAL**) + DR(`portLevelSettings` 443 `ISTIO_MUTUAL` + `sni: www.wikipedia.org`) + client-VS(mesh, `tls`
→ gw:443) + gateway-VS(egress-mtls, **`tcp`** :443 → wikipedia:443).
여기서 **route 타입(`tls` vs `tcp`)이 왜 갈리는가**가 이 랩의 가장 비자명한 지점이다. 규칙은 단순하다:
**gateway가 종단(decrypt)하면 SNI가 이미 소비돼 사라지므로 SNI 매칭(`tls`/`sniHosts`)을 더는 쓸 수 없다 → `tcp`
route(포트 매칭)로 가야 한다.** passthrough는 종단을 안 하니 SNI가 그대로 살아 있어 `tls` route로 라우팅한다.
gateway-VS(2단)는 이 규칙과 직결되므로 정확한 필드 구조를 보인다 — **ISTIO_MUTUAL 종단 listener는 `tcp` route**다:
```yaml
# (2) egress-mtls gateway -> external (inner 앱 TLS passthrough). ISTIO_MUTUAL listener는 tcp route.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata: { name: wiki-gateway, namespace: egress-mtls }
spec:
exportTo: ["."] # 가시성을 egress-mtls ns로 닫음(함정 ② 방지) — LOGICAL 축
hosts: [www.wikipedia.org]
gateways: [egw-mtls-gateway] # mesh가 아니라 egress-mtls gateway에만 적용
tcp: # ← tls/sniHosts가 아님. outer mTLS가 이미 종단돼 SNI는 소비됨
- match:
- gateways: [egw-mtls-gateway]
port: 443 # 포트 매칭만으로 충분 → inner 앱 TLS를 tcp_proxy로 그대로 흘림
route:
- destination: { host: www.wikipedia.org, port: { number: 443 } }
weight: 100
```
전체 필드(client-VS의 `tls.match.sniHosts`, DR `portLevelSettings.sni`, Gateway `ISTIO_MUTUAL` 서버)는
[HTTPS over mTLS 구조](/docs/istio/egress/https-over-mtls/)의 CRD 해부와 첨부 manifest를 참조.
스코핑은 [전제 문서](/docs/istio/egress/vs-scoping/)대로 client-VS / gateway-VS를 분리하고 각자 `exportTo: ["."]`로
닫았다 — 이게 위 멘탈모델의 LOGICAL 축을 코드로 채우는 부분이다.
## 4. 검증 (기대 vs 실제)
```bash
SLEEP=$(kubectl -n mesh-test get pod -l app=sleep -o jsonpath='{.items[0].metadata.name}')
# A) passthrough
kubectl -n mesh-test exec $SLEEP -c sleep -- curl -sS -o /dev/null -w "%{http_code} %{remote_ip}\n" https://example.org
# -> 200 104.20.26.136 egw-pt access log: outbound|443||example.org (egw-pt pod :8443 경유)
# B) mTLS
kubectl -n mesh-test exec $SLEEP -c sleep -- curl -sS -o /dev/null -w "%{http_code} %{remote_ip}\n" https://www.wikipedia.org
# -> 200 103.102.166.224 egw-mtls access log: outbound|443||www.wikipedia.org (egw-mtls pod :8443 경유)
```
cluster 이름 `outbound|443||example.org`(`direction|port|subset|fqdn`)이 각자의 gateway pod access log에 찍히는 게
**경로가 실제로 갈렸다는 직접 증거**다 — 둘 다 200이어도 같은 pod로 샜다면 격리가 깨진 것이다.
Envoy 증거 — listener/cluster 레벨에서 "종단 vs 미종단"을 못 박는다:
```bash
# egw-mtls 8443 listener = outer mTLS 강제됨
istioctl proxy-config listener deploy/egw-mtls.egress-mtls --port 8443 -o json | grep requireClientCertificate
# "requireClientCertificate": true ← client cert 요구 + ROOTCA 검증(SPIFFE)
# egw-pt 클러스터에 originate-TLS 없음 = 순수 passthrough
istioctl proxy-config cluster deploy/egw-pt.egress-pt --fqdn example.org -o json | grep -c transportSocket
# 0 ← TLS transport socket 없음
istioctl analyze -A # 충돌 0 (default-ns Info 무관)
```
**합격 판정:** 두 경로 모두 200 + 각자의 gateway pod 경유(access log) + egw-mtls는 outer mTLS 강제
(`requireClientCertificate: true`) + egw-pt는 미복호 passthrough(`transportSocket` 0개) + analyze 청정.
## 5. 실측 함정 2건 (검증 중 실제로 막힌 곳)
### 함정 ① 종단 listener에 `tls` route → 0 chains
mTLS gateway-VS(2단)를 처음에 `tls`/`sniHosts`로 작성했더니 egw-mtls의 **listener가 아예 안 떴다**.
(당시 포트는 15443 — 이후 §3처럼 443→8443으로 통일했고, 아래 로그의 15443은 그 시점 값이다.) istiod 로그:
```
gateway egress-mtls/egw-mtls-gateway:15443 listener missed network filter
omitting listener "0.0.0.0_15443" due to: must have more than 0 chains
```
**메커니즘:** ISTIO_MUTUAL은 gateway가 outer mTLS를 *종단*하므로 그 시점에 SNI는 이미 소비됐다. 그런데 `tls`/
`sniHosts` route는 SNI로 filter chain을 매칭하려 한다 → 매칭할 SNI가 없어 **유효 chain이 0개** → Envoy가 빈
listener를 거부(`must have more than 0 chains`). `tls`→`tcp`(포트 매칭)로 바꾸자 listener가
`SNI: www.wikipedia.org / Cluster: outbound|443||www.wikipedia.org`로 떴다. 이것이 §3의 "종단하면 `tcp`" 규칙의 실증이다.
### 함정 ② exportTo 누락 SE가 mesh 전체 gateway를 NACK (당시 관찰 — 정정 있음, 아래 참고)
전역 export된 데모 SE(`example-logicaldns`, LOGICAL_DNS)가 multi-IP가 된 example.com 때문에 Envoy에서 거부됐고,
그 push에 실린 listener까지 모든 gateway에서 드롭되는 것으로 당시 관측했다(기존 egress gateway 포함). 즉
**물리적으로 멀쩡히 분리된 두 gateway가 전역 리소스 하나에 동반 사망**한 사건으로 기록했다 — 이게 §1·§2에서 말한
"물리 분리만으론 부족, LOGICAL 축이 필요"의 근거로 삼았던 사건이다. `exportTo: ["."]`로 가시성을 좁혀 해소했다.
메커니즘 상세는 [스코핑 §5](/docs/istio/egress/vs-scoping/).
> [!warning] 2026-07-05 정정
> 원 서술은 이 현상의 원인을 **xDS의 트랜잭션성(한 push는 all-or-nothing)** 이라고 설명했으나, Envoy 공식 xDS
> 프로토콜 문서는 "a NACK does not necessarily mean that none of the resources were accepted"라고 명시해 이
> 표현과 정확히 일치하지 않는다. 더 정합적인 설명은 **exportTo 기본값이 전역 가시성이라, 무관한 gateway의 config
> 생성 로직에도 같은 깨진 리소스가 섞여 들어간다**는 쪽이다(xDS 레벨의 전역 원자성이 아니라 istiod가 전역 노출된
> SE를 모든 관련 proxy의 config snapshot에 포함시키는 로직 문제일 가능성이 큼).
>
> 또한 homelab에서 동일 시나리오(전역 노출 SE + 서로 무관한 두 물리적 분리 gateway)를 **Istio 1.30.0으로
> 재현**했을 때는(T03) egw-a/egw-b의 CDS configStatus가 계속 `SYNCED`로 유지됐고 istiod 로그에도 NACK·거부
> 흔적이 전혀 없어 — **"물리 분리만으론 불충분, 전역 리소스 하나가 무관한 gateway까지 동반 파괴한다"는
> blast-radius 현상 자체가 이 조건에서는 재현되지 않았다.** 다만 원 사건은 LOGICAL_DNS SE가 실제 DNS 응답으로
> multi-IP가 되며 깨진 경우였고, 재현은 DNS_ROUND_ROBIN + 고정 2-IP static endpoint로 근사한 것이라 완전히 같은
> 조건은 아니다. 즉 이 사건이 원천적으로 "일어날 수 없다"는 뜻이 아니라, **이 조건에서는 실증되지 않았고
> "xDS 트랜잭션성"이라는 인과 설명은 부정확하다**는 뜻으로 정정한다. 상세는 문서 끝 검증 기록(C1/C6) 참고.
## 핵심 정리
- 멀티-gateway egress의 격리는 **물리 축 × 논리 축의 곱**이다: label 분리(selector) + ns 분리(소유권) +
exportTo(가시성) + sourceLabels(적용대상). 한 축만으론 "분리된 척"에 그친다.
- 종단 여부가 2단 route 타입을 결정한다: **PASSTHROUGH=`tls`(SNI 살아있음), ISTIO_MUTUAL 종단 후=`tcp`(SNI 소비됨)**.
거꾸로 쓰면 0-chains로 listener가 안 뜬다(함정 ①).
- 별도 ns·pod로 나누면 장애·정책·소스 IP가 tier별 격리되지만, 같은 메시인 이상 **전역 리소스(`exportTo` 누락 SE)가
exportTo 기본값(전역 가시성) 때문에 무관한 gateway의 config 생성에도 섞여 들어갈 수 있다**는 것이 §5 함정 ②의
원래 관측이었다 — 다만 2026-07-05 homelab 재현(T03)에서는 이 blast-radius 현상 자체가 재현되지 않았으므로, 실제
영향 범위는 조건(SE의 resolution 모드, 실패 양상 등)에 따라 달라질 수 있다는 점을 함께 기록해둔다.
- 검증의 핵심 증거는 "200" 자체가 아니라 **각자의 gateway pod access log에 찍힌 cluster 이름**(경로가 실제 갈렸는지)
+ listener의 `requireClientCertificate`/`transportSocket`(종단/미종단)이다.
## 다음 작업
- tier별 client를 `sourceLabels`로 각 gateway에 강제 매핑(현재는 host로 분기) → LOGICAL 축의 sourceLabels 절반을
채워 egress 거버넌스 강화.
## What you might be missing
"별도 gateway pod"의 실익은 **장애·정책·소스 IP의 격리**다(한 tier의 graceful drain·SNAT 고갈·정책 변경이 다른
tier에 안 번짐). 그러나 격리는 배포만으로 완성되지 않는다 — 같은 메시 안에 사는 이상, **잘못된 전역
리소스(exportTo 누락 SE)가 exportTo 기본값(전역 가시성)으로 인해 무관한 gateway의 config 생성에도 섞여 들어갈 수
있다**는 것이 이 문서 §5 함정 ②의 원래 교훈이다(단, 2026-07-05 homelab 재검증에서는 이 현상 자체가 재현되지
않았다 — 위 정정과 문서 끝 검증 기록 참고). 즉 멀티-gateway는 물리적 분리(pod/ns)와 논리적 스코핑
(exportTo/sourceLabels)을 **함께** 갖춰야 비로소 격리다. 한쪽만으로는 "분리된 것처럼 보이는" 상태에 그친다.
그리고 이 함정은 **gateway를 늘릴수록 잠재적으로 더 위험해진다** — exportTo를 기본값(전역)으로 방치하면 무관한
gateway 개수만큼 노출 대상이 늘어나기 때문이다(다만 실제 xDS push 실패가 gateway 수에 비례해 함께 늘어나는지는
이번 재현에서 직접 확인되지 않았다). 그래서 멀티-gateway 환경일수록 `exportTo` 위생이 선택이 아니라 전제라는
결론 자체는 유효하다.
덧붙여, 두 패턴의 공존에는 **pod 분리가 아닌 다른 축도 있다**: mode는 Deployment 속성이 아니라 Gateway CR server(=리스너/포트) 속성이라, **같은 Deployment 하나에서 8443=ISTIO_MUTUAL, 9443=PASSTHROUGH로 포트 단위 공존**이 가능하다. 클라이언트·인프라 무변경으로 목적지별 점진 전환이 되는 게 이 축의 실익이고, 장애·소스IP 격리가 필요하면 이 문서의 pod 분리 축을 택한다 — 프로덕션 채택 맥락은 [도입 가이드 (사내 공유본)](/docs/istio/egress/adoption-passthrough-vs-mtls/) §3 참고.
---
## 관련 파일 · 참조
- 📎 [00-namespaces.yaml](files/00-namespaces.yaml) — egress-pt / egress-mtls (둘 다 injection ON)
- 📎 [10-passthrough.yaml](files/10-passthrough.yaml) — SE example.org + Gateway :443 PASSTHROUGH + DR subset + client/gateway-VS(`tls`)
- 📎 [20-mtls.yaml](files/20-mtls.yaml) — SE www.wikipedia.org + Gateway :443 ISTIO_MUTUAL + DR(`sni`) + client-VS(`tls`)/gateway-VS(`tcp`)
- 📎 [values-egw-pt.yaml](files/values-egw-pt.yaml) — label `istio=egw-pt`, svc 443→8443, ClusterIP
- 📎 [values-egw-mtls.yaml](files/values-egw-mtls.yaml) — label `istio=egw-mtls`, svc 443→8443, ClusterIP
**선행/전제:** [egress route 스코핑](/docs/istio/egress/vs-scoping/) · [egress CRD 멘탈모델](/docs/istio/egress/crd-mental-model/) · **패턴 상세:** [passthrough 가이드](/docs/istio/egress/gateway-https/) · [HTTPS over mTLS 구조](/docs/istio/egress/https-over-mtls/) · [mTLS 테스트 리포트](/docs/istio/egress/report-egress-mtls/)
---
## 검증 기록 (2026-07-05 · Istio 1.30.0 / k8s 1.30.6)
검증 방법 요약: 공식 문서(istio.io/envoyproxy.io/GitHub 이슈) 대조 + homelab 클러스터 실측(T03 — 물리적으로 분리된 두 egress gateway + 전역 노출 ServiceEntry의 blast-radius 재현 시도, T07 — ISTIO_MUTUAL 종단 리스너의 tls/tcp route 전환 재현).
| 주장 | 판정 | 근거 |
|---|---|---|
| C1. 멀티-gateway egress의 진짜 격리는 물리 축(label/ns/pod)과 논리 축(exportTo/sourceLabels)이 둘 다 채워져야 성립 | ✅ 실측 확인 (기본 메커니즘) | istio.io/.../sidecar/ · [T03 실측](/docs/istio/egress/vs-scoping/files/verify/T03/) — 물리 분리된 두 gateway의 독립 동작 자체는 확인됨(blast-radius 세부 주장은 C6 참고) |
| C2. Gateway `tls.mode: PASSTHROUGH`는 SNI만으로 라우팅하며 페이로드를 복호화하지 않음 | ✅ 문헌 확인 | istio.io/latest/docs/reference/config/networking/gateway/ |
| C3. ISTIO_MUTUAL은 outer mesh mTLS를 종단·client cert(SPIFFE) 검증 후 inner 앱 TLS는 tcp_proxy로 통과 | ✅ 문헌 확인 | istio.io/latest/docs/reference/config/networking/gateway/ |
| C4. 종단 시 SNI 소비 → tcp route 필요, PASSTHROUGH는 SNI 생존 → tls route | ✅ 실측 확인 | istio.io/.../virtual-service/ · [T07 실측](/docs/istio/egress/https-over-mtls/files/verify/T07/) |
| C5. 종단 listener에 tls/sniHosts route 사용 시 0 chains로 Envoy가 리스너 거부, "must have more than 0 chains" 로그 | ✅ 문헌 확인 | github.com/istio/istio/issues/17517 (재현은 C4의 T07로 커버) |
| C6. exportTo 누락 SE가 xDS "트랜잭션성(all-or-nothing)"으로 무관한 gateway까지 동반 파괴 | 🔬 실측 반증 — 본문 교정 | envoyproxy.io/.../xds_protocol · [T03 실측](/docs/istio/egress/vs-scoping/files/verify/T03/) |
| C7. `exportTo: ["."]`로 가시성을 자기 ns로 닫으면 전역 push에 안 끼어듦 | ✅ 문헌 확인 | istio.io/.../sidecar/ |
| C8. `PILOT_SCOPE_GATEWAY_TO_NAMESPACE=true`면 gateway workload가 같은 ns의 Gateway만 선택(기본값 false) | ✅ 문헌 확인 | pkg.go.dev/istio.io/istio/pilot/pkg/features |
| C9. TLS mode는 Deployment가 아닌 Gateway CR server(포트) 속성 — 한 Deployment 내 포트별 공존 가능 | ✅ 문헌 확인 | istio.io/latest/docs/reference/config/networking/gateway/ |
| C10. `requireClientCertificate: true`는 client cert 요구 + root CA(SPIFFE) 검증을 의미 | ✅ 문헌 확인 | envoyproxy.io/.../tls.proto |
| C11. `transportSocket` 개수 0은 해당 클러스터에 TLS origination이 없다는(순수 passthrough) 증거 | ✅ 문헌 확인 | istio.io/.../egress-gateway-tls-origination/ |
| C12. Envoy/Istio 클러스터 이름 포맷은 `direction\|port\|subset\|fqdn` | ✅ 문헌 확인 | istio.io/latest/docs/ops/diagnostic-tools/proxy-cmd/ |
| C13. PASSTHROUGH·outer mTLS+inner TLS 모두 gateway가 inner를 복호화 못 해 L7 정책 적용 불가 | ✅ 문헌 확인 | istio.io/.../egress-gateway/ |
| C14. passthrough는 gateway단에서 호출자 identity 식별 불가, ISTIO_MUTUAL은 SPIFFE로 식별 가능 | ✅ 문헌 확인 | istio.io/latest/docs/concepts/security/ |
| C15. DestinationRule `portLevelSettings`로 포트별 ClientTLSSettings 적용, 상위 설정을 오버라이드 | ✅ 문헌 확인 | istio.io/.../destination-rule/ |