Databend 测试 TLS 证书体系全解析:基于 cfssl + OpenSSL 的证书生成、轮换与集成
【免费下载链接】databendData Agent Ready Warehouse : One for Analytics, Search, AI, Python Sandbox. — rebuilt from scratch. Unified architecture on your S3.项目地址: https://gitcode.com/GitHub_Trending/da/databend
导读
Databend 的 TLS 相关能力(MySQL 协议加密、HTTP Handler TLS、Admin API TLS、Flight RPC 加密等)都需要在测试环境中验证,而一套可重复生成、可轮换的测试证书是这一切的基础。本文以仓库中的 tests/certs/tls/README.md 为骨架,结合cfssl签发配置、gencert.sh生成脚本以及 Rust 集成测试中的实际引用,完整讲解 Databend 测试证书体系的目录结构、一键重建流程、X.509 证书细节解析,以及证书过期后的刷新方法。读完本文,你将掌握这套证书从生成、解读到落地测试的完整链路。
测试证书的用途与设计思路
Databend 在开发和 CI 阶段需要对以下 TLS 场景做端到端验证:
- RPC(gRPC)通道加密:query 与 meta 服务之间的通信;
- MySQL 协议 TLS:
mysql客户端以--ssl-mode=REQUIRED连接 query 服务; - HTTP Handler TLS:REST API 走 HTTPS;
- Admin API TLS:管理端口启用双向认证;
- Flight SQL TLS:Flight SQL 协议加密。
由于这些能力无法在无证书环境下测试,仓库维护了一套专用的测试证书。根据 tests/certs/tls/README.md,证书存放在cfssl子目录下,全部由 cfssl 工具链签发,而非手工openssl req生成——这样可以保证每次重建时密钥强度、扩展字段、SAN 等属性完全一致,且整个过程可脚本化、可审计。
仓库中实际存在两套测试证书,注意区分:
tests/certs/根目录下的ca.pem、server.pem、server.key:供 shell 级 CI 脚本(如ci-run-stateless-tests-cluster-tls.sh)使用;tests/certs/tls/cfssl/下的完整 CA + server + client 证书体系:供 Rust 集成测试使用,也是本文 README 对应的主体。
证书体系布局:目录与文件清单
先看cfssl目录的完整结构:
tests/certs/tls/cfssl/ ├── ca/ │ ├── ca-config.json # 签发策略与 profile 定义 │ ├── ca-csr.json # CA 根证书签名请求描述 │ ├── ca-key.pem # CA 私钥(PKCS#8 格式为 pkcs8-ca-key.pem) │ ├── ca.csr │ ├── ca.pem # CA 根证书(测试环境的信任锚) │ └── pkcs8-ca-key.pem ├── server/ │ ├── server.json # server 证书请求描述(含 SAN) │ ├── server-key.pem # server 私钥 │ ├── server.csr │ ├── server.pem # server 证书 │ └── pkcs8-server-key.pem # PKCS#8 格式私钥(Rust 测试实际加载的) ├── client/ │ ├── client.json # client 证书请求描述 │ ├── client-identity.pfx # PKCS#12 客户端身份文件(密码 databend) │ ├── client-key.pem │ ├── client.csr │ ├── client.pem # client 证书 │ └── pkcs8-client-key.pem └── gencert.sh # 一键生成/清理脚本其中ca/、server/、client/分别代表证书链的三层角色:根 CA(自签信任锚)、服务端证书(Databend Server,含 SAN 与 server auth 用途)、客户端证书(Databend Client,用于双向 TLS 认证)。
一键重建证书:gencert.sh 生成流程
README 中给出的一句话刷新命令为:
./tests/certs/tls/cfssl/gencert.sh generate_keys该脚本完整实现在 tests/certs/tls/cfssl/gencert.sh。脚本开头通过need_cmd/check_cmd两个辅助函数校验环境中必须存在cfssl与openssl,缺失任一命令都会以 "need 'xxx' (command not found)" 退出,避免生成到一半才发现环境不完整:
need_cmd cfssl need_cmd openssl随后通过"$@"把命令行传入的函数名派发执行,因此还支持清理子命令:
./tests/certs/tls/cfssl/gencert.sh clean_tmp_keysgenerate_keys 的四个阶段
阶段一:初始化 CA(根证书)
(cd ca; cfssl gencert -initca ca-csr.json | cfssljson -bare ca -)在ca/目录内用cfssl gencert -initca读取 ca-csr.json,生成 CA 私钥、CSR 与自签根证书(ca.pem)。
阶段二:签发 server 与 client 证书
(cd server; cfssl gencert -ca=../ca/ca.pem -ca-key=../ca/ca-key.pem -config=../ca/ca-config.json -profile=server server.json | cfssljson -bare server) (cd client; cfssl gencert -ca=../ca/ca.pem -ca-key=../ca/ca-key.pem -config=../ca/ca-config.json -profile=client client.json | cfssljson -bare client)这里的关键在于-profile=server/-profile=client参数——它决定了使用 ca-config.json 中哪个 profile 的扩展策略(详见下一节)。
阶段三:私钥格式转换为 PKCS#8
(cd ca; openssl pkcs8 -topk8 -nocrypt -in ca-key.pem -out pkcs8-ca-key.pem) (cd client; openssl pkcs8 -topk8 -nocrypt -in client-key.pem -out pkcs8-client-key.pem) (cd server; openssl pkcs8 -topk8 -nocrypt -in server-key.pem -out pkcs8-server-key.pem)cfssl默认输出 PKCS#1 格式的 RSA 私钥,而 Databend 的 Rust 侧 TLS 加载代码(基于rustls/tokio-rustls)通常要求 PKCS#8 编码。因此脚本用openssl pkcs8 -topk8 -nocrypt将三份私钥统一转换为无加密的 PKCS#8形式,这正是tls_constants.rs中加载pkcs8-server-key.pem、pkcs8-client-key.pem的原因。
阶段四:生成 PKCS#12 客户端身份文件
(cd client; openssl pkcs12 -export -out client-identity.pfx -inkey pkcs8-client-key.pem -in client.pem -certfile ../ca/ca.pem -password pass:databend)将客户端私钥、客户端证书与 CA 根证书打包为 PKCS#12 文件client-identity.pfx,密码固定为databend。该文件可用于需要在单一文件中携带完整身份的 TLS 客户端场景(如某些驱动或工具链)。
clean_tmp_keys 的清理逻辑
(cd ca; rm -rf *.pem; rm -rf *.csr) (cd server; rm -rf *.pem; rm -rf *.csr) (cd client; rm -rf *.pem; rm -rf *.csr; rm -r *.pfx)该子命令会删除三个子目录下所有生成的.pem、.csr以及 client 目录的.pfx,用于在重建前彻底清空旧证书,防止同名文件覆盖带来混淆。
cfssl 签发配置逐项解读
CA 根证书请求:ca-csr.json
tests/certs/tls/cfssl/ca/ca-csr.json 定义根 CA 的自身属性:
{ "CN": "Databend", "CA": { "expiry": "87600h" }, "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "CA", "O": "Datafuselabs", "ST": "LA", "OU": "Databend" } ] }要点:
- CN = Databend,
CA.expiry = 87600h(即 10 年),保证根 CA 长期有效,不必频繁轮换信任锚; - 密钥算法为RSA 2048;
names定义了签发者(Issuer)DN:C=US, ST=LA, L=CA, O=Datafuselabs, OU=Databend, CN=Databend,这与 README 中openssl x509输出的 Issuer 字段完全吻合,可以作为"证书确实由该 CA 签发"的验证依据。
签发策略:ca-config.json
tests/certs/tls/cfssl/ca/ca-config.json 定义了两套 profile:
{ "signing": { "default": { "expiry": "8760h" }, "profiles": { "server": { "expiry": "8760h", "usages": ["signing", "key encipherment", "server auth", "client auth"] }, "client": { "expiry": "8760h", "usages": ["signing", "key encipherment", "client auth"] } } } }关键设计:
- 默认与 profile 的有效期均为 8760h(1 年),这意味着每年必须刷新一次叶子证书——README 中"下次刷新时间 2024-09-14"正是由此推算;
- server profile同时包含
server auth与client auth两种用途,说明该证书既可作服务端证书,也能在双向 TLS 中充当客户端身份,适配 Databend 多场景复用的需要; - client profile仅含
client auth,是纯客户端认证证书。
服务端证书请求:server.json
tests/certs/tls/cfssl/server/server.json 中hosts字段是 SAN(Subject Alternative Name)的来源:
{ "CN": "Databend Server", "hosts": ["127.0.0.1", "localhost", "0.0.0.0", "::1"], "key": { "algo": "rsa", "size": 2048 }, "names": [{ "C": "US", "ST": "CA", "L": "San Francisco" }] }SAN 覆盖了测试环境可能出现的全部连接地址:IPv4 回环127.0.0.1、主机名localhost、通配监听地址0.0.0.0以及 IPv6 回环::1。这样无论测试以何种方式连接本机服务,主机名校验都能通过。
客户端证书请求:client.json
tests/certs/tls/cfssl/client/client.json 的hosts为空数组,因为客户端证书无需绑定任何主机名,只作为身份凭证参与双向认证。
深入解析 server 证书的 X.509 细节
README 提供了核对证书真实属性的标准命令:
openssl x509 -noout -text -in ./tests/certs/tls/cfssl/server/server.pem结合 README 中记录的输出,可以从 X.509 角度解读这张证书的每个关键字段:
| 字段 | 值 | 说明 |
|---|---|---|
| Version | 3 (0x2) | X.509 v3,支持扩展字段 |
| 签名算法 | sha256WithRSAEncryption | SHA-256 摘要 + RSA 签名 |
| Issuer | C=US, ST=LA, L=CA, O=Datafuselabs, OU=Databend, CN=Databend | 与 ca-csr.json 的 names 一致 |
| Subject | C=US, ST=CA, L=San Francisco, CN=Databend Server | 来自 server.json 的 names |
| 有效期 | Not Before 2023-09-15,Not After 2024-09-14 | 对应 ca-config.json 中 8760h 的 expiry |
| 公钥 | RSA 2048 bit,Exponent 65537 | 与 server.json 的 key 配置一致 |
| Key Usage(critical) | Digital Signature, Key Encipherment | 用于签名与密钥加密 |
| Extended Key Usage | TLS Web Server Authentication, TLS Web Client Authentication | 对应 server profile 的server auth+client auth |
| Basic Constraints(critical) | CA: FALSE | 明确该证书不是 CA,防止被滥用为签发者 |
| Subject Key Identifier | 26:B7:1C:6F:DD:34:86:82:37:4C:BC:8C:B3:DE:AE:E2:A1:80:D5:56 | 证书自身公钥的指纹标识 |
| Authority Key Identifier | keyid:12:1C:A8:35:73:6D:44:3C:60:44:15:EB:7D:69:28:72:D8:22:D7:F8 | 指向签发它的 CA 公钥 |
| Subject Alternative Name | DNS:localhost, IP:127.0.0.1, IP:0.0.0.0, IP:0:0:0:0:0:0:0:1 | 即 server.json 中hosts字段的展开 |
其中IP Address:0:0:0:0:0:0:0:1是 IPv6 地址::1的完整展开形式。值得注意的是,README 记录的证书有效期止于2024-09-14,属于"已过期"的测试证书——这正是 README 提示需要在到达Sep 14 09:09:00 2024 GMT之前执行gencert.sh generate_keys完成轮换的原因。测试证书过期本身不影响代码测试的"完整性"逻辑,但在涉及 TLS 握手严格校验(校验证书当前有效性)的测试场景中必须刷新后重跑。
证书在测试与 CI 中的真实用法
Rust 集成测试的常量定义
证书路径被统一收口在 src/query/service/tests/it/tests/tls_constants.rs:
pub const TEST_TLS_CA_CERT: &str = "../../../tests/certs/tls/cfssl/ca/ca.pem"; pub const TEST_TLS_SERVER_CERT: &str = "../../../tests/certs/tls/cfssl/server/server.pem"; pub const TEST_TLS_SERVER_KEY: &str = "../../../tests/certs/tls/cfssl/server/pkcs8-server-key.pem"; pub const TEST_TLS_CLIENT_CERT: &str = "../../../tests/certs/tls/cfssl/client/client.pem"; pub const TEST_TLS_CLIENT_KEY: &str = "../../../tests/certs/tls/cfssl/client/pkcs8-client-key.pem"; // pub const TEST_TLS_CLIENT_IDENTITY: &str = // "../../../tests/certs/tls/cfssl/client/client-identity.pfx";可以观察到两个实现细节:
- 加载的私钥均为 PKCS#8 格式(
pkcs8-server-key.pem/pkcs8-client-key.pem),印证了gencert.sh中第三步格式转换的必要性; client-identity.pfx的常量被注释掉,从源码结构看,PKCS#12 身份文件当前未在 Rust 测试中直接使用,但它已随生成脚本一并产出,为需要完整身份文件的场景预留了能力。
测试用例如何消费证书
在 src/query/service/tests/it/servers/admin/admin_service.rs 中,Admin API 的 TLS 测试把TEST_TLS_SERVER_CERT配置为服务端证书、TEST_TLS_CA_CERT作为根 CA,并用客户端证书发起双向 TLS 请求:
.api_tls_server_cert(TEST_TLS_SERVER_CERT) .api_tls_server_root_ca_cert(TEST_TLS_CA_CERT)类似的用法也出现在 http_query_handlers.rs 的 HTTP Handler TLS 测试中(http_handler_tls_server_cert/http_handler_tls_server_root_ca_cert)。这些测试通过真实证书完成 TLS 握手,验证了证书体系在代码路径上的端到端可用性。
Shell 级 CI 中的另一套证书
与cfssl体系并行,tests/certs/根目录下还有一套server.pem/server.key/ca.pem,被 scripts/ci/ci-run-stateless-tests-cluster-tls.sh 通过环境变量注入集群测试:
export RPC_TLS_SERVER_CERT="./tests/certs/server.pem" export RPC_TLS_SERVER_KEY="./tests/certs/server.key" export RPC_TLS_QUERY_SERVER_ROOT_CA_CERT="./tests/certs/ca.pem" export RPC_TLS_QUERY_SERVICE_DOMAIN_NAME="localhost" export RPC_TLS_STORE_SERVER_ROOT_CA_CERT="./tests/certs/ca.pem" export RPC_TLS_STORE_SERVICE_DOMAIN_NAME="localhost" export QUERY_MYSQL_TLS_SERVER_CERT="./tests/certs/server.pem" export QUERY_MYSQL_TLS_SERVER_KEY="./tests/certs/server.key" export MYSQL_CLIENT_TLS_OPTS="--ssl-mode=REQUIRED"该脚本对应 query 侧 src/query/config/src/config.rs 中rpc_tls_server_key、mysql_tls_server_key、http_handler_tls_server_key、flight_sql_tls_server_key、api_tls_server_key等配置项。从源码结构看,tests/certs/这套证书服务于外部进程级 CI,而tests/certs/tls/cfssl/这套服务 Rust 单元/集成测试,二者职责互补。
证书轮换与过期管理实战
综合 README 与生成脚本,维护这套测试证书的标准流程如下:
环境准备:确保系统已安装
cfssl、cfssljson(通常随 cfssl 一同提供)与openssl;清理旧证书(可选,避免残留干扰):
./tests/certs/tls/cfssl/gencert.sh clean_tmp_keys重新生成全部证书:
./tests/certs/tls/cfssl/gencert.sh generate_keys校验新证书:用 README 中的命令核对有效期与 SAN 是否符合预期:
openssl x509 -noout -text -in ./tests/certs/tls/cfssl/server/server.pem openssl x509 -noout -text -in ./tests/certs/tls/cfssl/ca/ca.pem跑一遍 TLS 相关测试验证链路:证书刷新后,建议重新执行引用了
TEST_TLS_*常量的集成测试(Admin API TLS、HTTP Handler TLS 等)以及ci-run-stateless-tests-cluster-tls.sh对应的集群 TLS 用例。
几点实践提醒:
- 根 CA 有效期为 10 年,叶子证书有效期为 1 年,因此每年的例行工作主要是刷新 server/client 叶子证书,CA 除非私钥泄漏否则无需重建;
- 如果重建了 CA,所有依赖
TEST_TLS_CA_CERT作为信任根的测试都会随新根自动生效,但旧根签发的证书将无法通过新根校验; gencert.sh脚本以"$@"派发子命令且当前未提供帮助信息(源码中留有TODO add usage and help message for new coming contributors),新增子命令时需在脚本内补充函数定义。
通过这套"cfssl 签发 + openssl 格式转换 + 常量收口 + CI 注入"的完整链路,Databend 得以在任何环境下一键重建、快速轮换测试证书,为 RPC、MySQL、HTTP、Admin API、Flight SQL 等多条 TLS 通道的自动化测试提供了稳定可信的凭据基础。
【免费下载链接】databendData Agent Ready Warehouse : One for Analytics, Search, AI, Python Sandbox. — rebuilt from scratch. Unified architecture on your S3.项目地址: https://gitcode.com/GitHub_Trending/da/databend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考