- API网关
- 云原生
- 微服务
【免费下载链接】kgateway
The Cloud-Native API Gateway and AI Gateway
本篇指南基于 kgateway 仓库中test/e2e/features/frontendtls/testdata/下的客户端证书体系(CLIENT_CERTS_README.md),系统讲解 kgateway 双向 TLS(mTLS)端到端测试所需的 CA 与客户端证书的生成、指纹计算、Kubernetes 资源封装方式,以及它们如何被verify-certificate-hash注解、verify-subject-alt-names注解和 Gateway API 的FrontendTLSConfig验证机制消费。读完本文,你将掌握一套可复现的 mTLS 测试证书生产流程,并理解 kgateway 前端 TLS 验证链路的底层实现。
背景:为什么需要一套专门的 mTLS 测试证书
kgateway 作为云原生 API 网关,支持在 HTTPS 监听器上启用双向 TLS(mutual TLS)认证。在 test/e2e/features/frontendtls/ 目录下的端到端测试套件中,验证了多种客户端证书校验机制:
verify-certificate-hash注解:基于客户端证书的 SHA256 指纹做"证书钉扎"(certificate pinning),只接受指纹匹配的特定证书;FrontendTLSConfig:基于 CA 证书链校验客户端证书,并通过AllowValidOnly/AllowInsecureFallback两种模式控制客户端证书是"必需"还是"可选";verify-subject-alt-names注解:校验客户端证书的 SAN(Subject Alternative Name)扩展;signature-algorithms注解:校验客户端证书所使用的签名算法。
这些机制对应的注解与 TLS 选项定义在 api/annotations/gateway.go 中,包括kgateway.dev/verify-subject-alt-names、kgateway.dev/verify-certificate-hash、kgateway.dev/signature-algorithms、kgateway.dev/alpn-protocols、kgateway.dev/cipher-suites、kgateway.dev/ecdh-curves、kgateway.dev/min-tls-version与kgateway.dev/max-tls-version。
为了让测试能够精确覆盖"证书指纹匹配 / 不匹配""CA 链有效 / 无效""SAN 匹配 / 不匹配""签名算法受支持 / 不受支持"等正反场景,测试仓库维护了多套由不同 CA 签发的客户端证书。证书本身不直接存放原始.crt/.key/.pem文件(仓库中仅保留少量用于说明的原始文件,如certs/ca1/下的client-8443.crt),而是以 base64 编码的 Kubernetes Secret 与 ConfigMap 形态提交,并配有一份详细的生成文档。
证书目录结构
证书按照用途组织在 test/e2e/features/frontendtls/testdata/certs/ 下,每个子目录对应一组测试场景:
| 目录 | 用途 | 关键资源 |
|---|---|---|
certs/ca1/ | CA 证书 1 及其签发的客户端证书 | ca-cert-configmap.yaml(CA 1 ConfigMap)、client-cert-secret.yaml(FrontendTLSConfig 测试用客户端证书)、client-certs-8443-9443-secret.yaml(8443/9443 端口用客户端证书) |
certs/ca2/ | CA 证书 2 及其签发的客户端证书 | ca-cert-2-configmap.yaml(CA 2 ConfigMap,位于独立命名空间ca-cert-2)、client-cert-2-secret.yaml(多 CA 测试用客户端证书) |
certs/ca-alt-names/ | verify-subject-alt-names 测试的 CA 与客户端证书 | ca-alt-names-configmap.yaml、client-matching-san-secret.yaml(SAN 为DNS:mtls.example.com,应通过)、client-non-matching-san-secret.yaml(SAN 为DNS:mtls-alt.example.com,应拒绝) |
certs/ca-sigalgs/ | signature-algorithms 测试的 CA 与客户端证书 | ca-sigalgs-configmap.yaml、client-matching-signature-secret.yaml(RSA+SHA256 签名,应通过)、client-non-matching-signature-secret.yaml(ECDSA+SHA256 签名,应拒绝) |
关于该目录结构,有几点需要注意:
- 测试只消费 YAML 清单中的 base64 编码证书。原始证书文件(
.crt、.key、.pem)是证书生成的产物,仓库中一般不提交,可通过下文"证书生成"一节的命令重新生成。 - 中间产物不提交。生成过程中产生的
.csr(证书签名请求)和.srl(序列号)文件属于临时工件,同样不进入仓库,只有最终封装进 YAML 清单的证书才是必需的。 - 服务端 TLS 证书单独存放。服务端证书
tls-rsa-secret.yaml、tls-ecdsa-p256-secret.yaml位于 testdata 根目录,与gw.yaml、curl-pod-with-certs.yaml、curl-namespace.yaml(负责创建curl命名空间、curl Pod 及其挂载的客户端证书 Secret)并列。 - 两类验证机制共享同一套 CA。
ca1与ca2的 CA 证书同时支撑两种校验路径:verify-certificate-hash校验具体证书的指纹,FrontendTLSConfig校验证书链是否由受信 CA 签发。也正因为如此,gw.yaml的 8443/9443 监听器可以在同一监听器上同时配置这两个机制。
证书生成:完整的 openssl 流程
以下命令来自关联文档的"Certificate Generation"章节,是当前测试证书体系的可复现生产流程。所有证书均使用 RSA 2048 密钥。
Step 1:生成 CA 证书与私钥
openssl genrsa -out ca-key.pem 2048 openssl req -new -x509 -days 365 -key ca-key.pem -out ca-cert.pem \ -subj "/CN=Test CA/O=Test Org"CA 证书(CN=Test CA, O=Test Org)随后以 PEM 格式写入 ConfigMap。参考 ca-cert-configmap.yaml,其data.ca.crt字段即该证书文本,kgateway 从 ConfigMap 中读取该值作为信任锚。
Step 2:生成 8443 端口客户端证书
openssl genrsa -out client-8443.key 2048 openssl req -new -key client-8443.key -out client-8443.csr \ -subj "/C=US/ST=California/L=San Francisco/O=Client Inc./CN=client.example.com" openssl x509 -req -days 3650 -in client-8443.csr -CA ca-cert.pem -CAkey ca-key.pem \ -CAcreateserial -out client-8443.crtStep 3:生成 9443 端口客户端证书
openssl genrsa -out client-9443.key 2048 openssl req -new -key client-9443.key -out client-9443.csr \ -subj "/C=US/ST=California/L=San Francisco/O=Client Inc./CN=client-alt.example.com" openssl x509 -req -days 3650 -in client-9443.csr -CA ca-cert.pem -CAkey ca-key.pem \ -CAcreateserial -out client-9443.crt8443 与 9443 两套证书均被封装进 client-certs-8443-9443-secret.yaml,字段为client-8443.crt、client-8443.key、client-9443.crt、client-9443.key。由于两者由同一 CA 签发且证书指纹不同,正好用于验证"跨监听器交叉使用证书必然失败"的负向场景。
Step 4:生成 FrontendTLSConfig 测试客户端证书(8444/8445 端口)
openssl genrsa -out client-frontend.key 2048 openssl req -new -key client-frontend.key -out client-frontend.csr \ -subj "/CN=client.example.com/O=Test Org" openssl x509 -req -days 3650 -in client-frontend.csr -CA ca-cert.pem -CAkey ca-key.pem \ -CAcreateserial -out client-frontend.crt # Base64 encode and update certs/ca1/client-cert-secret.yaml: base64 -i client-frontend.crt | tr -d '\n' base64 -i client-frontend.key | tr -d '\n'生成的证书与私钥分别写入 client-cert-secret.yaml 的data.tls.crt与data.tls.key。该 Secret 的tls.crt/tls.key键名与 Kubernetes 标准的kubernetes.io/tls类型 Secret 一致,挂载后可直接作为tls.crt/tls.key文件使用(参见下文 curl Pod 挂载路径/etc/client-certs-frontend/tls.crt)。
Step 5:生成第二套 CA(多 CA 测试)
# Generate second CA certificate and key openssl genrsa -out ca2-key.pem 2048 openssl req -new -x509 -days 365 -key ca2-key.pem -out ca2-cert.pem \ -subj "/CN=Test CA 2/O=Test Org" # Base64 encode and update certs/ca2/ca-cert-2-configmap.yaml: base64 -i ca2-cert.pem | tr -d '\n'第二套 CA 的 ConfigMap 比较特殊:它所在的命名空间为ca-cert-2(见 ca-cert-2-configmap.yaml,清单内同时声明了该 Namespace)。这意味着在网关配置中引用它时必须显式给出namespace字段(参见下文gw.yaml中 8446 端口的caCertificateRefs)。
Step 6:生成由第二套 CA 签发的客户端证书(8446 端口)
# Generate client certificate signed by CA 2 openssl genrsa -out client2-key.pem 2048 openssl req -new -key client2-key.pem -out client2.csr \ -subj "/CN=client2.example.com/O=Test Org" openssl x509 -req -days 3650 -in client2.csr -CA ca2-cert.pem -CAkey ca2-key.pem \ -CAcreateserial -out client2-cert.pem # Base64 encode and update certs/ca2/client-cert-2-secret.yaml: base64 -i client2-cert.pem | tr -d '\n' base64 -i client2-key.pem | tr -d '\n'除上述命令外,SAN 与签名算法测试的证书还涉及两个额外要点(记录在对应 Secret 清单的生成注释中):
- SAN 证书需要在
req与x509阶段附加扩展:openssl req ... -addext "subjectAltName = DNS:mtls.example.com",并在签发时通过-extfile <(echo "subjectAltName = DNS:mtls.example.com")保留 SAN; - 签名算法测试中,匹配证书以 RSA 密钥 + SHA256 签名(对应
rsa_pss_rsae_sha256),不匹配证书使用 ECDSA 密钥,用于验证签名算法白名单的拒绝行为。
SHA256 指纹计算
verify-certificate-hash注解要求提供客户端证书的 SHA256 指纹。关联文档给出了从证书文件计算冒号分隔大写指纹的命令:
# For port 8443 certificate openssl x509 -in client-8443.crt -noout -fingerprint -sha256 | cut -d= -f2 | \ tr -d ':' | awk '{for(i=1;i<=length($0);i+=2) printf "%s:", substr($0,i,2); print ""}' | \ sed 's/:$//' | tr '[:lower:]' '[:upper:]' # For port 9443 certificate openssl x509 -in client-9443.crt -noout -fingerprint -sha256 | cut -d= -f2 | \ tr -d ':' | awk '{for(i=1;i<=length($0);i+=2) printf "%s:", substr($0,i,2); print ""}' | \ sed 's/:$//' | tr '[:lower:]' '[:upper:]'该流程依次执行:提取-fingerprint输出的等号后指纹、去掉冒号、按两个字符一组重新插入冒号、去掉尾部冒号并转为大写。
已计算好的指纹如下:
- Port 8443 cert:
59:CF:81:16:72:71:C7:17:23:4E:CE:4F:F9:B9:68:B5:B0:40:2A:00:BB:14:64:E0:42:B9:AD:DA:61:C7:03:F0 - Port 9443 cert:
E8:44:3B:AA:87:7C:DD:71:31:59:02:77:A4:2F:20:C6:B6:F5:7B:02:29:07:E0:F7:8C:21:DE:41:5B:28:CF:BB
注解值的底层解析与校验
从源码看,kgateway.dev/verify-certificate-hash注解的解析位于 pkg/kgateway/translator/sslutils/ssl_utils.go 的ApplyVerifyCertificateHash函数:
- 注解值先经
splitFakeYamlArray切分——该函数支持以逗号(,)或连字符(-)分隔、允许空白填充的"伪 YAML 数组"语法,因此单个监听器可配置多个允许的证书指纹; - 每个 hash 再经
validateCertificateHash校验:合法形式是 64 个十六进制字符(无分隔),或由冒号分隔成 32 对十六进制字符(对应正则^[[:xdigit:]]{2}(:[[:xdigit:]]{2}){31}$); - 校验通过后写入 IR 结构
ir.TLSConfig.VerifyCertificateHash(定义于 pkg/pluginsdk/ir/gw2.go)。
注解常量本身定义在 api/annotations/gateway.go:值可以是 "7D:86:..."(冒号分隔)或 "7D86..."(连续 hex)等格式,且受 Gateway API AnnotationValue 规范约 4096 字符长度限制。TLSExtensionOptionFuncs映射(见 ssl_utils.go)统一将kgateway.dev/前缀下的 8 个 TLS 扩展选项分发到各自的解析函数,并保证"未知键报错、拼写错误不静默忽略"。
证书在测试中的挂载与使用
curl Pod 与 Secret 挂载
测试通过 curl-pod-with-certs.yaml 创建一个curl命名空间下的 Pod(镜像ghcr.io/kgateway-dev/curl:8.20.0),以只读方式挂载 7 个 Secret,并设置defaultMode: 0444。挂载路径与证书来源的对应关系如下:
| Pod 内路径 | 对应 Secret | 来源清单 | 用途 |
|---|---|---|---|
/etc/client-certs/client-8443.crt/client-8443.key | client-certs | client-certs-8443-9443-secret.yaml | 8443 监听器(verify-certificate-hash) |
/etc/client-certs/client-9443.crt/client-9443.key | client-certs | 同上 | 9443 监听器(verify-certificate-hash) |
/etc/client-certs-frontend/tls.crt/tls.key | client-cert | client-cert-secret.yaml(CA 1 签发) | 8444/8445 端口 FrontendTLSConfig 测试 |
/etc/client-certs-2-frontend/tls.crt/tls.key | client-cert-2 | client-cert-2-secret.yaml(CA 2 签发) | 8446 端口多 CA 测试 |
/etc/client-matching-san/tls.crt/tls.key | client-matching-san | client-matching-san-secret.yaml(SANDNS:mtls.example.com) | verify-subject-alt-names 正向用例 |
/etc/client-non-matching-san/tls.crt/tls.key | client-non-matching-san | client-non-matching-san-secret.yaml(SANDNS:mtls-alt.example.com) | verify-subject-alt-names 负向用例 |
/etc/client-matching-signature/tls.crt/tls.key | client-matching-signature | certs/ca-sigalgs/下对应 Secret(RSA 签名) | signature-algorithms 正向用例 |
/etc/client-non-matching-signature/tls.crt/tls.key | client-non-matching-signature | certs/ca-sigalgs/下对应 Secret(ECDSA 签名) | signature-algorithms 负向用例 |
在 test/e2e/features/frontendtls/suite.go 中,prerequisiteManifests()将这些 Secret、CA ConfigMap 与服务端证书清单统一收集,由SetupSuite在 curl Pod 调度之前按顺序应用——因为 kubelet 若在 Secret 尚不存在时挂载卷,会因失败进入指数退避,导致 Pod 卡在ContainerCreating超过基础套件的 60s 就绪超时。TearDownSuite则负责清理这些前置资源(遵循框架不删除命名空间的规则,ca-cert-2与curl命名空间得以保留)。
网关配置:验证机制如何接线
gw.yaml 是理解各机制如何协同的关键。它同时使用了两种配置入口:
入口一:Gateway 的spec.tls.frontend(FrontendTLSConfig 风格)
在spec.tls.frontend下通过default与perPort定义全局与按端口的前端 TLS 校验:
default.validation.mode: AllowValidOnly+caCertificateRefs: [ca-cert]——默认要求客户端证书且必须由 CA 签发;- 端口 443:
AllowInsecureFallback——允许无客户端证书(用于测试 TLS 选项本身,而非证书校验); - 端口 8444:
AllowInsecureFallback——专门验证"客户端证书可选"的降级模式; - 端口 8446:
AllowValidOnly+两个caCertificateRefs(ca-cert与命名空间ca-cert-2下的ca-cert-2)——验证多 CA 支持(对应 issue #12938),客户端证书由任一 CA 签发均可被接受; - 端口 8447:
AllowValidOnly+ CAca-alt-names-cert——配合监听器上的verify-subject-alt-names做 SAN 校验; - 端口 8448:
AllowValidOnly+ CAca-sigalgs-cert——配合监听器上的signature-algorithms做签名算法校验。
其中mode的两个取值语义为:AllowValidOnly要求必须提供客户端证书且必须验证通过;AllowInsecureFallback在客户端未提供证书时降级放行(提供证书时仍会验证)。
入口二:Listener 的tls.options注解
在具体监听器的tls.options中配置 kgateway 扩展注解:
https-mtls(8443,主机mtls.example.com):kgateway.dev/verify-certificate-hash: "59:CF:81:16:72:71:C7:17:23:4E:CE:4F:F9:B9:68:B5:B0:40:2A:00:BB:14:64:E0:42:B9:AD:DA:61:C7:03:F0"——该值即 8443 客户端证书的 SHA256 指纹,与 FrontendTLSConfig 默认配置(AllowInsecureFallback)叠加;https-mtls-alt(9443,主机mtls-alt.example.com):verify-certificate-hash指向 9443 证书指纹;https-verify-subject-alt-names(8447):kgateway.dev/verify-subject-alt-names: "mtls.example.com"——客户端证书必须携带该 SAN;https-client-signature-algorithms(8448):kgateway.dev/signature-algorithms: "rsa_pss_rsae_sha256,rsa_pkcs1_sha256"——客户端证书签名算法必须命中白名单。
注意verify-certificate-hash与 FrontendTLSConfig 可以在同一监听器上并存:前者做"证书钉扎"(校验特定证书指纹),后者做"链校验"(校验证书链可信性),两者是互补关系。此外 api/annotations/gateway.go 明确指出verify-subject-alt-names注解要求先配置受信 CA。
对应地,gw.yaml为每个 mTLS 监听器都定义了指向httpbin服务的 HTTPRoute(如mtls-route关联https-mtls、mtls-alt-route关联https-mtls-alt、frontend-tls-multiple-ca-route使用通配主机*.example.com),测试通过 curl 携带--cert/--key发起真实请求来断言结果。
测试验证:四组用例的行为矩阵
测试套件注册了 11 个测试用例(见 suite.go),其中与客户端证书直接相关的是TestVerifyCertificateHash、TestFrontendTLSConfig、TestMultipleCACertificates、TestVerifySubjectAltNames与TestClientSignatureAlgorithms。
verify-certificate-hash 测试(TestVerifyCertificateHash)
对应 suite.go,使用 curl 的--cert/--key参数携带证书断言:
- 8443 监听器上,使用指纹匹配的 8443 证书 → 连接成功;
- 8443 监听器上,使用 9443 证书(指纹不匹配)→ 连接失败;
- 8443 监听器上,不提供客户端证书 → 连接失败(mTLS 强制校验);
- 9443 监听器上,使用 9443 证书 → 成功;使用 8443 证书 → 失败;不提供证书 → 失败(端口间交叉验证);
- 常规监听器 443 无需客户端证书即可正常工作——证明 mTLS 仅作用于配置过的监听器。
verify-subject-alt-names 测试(TestVerifySubjectAltNames)
对应 suite.go,针对 8447 监听器(要求 SANmtls.example.com):
- 使用
client-matching-san.crt(SAN 为DNS:mtls.example.com)→ 握手成功; - 使用
client-non-matching-san.crt(SAN 为DNS:mtls-alt.example.com)→ 握手被拒(断言 curl 错误码 55,CURLE_SEND_ERROR)。
该用例确保缺少必需 SAN 的证书在 mTLS 握手阶段即被正确拒绝。
signature-algorithms 测试(TestClientSignatureAlgorithms)
对应 suite.go,针对 8448 监听器(要求rsa_pss_rsae_sha256):
- 使用 RSA+SHA256 签名的
client-matching-signature.crt→ 成功; - 使用 ECDSA+SHA256 签名的
client-non-matching-signature.crt→ 失败(错误码 55)。
该用例保证签名算法白名单之外的证书被拒绝。
FrontendTLSConfig 测试(TestFrontendTLSConfig)
对应 suite.go:
- 8445(AllowValidOnly):无客户端证书 → 失败(错误码 55,SSL 错误);
- 8445(AllowValidOnly):携带 CA 1 签发的有效客户端证书 → 成功;
- 8444(AllowInsecureFallback):无客户端证书 → 成功;
- 8444(AllowInsecureFallback):携带有效证书 → 成功。
多 CA 证书测试(TestMultipleCACertificates)
对应 suite.go,针对 8446 监听器(AllowValidOnly + 多 CA 引用 + 通配域*.example.com,访问test.example.com):
- 使用 CA 1 签发的证书 → 成功;
- 使用 CA 2 签发的证书 → 成功;
- 不提供证书 → 失败(错误码 55)。
多 CA 支持的关键结论:FrontendTLSConfig 的caCertificateRefs数组支持多个 CA 引用;配置多个 CA 后,由其中任意一个 CA 签发的客户端证书都会被接受。这在 CA 迁移或需要同时服务多个客户组织(不同证书颁发机构)的场景下非常实用;并且该能力与通配域协同工作——同一通配域模式(如*.example.com)可以对应多个根 CA 证书。
源码实现链路小结
把上文内容串起来,kgateway 对这几类校验的执行路径是:
- 配置读取:Gateway 的
spec.tls.frontend(FrontendTLSConfig)与监听器tls.options注解(kgateway.dev/前缀)分别承载 CA 引用与指纹/SAN/签名算法约束,注解常量见 api/annotations/gateway.go; - IR 翻译:
ApplyTLSExtensionOptions将选项解析进ir.TLSConfig(VerifyCertificateHash、VerifySubjectAltNames等字段定义于 pkg/pluginsdk/ir/gw2.go),其中指纹解析与格式校验在 ssl_utils.go 完成; - XDS 下发与握手执行:最终由 Envoy 依据 IR 生成下行 TLS 配置,在握手阶段执行证书指纹比对、CA 链校验、SAN 校验与签名算法筛选;
- 端到端验证:测试套件以 curl Pod 挂载的各类证书驱动真实请求,断言成功或失败(错误码 35/55),形成闭环回归保障。
若要在本地复现这套证书体系,只需按本文的 openssl 命令依次执行并更新对应 ConfigMap/Secret 的 base64 数据即可;若只是运行现有 e2e 测试,则仓库中的 YAML 清单已经开箱即用,无需自行生成证书。
- API网关
- 云原生
- 微服务
【免费下载链接】kgateway
The Cloud-Native API Gateway and AI Gateway
相关推荐
OWASP MASTG 实战指南:iOS 证书固定(Certificate Pinning)与 mTLS 客户端证书验证测试(MSTG-NETWORK-4 / MASVS-NETWORK-2)
OWASP MASTG 实战指南:iOS 证书固定(Certificate Pinning)与 mTLS 客户端证书验证测试(MSTG NETWORK 4 /
文档教程网络安全Traefik 双向 TLS(mTLS)测试证书生成与客户端证书透传实践
Traefik 双向 TLS(mTLS)测试证书生成与客户端证书透传实践 本文以 Traefik 仓库集成测试夹具 integration/fixtures/t
后端API网关负载均衡微服务网络云原生GPT-Load监控告警配置终极指南:实时掌握AI代理系统健康状态 🚀
GPT Load监控告警配置终极指南:实时掌握AI代理系统健康状态 🚀 GPT Load是一款高性能的企业级AI API透明代理服务,专为需要集成多个AI服务
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考