Caddy mTLS 实战:条件化双向认证与客户端证书校验
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
一个常见的现实诉求:同一个 HTTPS 入口既服务公网,也服务内网服务——只有内网来源的请求需要出示客户端证书,公网保持普通访问。Caddy 的条件化 mTLS 正好覆盖这个场景:通过connection_policy在 TLS 握手阶段做条件匹配,对命中条件的连接启用客户端证书校验,其余连接照常放行。
一、为什么要"按条件开"双向认证
mTLS(Mutual TLS,双向 TLS)即客户端和服务器互验身份:客户端验证服务器的证书,服务器也要验证客户端的证书——可以理解为服务端也要求客户端出示"身份证"。
全量强制 mTLS 和条件化 mTLS 各有代价:
| 维度 | 全量强制 mTLS | 条件化 mTLS |
|---|---|---|
| 适用场景 | 内网服务网格、API 间互调 | 同一入口混合公网访问与内部服务 |
| 客户端体验 | 所有客户端都要装证书,浏览器/脚本接入成本高 | 仅命中条件的连接需要证书 |
| 安全边界 | 一刀切,简单清晰 | 按来源收紧,敏感链路强校验 |
| 主要代价 | 可用性受损,排障时也要先解决证书问题 | 匹配条件本身成为安全边界,需认真设计 |
选择性双向认证的价值在于把校验强度交给条件,而不是交给"全部"。
二、一次握手里发生了什么
Caddy 在 TLS 握手阶段对每个连接按顺序评估 TLS 连接策略(connection policy,源码见modules/caddytls/connpolicy.go),命中的第一条策略决定该连接是否要求客户端证书:
client_auth的mode有两个常用取值:
request:向客户端请求证书,但不强制、不校验,拿不到也能连上;require_and_verify:必须提供有效证书且能通过校验,否则握手失败(提供 trust_pool 时这也是默认值)。
三、一份能直接用的 Caddyfile 与 connection_policy 条件匹配写法
先把 CA 根证书放到 Caddy 配置目录(如/etc/caddy/caddy.ca.cer),再使用下面这份完整配置:
{ # 全局可选:日志等 } https://api.example.com { tls { client_auth { mode request trust_pool file { pem_file /etc/caddy/caddy.ca.cer } } } connection_policy { match { remote_ip 192.168.1.0/24 } client_auth { mode require_and_verify trust_pool file { pem_file /etc/caddy/caddy.ca.cer } } } respond "ok" }语义是:全站默认request(不挡任何人);来源落在192.168.1.0/24的连接命中connection_policy,升级为require_and_verify。
匹配维度速查(匹配器定义见modules/caddytls/matchers.go):
| 匹配项 | 匹配对象 | 典型用途 |
|---|---|---|
sni | 域名,支持左标签通配符 | 仅对某个域名启用 mTLS |
sni_regexp | 域名的正则表达式 | 一批子域统一命中 |
remote_ip | 客户端 IP / CIDR,!前缀表示排除 | mTLS 按 IP 认证,如仅内网段强制 |
local_ip | 服务器本地接口 IP | 区分多网卡、多监听端点 |
注意:连接策略按定义顺序匹配,靠前者优先。
四、验证矩阵:curl 按"证书 × 来源"四象限验证
| 场景 | 来源 | 是否带客户端证书 | 预期 |
|---|---|---|---|
| 1 | 内网(条件内) | 不带 | 握手失败(要求证书而未提供) |
| 2 | 内网(条件内) | 带有效证书 | 成功,返回 ok |
| 3 | 外部(条件外) | 不带 | 成功(仅 request 模式) |
| 4 | 外部(条件外) | 带 | 成功(证书收到但不强制) |
场景 1、2 在内网机器上执行(服务器证书若也是自签 CA 签发,--cacert需同时能验证服务器证书):
# 场景1:不带证书,应失败 curl -v https://api.example.com # 场景2:带客户端证书,应成功 curl --cert /etc/caddy/client.crt --key /etc/caddy/client.key \ --cacert /etc/caddy/caddy.ca.cer https://api.example.com # 场景3:从外部主机执行,不带证书应成功 curl --cacert /etc/caddy/caddy.ca.cer https://api.example.com改动配置后,先用 adapt 校验语法再生效:
caddy adapt --config /etc/caddy/Caddyfile --pretty输出 JSON 中的tls_connection_policies顺序应与 Caddyfile 中connection_policy的书写顺序一致,可借此核对"谁先匹配"。
五、踩坑清单
- CA 路径写错:
pem_file相对路径是相对 Caddyfile 所在目录解析的,不确定就用绝对路径/etc/caddy/...。 - 证书时效与系统时间:客户端证书过期、未生效或服务器时钟偏移,都会导致
require_and_verify失败;先核对时间。 - 策略顺序:多条
connection_policy按定义顺序匹配,先写的具体条件会被后写的宽泛条件"吃掉"时,调整顺序。 - 先 adapt 后 reload:
caddy adapt能提前暴露拼写与结构错误,比 reload 后看日志更快。 - IP 可伪造:
remote_ip匹配的是 TCP 层来源 IP,经过代理/隧道时可被伪装,不要把它当作唯一鉴权手段,mTLS 按 IP 认证只应作为纵深防御的一环。
六、进阶方向
- PKI 自动管理证书:用 Caddy 内置 PKI 模块签发并管理客户端证书与内部 CA,省去手工换证,让证书生命周期随配置走。
- 握手日志与失败率监控:开启 TLS 握手日志,结合访问日志统计认证失败率,异常飙升往往意味着证书过期或有人试探。
- 性能与会话复用:内部高频互调的服务启用会话复用,减少重复握手与证书校验的开销。
条件化 mTLS 把"是否出示身份证"的决定权交给了匹配条件,接下来更值得琢磨的,是你打算用哪几个维度组合这个条件——sni_regexp加remote_ip的组合,能覆盖哪些单维度覆盖不了的场景?
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考