Metabase 测试 SSL 证书实战:用 cfssl 生成本地 CA 与服务器证书并驱动 Driver 测试管线
【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase
test_resources/ssl/是 Metabase 仓库中一套为“把服务当作 localhost 运行”而准备的测试证书资产,涵盖 CA 与服务器证书的生成方法(基于 cfssl)、各文件的用途说明,以及这些证书如何被 driver 层的 SSL 工具函数和单元测试消费。读完本文,你可以完整复现这套证书的重建流程,理解 CA、服务器证书与 SAN hostname 的对应关系,并看清这些测试资产在 Metabase 后端测试管线中的真实调用链路与再生成证书时的注意事项。
一、这套测试证书的定位与文件清单
Metabase 需要连接支持 SSL/TLS 的数据库(Mongo、Oracle 等),其 driver 测试管线必须持有可用的密钥与证书文件。test_resources/ssl/顶层目录提供了一组本地测试证书,目录说明 明确其用途是“running a service as localhost”。目录内各文件的职责如下:
| 文件 | 用途 |
|---|---|
| ca-csr.json | 生成 CA 所用的 CSR 配置 |
| ca-key.pem | CA 的私钥 |
| ca.pem | CA 的公钥证书(即客户端需要“信任”的部分) |
| server-csr.json | 生成服务器密钥所用的 CSR 配置 |
| server.key | serverd服务器证书的私钥 |
| server.pem | server.key对应的公钥证书 |
| ca.csr | 生成过程产出的 CSR 中间文件 |
除顶层文件外,该目录还包含两个面向具体数据库驱动的证书子目录:mongo/ 与 oracle/,后者会在第四部分展开。
二、用 cfssl 生成 CA 与服务器证书
这套证书由 Cloudflare 开源的证书工具 cfssl 生成。原 README 给出的两条命令是完整的最小生成流程:
1. 生成 CA(自签名根证书)
cfssl genkey -initca ca-csr.json | cfssljson -bare -stdoutcfssl genkey -initca读取 CSR 配置并生成“初始 CA”:产出 CA 的 CSR 与私钥,并由该 CA 自签名出根证书;- 管道后接
cfssljson -bare -stdout将 cfssl 的输出 JSON 拆解为裸文件(-bare会去掉文件名中的随机后缀),直接输出到标准输出。
ca-csr.json 的内容非常简洁,关键参数如下:
{ "hosts": [ "127.0.0.1" ], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "Seattle", "O": "Someone", "OU": "WWW", "ST": "Washington" } ] }hosts指定了证书 subjectAltName 覆盖127.0.0.1,这是本地服务回环地址校验的基础;key指定 RSA 2048 位密钥;names中的C/L/O/OU/ST组成证书 DN(Distinguished Name),即ou=www,o=someone,l=seattle,st=washington,c=us——这个 DN 后面会出现在单元测试的断言里。
2. 生成服务器证书(由该 CA 签发)
cfssl gencert -ca ca.pem -ca-key ca-key.pem -hostname=127.0.0.1,localhost server-csr.json | cfssljson -bare -stdout-ca ca.pem -ca-key ca-key.pem指定用刚生成的 CA 证书与 CA 私钥来签发服务器证书,形成“CA → 服务器证书”的信任链;-hostname=127.0.0.1,localhost将两个主机名写入 SAN,使同一张证书既能匹配127.0.0.1又能匹配localhost,与“running a service as localhost”的用途直接对应;- server-csr.json 中额外声明了
"CN": "server.local",且hosts同时包含127.0.0.1与localhost,与命令行-hostname参数保持一致。
cfssljson -bare的输出命名以输入文件名(去掉扩展名)为前缀:CA 流程产出ca.csr、ca-key.pem、ca.pem,服务器流程产出server.csr、server.key、server.pem,与目录中实际存在的文件一一对应。
三、这些证书在 Metabase 源码中的真实消费链路
证书生成只是第一步,更关键的是它们在测试管线里如何被消费。
1. 单元测试如何读取这些文件
test/metabase/driver/util_test.clj 中的generate-trust-store-test直接以相对路径读取本目录的两个证书:
(let [cert-string (slurp "./test_resources/ssl/ca.pem") keystore (driver.u/generate-trust-store cert-string)] (is (true? (.containsAlias keystore test-ca-dn))))该测试还覆盖:
- 传入非法证书字符串
"fooobar"时必须抛出java.security.cert.CertificateException; - 将
ca.pem与server.pem拼接成多证书字符串后,信任库应同时包含 CA 与服务器证书两个别名; - 用
ca.pem构造ssl-socket-factory应返回javax.net.ssl.SSLSocketFactory实例。
值得注意的是测试文件顶部的两个常量与注释(util_test.clj):
;; if the CA certificate (ca.pem) used in this test is regenerated, ;; you'll need to update this DN (def ^:private test-ca-dn "ou=www,o=someone,l=seattle,st=washington,c=us") ;; if the server certificate (server.pem) used in this test is regenerated, ;; you'll need to update this DN (def ^:private test-server-dn "cn=server.local,ou=www,o=someone,l=seattle,st=washington,c=us")这印证了第二部分的 CSR 配置与测试断言之间的强耦合:证书别名取自证书的 DN 字符串,若按 ca-csr.json / server-csr.json 原样重新生成,这两个 DN 常量可保持不变;一旦修改names或CN,就必须同步更新这两个常量,否则断言失败。
2. driver 层 SSL 工具的实现原理
上述测试调用的工具函数定义在 src/metabase/driver/util.clj,其“TLS Helpers”部分构成一条清晰的调用链:
dn-for-cert:通过getSubjectX500Principal getName提取证书 DN,作为 KeyStore 条目的别名来源;parse-certificates:用CertificateFactory/X.509从 PEM 字符串解析出一组证书——这正是generate-trust-store支持“多证书拼接字符串”的原因;generate-identity-store:把 PEM 私钥(parse-rsa-key做 PKCS8 解码)与证书链装入KeyStore,以证书 DN 为别名写入setKeyEntry,用于“身份证明”(client 侧);generate-trust-store:除了把传入的自定义证书逐一setCertificateEntry之外,还会初始化一个TrustManagerFactory,把 JVM 默认信任库中全部getAcceptedIssuers也克隆进新的 KeyStore——即“内置 CA + 自定义 CA”合并信任;ssl-context/ssl-socket-factory:组装KeyManager(需要private-key+own-cert)与TrustManager(需要trust-cert),最终产出SocketFactory。
从源码结构看,test_resources/ssl/顶层证书主要覆盖“仅信任”这一路径(trust-cert分支),而完整的双向身份验证路径则由ssl-socket-factory-test(util_test.clj)用 mongo/ 目录下的metabase.key、metabase.crt、metaca.crt来验证,例如仅凭信任信息、仅凭身份信息两种组合都应能成功构造SSLSocketFactory。
四、面向具体数据库的证书子目录
1. mongo/:Mongo SSL 测试客户端证书
mongo/README.md 说明该目录支撑metabase-qa的 Mongo docker 服务(支持 plain、ssl、tls 不同 SSL 配置),run-server.sh 用于拉起测试服务器。其要点是:docker 镜像是“source of truth”,容器启动时会把后端测试所需的客户端密钥与客户端/CA 证书拷贝进该目录;但客户端证书仍然签入仓库,因为ssl-socket-factory等测试必须在 CI 中拿到有效证书与密钥。文档同时给出了对 TLS Mongo 跑测试的完整命令:
MB_MONGO_TEST_USER=metabase MB_MONGO_TEST_PASSWORD=metasample123 MB_TEST_MONGO_REQUIRES_SSL=1 DRIVERS=mongo clj -X:dev:ci:ee:ee-dev:drivers:drivers-dev:test这与第三部分ssl-socket-factory-test中读取的ssl/mongo/metabase.key、metabase.crt、metaca.crt资源路径相互印证(测试中客户端密钥口令为"passw")。
2. oracle/:PKCS12 格式的 keystore 与 truststore
oracle/README.md 说明 keystore.p12 与 truststore.p12 是 CI 中测试 Oracle driver SSL 认证所用的 PKCS12 keystore:
keystore.p12包含CN=metabase,C=US的私钥与公钥,以及由 docker 镜像内 CA 签发的证书;truststore.p12包含 docker 镜像内自签名 CA 的证书。
这两个 keystore 与metabase/qa-databases:oracle-xe-21.3测试镜像配套,整体配置遵循 Oracle JDBC thin 客户端的 SSL 方案。与顶层目录的 PEM 文件相比,这里体现的是 Java 生态常见的 PKCS12 二进制 keystore 形态,对应 driver 连接配置中 keystore 类型的信任/身份材料。
五、实践要点与适用边界
- 复现生成:只需安装 cfssl,按“第二部分”两条命令在 test_resources/ssl/ 下执行即可重建
ca.*与server.*文件;-hostname参数决定 SAN 覆盖范围,本地服务场景应保持127.0.0.1,localhost不变。 - 再生成的同步义务:修改 ca-csr.json 或 server-csr.json 的
names/CN后,util_test.clj 中test-ca-dn、test-server-dn两个 DN 常量必须同步更新,这是测试注释中明示的约束。 - 信任库语义:
generate-trust-store并非“只信传入的 CA”,而是自定义证书叠加 JVM 默认信任(见 util.clj),理解这一点有助于判断哪些自签名场景会被该测试信任链接受。 - 资产边界:这些文件全部服务于测试管线(本地 localhost 服务、Mongo/Oracle QA 镜像),私钥以明文 PEM 形式存放且被测试硬编码引用(如口令
"passw"),不应在任何生产环境复用;生产连接应使用正规 CA 签发的证书,并通过 Metabase 数据库连接配置中的 SSL/keystore 选项提供。
【免费下载链接】metabaseThe easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart:项目地址: https://gitcode.com/GitHub_Trending/me/metabase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考