HTTPS 入口返回 200,内部链路也经过 TLS 吗

读者访问 frontend,frontend 再请求 orders → inventory-v1 + external-stub。入口网关若终止浏览器的传输层安全协议(TLS),获得的是客户端到网关这一段的加密连接;网关转发到 frontend、工作负载间的连接要分别确认。入口 HTTP 200 至多说明本次完整请求得到了响应,不说明内部使用了双向 TLS(mTLS),更不证明有人无法绕开入口直接访问后端。

入口 TLS 终止、Host 和路径匹配、内部调用与可信来源的不同边界

上图把客户端、入口网关、frontend 和内部上游画成四个观察点。TLS 握手里使用的服务器名称指示(SNI)与随后 HTTP Host/:authority 是不同字段:客户端可以在证书匹配的域名上发送另一个 Host。TLS 失败时请求尚未进入应用层路由;证书通过但 Host 或 path 不匹配,是网关路由层的另一类负例;命中路由以后内部服务返回 502/503,还需查看哪一跳报错。不能把这三类失败都记作“网关拒绝”。

匹配、来源地址、身份分别在哪里生效

在网关上声明监听器和 TLS 证书,再把匹配的 Host/path 请求转给 frontend。是否允许某条 Route 附着,须看其 Gateway、命名空间作用域与控制器状态;配置对象提交成功、证书有效和后端可达是三个独立检查。入口终止后若要到后端继续加密,应明确网关到服务端的 TLS 配置和校验对象;如果使用 TLS 透传,网关一般不能直接匹配加密内的 HTTP 路径。工作负载间的 mTLS 还属于另一段连接,不由入口证书自动提供。

跨负载均衡器和代理传递客户端地址时,X-Forwarded-For(XFF)只是 HTTP 头。外部客户端可伪造输入值;Istio 的 numTrustedProxies 必须按网关前可信代理的实际跳数配置,再区分连接对端地址、可信转发链中提取的地址与业务用户身份。即便提取到了一个 IP,也不等于通过了用户认证;只依靠来源 IP 做高权限授权须另证可信路径。更重要的是,若后端存在可直接访问的入口,网关的 Host/path 或鉴权规则不能保护这条绕行路径;应再评估后端网络边界和工作负载授权。

用已有进程先排除错误归因

从 examples/service-mesh/ 分别运行两组已存在的进程对照:

1
2
bash scripts/run-09.sh
bash scripts/run-00.sh

run-09.sh 在回环地址启动有证书的库存服务:正确名字并信任证书时是正常路径,错误主机名会在客户端证书校验阶段失败;它演示的是客户端到单个 Go 服务的 TLS,不是网关 TLS 终止。run-00.sh 的正常链路会返回库存与 stub 数据;其错误端口、空上游等分支用于识别内部调用失败,不是网关 Host 路由结果。若使用有效证书但请求未知路径,应用可能返回自己的 404;也不可改称 Gateway API 的路由拒绝。两组脚本各自保存原始命令与退出码,不应跨组拼接成一次真实网关请求。

独立 OpenSSL 名称校验记录见 examples/service-mesh/evidence/verification/mainline-13-25/17-hostname-check.txt:固定连接端点、SNI 和 CA,只改变 verify_hostname,正常/错误名称/恢复的退出码依次为 0、1、0,错误项包含 hostname mismatch。它只证明客户端名称校验,不证明网关路由或内部 mTLS。

需在隔离环境另做端到端验收:同一入口证书、目标 Host/path、来源地址分别测试正确请求、错误证书/SNI、错误 Host/path 与正确入口但内部故障;保存 TLS 握手、网关路由、代理日志、frontend 与 orders 同一请求 ID 的记录,再在受控网络中尝试直接连后端验证绕过边界。当前没有 Istio 入口网关实验环境,网关监听器、来源 IP 信任链和内部 mTLS 全部 NOT_RUN;不得给这些待测组合填伪造的响应码。

待验收请求 TLS 握手 网关实际路由 内部调用 直连后端限制
正确 Host/path 与证书 待运行 待运行 待运行 待运行
错误证书或 SNI 待运行 待运行 待运行 待运行
正确证书、错误 Host/path 待运行 待运行 待运行 待运行
内部服务失败或绕过入口 待运行 待运行 待运行 待运行

建立可复现的 HTTPS 入口

以下命令在 examples/service-mesh 目录执行,使用 Kubernetes 1.33.4、Istio 1.27.0 的 sidecar 实验基线。先完成 04 的安装与累计应用部署;容器名为 app,Service 端口为 8080。先恢复上一章策略,再执行本章,避免超时、授权或出口规则互相污染。所有预期结果都是验收条件,未运行集群时保留 NOT_RUN。

1
2
3
4
5
6
export SM_CONTEXT=kind-service-mesh-sidecar
k() { kubectl --context="$SM_CONTEXT" -n service-mesh-lab "$@"; }
k get deploy frontend orders inventory-v1 inventory-v2
k apply -f mainline/13/probe.yaml
k rollout status deploy/sm-probe --timeout=120s
OUT="evidence/mainline-17/$(date -u +%Y%m%dT%H%M%SZ)"; mkdir -p "$OUT"

sm-probe 使用 orders ServiceAccount,明确注入 sidecar;它提供 curl,不依赖 scratch 应用镜像里不存在的 shell。每次请求同时保存客户端结果、来源代理配置和目标应用日志。

使用 Istio 1.27.0 自带 ingressgateway Deployment。若 04 的安装 profile 未安装该网关,先用同版本安装清单启用它并等待就绪。mainline/17/gateway.yaml 声明 443、SIMPLE TLS 和 mesh.example.test,只将 /quote 送到 frontend。证书是本机临时自签证书,不能用于生产域名。

