OpenBao 需要一个可靠的持久化后端来保存密钥、策略和租约。这里的方案把 OpenBao 运行在 Kubernetes 中,以 CloudNativePG 管理的 PostgreSQL 作为存储层,并通过 TLS 客户端证书完成数据库身份认证。整个链路不保存数据库密码,Kubernetes、CloudNativePG 和 OpenBao 也都属于开源技术栈,减少了迁移时对特定云厂商或托管服务的依赖。
这套架构解决了什么问题
传统部署通常要给 OpenBao 配置 PostgreSQL 用户名和密码,再把密码放进 Kubernetes Secret。即使 Secret 已经加密,它仍然带来轮换、同步和泄露面等问题:数据库密码可能同时存在于 GitOps 系统、工作负载环境变量、备份和运维终端中。
基于客户端证书的连接把认证材料换成了一组有明确用途和生命周期的文件:
- PostgreSQL 服务端证书用于证明数据库服务器身份。
- CA 证书让 OpenBao 验证服务端证书。
- OpenBao 客户端证书向 PostgreSQL 证明自身身份。
- 客户端私钥只挂载到 OpenBao Pod,不写入连接字符串或日志。
- CloudNativePG 1.30 的
DatabaseRoleCRD 声明并维护对应的 PostgreSQL 角色。
数据路径可以概括为:OpenBao Pod 使用客户端证书连接 PostgreSQL Service,PostgreSQL 根据证书身份和 pg_hba 规则完成认证,然后把数据写入 CloudNativePG 集群。数据库角色、证书和工作负载因此可以分别声明、审计和轮换。
DatabaseRole 让数据库身份进入声明式管理
DatabaseRole 的价值不只是少执行一条 CREATE ROLE。它把数据库角色纳入 Kubernetes 控制循环:角色是否允许登录、是否应存在、拥有哪些权限,都能通过资源清单表达。
下面是一个可改造的最小示例。假设 CloudNativePG 集群名为 openbao-db,OpenBao 客户端证书的身份映射为数据库角色 openbao。不同的 CloudNativePG 1.30 小版本可能对字段有进一步约束,应用前应使用 kubectl explain databaserole.spec 核对安装版本的 CRD。
apiVersion: postgresql.cnpg.io/v1
kind: DatabaseRole
metadata:
name: openbao
namespace: security
spec:
cluster:
name: openbao-db
name: openbao
ensure: present
login: true
superuser: false
应用并检查角色状态:
kubectl apply -f openbao-database-role.yaml
kubectl -n security get databaserole openbao -o yaml
kubectl -n security describe databaserole openbao
这个角色不需要设置密码。真正允许证书登录的关键还包括 PostgreSQL 的客户端 CA 配置、证书主体到角色的映射,以及 pg_hba 中的证书认证规则。角色 CRD 只负责角色生命周期,不能替代这些认证配置。
权限也应保持最小化。可以先由受控的数据库管理员会话创建专用数据库和 schema,再把对象权限授予 openbao,而不是让 OpenBao 使用超级用户连接:
CREATE DATABASE openbao;
\connect openbao
CREATE SCHEMA IF NOT EXISTS openbao AUTHORIZATION openbao;
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
GRANT CONNECT ON DATABASE openbao TO openbao;
GRANT USAGE, CREATE ON SCHEMA openbao TO openbao;
把证书挂载给 OpenBao
下面的 Deployment 片段展示了一种可以这样实践的接线方式。这里作出三个明确假设:
openbao-db-rw.security.svc是 CloudNativePG 的读写 Service,并且出现在数据库服务端证书的 SAN 中。- Secret
openbao-db-client-tls包含tls.crt和tls.key。 - ConfigMap
openbao-db-ca包含ca.crt,其 CA 能验证数据库服务端证书。
运行前需要替换镜像版本、证书资源名和域名。生产环境不应使用浮动标签。
apiVersion: v1
kind: ConfigMap
metadata:
name: openbao-config
namespace: security
data:
openbao.hcl: |
ui = true
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
storage "postgresql" {
connection_url = "postgresql://openbao@openbao-db-rw.security.svc:5432/openbao?sslmode=verify-full&sslrootcert=/etc/openbao/db-tls/ca.crt&sslcert=/etc/openbao/db-tls/tls.crt&sslkey=/etc/openbao/db-tls/tls.key"
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: openbao
namespace: security
spec:
replicas: 1
selector:
matchLabels:
app: openbao
template:
metadata:
labels:
app: openbao
spec:
securityContext:
fsGroup: 1000
containers:
- name: openbao
image: quay.io/openbao/openbao:2.1.0
args: ["server", "-config=/etc/openbao/config/openbao.hcl"]
ports:
- name: http
containerPort: 8200
volumeMounts:
- name: config
mountPath: /etc/openbao/config
readOnly: true
- name: db-client-tls
mountPath: /etc/openbao/db-tls/tls.crt
subPath: tls.crt
readOnly: true
- name: db-client-tls
mountPath: /etc/openbao/db-tls/tls.key
subPath: tls.key
readOnly: true
- name: db-ca
mountPath: /etc/openbao/db-tls/ca.crt
subPath: ca.crt
readOnly: true
volumes:
- name: config
configMap:
name: openbao-config
- name: db-client-tls
secret:
secretName: openbao-db-client-tls
defaultMode: 0400
- name: db-ca
configMap:
name: openbao-db-ca
示例为了突出数据库认证,把 OpenBao 监听器设成了明文 HTTP。它只适合由受控的 Service Mesh 或前置代理终止 TLS 的场景;否则必须同时为 OpenBao 的客户端入口启用 TLS。数据库链路使用 TLS,并不意味着用户到 OpenBao 的链路已经受到保护。
部署前,可以先用相同证书验证 PostgreSQL 连接。下面的临时 Pod 不携带密码,并在退出后自动删除:
kubectl -n security run pg-cert-check --rm -it --restart=Never \
--image=postgres:16 \
--overrides='{
"spec": {
"containers": [{
"name": "pg-cert-check",
"image": "postgres:16",
"command": ["psql"],
"args": [
"postgresql://openbao@openbao-db-rw.security.svc:5432/openbao?sslmode=verify-full&sslrootcert=/tls/ca.crt&sslcert=/tls/tls.crt&sslkey=/tls/tls.key",
"-c",
"select current_user, current_database();"
],
"volumeMounts": [{
"name": "client-tls",
"mountPath": "/tls",
"readOnly": true
}]
}],
"volumes": [{
"name": "client-tls",
"projected": {
"sources": [
{"secret": {"name": "openbao-db-client-tls"}},
{"configMap": {"name": "openbao-db-ca"}}
]
}
}]
}
}'
如果命令报主机名不匹配,不要把 sslmode 降为 require 来绕过验证;应修正证书 SAN 或连接使用的 Service DNS 名称。若报私钥权限问题,则应调整挂载模式、容器用户或 fsGroup,同时避免把私钥放宽到所有用户可读。
上线时真正需要盯住的边界
无密码不等于无凭据。客户端私钥仍然是高价值认证材料,需要限制 RBAC、禁止无关 Pod 读取 Secret,并建立自动签发、到期告警和轮换流程。证书轮换还要验证 OpenBao 是否会重新读取文件;如果不会,就需要受控地滚动 Pod。
生产采用前建议逐项检查:
DatabaseRole只拥有 OpenBao 所需的数据库和 schema 权限。- PostgreSQL 要求可信客户端 CA 签发的证书,并正确映射证书身份。
- 连接参数使用
sslmode=verify-full,数据库 Service DNS 位于证书 SAN 中。 - OpenBao 入口本身启用了 TLS,或由明确受控的代理负责 TLS 终止。
- CloudNativePG 已配置备份、恢复演练、资源限制和高可用策略。
- 证书轮换在预发布环境中经过验证,包括旧证书撤销或失效后的行为。
- 日志、诊断命令和 Pod 描述中不会暴露私钥内容。
这套方案的核心收益不是简单地删掉密码字段,而是把身份从长期共享字符串变成可声明、可轮换、用途明确的证书。代价是 PKI、证书映射和轮换流程必须做扎实。对于已经运行 Kubernetes、CloudNativePG 和证书管理设施的团队,这通常是一笔值得的复杂度投入。