- 后端
- API网关
- 微服务
【免费下载链接】fabio
Consul Load-Balancing made simple
fabio 是 Go 编写的 HTTP/TCP 反向代理与 Consul 负载均衡器,其proxy.cs配置项用于声明一个或多个证书源(certificate source),为监听器(listener)提供 TLS 服务器证书与客户端认证 CA 证书。本文以 docs/content/ref/proxy.cs.md 为骨架,结合仓库中 cert/ 目录的源码实现与 config/ 的解析逻辑,完整讲解六种证书源类型的配置语法、各选项语义、刷新机制与源码级原理,读者读完即可在自己的 fabio 部署中配置出可靠、可自动轮换的 TLS 证书供给链路。
证书源是什么:配置语法与基本概念
proxy.cs配置一个或多个证书源。每个证书源由一组键/值选项构成,语法如下:
cs=<name>;type=<type>;opt=arg;opt[=arg];...其中:
cs:证书源的唯一名称。监听器配置(proxy.addr、ui.addr等)通过cs=<name>引用该证书源,名称必须唯一。type:证书源类型,支持file、path、http、consul、vault、vault-pki六种。- 其余选项按类型而定,常见的有
cert、key、clientca、refresh、hdr、caupgcn等。 - 所有证书必须以PEM 格式提供。
从源码看,该配置在 config/load.go#L573-L587 中通过parseCertSources解析:先经parseKVSlice按,与;拆分成多个键值映射,再逐个调用parseCertSource(config/load.go#L589-L647)生成config.CertSource结构体(字段定义见 config/config.go#L25-L35),最后以cs名称为 key 存入 map。每个源都被要求必须提供cs与cert,且type不能为空;refresh选项通过time.ParseDuration解析成time.Duration;hdr选项按": "分隔为 HTTP 头名与值,存入http.Header。
证书源定义在 cert/source.go#L13-L23 的Source接口中,包含两个方法:
Certificates() chan []tls.Certificate:产出 TLS 证书列表。列表中的第一张证书在客户端不支持 SNI 或找不到匹配证书时作为默认证书使用;证书可在运行期更新。LoadClientCAs() (*x509.CertPool, error):提供用于客户端证书认证的 CA 证书池。
NewSource(cert/source.go#L34-L87)根据cfg.Type把配置分发到对应的 Source 实现;TLSConfig(cert/source.go#L95-L151)则构建tls.Config:设置GetCertificate回调从证书存储中按 SNI 查找证书,若未命中且源实现了Issuer接口(cert/source.go#L27-L31)则调用Issue按需签发(vault-pki 场景);若LoadClientCAs返回非空池,则设置ClientCAs并把ClientAuth置为RequireAndVerifyClientCert,实现双向 TLS(mTLS)。
file:启动时加载、进程生命周期内缓存的单证书源
file证书源只支持一张证书,在启动时加载一次并缓存至服务退出,适合证书极少变动的场景。其选项:
cert:证书文件路径。key:私钥文件路径。若证书文件同时包含证书与私钥,可省略key。clientca:一个或多个客户端认证证书的文件路径。
配置示例:
cs=<name>;type=file;cert=p/a-cert.pem;key=p/a-key.pem;clientca=p/clientAuth.pem源码实现位于 cert/file_source.go。FileSource.Certificates向带缓冲的 channel 一次性发送loadX509KeyPair(certFile, keyFile)后立即close,因此只加载一次;loadX509KeyPair(cert/file_source.go#L41-L55)在keyFile为空时把keyFile设为certFile(即证书与私钥同文件),通过tls.LoadX509KeyPair加载,失败则调用exit.Fatalf直接终止进程。注释也明确指出该源"仅用于支持遗留配置,应优先使用 PathSource"。
path:按目录自动轮换的多证书源
path证书源从一个目录中按字母序加载证书,并定期刷新。选项:
cert:TLS 证书目录路径。clientca:客户端认证证书目录路径。refresh:刷新间隔。
TLS 证书可存储为单文件或双文件两种形态:
www.example.com.pem # 单文件:证书与私钥合并在一个 PEM 中 www.example.com-{cert,key}.pem # 双文件:-cert.pem 证书、-key.pem 私钥证书按字母序加载,第一张证书作为不支持 SNI 客户端的默认证书。
refresh的默认值为3 秒,且不能低于 1 秒(防止忙循环);设为0则仅加载一次、禁用自动刷新。
配置示例:
cs=<name>;type=path;cert=path/to/certs;clientca=path/to/clientcas;refresh=3s源码实现位于 cert/path_source.go 与 cert/watch.go。PathSource.Certificates会先按PathSource.Path、CertPath与默认子目录cert拼出实际目录(见 cert/path_source.go#L35-L40 的makePath),再启动 goroutine 执行watch。watch(cert/watch.go#L11-L46)的核心逻辑:
refresh <= 0视为once模式,只加载一次;- 小于 1 秒的 refresh 被强制提升到 1 秒,正是文档所说"不能低于 1 秒"的落地实现;
- 每次调用
loadFn扫描目录,若内容与上次reflect.DeepEqual相同则休眠等待,发生变化才向 channel 推送新的证书列表。
目录扫描本身在 cert/load.go#L68-L107 的loadPath中完成:递归遍历目录,只收集扩展名为.pem且不以.开头的文件,单个文件超过MaxSize(1MB,见 cert/load.go#L20-L21)时打印警告并跳过;loadCertificates(cert/load.go#L109-L157)识别-cert.pem/-key.pem配对与单文件.pem,用tls.X509KeyPair组装证书,并按证书文件名字母序排序输出——这就是"第一张证书为默认证书"的来源。
http:从 HTTP/HTTPS 服务器拉取的多证书源
http证书源从 HTTP/HTTPS 服务器加载证书。其cert选项指向一个文本文件 URL,该文本文件列出应加载的所有证书文件名(每行一个,与 path 源同样的命名规则)。该清单可用如下命令生成:
ls -1 *.pem > listclientca选项用法与cert类似,提供客户端认证证书清单的 URL。认证凭据可以三种方式附加在 URL 上:
- 作为 URL 查询参数:
?token=123 - 作为 Basic Auth:
https://user:pass@host.com/... - 通过
hdr选项指定请求头:hdr=Authorization: Bearer 1234
refresh语义与 path 源一致:默认 3 秒、最低 1 秒、置 0 禁用刷新。文档给出的示例:
cs=<name>;type=http;cert=https://host.com/path/to/cert/list&token=123 cs=<name>;type=http;cert=https://user:pass@host.com/path/to/cert/list cs=<name>;type=http;cert=https://host.com/path/to/cert/list;hdr=Authorization: Bearer 1234源码实现位于 cert/http_source.go。HTTPSource.Certificates同样以watch循环驱动,加载函数为 cert/load.go#L23-L66 的loadURL:先抓取清单文本,再对每一行文件名执行http.Get拉取证书内容,存为map[string][]byte。值得注意的细节:hdr选项虽然由配置解析器存入config.CertSource.Header,当前loadURL使用裸http.Get发送请求,因此若你的证书服务需要自定义头,请确认使用支持该选项的分支/版本,或改用 URL 内嵌凭据的方式传递认证信息。
consul:从 Consul KV 实时同步的证书源
consul证书源从 Consul 的 KV 存储加载证书。cert选项提供 KV 存储中存放 TLS 证书的 URL;clientca提供 KV 中客户端认证证书所在路径的 URL。文件名规则与 path 源相同。
TLS 证书在KV 存储内容变化时自动更新;客户端认证证书无法自动更新(Go 目前没有提供相应机制)。
配置示例:
cs=<name>;type=consul;cert=http://localhost:8500/v1/kv/path/to/cert&token=123源码实现位于 cert/consul_source.go。parseConsulURL(cert/consul_source.go#L29-L53)解析 URL:地址与协议组成 Consul client 配置,?token=查询参数作为 ACL Token,路径必须带/v1/kv/前缀,前缀之后的剩余部分才是真正的 KV key。watchKV(cert/consul_source.go#L110-L128)利用 Consul 的 blocking query(WaitIndex,见getCerts,cert/consul_source.go#L130-L144)监听 key 变化:仅当 KV 内容或索引变化时才向 channel 推送新的 PEM 块,随后在Certificates()内部 goroutine 中经loadCertificates组装成证书列表。这也是该源能够"KV 一变即更新"的原理:相比 path/http 的轮询,Consul 源直接订阅变更事件,效率更高、延迟更低。
vault:以 HashiCorp Vault 为后端的证书源
vault证书源使用 HashiCorp Vault 作为证书存储。cert选项为 TLS 证书的 Vault 路径;clientca为客户端认证证书的路径。refresh语义与 path/http 一致(默认 3 秒、最低 1 秒、置 0 仅加载一次)。
Vault 的连接信息通过环境变量提供:
VAULT_ADDR:Vault 服务地址。VAULT_TOKEN:访问令牌。
配置示例:
cs=<name>;type=vault;cert=secret/fabio/certs源码实现位于 cert/vault_source.go。VaultSource.load(cert/vault_source.go#L44-L129)先通过isKVv2探测当前路径使用的是 KV v1 还是 KV v2 引擎(cert/vault_source.go#L136-L143,旧版 Vault 返回 404 时默认按 v1 处理),然后Logical().List列出路径下的子密钥,再逐个Read,把密钥中的cert与key值分别落盘为<name>-cert.pem与<name>-key.pem——这正是为了匹配loadCertificates的双文件命名约定。取值支持字符串与字节数组两种类型,KV v2 引擎下会自动剥掉data包装层。整个加载仍由watch循环以refresh间隔驱动。
vault-pki:按需签发、过期前自动续期的 PKI 证书源
vault-pki证书源使用 HashiCorp Vault 的PKI 后端按需签发证书,是唯一实现Issuer接口、可动态签发新证书的源。
cert:用于签发证书的 PKI 后端路径(如pki/issue/example-dot-com)。clientca:与普通 Vault 源相同的客户端认证证书路径。refresh:距证书过期多少时间前重新签发。小于 1 小时的值会被静默提升为 1 小时,1 小时也是默认值。
配置示例:
cs=<name>;type=vault-pki;cert=pki/issue/example-dot-com;refresh=24h;clientca=secret/fabio/client-certs该示例会通过 PKI 后端按需签发服务器证书,并在证书过期前 24 小时重新签发;客户端认证的 CA 预期存放在secret/fabio/client-certs。
源码实现位于 cert/vault_pki_source.go。核心是Issue(commonName)(cert/vault_pki_source.go#L57-L132):向CertPath写入{"common_name": commonName}获取新证书,把响应中的private_key、certificate与ca_chain拼接成完整链,用tls.X509KeyPair组装;随后以refresh := max(s.Refresh, time.Hour)保证续期窗口不小于 1 小时,并通过time.AfterFunc(certTTL, ...)在NotAfter - refresh时刻自动触发重签,实现"过期前自动续期"。签发完成的证书写入内部 map 并推送整个列表到 channel。
按需签发的调用入口在 cert/source.go#L108-L136 的GetCertificate回调:当证书存储中找不到匹配 SNI 的证书且错误为ErrNoCertsStored时,若源实现了Issuer,就以clientHello.ServerName为 common name 调用Issue,并用singleflight.Group保证同一主机名并发握手时只签发一次。这正是 vault-pki 区别于其他源的杀手锏:证书完全按域名动态签发,无需预置任何 PEM 文件。
通用选项:caupgcn 与配置解析规则
所有证书源都支持以下通用选项:
caupgcn: 将 common name 匹配的自签名客户端认证证书升级为 CA 证书。 典型用于 Amazon AWS Api Gateway 的自签名证书——这类证书没有设置 CA 标志位,导致 Go 中无法直接用于客户端证书认证。 对 AWS Api Gateway 请将此值设为 `ApiGateway`,以允许客户端证书认证。 该参数取代了 1.1.5 版本引入的已废弃参数 `aws.apigw.cert.cn`。配置示例:
proxy.cs = cs=some-name;type=path;path=path/to/certs;clientca=path/to/clientcas;caupgcn=ApiGateway底层实现在 cert/load.go#L209-L218 的upgradeCACertificate:当caUpgradeCN非空且与证书Issuer.CommonName相等时,将该证书的BasicConstraintsValid、IsCA置为 true 并把KeyUsage加上KeyUsageCertSign,使其在 Go 的x509.CertPool中能够作为 CA 信任锚点使用(代码注释引用 issue #108:允许 AWS Api Gateway 生成的证书用于客户端证书认证)。
在parseCertSource(config/load.go#L589-L647)中还可以看到两点隐藏规则:
type=file与type=consul的Refresh被强制置 0(file 本来就只加载一次;consul 走事件订阅无需轮询);type=path、http、vault、vault-pki保持用户设置的刷新值,默认 3 秒。
完整配置示例与默认值
以下示例覆盖全部六种类型及多证书源组合(来自文档 proxy.cs.md 的 Examples 章节):
# file 证书源 proxy.cs = cs=some-name;type=file;cert=p/a-cert.pem;key=p/a-key.pem # path 证书源 proxy.cs = cs=some-name;type=path;path=path/to/certs # HTTP 证书源 proxy.cs = cs=some-name;type=http;cert=https://user:pass@host:port/path/to/certs # Consul 证书源 proxy.cs = cs=some-name;type=consul;cert=https://host:port/v1/kv/path/to/certs?token=abc123 # Vault 证书源 proxy.cs = cs=some-name;type=vault;cert=secret/fabio/certs # Vault PKI 证书源 proxy.cs = cs=some-name;type=vault-pki;cert=pki/issue/example-dot-com # 多个证书源(反斜杠续行) proxy.cs = cs=srcA;type=path;path=path/to/certs,\ cs=srcB;type=http;cert=https://user:pass@host:port/path/to/certs # 面向 AWS Api Gateway 的 path 证书源 proxy.cs = cs=some-name;type=path;path=path/to/certs;clientca=path/to/clientcas;caupgcn=ApiGateway该配置项在 fabio 中的默认值为空:
proxy.cs =命令行注册见 config/load.go#L162:proxy.cs作为字符串参数被注册,与配置文件方式等价。配置解析的单测覆盖了全部六种类型,可参考 config/load_test.go#L154-L214(含-proxy.addr与-proxy.cs的先后顺序、strictmatch、proto=https、proto=tcp+sni等组合场景),以及 config/load_test.go#L225 中refresh=2s;hdr=a: b;caupgcn=furb的完整选项解析断言。
证书源的刷新机制对比与选型建议
综合文档与源码,六种证书源在加载与刷新策略上有明显差异,可直接作为选型依据:
| 类型 | 加载时机 | 更新方式 | 适用场景 |
|---|---|---|---|
| file | 启动时一次 | 不更新(缓存至退出) | 证书极少变动、单证书、遗留配置 |
| path | 启动时 + 轮询 | 默认 3s 轮询目录(最低 1s,0 禁用) | 本地目录存放多证书,需自动轮换 |
| http | 启动时 + 轮询 | 默认 3s 轮询清单 URL(最低 1s,0 禁用) | 证书托管在 HTTP/HTTPS 文件服务上 |
| consul | 启动时 + 事件订阅 | KV 变化即更新(blocking query) | 证书已纳入 Consul KV,追求低延迟更新 |
| vault | 启动时 + 轮询 | 默认 3s 轮询(最低 1s,0 禁用) | 证书集中存于 Vault 机密引擎 |
| vault-pki | 按需签发 | 过期前自动续期(默认提前 1h,refresh可调,低于 1h 按 1h 处理) | 证书完全动态签发,无需预置 PEM |
其中 vault-pki 的按需签发与单次签发去重(singleflight)设计、consul 的 KV 变更订阅,是 fabio 在证书管理上最有特色的能力:前者让"任何 SNI 主机名都能即时获得证书"成为可能,后者让"证书轮换秒级生效"无需重启代理。而 path/http/vault 三者的轮询刷新则通过 cert/watch.go 的watch循环统一实现,1 秒下限正是为了防止目录/接口异常时造成 CPU 忙循环。
无论选择哪种证书源,所有证书都必须以 PEM 格式提供;如果希望启用客户端证书认证(mTLS),务必配置clientca并在必要时使用caupgcn修正 AWS Api Gateway 这类未设置 CA 标志的自签名证书。完整配置方式还可结合 feature/certificate-stores.md 与 ref/proxy.addr.md(监听器如何引用cs=<name>)进一步查阅。
- 后端
- API网关
- 微服务
【免费下载链接】fabio
Consul Load-Balancing made simple
相关推荐
fabio:基于 Consul 的零配置 HTTP(S) 与 TCP 负载均衡路由器实战指南
fabio:基于 Consul 的零配置 HTTP S 与 TCP 负载均衡路由器实战指南 fabio 是一个为 Consul 管理的应用集群而设计的快速、现代
后端API网关微服务Fabio:基于 Consul 零配置自更新的 HTTP/TCP 反向代理与负载均衡器详解
Fabio:基于 Consul 零配置自更新的 HTTP/TCP 反向代理与负载均衡器详解 Fabio 是一款开源的 HTTP 与 TCP 反向代理和负载均衡器
后端API网关微服务Fabio 功能全景:Consul 负载均衡器的核心特性与配置深度解析
Fabio 功能全景:Consul 负载均衡器的核心特性与配置深度解析 Fabio 是一款以 Consul 服务注册与健康检查为核心数据源的 HTTP/TCP
后端API网关微服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考