首页
/ Traefik 双向 TLS(mTLS)测试证书生成与客户端证书透传实践

Traefik 双向 TLS(mTLS)测试证书生成与客户端证书透传实践

2026-09-07 10:04:43作者:齐冠琰

本文以 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.pemserver.keyserver.pemroot.keyserver.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 中的 TestTLSClientHeadersL39-L77)完整复现了“生成证书 → 配置 mTLS → 客户端出示证书 → 后端收到透传头”的闭环:

  1. 读取夹具目录中的 root.pemserver.pemserver.key 三个文件;
  2. 调用 adaptFile 把证书内容替换进 simple.toml 模板(对应 RootCertContentServerCertContentServerKeyContent),生成真实配置文件并启动 Traefik;
  3. 轮询 http://127.0.0.1:8080/api/rawdata,等待路由环境就绪(断言出现 PathPrefix(/foo));
  4. 构造 https://127.0.0.1:8443/foo 请求,客户端通过 tls.LoadX509KeyPair(server.pem, server.key) 加载“客户端证书”,并在 TLS 配置中附带(测试中使用 InsecureSkipVerify 跳过对 Traefik 服务端证书的校验证书,这是测试环境对自签服务端证书的常见处理);
  5. 后端回显的响应体中必须包含 Forwarded-Tls-Client-Cert: MIIDNTCCAh0... 的完整 base64 内容——该值正是 server.pem 去掉 PEM 分隔符与换行后的证书体,验证了透传内容与实际握手证书一致。

测试套件同时提供了 SetupSuite/TearDownSuiteL28-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.keyserver.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 接入 → 客户端身份透传”验证环境。

登录后查看全文
热门项目推荐
相关项目推荐