{
  "test_id": "T90", "verdict": "pass",
  "observed": "이중 mTLS egress 가이드의 배선 패턴을 전용 gateway(istio-vt-t90 ns, inject.istio.io/templates=gateway, istio: dmtls-egress)로 E2E 재현했다. (a) 양성: client의 curl http://api.partner.example/ = 200, 파트너 nginx(ssl_verify_client on)가 응답 헤더/바디로 x-client-verify: SUCCESS, dn=CN=egress-client를 에코 — gateway가 hop2에서 credentialName 기반 client cert를 실제 제시·검증받음을 증명. (b) 경로 증명: 전용 gateway pod access log에 요청 기록(outbound|443||api.partner.example로 발신), client 사이드카 access log는 outbound|8443|partner|dmtls-egress.istio-vt-t90.svc.homelab.local 선택, nginx의 TLS peer = gateway pod IP(10.255.126.12). client 사이드카의 hop1 cluster에 DR-hop1이 그대로 컴파일됨: maxConnections=77, connectTimeout=3s, tcpKeepalive 300/30/3, TLS transport socket sni=api.partner.example + SDS default(메시 인증서) = ISTIO_MUTUAL. gateway의 hop2 cluster에 DR-hop2 컴파일: maxConnections=83, keepalive 동일, client cert SDS=kubernetes://dmtls-partner-client-cred, 검증 CA=kubernetes://dmtls-partner-client-cred-cacert, matchSubjectAltNames=[api.partner.example](deprecated 필드명으로 컴파일). (c) 음성 1: DR-hop2 삭제 시 gateway가 평문을 TLS 포트로 보내 400 'plain HTTP request was sent to HTTPS port'; SIMPLE+credentialName(서버 CA만 신뢰, client cert 미제시)로 바꾸면 정확히 400 'No required SSL certificate was sent' + nginx error log 'client sent no required SSL certificate' — 파트너가 진짜로 client cert를 요구함을 증명. 복원 후 다시 200. (d) 음성 2(가이드 함정 3): ALLOW_ANY에서 VS mesh 라우트 제거 시 트래픽이 gateway를 조용히 우회 — gateway access log 카운트 불변, client 사이드카는 outbound|80||api.partner.example(SE cluster)로 직행, nginx peer가 client pod IP(10.255.126.11)로 바뀌고 평문 도착(400). 단 이 구성은 SE가 80 포트를 등록했기 때문에 PassthroughCluster가 아닌 SE cluster로 직행 — 가이드 원문 YAML(SE에 443만 등록)에서는 PassthroughCluster가 맞고, 어느 쪽이든 'gateway 미경유 + mTLS 0겹 + 조용한 실패'는 동일. 추가 실측 2건: (1) 가이드 §5 YAML의 tcpKeepalive {time: 300, interval: 30}(단위 없는 정수)은 CRD validation에서 즉시 거부됨(Duration은 '300s' 문자열이어야 함) — 본문 교정. (2) istio-system 밖의 전용 gateway에서 credentialName SDS는 gateway pod의 SA에 secrets get/watch/list Role이 없으면 istiod가 SubjectAccessReview로 거부(503 UF 'Secret is not supplied by SDS', istiod: 'not authorized to read secrets') — 본문 보강. 환경 특이: 검증 도중 istiod가 --domain homelab.local로 변경돼(2026-07-05 04:40Z) 레지스트리 이름이 *.svc.homelab.local로 바뀜(기존 harness CORRECTION의 cluster.local 규칙은 이 시점부터 무효); DR-hop1/VS의 gateway Service host는 homelab.local로 적용해 검증. injection webhook이 일시 비활성(never-match) 상태여서 istioctl kube-inject로 주입(이후 webhook 자가 복구). 정리 완료(ns istio-vt-t90 + istio-verify-ext의 dmtls-partner 3종 삭제).",
  "claims": [
    {"doc": "double-mtls-gateway-connection", "cid": "G1", "empirical": "supports-claim",
     "note": "hop1 sidecar->전용 gateway ISTIO_MUTUAL: DR-hop1(클라이언트 쪽)+Gateway server(서버 쪽) 짝으로 성립. client 사이드카 cluster에 TLS transport socket(sni=api.partner.example, 메시 SDS 'default') 컴파일 + gateway가 종단 후 L7 라우팅. gateway access log로 경유 증명."},
    {"doc": "double-mtls-gateway-connection", "cid": "G2", "empirical": "supports-claim",
     "note": "hop2 MUTUAL+credentialName: gateway ns의 generic secret(tls.crt/tls.key/ca.crt) -> SDS 2리소스(kubernetes://<name> client cert, kubernetes://<name>-cacert 검증 CA)로 컴파일, 파트너가 verify=SUCCESS dn=CN=egress-client 에코. subjectAltNames는 matchSubjectAltNames exact로 컴파일. 보강 1건: istio-system 밖 전용 gateway는 SA에 secrets get/watch/list Role 필수(istiod SubjectAccessReview) — 없으면 503 UF 'Secret is not supplied by SDS'."},
    {"doc": "double-mtls-gateway-connection", "cid": "G3", "empirical": "supports-claim",
     "note": "connectionPool 발효 위치: hop1 값(77/3s/keepalive 300,30,3)은 호출자 사이드카의 outbound|8443|partner|... cluster에, hop2 값(83/3s/keepalive)은 gateway의 outbound|443||api.partner.example cluster에 각각 컴파일됨을 istioctl proxy-config cluster로 양쪽에서 확인."},
    {"doc": "double-mtls-gateway-connection", "cid": "G3-yaml", "empirical": "refutes-claim",
     "note": "가이드 §5 YAML의 tcpKeepalive: { time: 300, interval: 30, probes: 3 } 표기는 적용 불가 — CRD validation이 'must be of type string' 오류로 거부. Duration 필드는 '300s'/'30s' 문자열이어야 함. 본문 2곳 교정."},
    {"doc": "double-mtls-gateway-connection", "cid": "G4", "empirical": "supports-claim",
     "note": "함정 3(조용한 우회) 재현: ALLOW_ANY + VS mesh 라우트 부재 시 gateway access log 불변(미경유), 트래픽은 사이드카에서 직접 외부로(mTLS 0겹, nginx peer=client pod IP). 정밀화: SE가 해당 포트(80)를 등록한 구성에서는 PassthroughCluster가 아니라 SE cluster(outbound|80||api.partner.example)로 직행 — 가이드 원문처럼 SE에 443만 있으면 PassthroughCluster. 본문에 한 줄 정밀화 반영."}
  ]
}
