3分钟配置Caddy选择性mTLS认证:内网强制双向认证,公网照常访问
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
内部服务既要防住未授权的客户端,又不能拦掉正常用户,怎么办?用 Caddy(多平台 Web 服务器,内置自动 HTTPS)做选择性 mTLS,只让"该来的"交客户端证书。
- 理解 mTLS 和"选择性"认证的差别
- 会写 Caddy 的 client_auth 与 trust_pool 配置
- 学会按 IP / SNI 区分认证强度
- 拿到一套可直接跑的验证命令
原理拆解
普通 HTTPS 只验服务器,客户端可以随便来;mTLS(Mutual TLS,双向 TLS)反过来再验一次客户端。Caddy 的做法是在 TLS 握手的 ClientHello 阶段做判断:拿域名(SNI)或客户端 IP 去匹配一组连接策略,命中哪条,就按哪条的认证强度办事。好比小区门禁:员工刷卡进内区,访客登记即可进大堂,同一道闸、两把规则。
动手实践
先跑通:把 CA 放进信任池,只询问不拦截
给 Caddy 一份"可信签发者名单"(信任池),再开最弱的request模式:询问客户端证书,但没有也不拦。用这个模式先验证链路通不通,不会把真实用户挡在门外。
# Caddyfile —— 先确认文件放在配置目录,如 /etc/caddy/Caddyfile { local_certs } localhost:8443 { tls a.caddy.localhost.crt a.caddy.localhost.key tls { client_auth { mode request # 只询问证书,没有也放行 trust_pool file { pem_file caddy.ca.cer # 你的客户端 CA 根证书 } } } respond "hello, verified or not" }测试仓库里就有一份现成的 CA 和服务器证书(caddytest/caddy.ca.cer、caddytest/a.caddy.localhost.crt)。改完跑caddy reload --config /etc/caddy/Caddyfile,日志不报 trust_pool 相关错误、用 curl 能收到响应,说明信任池加载成功。
收严规则:只让内网 IP 交出证书
要按条件区分认证强度,就写多条 connection_policy(连接策略),靠前的先匹配。下面第一条把内网段设为"必须交证书且验签",第二条兜底,其余 IP 保持询问模式。
localhost:8443 { tls a.caddy.localhost.crt a.caddy.localhost.key # 内网段:强制 mTLS(必须先匹配,否则被兜底策略吃掉) connection_policy { match { remote_ip 192.168.0.0/16 } client_auth { mode require_and_verify # 无证书直接拒绝握手 trust_pool file { pem_file caddy.ca.cer } } } # 兜底策略:其余来源只询问 connection_policy { client_auth { mode request trust_pool file { pem_file caddy.ca.cer } } } respond "policy matched" }跑起来后用两条命令各测一次(客户端证书需由 caddy.ca.cer 签发):
# 不带证书,模拟内网访问 —— 应被拒绝 curl -k https://localhost:8443 # 带上 CA 签发的客户端证书 —— 应返回 "policy matched" curl -k --cert a.caddy.localhost.crt --key a.caddy.localhost.key https://localhost:8443换个维度:按 SNI 域名匹配
不想按 IP 分,就换 SNI(客户端在握手时申报的域名)。握手匹配器一共四个:sni精确或左通配符匹配、sni_regexp正则、remote_ip、local_ip。例如只对admin.example.com强制认证:
connection_policy { match { sni admin.example.com } client_auth { mode require_and_verify trust_pool file { pem_file caddy.ca.cer } } }caddy adapt --config /etc/caddy/Caddyfile --pretty能展开出完整的 JSON 配置。检查tls_connection_policies数组里 match 字段是否写进了你的 sni 或 remote_ip,确认无误再 reload。
对照测试用例核对写法
不确定语法时,可以直接看仓库里的适配测试,左边是 Caddyfile、右边是期望的 JSON。比如 tls_client_auth_cert_file.caddyfiletest 演示了 file 信任池的完整展开结果,照着它检查你的 adapt 输出,基本不会写错。
进阶玩法
- 信任池来源不止文件:capools.go 提供
inline(配置内嵌)、system(系统根证书)、folder(目录批量加载)等 provider,轮换 CA 时换文件夹即可。 - 加一层吊销校验:
client_auth里可加verifier leaf模块做额外校验,避免"证书有效但已被吊销"的漏网情况。 - 策略命中顺序速查:Caddy 按声明顺序取第一条命中的策略,所以把最严的放最前。
排错速查
| 现象 | 原因 | 处理 |
|---|---|---|
| 不带证书的请求没被拒 | 兜底策略用了request,或严格策略写在了后面 | 检查策略声明顺序,把require_and_verify那条放最前 |
握手直接报bad certificate | 客户端证书不是信任池里 CA 签的 | 确认pem_file指向的正是签发该证书的那份根证书 |
adapt 报unknown subdirective | 子指令拼错(如写成trust_pool_file) | 只能是mode/trust_pool/verifier,见 connpolicy.go 注释 |
| pem_file 报错 | 文件里没有 CERTIFICATE 块或路径相对配置目录解析不到 | 用绝对路径,openssl x509 -in caddy.ca.cer -noout验证文件可读 |
| 条件匹配不生效 | 写了多条tls {}块却没写connection_policy | 条件必须配在match {}里,纯tls {}块对所有连接生效 |
到这里,你的 Caddy 已经做到"内网必验、公网放行"的选择性 mTLS。觉得有用的话收藏备用,配置里踩过的坑也欢迎留言交流。
【免费下载链接】caddyFast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS项目地址: https://gitcode.com/GitHub_Trending/ca/caddy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考