本文介绍如何把 acme.sh 已签发的 ZeroSSL ECC 证书自动复制到 Db2 服务器,再使用 OpenSSL 和 IBM GSKit 转换、导入 Db2 可识别的 KDB 密钥数据库,最后配置 Db2 TLS。
示例域名为
example.com,Db2 实例用户为db2inst1,服务器为db2.example.com(域名和IP地址皆可)。请按实际环境替换。
1. 工作原理
1. acme.sh 从 ZeroSSL 获取或续期证书;
2. SSH deploy hook 将服务器私钥、服务器证书和中间 CA 复制到 Db2 主机;
3. 远程执行 update-db2-cert.sh;
4. 脚本从系统信任库提取 USERTrust ECC 根证书;
5. OpenSSL 将私钥、服务器证书和中间证书打包为 PKCS#12;
6. GSKit 创建 KDB,导入个人证书及完整信任链;
7. 更新 Db2 Database Manager Configuration 并重启服务。
证书链通常为:
example.com 服务器证书
└─ ZeroSSL ECC DV SSL CA 2(中间 CA)
└─ USERTrust ECC Certification Authority(根 CA)KDB 中必须具备带私钥的服务器证书(personal certificate),以及中间 CA、根 CA(signer certificate)。
2. 前置条件
Db2 服务器需要安装 IBM Db2、GSKit、OpenSSL 和 trust(通常来自 p11-kit):
command -v db2
command -v gsk9certutil_64
command -v openssl
command -v trust创建证书目录和密码文件:
# 这里假定你创建db2实例是使用的用户的默认用户组是db2iadm1
sudo install -d -m 700 -o db2inst1 -g db2iadm1 /home/db2inst1/ssl
sudo -u db2inst1 sh -c 'umask 077; printf "%s\n" "替换为高强度密码" > /home/db2inst1/ssl/.kdb_password'建议私钥、KDB、stash 和密码文件权限为 600。.sth 虽不是明文密码文件,仍应按敏感凭据保护。
如果脚本通过 systemd 管理 Db2,需给 db2inst1 最小化 sudo 权限。先确认服务单元和 systemctl 路径,再通过 visudo 配置,例如:
db2inst1 ALL=(root) NOPASSWD: /usr/bin/systemctl stop db2inst1, /usr/bin/systemctl start db2inst13. 配置 SSL 服务端口
本文使用服务名 db2cs_db2inst1。确保 /etc/services 中有对应条目:
db2cs_db2inst1 50001/tcp验证并开放防火墙端口:
getent services db2cs_db2inst14. 部署脚本
将以下内容保存为 /home/db2inst1/ssl/update-db2-cert.sh:
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
SSL_DIR="/home/db2inst1/ssl"
KDB="$SSL_DIR/db2.kdb"
P12="$SSL_DIR/db2.p12"
ACME_DOMAIN_NAME="example.com" # 换成你自己的域名
ROOT_CA="$SSL_DIR/usertrust-ecc-root.pem"
KEY_FILE="$SSL_DIR/$ACME_DOMAIN_NAME.key.pem"
CERT_FILE="$SSL_DIR/$ACME_DOMAIN_NAME.pem"
CA_FILE="$SSL_DIR/ca.pem"
PASSWORD_FILE="$SSL_DIR/.kdb_password"
CERT_LABEL="CN=$ACME_DOMAIN_NAME"
DB2_SSL_SERVICE="db2cs_db2inst1"
SYSTEMD_UNIT="db2inst1"
# SSH 非交互会话通常不会自动加载 Db2 环境。
. /home/db2inst1/sqllib/db2profile
cd "$SSL_DIR"
TMP_DIR=""
cleanup() {
rm -f "$P12"
if [[ -n "$TMP_DIR" && -d "$TMP_DIR" ]]; then
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT
for cmd in openssl trust gsk9certutil_64 db2; do
command -v "$cmd" >/dev/null 2>&1 || {
echo "错误:找不到命令 $cmd" >&2
exit 1
}
done
for file in "$KEY_FILE" "$CERT_FILE" "$CA_FILE" "$PASSWORD_FILE"; do
[[ -s "$file" ]] || {
echo "错误:文件不存在或为空:$file" >&2
exit 1
}
done
PASSWORD="$(<"$PASSWORD_FILE")"
[[ -n "$PASSWORD" ]] || { echo "错误:KDB 密码为空" >&2; exit 1; }
# 至少保证证书在未来 24 小时内仍有效。
openssl x509 -in "$CERT_FILE" -checkend 86400 -noout
# ECC/RSA 均适用:比较公钥摘要,确认私钥与证书匹配。
CERT_PUBKEY_HASH="$(openssl x509 -in "$CERT_FILE" -pubkey -noout | openssl pkey -pubin -outform DER | openssl sha256)"
KEY_PUBKEY_HASH="$(openssl pkey -in "$KEY_FILE" -pubout -outform DER | openssl sha256)"
[[ "$CERT_PUBKEY_HASH" == "$KEY_PUBKEY_HASH" ]] || {
echo "错误:证书与私钥不匹配" >&2
exit 1
}
echo "[1/10] 停止 Db2"
sudo systemctl stop "$SYSTEMD_UNIT" || true
echo "[2/10] 提取 USERTrust ECC 根 CA"
TMP_DIR="$(mktemp -d)"
trust extract --format=pem-bundle --filter=ca-anchors "$TMP_DIR/ca-bundle.pem"
awk -v dir="$TMP_DIR" '
/BEGIN CERTIFICATE/ { n++ }
n > 0 { print > (dir "/cert-" n ".pem") }
' "$TMP_DIR/ca-bundle.pem"
FOUND=0
for cert in "$TMP_DIR"/cert-*.pem; do
if openssl x509 -in "$cert" -subject -noout 2>/dev/null | grep -Fq "USERTrust ECC Certification Authority"; then
cp "$cert" "$ROOT_CA"
FOUND=1
break
fi
done
[[ "$FOUND" -eq 1 ]] || { echo "错误:未找到 USERTrust ECC 根 CA" >&2; exit 1; }
echo "[3/10] 删除旧 KDB"
rm -f "$KDB" "${KDB%.kdb}.rdb" "${KDB%.kdb}.crl" "${KDB%.kdb}.sth" "$P12"
echo "[4/10] 创建 KDB"
gsk9certutil_64 -keydb -create -db "$KDB" -pw "$PASSWORD" -type kdb -stash
echo "[5/10] 创建 PKCS#12"
openssl pkcs12 -export \
-inkey "$KEY_FILE" \
-in "$CERT_FILE" \
-certfile "$CA_FILE" \
-name "$CERT_LABEL" \
-out "$P12" \
-passout pass:"$PASSWORD"
echo "[6/10] 导入服务器证书"
gsk9certutil_64 -cert -import \
-file "$P12" -type p12 -pw "$PASSWORD" \
-target "$KDB" -target_type kdb -target_pw "$PASSWORD"
echo "[7/10] 导入中间 CA"
# PKCS#12 导入后中间证书可能已存在;如版本行为不同,可保留此显式导入。
if ! gsk9certutil_64 -cert -list -db "$KDB" -pw "$PASSWORD" | grep -Fq "ZeroSSL ECC DV SSL CA 2"; then
gsk9certutil_64 -cert -add -db "$KDB" -pw "$PASSWORD" \
-file "$CA_FILE" -label "ZeroSSL ECC DV SSL CA 2"
fi
echo "[8/10] 导入根 CA"
if ! gsk9certutil_64 -cert -list -db "$KDB" -pw "$PASSWORD" | grep -Fq "USERTrust ECC Certification Authority"; then
gsk9certutil_64 -cert -add -db "$KDB" -pw "$PASSWORD" \
-file "$ROOT_CA" -label "USERTrust ECC Certification Authority"
fi
echo "[9/10] 验证 KDB"
gsk9certutil_64 -cert -list -db "$KDB" -pw "$PASSWORD"
gsk9certutil_64 -cert -details -db "$KDB" -pw "$PASSWORD" -label "$CERT_LABEL"
chmod 600 "$KDB" "${KDB%.kdb}.sth" "$PASSWORD_FILE" "$KEY_FILE"
echo "[10/10] 配置 Db2 TLS"
db2 update dbm cfg using SSL_SVR_KEYDB "$KDB"
db2 update dbm cfg using SSL_SVR_STASH "${KDB%.kdb}.sth"
db2 update dbm cfg using SSL_SVR_LABEL "$CERT_LABEL"
db2 update dbm cfg using SSL_SVCENAME "$DB2_SSL_SERVICE"
db2set DB2COMM=TCPIP,SSL
sudo systemctl start "$SYSTEMD_UNIT"
echo "完成"db2无法使用fullchain证书公钥,必须将证书链层层拆开
赋予执行权限:
chown db2inst1:db2iadm1 /home/db2inst1/ssl/update-db2-cert.sh
chmod 700 /home/db2inst1/ssl/update-db2-cert.shShell 续行符
\后不能有空格。复制脚本时请勿把显示层的空格带到反斜杠后面。
5. acme.sh Multi Deploy 配置
首先在db2的证书存储路径先创建证书的同名文件占位,防止acme脚本失败(它会备份旧的证书,如果路径里找不到证书同名文件,会直接报错)
cd /home/db2inst1/ssl/
touch example.com.key.pem" # 将“example.com”换成你的域名
touch example.com.pem" # 将“example.com”换成你的域名接下来,推荐使用 multideploy 的 delployhook 来部署证书。
在 acme.sh 的配置存储路径中新建multideploy.ymltouch ~/.acme.sh/example.com_ecc/multideploy.yml 将“example.com”换成你的域名
示例节选:
version: "1.0"
services:
- name: "db2"
hook: "ssh"
environment:
DEPLOY_SSH_USER: "db2inst1"
DEPLOY_SSH_SERVER: "db2.example.com" # 将db2.example.com换成你的db2服务器的域名/IP地址
DEPLOY_SSH_KEYFILE: "/home/db2inst1/ssl/example.com.key.pem" # 将“example.com”换成你的域名
DEPLOY_SSH_CERTFILE: "/home/db2inst1/ssl/example.com.pem" # 将“example.com”换成你的域名
DEPLOY_SSH_CAFILE: "/home/db2inst1/ssl/ca.pem"
DEPLOY_SSH_REMOTE_CMD: "/home/db2inst1/ssl/update-db2-cert.sh"
DEPLOY_SSH_USE_SCP: "yes"脚本写完之后,部署证书:
acme.sh --deploy -d example.com --deploy-hook multideploy # 将“example.com”换成你的域名注意:原配置中的 DEPLOY_SSH_USE_SCP: "yes 缺少右侧双引号,会导致 YAML 解析失败。
这些路径表示证书和脚本的远端存储位置。acme.sh 在续期成功后复制证书并执行远程命令。首次自动部署前,建议先手工运行脚本验证 sudo、Db2 profile、GSKit 和服务名均正确。
6. 标签为什么必须一致
SSL_SVR_LABEL 必须指向 KDB 中包含私钥的个人证书标签,而不是 CA 标签。PKCS#12 导入时标签可能来自 friendlyName,也可能被工具按证书主题生成。
脚本通过以下参数固定标签:
openssl pkcs12 -export -name "CN=example.com" ...随后 Db2 使用同一个值:
db2 update dbm cfg using SSL_SVR_LABEL "CN=example.com"务必以实际列表为准:
gsk9certutil_64 -cert -list -db /home/db2inst1/ssl/db2.kdb -stashed如果 GSKit 版本未保留 friendlyName,应把 CERT_LABEL 和 SSL_SVR_LABEL 改为 KDB 中实际显示的个人证书标签。
7. 验证部署
查看 Db2 配置:
db2 get dbm cfg | grep -i SSL
db2set -all | grep DB2COMM查看监听端口:
ss -lntp | grep 50001从客户端检查握手和证书链:
openssl s_client \
-connect db2.example.com:50001 \
-servername example.com # 换成你申请证书的域名\
-showcerts \
-verify_return_error </dev/null应重点确认:
- 服务端返回
example.com证书; - SAN 包含客户端实际使用的主机名;
- 能构建到 USERTrust ECC 根 CA 的信任链;
- 最终显示
Verify return code: 0 (ok)。
客户端连接时必须使用 SSL 端口并启用 SSL。不同 Db2 驱动的参数名称不同,常见形式为 security=SSL 或 sslConnection=true。
8. 技术细节
8.1 PEM、PKCS#12 与 KDB
acme.sh 输出通常是 PEM:私钥与证书分别存储。Db2 服务端使用 GSKit KDB,服务器证书必须在 KDB 中与私钥关联。PKCS#12 是两者之间的通用中间格式,可同时携带私钥、服务器证书和附加证书链。
8.2 KDB 配套文件
创建 KDB 时可能出现:
db2.kdb:密钥数据库;db2.sth:stash 文件,供 Db2 无人值守读取 KDB;db2.rdb:请求数据库;db2.crl:证书吊销相关数据库。
SSL_SVR_STASH 必须指向实际生成的 db2.sth。
8.3 为什么不应只导入服务器证书
只有服务器证书和私钥时,GSKit 或客户端可能无法构建完整证书链。中间 CA 通常必须由服务端提供;根 CA主要用于建立信任。显式导入中间证书和根证书可减少不同 GSKit 版本、PKCS#12 导入行为和系统信任库差异造成的问题。
8.4 为什么比较公钥而不是 modulus
传统 RSA 脚本常比较 modulus,但 ECC 证书没有 RSA modulus。本文提取证书和私钥的 DER 公钥并计算 SHA-256,因而同时适用于 RSA 和 ECC。
8.5 重建 KDB 的取舍
本文每次续期都重建 KDB,优点是流程确定、不会残留旧标签;缺点是脚本失败期间服务可能处于停止状态,且会移除 KDB 中其他证书。若同一 KDB 还保存其他用途证书,应改为原位更新,或为 Db2 TLS 使用独立 KDB。
生产环境还可先构建临时 KDB,验证成功后再原子替换,以缩短中断并保留回滚能力。
9. 常见故障
db2: command not found
SSH 非交互会话没有加载实例环境。确认脚本包含:
. /home/db2inst1/sqllib/db2profile找不到根证书
检查系统是否安装或更新 CA 信任库:
trust list | grep -A3 -B3 "USERTrust ECC"也可将经过校验的根证书固定放入 SSL 目录,但不要从不可信 URL 临时下载后直接导入。
SSL_SVR_LABEL 不存在
列出 KDB,找到带私钥的个人证书标签,并同步更新 CERT_LABEL 与 Db2 配置。不要误用中间 CA 或根 CA 标签。
Db2 启动后未监听 SSL 端口
检查 /etc/services、SSL_SVCENAME、DB2COMM、防火墙和 Db2 日志;修改 Database Manager Configuration 后通常需要重启实例。
客户端提示证书主机名不匹配
客户端连接名必须存在于证书 SAN。使用 IP 地址连接时,证书通常没有对应 IP SAN;应使用证书中的 DNS 名,并保证 DNS 或 hosts 解析正确。
脚本停止 Db2 后失败
当前脚本采取 fail-fast 策略。生产环境建议增加失败恢复 trap、保留上一套 KDB,或先在临时路径完成构建和验证,再切换文件并重启。
10. 安全建议
- 不要把
.kdb_password、私钥、KDB、stash 提交到 Git; - 限制 SSL 目录权限,仅允许实例用户访问;
- acme.sh 主机到 Db2 主机使用专用 SSH key,并限制 authorized_keys 权限;
- sudo 只放行指定的 start/stop 命令;
- 定期检查证书到期时间和自动部署日志;
- 若无兼容性需求,可考虑仅启用
DB2COMM=SSL,避免同时暴露明文 TCP 监听; - 在生产变更前保留旧 KDB 和 Db2 配置,以便快速回滚。
完成以上配置后,后续 acme.sh 续期成功时即可自动复制新证书、重建 Db2 KDB 并使新证书生效。