#!/usr/bin/env bash # T46: ISTIO_MUTUAL gateway listener - requireClientCertificate enforcement, # mesh client (sidecar+DR outer mTLS) vs non-mesh caller (no cert) handshake outcome. # # NOTE on manifest.yaml (2 environmental fixes applied vs the raw spec draft, both required # to get a legitimate, applicable config on this cluster -- neither changes the mTLS mechanism # under test): # 1. Gateway server protocol TCP -> TLS (with tls.mode present). The admission webhook # (validation.istio.io) rejects "TLS settings on a non-HTTPS/TLS protocol server" outright, # so `protocol: TCP` + a `tls:` stanza never applies. `protocol: TLS` is required to legally # carry `tls.mode: ISTIO_MUTUAL`; the VirtualService keeps its original `tcp:` route (a # TLS-terminating, non-PASSTHROUGH server routes the decrypted bytes as plain TCP). # 2. DestinationRule.host / VirtualService route destination.host: svc.homelab.local -> svc.cluster.local. # This cluster's real DNS domain is homelab.local (CoreDNS/kubeadm clusterDomain), but istiod's # own k8s-Service-registry hostname suffix is still cluster.local (global.proxy.clusterDomain was # never set to homelab.local at install time) -- confirmed via `istioctl analyze` ("Referenced # host not found") and via the client sidecar's CDS dump (only the *.svc.cluster.local cluster # exists / carries the DR's TLS policy). Left unfixed, the DestinationRule silently never binds, # so the mesh client would send *plaintext* TCP to a TLS-terminating listener -- indistinguishable # from the non-mesh-caller failure and unable to exercise the actual claim under test. # The curl target hosts in the observe commands below intentionally stay on *.svc.homelab.local -- # that's real CoreDNS resolution, unaffected by istiod's internal registry suffix. set -euo pipefail NS=istio-vt-t46 DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cleanup() { echo "=== cleanup: deleting namespace $NS ===" kubectl delete namespace "$NS" --wait=false --ignore-not-found } trap cleanup EXIT echo "=== create namespace $NS (istio-injection=enabled) ===" kubectl create namespace "$NS" --dry-run=client -o yaml \ | kubectl label -f - istio-injection=enabled --local -o yaml \ | kubectl apply -f - echo "=== apply manifest.yaml ===" kubectl apply -f "$DIR/manifest.yaml" echo "=== wait for client/non-mesh-caller Ready, mutual-gw/echo rollout ===" kubectl -n "$NS" wait --for=condition=Ready pod/client pod/non-mesh-caller --timeout=90s kubectl -n "$NS" rollout status deploy/mutual-gw --timeout=90s kubectl -n "$NS" rollout status deploy/echo --timeout=90s echo "=== [1] listener requireClientCertificate ===" istioctl proxy-config listener "deploy/mutual-gw.$NS" --port 9443 -o json \ | jq '.[0].filterChains[0].transportSocket.typedConfig.requireClientCertificate' echo "=== [2] mesh client (sidecar + DR outer mTLS) -> expect 200 ===" kubectl -n "$NS" exec client -c curl -- curl -sS -o /dev/null -w 'mesh_client=%{http_code}\n' \ --max-time 8 "http://mutual-gw.$NS.svc.homelab.local:9443/" || echo "mesh_client_exit=$?" echo "=== [3] non-mesh caller (no sidecar, no cert) -> expect handshake failure, no HTTP code ===" kubectl -n "$NS" exec non-mesh-caller -- curl -sk -o /dev/null -w 'non_mesh_https_attempt=%{http_code}\n' \ --max-time 4 "https://mutual-gw.$NS.svc.homelab.local:9443/" ; echo "non_mesh_exit=$?"