External Secrets Operator 集成 OpenBao:基于 Vault Provider 的密钥同步实战指南
【免费下载链接】external-secretsExternal Secrets Operator reads information from a third-party service like AWS Secrets Manager and automatically injects the values as Kubernetes Secrets.项目地址: https://gitcode.com/GitHub_Trending/ex/external-secrets
External Secrets Operator(ESO)通过复用其 HashiCorp Vault Provider 的成熟实现,为 OpenBao 与 providers/v1/openbao 下的完整源码实现,系统讲解 OpenBao Provider 的架构、配置参数、四种认证方式、密钥读取与校验机制,并给出可直接落地的 SecretStore / ExternalSecret 配置示例。读完本文,你将掌握如何在 Kubernetes 中把 OpenBao KV 引擎中的密钥安全同步为 Kubernetes Secret,并能根据源码理解其只读能力边界与底层调用链。
OpenBao 与 ESO 的集成方式
OpenBao 是一个由社区维护的、API 兼容 HashiCorp Vault 的开源密钥管理项目,两者的 HTTP API 与认证协议高度一致。正因如此,OpenBao 集成文档 明确说明:External Secrets Operator 通过使用 HashiCorp Vault Provider 来与 OpenBao 集成,具体实现细节请以该文档为准。
集成文档还给出了经官方验证的版本组合:该集成使用External Secrets Operator v0.16.1与OpenBao v2.2.0完成了测试。这意味着在选用 OpenBao 作为密钥后端时,这两个版本是当前仓库明确验证过的组合。
仓库内pkg/register/openbao.go展示了 OpenBao Provider 的注册方式:
//go:build openbao || all_providers // Register openbao provider esv1.Register(openbao.NewProvider(), openbao.ProviderSpec(), openbao.MaintenanceStatus())从中可以确认两点事实:
- OpenBao Provider 与 Vault Provider 是仓库中独立注册的 Provider 实例,通过
openbao或all_providers构建标签启用; openbao.MaintenanceStatus()返回esv1.MaintenanceStatusMaintained(见 provider.go),表明该 Provider 处于活跃维护状态。
底层实现:复用 Vault API 的独立 Provider
从源码结构看,OpenBao Provider 是providers/v1/openbao下的完整独立实现,而并非简单地将SecretStore.spec.provider.vault改名复用。其核心入口在 provider.go:
// Provider implements the ESO Provider interface for OpenBao. type Provider struct { HTTPClientFactory httpClientFactory AuthMethodFactory auth.Factory } // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite). func (p *Provider) Capabilities() esv1.SecretStoreCapabilities { return esv1.SecretStoreReadOnly }实现要点如下:
- 使用 OpenBao 官方 Go 客户端
github.com/openbao/openbao/api/v2构建连接(见 client.go),这与 Vault Provider 使用hashicorp/vault/api形成对应关系——两个 Provider 各自对接各自的官方 SDK,但共享 ESO 侧的统一接口抽象; - 认证方式通过
internal/auth包内的工厂接口(auth.Factory)创建,默认工厂DefaultAuthMethodFactory直接调用 OpenBao 官方 SDK 中对应的认证方法构造器(见 internal/auth/impl.go); - Provider 的能力为只读(
SecretStoreReadOnly),这与下文"PushSecret / DeleteSecret 未实现"的限制一致。
因此,OpenBao 集成文档中"请参阅 HashiCorp Vault Provider 文档"的指引,本质上指向的是使用方式与 KV 语义的互通性(SecretStore/ExternalSecret 的编排模型、metadataPolicy、dataFrom等),而 HTTP 层与认证层则由 OpenBao 自身的实现负责。
配置 SecretStore:字段与默认值
OpenBao Provider 的 CRD 类型定义在 apis/externalsecrets/v1/secretstore_openbao_types.go,通过spec.provider.openbao配置。核心字段如下:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
server | string | 是 | OpenBao 服务地址,如https://openbao.example.com:8200 |
path | string | 否 | KV 引擎挂载路径,默认kv(见 client.go 的path()方法);若填写了 v2 引擎特有的/data后缀,客户端会自动兼容 |
version | string | 否 | KV 引擎版本,v1或v2,默认v2(CRD 中通过+kubebuilder:default:="v2"声明) |
namespace | string | 否 | OpenBao Namespace(多租户特性),如ns1 |
caBundle | []byte | 否 | PEM 编码的 CA 证书,用于校验 OpenBao 服务端证书 |
caProvider | object | 否 | 从 Kubernetes Secret/ConfigMap 引用的 CA 证书 |
auth | object | 否 | 认证配置,见下文 |
两个容易踩坑的约束(已由 CRD 校验注解表达):
caBundle与caProvider通过+kubebuilder:validation:AtMostOneOf=caBundle;caProvider约束为二选一;version通过+kubebuilder:validation:Enum="v1";"v2"限定取值范围。
一个最小可用的 SecretStore 示例:
apiVersion: external-secrets.io/v1 kind: SecretStore metadata: name: openbao-backend spec: provider: openbao: server: "https://openbao.example.com:8200" path: "secret" version: "v2" auth: tokenSecretRef: name: "openbao-token" key: "token" --- apiVersion: v1 kind: Secret metadata: name: openbao-token data: token: cm9vdA== # "root"注意:若使用
ClusterSecretStore,tokenSecretRef等引用必须显式指定namespace,指明 Secret 所在的 Kubernetes 命名空间。这是 ESO 对集群级 Store 引用命名空间 Secret 的通用要求,详见 HashiCorp Vault Provider 文档。
认证方式:四种受支持的模式
OpenBaoAuth 类型通过+kubebuilder:validation:ExactlyOneOf=appRole;tokenSecretRef;userPass;kubernetes约束四种认证方式必须且只能选择一种。客户端装配逻辑位于 client.go 的setupAuth方法。
Token 认证(静态令牌)
最直接的认证方式:从 Kubernetes Secret 中读取静态 token 并直接设置到客户端:
spec: provider: openbao: server: "https://openbao.example.com:8200" auth: tokenSecretRef: name: "openbao-token" key: "token"对应实现:resolvers.SecretKeyRef解析 Secret 后调用c.client.SetToken(token)(见 client.go)。
AppRole 认证
AppRole 认证从 Secret 中读取 secret ID,并配合roleId(内联或通过roleRef引用)换取临时令牌。roleId与roleRef通过ExactlyOneOf约束为互斥:
spec: provider: openbao: server: "https://openbao.example.com:8200" auth: appRole: path: "approle" # 默认 "approle" roleId: "my-role-id" # 或使用 roleRef 引用 Secret 中的值 secretRef: name: "openbao-approle-secret" key: "secretid"实现上,AppRole 的登录由 OpenBao 官方 SDK 的approle.NewAppRoleAuth完成(见 internal/auth/impl.go)。值得注意的是,若同时指定了roleRef(RoleID 也来自 Secret),源码会优先使用roleRef解析出的 RoleID(见 client.go)。
UserPass 认证
使用用户名/密码对换取访问令牌,用户名直接写在 Store 中,密码存放在 Kubernetes Secret 中:
spec: provider: openbao: server: "https://openbao.example.com:8200" auth: userPass: path: "userpass" # 默认 "userpass" username: "alice" secretRef: name: "openbao-userpass-secret" key: "password"这是唯一被 HTTP 录制测试覆盖的认证方式(详见下文测试章节),因为 UserPass 交互相对静态、易于回放。
Kubernetes 原生认证
通过 Kubernetes ServiceAccount 令牌向 OpenBao 认证,令牌来源二选一(ExactlyOneOf=serviceAccountRef;secretRef):
serviceAccountRef:通过 Kubernetes TokenRequest API 请求短期令牌,可自定义audiences(见 client.go 的getJwt方法);secretRef:从 Secret 中读取已存在的 ServiceAccount JWT,未指定key时默认使用token。
spec: provider: openbao: server: "https://openbao.example.com:8200" auth: kubernetes: path: "kubernetes" # 默认 "kubernetes" role: "my-role" # 必填,绑定 ServiceAccount 与 OpenBao 策略 serviceAccountRef: name: "my-sa" audiences: ["vault"]从源码注释看,OpenBao Kubernetes 认证不支持直接使用控制器 Pod 的 ServiceAccount 令牌("Using the controller pod's ServiceAccount token is not supported"),必须显式指定
serviceAccountRef或secretRef。另外,由于 Kubernetes 认证依赖 OpenBao 侧通过 TokenReview API 校验令牌,需按 OpenBao 官方文档完成 ServiceAccount 的 RBAC 授权(system:auth-delegator),相关说明可参考 HashiCorp Vault Provider 文档 的 Kubernetes 认证一节。
认证命名空间
OpenBao 的 Namespace 支持在数据读取与认证两个层面分别指定:
spec.provider.openbao.namespace:数据读取所用命名空间;spec.provider.openbao.auth.namespace:认证所用命名空间(可不同于数据命名空间);若未设置,默认回退到openbao.namespace。
对应实现见 client.go 与 client.go:认证请求会通过authClient.WithNamespace(...)在独立命名空间上下文中执行,登录成功后取得的令牌再设置回主客户端。
从 OpenBao 读取密钥
登录完成后,Provider 通过GetSecret/GetSecretMap/GetAllSecrets三个接口向 ESO 提供数据(见 client.go)。
单密钥读取:property 与 version
KV v2 引擎的读取逻辑(见 client.go):
- 不指定
remoteRef.property时,返回整个 secret 的 JSON 编码数据; - 指定
property时,仅返回该字段的值(缺失则报cannot find secret data for key); - KV v2 可通过
remoteRef.version指定读取特定版本(内部先做strconv.Atoi转换,再调用KVv2.GetVersion); - KV v1 不支持版本化读取,指定
version会直接报错OpenBao KVv1 secrets do not support versioning (use KVv2)。
一个完整的 ExternalSecret 示例(对应 Vault 文档中的模式):
apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: openbao-example spec: refreshInterval: "15s" secretStoreRef: name: openbao-backend kind: SecretStore target: name: example-sync data: - secretKey: foobar remoteRef: key: foo property: my-value先通过bao kv put -mount=secret foo my-value=s3cr3t在 OpenBao 中写入密钥,ESO 控制器即可将该值同步为名为example-sync的 Kubernetes Secret 中的foobar字段。
整包读取:GetSecretMap 与 dataFrom
GetSecretMap将整个 secret 的 JSON 数据反序列化为map[string][]byte,用于dataFrom.extract场景:当remoteRef.property留空时,dataFrom.extract会把该路径下的全部键值对展开为多个目标字段。嵌套值可通过property指定 gjson 表达式(如foo.nested.bar)提取,具体用法与 HashiCorp Vault Provider 文档 中的示例一致。
批量查找:GetAllSecrets 与 dataFrom.find
GetAllSecrets实现了dataFrom.find的能力(见 client.go):
- 在
ref.path指定的目录下列出密钥名(默认从 Provider 的path挂载点开始); - 通过
runtime/find包的正则匹配器过滤名称; - 对匹配的每个密钥逐一调用
GetSecret读取完整值。
需要特别说明的限制:OpenBao Provider 的GetAllSecrets尚未实现基于 tag(custom_metadata)的搜索,传入ref.Tags会直接返回 "tag based search is not implemented" 错误;同时,SecretExists(PushSecret 配套接口)也未实现。因此基于标签的dataFrom.find目前只适用于 Vault Provider,OpenBao 用户应使用name.regexp方式按名称匹配。
配置校验:ValidateStore 与 Validate
ESO 在两层面对 OpenBao 配置进行校验。
准入校验(ValidateStore)
validate.go 实现ValidateStore,在 SecretStore 被写入时校验其结构合法性,包括:Store / Spec / Provider 非空、provider.openbao存在,以及对appRole、tokenSecretRef、userPass、kubernetes四种认证下的所有 Secret/ServiceAccount 引用执行ValidateReferentSecretSelector/ValidateReferentServiceAccountSelector检查(保证 ClusterSecretStore 的引用带上了 namespace 等必要条件)。
连通性校验(Validate)
client.go 的Validate方法在 Store 就绪时探测 OpenBao:
- 设置 5 秒超时上下文,调用
Sys().MountInfoWithContext查询挂载信息; - 校验挂载类型必须为
kv,否则报expected mount type "kv" found %q; - 校验 KV 引擎版本与
spec.provider.openbao.version一致,否则报expected kv engine version %s found version %s。
一个例外:当ClusterSecretStore使用 referent namespace 引用(即认证引用的 namespace 未内联指定)时,由于Validate()阶段尚无法确定 namespace,会返回ValidationResultUnknown,跳过深度校验。
能力边界:当前为只读 Provider
结合 provider.go 的Capabilities()与 client.go 中未实现的接口方法,OpenBao Provider 当前的能力边界非常清晰:
| 能力 | 状态 | 说明 |
|---|---|---|
| 读取密钥(GetSecret / GetSecretMap / GetAllSecrets) | ✅ 支持 | 覆盖 KV v1 / v2 |
| PushSecret | ❌ 不支持 | PushSecret返回 "push secret is not supported (the OpenBao provider is currently read only)" |
| DeleteSecret | ❌ 不支持 | 同上,返回明确的只读错误 |
| SecretExists | ❌ 未实现 | 返回 "not implemented" |
因此,OpenBao Provider 不能用于 PushSecret 等写入型工作流(对比之下 Vault Provider 支持 PushSecret,详见 HashiCorp Vault Provider 文档 的 PushSecret 章节)。如果你的场景需要向 OpenBao 推送密钥,需要等待该 Provider 后续迭代补齐写入能力。
测试与质量保障:基于真实流量的录制回放
OpenBao Provider 的测试策略很有特色(详细说明见 DEV.md):大部分逻辑测试基于与真实 OpenBao 服务器交互录制的 HTTP 流量(存储在 testdata/http 目录下),既贴近真实环境,又保持毫秒级执行速度。
测试初始化脚本 testdata/init-bao.sh 展示了测试环境的搭建过程,也侧面印证了 Provider 的能力矩阵:
bao kv put -mount=secret foo bar=bazz lorem=ipsum # KV v2 写入 bao secrets enable -version=1 -path=secret_v1 kv # 启用 KV v1 引擎 bao auth enable --path=customuserpasspath userpass # 启用 UserPass 认证 bao namespace create my-namespace # 创建命名空间 bao secrets enable -version=2 --namespace=my-namespace kv # 命名空间内启用 KV v2重新录制测试流量只需执行ESO_PROVIDER_OPENBAO_RERECORD=true go test .(要求bao命令在 PATH 中)。录制时会清理随机值(如 mount accessor)与时间戳,保证 git diff 可读性。
测试覆盖范围(从testdata/http文件名可推断)包括:KV v1 / KV v2 的单密钥、属性提取、版本化读取、全量列举、命名空间认证(TestProvider_BaoNamespaces)以及 UserPass 登录与配置校验等。
总结
OpenBao Provider 是 ESO 生态中对开源密钥管理后端的有力补充:它通过与 Vault Provider 一致的编排模型(SecretStore / ExternalSecret / ClusterSecretStore)降低了学习成本,同时以独立的官方 SDK 实现保证了协议兼容性。当前版本经 ESO v0.16.1 + OpenBao v2.2.0 验证,支持 KV v1/v2 读取、四种认证方式与命名空间多租户,但能力边界为只读。接入生产环境前,建议结合本文的字段说明核对 SecretStore 配置,并通过Validate校验(挂载类型与 KV 版本一致性)尽早暴露配置错误;更多与 Vault 行为一致的语义细节(如metadataPolicy、gjson 嵌套提取),可直接查阅 HashiCorp Vault Provider 文档 与 OpenBao Provider 源码。
【免费下载链接】external-secretsExternal Secrets Operator reads information from a third-party service like AWS Secrets Manager and automatically injects the values as Kubernetes Secrets.项目地址: https://gitcode.com/GitHub_Trending/ex/external-secrets
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考