Gin服务需自行实现mTLS并暴露HTTP健康端点供Consul检查;Consul仅负责服务发现,不参与TLS握手,其健康检查默认不支持HTTPS,故须分离业务HTTPS端口与健康HTTP端口,并显式配置tls.Config.ClientAuth为RequireAndVerifyClientCert。

Gin 服务要和 Consul 配合实现 TLS 双向认证(mTLS),关键不在 Consul 本身——Consul 不参与服务间通信的 TLS 握手,它只管服务注册、发现与健康检查;真正承担 mTLS 的是 Gin 服务自身的 HTTP server 和调用方(比如另一个微服务或网关)。所以问题本质是:Gin 怎么启用强制校验客户端证书的 HTTPS 服务,并确保 Consul 能正确上报该服务的健康状态。
为什么 Consul 注册地址不能写 HTTPS?
Consul 的 /v1/agent/service/register 接口要求填的 Address 和 Port 仅用于服务发现时构造调用地址,而它的健康检查(Check.HTTP)默认走 HTTP 协议。如果你把 Gin 服务跑在 https://:8443 上,但 Consul 健康检查仍发 HTTP GET 请求到 http://ip:8443/health,必然失败,报 connection refused 或 bad status。
- Consul 健康检查不支持直接发 HTTPS 请求(除非你用 script + curl,但会引入额外依赖和超时不可控)
- 解决方案是:为 Gin 同时暴露两个端口——一个 HTTPS(
:8443)用于真实业务通信,一个 HTTP(:8080)仅用于 Consul 健康检查 - 或者改用 TCP 检查(
Check.TCP),但无法验证服务内部逻辑是否就绪(比如证书加载失败、路由未注册等)
Gin 启动 mTLS 服务必须显式设 ClientAuth
很多人以为只要传了 cert.pem 和 key.pem 给 http.ListenAndServeTLS 就自动双向了,其实完全不是。Go 的 http.Server 默认 ClientAuth = tls.NoClientCert,哪怕你配了 ClientCAs,客户端不发证书也照常建立连接,静默降级为单向 TLS。
- 必须手动构建
*tls.Config,且ClientAuth字段只能设为tls.RequireAndVerifyClientCert -
ClientCAs必须是有效加载了 CA 根证书的*x509.CertPool,不能是空池或错误 PEM - 服务端自己的证书仍走
Certificates字段,别误塞进ClientCAs - 启动代码不能用
router.Run(":8443"),得自己 newhttp.Server并赋值TLSConfig
示例片段:
srv := &http.Server{
Addr: ":8443",
Handler: router,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: caPool,
MinVersion: tls.VersionTLS12,
},
}
log.Fatal(srv.ListenAndServeTLS("", ""))
Consul 健康检查路径必须绕过 mTLS 中间件
如果你在 Gin 中全局注册了「校验客户端证书」的中间件(比如拦截所有请求并解析 c.Request.TLS.PeerCertificates),那 Consul 的 HTTP 健康检查请求(来自内网 IP,无证书)会直接被拒绝,导致服务在 Consul 中显示为 critical。
- 健康检查路径(如
/health)必须放在 mTLS 中间件之外,或加白名单逻辑 - 推荐做法:用
router.Group("/healthz")单独挂载,不套任何中间件 - 避免在
/health返回敏感信息;Consul 只需要 200 状态码即可判定存活 - 如果坚持用 HTTPS 健康检查,就得让 Consul client 自带客户端证书——这在大多数部署中不现实,也不符合职责分离原则
客户端调用 Gin 服务时证书权限和链顺序不能错
即使 Gin 服务端配置全对,调用方(比如另一个 Go 微服务)若没按规范加载证书,照样握手失败,常见报错:x509: certificate signed by unknown authority 或 remote error: tls: bad certificate。
-
client.crt文件必须包含完整证书链:leaf 证书在最前,中间 CA 在后;root CA 单独交给RootCAs -
client.key文件权限必须是0600,否则tls.LoadX509KeyPair静默返回空tls.Certificate -
http.Transport.TLSClientConfig.RootCAs必须填服务端 CA 的根证书池,不是服务端的公钥证书 - 若用域名访问(如
https://order-service),必须设置ServerName字段,否则因 SAN 不匹配失败
容易被忽略的一点:Consul 本身不转发证书,也不修改 TLS 层;它只告诉调用方“order-service 在 10.1.2.3:8443”,剩下的证书加载、验证、握手,全由调用方自己负责——这个责任边界一旦模糊,排障就会陷入“不知道该查 Consul 还是查 Gin”的混乱。


