1
2
3
4
5
TLS_DIR=$(mktemp -d); chmod 700 "$TLS_DIR"
openssl req -x509 -newkey rsa:2048 -nodes -days 1 -subj '/CN=mesh.example.test' -addext 'subjectAltName=DNS:mesh.example.test' -keyout "$TLS_DIR/key.pem" -out "$TLS_DIR/cert.pem"
kubectl --context="$SM_CONTEXT" -n istio-system create secret tls sm-ingress --key="$TLS_DIR/key.pem" --cert="$TLS_DIR/cert.pem" --dry-run=client -o yaml | kubectl --context="$SM_CONTEXT" -n istio-system apply -f -
k apply -f mainline/17/gateway.yaml
kubectl --context="$SM_CONTEXT" -n istio-system port-forward svc/istio-ingressgateway 18443:443

port-forward 保持在一个终端;另一个终端设置本轮 TLS_DIR 和 OUT 的实际路径后运行下列命令,使用支持 -verify_hostname 的 OpenSSL 3。curl 正例验证完整报价,错误 Host 对照仍使用正确 SNI。名称校验组固定连接地址、SNI 和信任证书,只把 -verify_hostname 从 mesh.example.test 改成 wrong.example.test,再恢复正确名称;非零退出必须同时具有明确的 hostname mismatch,不能把网关因错误 SNI 拒绝握手算作名称校验。

1
2
3
4
5
6
7
8
9
10
11
curl -fsS --cacert "$TLS_DIR/cert.pem" --resolve mesh.example.test:18443:127.0.0.1 -H 'x-request-id: ch17-ok' 'https://mesh.example.test:18443/quote?sku=paper' > "$OUT/normal.json"
curl -H 'x-request-id: ch17-wrong-host' -sS --cacert "$TLS_DIR/cert.pem" --resolve mesh.example.test:18443:127.0.0.1 -H 'Host: wrong.example.test' -o /dev/null -w '%{http_code}\n' 'https://mesh.example.test:18443/quote?sku=paper' > "$OUT/wrong-host.txt"
openssl s_client -connect 127.0.0.1:18443 -servername mesh.example.test -CAfile "$TLS_DIR/cert.pem" -verify_hostname mesh.example.test -verify_return_error < /dev/null > "$OUT/tls-normal.stdout" 2> "$OUT/tls-normal.stderr"; tls_code=$?; printf '%s\n' "$tls_code" > "$OUT/tls-normal.exit"; test "$tls_code" -eq 0
if openssl s_client -connect 127.0.0.1:18443 -servername mesh.example.test -CAfile "$TLS_DIR/cert.pem" -verify_hostname wrong.example.test -verify_return_error < /dev/null > "$OUT/wrong-name.stdout" 2> "$OUT/wrong-name.stderr"; then tls_code=0; else tls_code=$?; fi; printf '%s\n' "$tls_code" > "$OUT/wrong-name.exit"; test "$tls_code" -ne 0
grep -E 'verify error:num=62:hostname mismatch|Verification error: hostname mismatch' "$OUT/wrong-name.stdout" "$OUT/wrong-name.stderr"
openssl s_client -connect 127.0.0.1:18443 -servername mesh.example.test -CAfile "$TLS_DIR/cert.pem" -verify_hostname mesh.example.test -verify_return_error < /dev/null > "$OUT/tls-recovered.stdout" 2> "$OUT/tls-recovered.stderr"; tls_code=$?; printf '%s\n' "$tls_code" > "$OUT/tls-recovered.exit"; test "$tls_code" -eq 0
kubectl --context="$SM_CONTEXT" -n istio-system logs deploy/istio-ingressgateway > "$OUT/gateway.log"
k logs deploy/frontend -c app > "$OUT/frontend.log"
curl -H 'x-request-id: ch17-recovered' -fsS --cacert "$TLS_DIR/cert.pem" --resolve mesh.example.test:18443:127.0.0.1 'https://mesh.example.test:18443/quote?sku=paper' > "$OUT/recovered.json"
k delete -f mainline/17/gateway.yaml
kubectl --context="$SM_CONTEXT" -n istio-system delete secret sm-ingress

正常和恢复应返回库存报价;错误 Host 预期没有匹配路由,核对网关日志与 frontend 无该错误请求;错误名称组只做 TLS 握手,不发送 HTTP 业务请求;要求正常、错误名称、恢复三个退出状态分别为零、非零、零,并核对错误名称日志中的验证错误 62。恢复后的 curl 还须返回报价,不能使用 -k 绕过校验。停止 port-forward,删除临时证书目录,证据只保留公有证书信息,不保留私钥。port-forward 跳过外部负载均衡器,因此本实验不能验证真实客户端 IP 或多级 XFF 信任配置。

固定源码入口:Istio 1.27.0 pilot/pkg/networking/core/gateway.go。该链接用于核对配置生成或执行机制;完整资源和命令保存在 examples/service-mesh/mainline/17/。静态检查通过不等于集群实验通过。

练习与参考解答

  1. run-09.sh 的错误主机名失败,能证明网关的 Host 匹配生效吗?参考解答:不能。它在直连服务时先发生证书名称验证失败;须让 TLS 名称有效,再分别改变 HTTP Host/path,并以实际网关请求和代理路由日志验证匹配结果。
  2. 公网负载均衡器后面有一层可信反向代理,客户端自行加一个 X-Forwarded-For 值;应用可以把最左端 IP 当认证用户吗?参考解答:不能。先按实际可信跳数核对网关提取的客户端地址及连接对端,再另做用户身份认证;被客户端注入的头不是可靠身份凭据。

官方参考与系列导航

系列导航:16 故障与镜像 → 17 入口网关(本文)→ 18 出口强制边界。