Traefik 双向 TLS(mTLS)测试证书生成与客户端证书透传实践
本文以 Traefik 仓库集成测试夹具 integration/fixtures/tlsclientheaders/readme.md 中记录的 OpenSSL 证书生成命令为主线,讲解如何用本地 CA 体系搭建一套完整的 mTLS 验证环境:服务端要求并校验客户端证书,Traefik 再将通过校验的客户端证书(或其指定字段)透传给后端服务。读完本文,你将掌握这套证书命令的每一步含义、生成产物如何被 Traefik 配置与集成测试消费,以及客户端证书透传(passTLSClientCert)中间件的头部格式与配置方法。
这份 readme 在仓库中的定位
integration/fixtures/tlsclientheaders/readme.md 并不是一篇介绍性文档,而是集成测试夹具的“配方”:它用 4 条 OpenSSL 命令产出该目录下供集成测试使用的全部证书材料。该夹具目录当前包含:
readme.md—— 证书生成命令说明;root.pem—— 自签根 CA 证书(公开部分);server.key—— 服务端/客户端使用的私钥;server.pem—— 由根 CA 签发的证书;simple.toml—— 集成测试的 Traefik 静态 + 动态配置模板。
这些产物唯一且明确的消费方是集成测试 integration/tls_client_headers_test.go,它验证的主题是 mTLS 握手成功后,Traefik 把客户端证书以 Forwarded-Tls-Client-Cert 请求头转发到后端,对应生产能力即 passTLSClientCert HTTP 中间件(源码、文档)。
环境与产物清单
执行命令前,只需要一台装有 OpenSSL 命令行的 Linux/macOS 主机(Traefik 集成测试本身使用 Go 测试套件 + Docker Compose,证书生成部分与语言无关)。
readme 中的 4 条命令会产生 5 个文件,其用途对应关系如下:
| 命令产物 | 角色 | 在该夹具中的用途 |
|---|---|---|
root.key |
根 CA 私钥 | 签发服务器证书;不应分发 |
root.pem |
根 CA 证书 | 作为服务器证书签发者、Traefik serversTransport.rootCAs、TLS 选项 clientAuth.caFiles |
server.key |
服务器私钥 | Traefik 默认证书私钥;同时被测试客户端用作 mTLS 客户端证书私钥 |
server.csr |
证书签名请求 | 中间产物,仅用于签发,可删除 |
server.pem |
服务器证书 | Traefik 默认证书;同时被测试客户端作为 mTLS 客户端证书 |
仓库中只提交了 root.pem、server.key、server.pem(root.key、server.csr 属于敏感/中间产物,不入库),集成测试启动时会从这三个文件读入内容并注入配置模板。
逐条拆解 OpenSSL 命令
readme 原文完整收录如下,然后逐条说明其技术细节:
openssl req -new -newkey rsa:2048 -x509 -days 3650 -nodes -extensions v3_ca -keyout root.key -out root.pem
openssl genrsa -out server.key 2048
openssl req -nodes -key server.key -new -out server.csr
openssl x509 -req -days 3650 -in server.csr -CA root.pem -CAkey root.key -CAcreateserial -out server.pem
第 1 步:生成自签根 CA(root.key / root.pem)
openssl req -new -newkey rsa:2048 -x509 -days 3650 -nodes -extensions v3_ca -keyout root.key -out root.pem
各参数含义:
-newkey rsa:2048:同时生成一把新的 RSA 2048 位私钥;-nodes(no DES)表示私钥不加密、不要求输入口令,保证集成测试中 Traefik 与 HTTP 客户端能无交互地加载证书;-x509:直接输出自签名 X.509 证书,而不经过 CSR;-days 3650:证书有效期 3650 天(约 10 年),长有效期让仓库内的测试证书长期免维护;-extensions v3_ca:为自签证书附加 v3 扩展,其中v3_ca是 OpenSSL 预置的扩展名,标记该证书可用作 CA(CA:TRUE);-keyout root.key -out root.pem:私钥与证书分别输出。
生成过程中 OpenSSL 会交互式询问 Subject 字段(国家、省份、组织、通用名 CN 等)。测试用例中根 CA 与服务器的 CN 必须保证“服务器证书由 root CA 签发”,mTLS 校验才会成立。
第 2 步:生成服务器私钥
openssl genrsa -out server.key 2048
生成一把独立的 RSA 2048 位私钥,供服务器证书使用。这把私钥不会混入根 CA 私钥,保证 CA 与业务证书密钥隔离——这也是真实 PKI 环境的安全惯例。注意:这一步产生的私钥与第 1 步 CA 私钥都处于 -nodes/无口令状态,仅适用于测试环境,生产环境必须加密保护私钥。
第 3 步:生成证书签名请求(CSR)
openssl req -nodes -key server.key -new -out server.csr
使用第 2 步的私钥生成 CSR:
-key server.key:指明私钥;-new:新建 CSR;-nodes:读取私钥时不要求口令;-out server.csr:输出签名请求。
CSR 中携带服务器公钥与申请者身份信息(同样通过交互式 Subject 输入填写),它本身不含私钥,可以安全地提交给 CA 进行签发。
第 4 步:用根 CA 签发服务器证书
openssl x509 -req -days 3650 -in server.csr -CA root.pem -CAkey root.key -CAcreateserial -out server.pem
这是从“自签材料”走向“CA 体系”的关键一步:
x509 -req:以“签发请求”模式工作,即用 CA 对 CSR 进行签名;-in server.csr:待签名的请求;-CA root.pem -CAkey root.key:签发者 CA 证书与 CA 私钥;-CAcreateserial:签发时自动生成序列号文件(root.srl),保证后续签发的证书序列号递增不重复;-out server.pem:输出由 root CA 签名的服务器证书。
验证产物可信链(延伸建议)
readme 未包含验证命令,但建议签发后用以下命令自检,确保“服务器证书由 root.pem 签发”这一 mTLS 前提成立:
openssl verify -CAfile root.pem server.pem
openssl x509 -in server.pem -noout -issuer -subject
verify 返回 OK 说明证书链成立,这是后续集成测试能够通过校验的前提。
证书如何在 Traefik 集成环境中被消费
夹具中的 integration/fixtures/tlsclientheaders/simple.toml 是一个带模板变量的 Traefik 配置,集成测试运行时会把上述证书文件内容替换进 {{ .RootCertContent }}、{{ .ServerCertContent }}、{{ .ServerKeyContent }} 占位符。三处消费点分别对应证书体系的三个角色:
1) 上游信任:[serversTransport]
[serversTransport]
rootCAs = [ """{{ .RootCertContent }}""" ]
rootCAs 声明 Traefik 作为客户端访问后端时信任的 CA 列表。将该内容指向根 CA 证书,是为了让 Traefik 在向后端发起 HTTPS 时能校验后端的服务器证书。
2) 服务端默认证书:[tls.stores]
[tls.stores]
[tls.stores.default.defaultCertificate]
certFile = """{{ .ServerCertContent }}"""
keyFile = """{{ .ServerKeyContent }}"""
tls.stores.default.defaultCertificate 指定默认 TLS 证书:任何没有匹配到专属证书的 TLS 握手都回落到这张证书。这里即 server.pem/server.key,保证入口能建立 TLS。
3) 客户端证书校验策略:[tls.options]
[tls.options]
[tls.options.default.clientAuth]
caFiles = [ """{{ .RootCertContent }}""" ]
clientAuthType = "RequireAndVerifyClientCert"
这是 mTLS 的核心开关:
caFiles:信任的客户端证书签发 CA 列表,即客户端必须出示由root.pem签发的证书;clientAuthType = "RequireAndVerifyClientCert":要求客户端必须提供证书并验证其有效性。只有满足该策略的客户端证书才会被 Traefik 接受,也才可能被透传到后端。
入口点方面,配置同时声明了:
[entryPoints]
[entryPoints.websecure]
address = ":8443"
即测试通过 https://127.0.0.1:8443 提供服务,TLS 选项 default 中的 mTLS 策略作用于该入口。
客户端证书如何透传到后端:passTLSClientCert 中间件
readme 目录名 tlsclientheaders 点明的正是要验证的“客户端证书头”。Traefik 侧对应实现是 passTLSClientCert 中间件,核心逻辑位于 pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go。该中间件依据 dynamic 配置 中的 PassTLSClientCert 结构体工作,可分为两种模式:
模式一:pem: true —— 透传整张证书
中间件读取 req.TLS.PeerCertificates(即 mTLS 握手时客户端出示的证书链),把每张证书编码为 DER/PEM 内容后写入 X-Forwarded-Tls-Client-Cert 请求头:
req.Header.Set(xForwardedTLSClientCert, getCertificates(ctx, req.TLS.PeerCertificates))
写入前会对 PEM 做 sanitize 处理(源码 L324-L331):去掉 -----BEGIN CERTIFICATE----- / -----END CERTIFICATE----- 分隔符和所有换行符,只保留纯 base64 的 DER 内容,使其可安全放进 HTTP 请求头。多张证书用 , 连接(源码常量 L26-L30)。
模式二:info —— 只透传指定字段
当配置 info 时,中间件把选中的证书信息拼接后写入 X-Forwarded-Tls-Client-Cert-Info 请求头,并通过 url.QueryEscape 转义成合法的 URL 查询串。字段取值范围可精确到 Subject / Issuer 的单个 DN 属性:
| 配置字段 | 含义 | 取值示例前缀 |
|---|---|---|
info.subject.country / info.issuer.country |
国家 | C= |
info.subject.province / info.issuer.province |
省份 | ST= |
info.subject.locality / info.issuer.locality |
地区 | L= |
info.subject.organization / info.issuer.organization |
组织 | O= |
info.subject.organizationalUnit |
组织单元(仅 Subject) | OU= |
info.subject.commonName / info.issuer.commonName |
通用名 | CN= |
info.subject.serialNumber / info.issuer.serialNumber |
序列号 | SN= |
info.subject.domainComponent / info.issuer.domainComponent |
域组件(RFC 2247,DC OID 映射见源码 L32-L34) | DC= |
info.serialNumber |
证书序列号 | SerialNumber= |
info.notBefore / info.notAfter |
有效期起止(Unix 秒) | NB= / NA= |
info.sans |
主题备用名(DNS/邮箱/IP/URI) | SAN= |
拼接规则为:同层字段用 ; 分隔,同名字段值之间用 ,,多张证书之间用 ,。官方文档给出的完整字段拼接示意(未转义形式,见 passtlsclientcert.md):
Subject="DC=org,DC=cheese,C=FR,...,CN=*.example.com";Issuer="...CN=Simple Signing CA 2";SerialNumber="1";NB="1747282426";NA="1778818426";SAN="*.example.org,...,10.0.1.2"
两个模式都必须在 mTLS 握手成功的前提下才生效:源码 ServeHTTP 会判断 req.TLS != nil && len(req.TLS.PeerCertificates) > 0,无有效客户端证书时只打印 DEBUG 日志而不设置请求头——也就是说,只有命中 clientAuth.clientAuthType 策略的证书才会被传递。
端到端验证:集成测试做了什么
测试入口 integration/tls_client_headers_test.go 中的 TestTLSClientHeaders(L39-L77)完整复现了“生成证书 → 配置 mTLS → 客户端出示证书 → 后端收到透传头”的闭环:
- 读取夹具目录中的
root.pem、server.pem、server.key三个文件; - 调用
adaptFile把证书内容替换进simple.toml模板(对应RootCertContent、ServerCertContent、ServerKeyContent),生成真实配置文件并启动 Traefik; - 轮询
http://127.0.0.1:8080/api/rawdata,等待路由环境就绪(断言出现PathPrefix(/foo)); - 构造
https://127.0.0.1:8443/foo请求,客户端通过tls.LoadX509KeyPair(server.pem, server.key)加载“客户端证书”,并在 TLS 配置中附带(测试中使用InsecureSkipVerify跳过对 Traefik 服务端证书的校验证书,这是测试环境对自签服务端证书的常见处理); - 后端回显的响应体中必须包含
Forwarded-Tls-Client-Cert: MIIDNTCCAh0...的完整 base64 内容——该值正是server.pem去掉 PEM 分隔符与换行后的证书体,验证了透传内容与实际握手证书一致。
测试套件同时提供了 SetupSuite/TearDownSuite(L28-L37)负责拉起与销毁 Docker Compose 环境,因此跑完整流程只需在仓库根目录具备 Docker 的前提下执行该套件(例如 go test ./integration -run TestTLSClientHeadersSuite),无需手工准备证书。
想在不写代码的情况下手工验证 mTLS 链路,也可以在 Traefik 按上述配置运行后,用 OpenSSL 客户端模拟“出示客户端证书”的握手并观察服务端是否下发证书请求:
openssl s_client -connect 127.0.0.1:8443 \
-CAfile root.pem -cert server.pem -key server.key -showcerts
若 mTLS 配置生效,握手过程会要求并提供客户端证书;不携带 -cert/-key 时,RequireAndVerifyClientCert 策略会使握手失败,与源码中“无证书则不设置透传头”的行为相互印证。
注意事项与最佳实践
结合 readme 命令与中间件行为,实践中有几点值得留意:
- 私钥安全:readme 中
-nodes/genrsa生成的root.key、server.key均为无口令明文私钥。在测试夹具中可以接受(保证自动化无交互加载),但生产环境的 CA 私钥必须离线保存并加密; - 有效期:
-days 3650为证书设置了约 10 年有效期,集成测试夹具因此几乎不需要轮换;实际业务证书应遵循组织的短期证书轮换策略; - 根私钥与业务私钥隔离:CA 只负责签发,绝不参与业务 TLS 握手,命令第 2 步独立生成
server.key正是这种隔离的体现; - 请求头大小:完整 PEM 证书可能较大,后端 Web 服务器请求头上限通常在 4KB~8KB。若透传整张证书遇到头部超限,官方文档建议将
pem设为false,改用info按需挑选证书字段(见 passtlsclientcert.md 中关于 Header size 的说明); - 身份信息不要依赖客户端自行填写:透传的证书内容来自已通过
RequireAndVerifyClientCert校验的握手证书,因此身份可信度由 mTLS 策略保证,而不是由请求方自定义的普通请求头决定。
总结
integration/fixtures/tlsclientheaders/readme.md 用 4 条 OpenSSL 命令浓缩了一套完整的“私有 CA + 服务器证书”构建流程,而围绕它的 simple.toml、集成测试 与 passTLSClientCert 中间件源码 则展示了这些证书材料的全部消费路径。掌握这套命令,你就同时理解了私有 PKI 搭建、Traefik mTLS 入口配置,以及客户端证书通过 X-Forwarded-Tls-Client-Cert / X-Forwarded-Tls-Client-Cert-Info 请求头透传到后端的完整机制,可以轻松复制出一套属于自己的“证书生成 → mTLS 接入 → 客户端身份透传”验证环境。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00