Caddy 选择性 mTLS 实战:同一站点,内网强制客户端证书,外网照常访问
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
你运营一个网站,既要给普通用户开普通 HTTPS,又要让内部运维工具走 mTLS(双向 TLS,握手时服务器和客户端互相出示证书)来防伪造。全局开 mTLS 最简单,但普通用户的浏览器没有证书,直接全军覆没。Caddy 的 TLS 连接策略正好解决这件事:client_auth指令可以配合 SNI 域名、客户端 IP 等条件,只对"命中条件的连接"索取并校验客户端证书,其余连接不受影响。
原理速览
Caddy 默认不向客户端要任何证书。打开开关的是tls指令里的client_auth,它有两种放法:直接写在tls块里(对本站点所有连接生效),或放进connection_policy里并挂上match条件(只对匹配的连接生效)。多条连接策略按定义顺序首个命中生效,所以"选择性"本质就是"条件策略 + 未命中即默认不验证书"。具体实现见 modules/caddytls/ 目录。
前置条件 📋
- Caddy 2.x(建议 2.8+),用
caddy version确认 - 你自己的 CA 根证书(PEM 格式),例如放到
/etc/caddy/caddy.ca.cer - 由该 CA 签发的客户端证书与私钥,如
client.crt/client.key - 本机装有 curl,用于模拟不同客户端做握手测试
# 快速确认环境 caddy version openssl verify -CAfile /etc/caddy/caddy.ca.cer client.crt # 应输出 client.crt: OK配置实战
最小可用配置
先让功能跑起来:服务器只接受"带证书且验过"的连接。把client_auth直接写进tls块,对本站点全部连接生效:
https://internal.example.com { tls /etc/caddy/server.crt /etc/caddy/server.key { # 显式指定服务器证书 client_auth { mode require_and_verify # 无有效证书直接拒绝 trust_pool file { pem_file /etc/caddy/caddy.ca.cer # 信任池:你自己的 CA } } } respond "Hello from mTLS" }mode有四个档位:request(要证书但不验)、require(要但不验真伪)、verify_if_given(给了就验,不给也放行)、require_and_verify(给了且必须验过)。配了trust_pool不写mode时,默认就是require_and_verify。
按条件精细控制
同一个域名既要服务内网又要对外时,把要求挪进connection_policy并加match条件。下面只对办公网段强制证书,外部流量不命中任何策略,照常握手:
https://example.com { tls { connection_policy { match { # 也可以换成 sni internal.example.com(按域名)或 sni_regexp(正则) remote_ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 } client_auth { mode require_and_verify trust_pool file { pem_file /etc/caddy/caddy.ca.cer } } } } respond "Hello" }策略按定义顺序匹配、首个命中生效,所以最具体的规则放最上面。match里还支持local_ip(按服务器本地接口 IP)和sni_regexp(正则匹配域名),解析逻辑在 modules/caddytls/matchers.go。
效果验证 🧪
先跑caddy adapt --config Caddyfile --pretty,确认语法无误并检查生成的 JSON 里client_authentication字段位置是否符合预期。然后分别模拟不同客户端:
| 场景 | 命令 | 预期结果 |
|---|---|---|
| 无客户端证书(全局 mTLS 站点) | curl -v --cacert caddy.ca.cer https://internal.example.com | 握手被拒,提示需要客户端证书 |
| 携带有效证书 | curl --cert client.crt --key client.key --cacert caddy.ca.cer https://internal.example.com | 返回Hello from mTLS |
| 外部 IP 访问(connection_policy 站点) | curl https://example.com | 正常返回,不要求证书 |
| 内网 IP 访问(connection_policy 站点) | 同上一条不带--cert | 握手被拒;带上证书则成功 |
排障清单 ⚠️
- 现象:握手直接失败,日志报证书校验错误→ 常见原因:客户端证书不是该 CA 签的,或
trust_pool路径写错 → 处理:先用openssl verify在本地确认证书链,再核对pem_file路径。 - 现象:所有访客都要证书,但只想限制一部分→ 常见原因:
client_auth写在了tls块外层,等于全局开启 → 处理:移入connection_policy并补上match条件。 - 现象:配置的策略不生效→ 常见原因:连接策略首个命中生效,前面的策略已匹配走该连接 → 处理:把更具体的策略挪到更靠前的位置。
- 现象:adapt 报 client auth mode not recognized→ 常见原因:
mode值拼错 → 处理:只能是request/require/verify_if_given/require_and_verify四选一。
更多解析细节可参考 modules/caddytls/connpolicy.go,测试用例 caddytest/integration/caddyfile_adapt/tls_client_auth_cert_file.caddyfiletest 里有一份最小可运行示例。
快速参考 ⚡
| 配置项 | 作用 | 默认值 |
|---|---|---|
client_auth | 开启客户端证书认证 | 未配置则不索取证书 |
mode | 认证强度(四个档位) | 配了信任池为require_and_verify,否则require |
trust_pool | 指定可信 CA 来源(file/inline) | 无 |
match | 策略命中条件(remote_ip/sni/sni_regexp/local_ip) | 不写则策略匹配全部连接 |
connection_policy | 承载条件策略,首个命中生效 | 无 |
下一步建议:证书验证通过还不够的,在 HTTP 路由里用{tls.remote_cert.*}占位符读取客户端证书主体,按身份做二次路由;需要吊销或白名单能力时,给client_auth追加verifier leaf模块,对叶子证书做额外校验。
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考