External Secrets Operator 集成 Passbolt:SecretStore 配置、ExternalSecret 同步与自定义字段全指南
【免费下载链接】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)官方提供的 Passbolt Provider,讲解如何将 Passbolt 密码库中的凭据通过SecretStore与ExternalSecret两个 CRD 同步为 Kubernetes Secret。读完本文,你将掌握 Passbolt SecretStore 的认证与自定义 CA 配置、按 ID 拉取与按名称查找(dataFrom.find)两种取值方式、property精确选取与custom_fields.<name>自定义字段语法,并能结合源码理解每条配置背后的实际行为。
一、Passbolt Provider 概述与集成原理
Passbolt 是一个开源的团队密码管理器,数据采用端到端加密存储。ESO 通过官方 go-passbolt 的包注释中写明:该 Provider 通过 Passbolt REST API 获取存储在其中的秘密(secrets)。
从源码看,整个集成有以下关键行为(providers/v1/passbolt/passbolt.go):
- 只读能力:
Capabilities()返回SecretStoreReadOnly(第 70–72 行),PushSecret、DeleteSecret、GetSecretMap、SecretExists均返回 "not implemented",因此本 Provider 不支持PushSecret等写入场景,只用于单向同步; - 立即登录与会话复用:
NewClient在创建客户端后立即调用client.Login(ctx)(第 111–113 行),后续每次取值前通过assureLoggedIn检查会话,失效时自动重新登录(第 335–340 行); - V5 元数据预取缓存:登录后调用
PreFetchCaches预取会话密钥与元数据密钥,以加速 V5 资源的解密;Close时还会调用SavePendingSessionKeys保存会话密钥(第 115–119、216–221 行); - 注册方式:Provider 通过
NewProvider()、ProviderSpec()与MaintenanceStatus()注册(第 387–401 行),当前维护状态为MaintenanceStatusMaintained(已维护)。
二、创建 Passbolt SecretStore:认证与 Host 配置
ESO 通过Kind=SecretStore定义与 Passbolt 的连接。官方要求passboltProvider 出现在spec.provider下,并且必须配置auth与host。Passbolt API 的认证需要两样东西:用户密码(password)与私钥(private key),二者都存放在一个 Kubernetes Secret 中,通过passwordSecretRef与privateKeySecretRef引用。
完整配置见 docs/snippets/passbolt-secret-store.yaml:
apiVersion: external-secrets.io/v1 kind: SecretStore metadata: name: passbolt spec: provider: passbolt: host: https://passbolt.passbolt.svc.cluster.local auth: passwordSecretRef: key: password name: passbolt-credentials privateKeySecretRef: key: privateKey name: passbolt-credentials字段说明:
spec.provider.passbolt.host:Passbolt 服务地址,必须是 HTTPS 协议。ValidateStore会解析该 URL 并强制校验host.Scheme == "https",否则报错host Url has to be https scheme(providers/v1/passbolt/passbolt.go);spec.provider.passbolt.auth.passwordSecretRef:引用存放 Passbolt 用户密码的 Secret,需指定name与key;spec.provider.passbolt.auth.privateKeySecretRef:引用存放用户私钥的 Secret(即 Passbolt 中该用户用于解密数据的私钥)。
字段类型定义位于 apis/externalsecrets/v1/secretsstore_passbolt_types.go:PassboltAuth由PasswordSecretRef与PrivateKeySecretRef两个SecretKeySelector组成,PassboltProvider由Auth、Host、可选CABundle与可选CAProvider组成。
2.1 SecretStore 校验规则(ValidateStore)
ValidateStore(providers/v1/passbolt/passbolt.go)按顺序校验以下内容,任一不满足都会拒绝该 SecretStore:
| 校验项 | 错误信息 |
|---|---|
spec.provider.passbolt存在 | missing: spec.provider.passbolt |
auth存在 | missing: spec.provider.passbolt.auth |
auth.passwordSecretRef的 name/key 非空 | missing: spec.provider.passbolt.auth.passwordSecretRef |
auth.privateKeySecretRef的 name/key 非空 | missing: spec.provider.passbolt.auth.privateKeySecretRef |
host非空 | missing: spec.provider.passbolt.host |
host可被解析且 scheme 为https | host Url has to be https scheme |
2.2 凭据解析逻辑
NewClient中通过resolvers.SecretKeyRef在 SecretStore 所在命名空间内解析password与privateKey两个引用(providers/v1/passbolt/passbolt.go),随后构造 go-passbolt 客户端。实际认证时,client.Login(ctx)会先向 Passbolt 发起登录请求,之后所有 API 调用均依赖该会话。
三、自定义 CA 证书配置
如果 Passbolt 实例使用了私有 CA 或自定义 CA 签发的证书,需要让 ESO 信任该 CA。两种方式任选其一,见 docs/snippets/passbolt-secret-store-ca.yaml:
apiVersion: external-secrets.io/v1 kind: SecretStore metadata: name: passbolt-with-custom-ca spec: provider: passbolt: host: https://passbolt.example.com # Reference a ConfigMap or Secret containing the CA bundle that signed # the Passbolt server certificate. caProvider: type: ConfigMap name: passbolt-ca-bundle key: ca.crt auth: passwordSecretRef: key: password name: passbolt-credentials privateKeySecretRef: key: privateKey name: passbolt-credentialscaBundle(内联 PEM):直接把 PEM 编码的 CA 证书内容写在spec.provider.passbolt.caBundle中;caProvider(引用外部来源):通过type(ConfigMap或Secret)、name、key引用一个 ConfigMap 或 Secret 中存放的 CA bundle。
如果两者都未设置,ESO 使用系统根证书校验 TLS 连接。底层实现在buildHTTPClient(providers/v1/passbolt/passbolt.go):
- 当
caBundle与caProvider均为空时返回nil,让 go-passbolt SDK 使用默认 HTTP 客户端与系统根 CA; - 否则通过
esutils.FetchCACertFromSource拉取 CA 内容,追加到x509.NewCertPool();若 PEM 解析失败返回failed to parse CA certificate for Passbolt provider; - 克隆默认
http.Transport并仅覆盖TLSClientConfig.RootCAs,同时强制MinVersion = TLS 1.2,保留原有的代理、拨号器、HTTP/2 与空闲连接设置。
四、创建 ExternalSecret 同步 Passbolt 秘密
要把 Passbolt 秘密同步为 Kubernetes Secret,需要创建Kind=ExternalSecret并引用上一步的 SecretStore。默认情况下,同步出的秘密包含name、username、uri、password、description五个标准属性;如需只取其中某一个属性,可以在remoteRef.property中指定。
示例见 docs/snippets/passbolt-external-secret-example.yaml:
apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: passbolt-example-simple spec: refreshInterval: "1h0m0s" secretStoreRef: name: passbolt kind: SecretStore target: name: passbolt-example data: - secretKey: full_secret remoteRef: key: e22487a8-feb8-4591-95aa-14b193930cb4 # Replace with ID of exising Passbolt secret - secretKey: password_only remoteRef: key: e22487a8-feb8-4591-95aa-14b193930cb4 # Replace with ID of exising Passbolt secret property: password # You can limit the secret to only display one property要点说明:
remoteRef.key是Passbolt 资源的 UUID(而非名称),需替换为实际存在的 Passbolt 秘密 ID;remoteRef.property可选,取值范围为name、username、uri、password、description或custom_fields.<name>(详见第六节)。源码GetProp中若传入其他值,会返回property must be one of name, username, uri, password, description, or custom_fields.<name>(providers/v1/passbolt/passbolt.go);- 不指定
property时,GetSecret走esutils.JSONMarshal(secret)分支,返回整个秘密的 JSON 对象(providers/v1/passbolt/passbolt.go)。
4.1 同步结果示例
上述 ExternalSecret 将生成如下形态的 Kubernetes Secret(见 docs/snippets/passbolt-secret-example.yaml):
apiVersion: v1 kind: Secret metadata: name: passbolt-example data: full_secret: '{"name":"passbolt-secret","username":"some-username","password":"supersecretpassword","uri":"passbolt.com","description":"some description"}' password_only: supersecretpassword type: Opaque可以看到:full_secret键保存的是完整 JSON 对象,password_only键则只保存password属性的原始字符串值。
4.2 取值流程(GetSecret 调用链)
GetSecret(providers/v1/passbolt/passbolt.go)的调用链为:
assureLoggedIn确保会话有效;getPassboltSecret(ctx, ref.Key)调用client.GetResource(id)拉取资源;secretFromResource获取资源类型(GetResourceType)与秘密数据(GetSecret),再通过helper.GetResourceFieldMaps解出元数据字段与机密字段,组装成Secret结构体(providers/v1/passbolt/passbolt.go);- 若
property为空返回 JSON 序列化结果,否则调用GetProp取单个属性。
Secret结构体的 JSON 字段名为name、username、password、uri、description与可选的custom_fields(providers/v1/passbolt/passbolt.go),这也正是上面同步结果 JSON 键名的来源。
五、按名称查找秘密(dataFrom.find)
除了按 ID 精确拉取,还可以用dataFrom配合find按名称(正则)搜索 Passbolt 中的秘密。示例见 docs/snippets/passbolt-external-secret-findbyname.yaml:
apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: passbolt-example spec: refreshInterval: "1h0m0s" secretStoreRef: name: passbolt kind: SecretStore target: name: passbolt-example dataFrom: - find: name: regexp: ".*"底层由GetAllSecrets实现(providers/v1/passbolt/passbolt.go),其行为要点:
- 必须提供
find.name.regexp:若ref.Name为空或RegExp为空,直接返回missing: find.name.regexp错误; - 先取全部资源再本地过滤:调用
client.GetResources拉取所有资源,再用编译后的正则nameRegexp.MatchString(secret.Name)按解密后的名称过滤,结果以资源ID -> JSON的 map 形式返回; - V5 加密元数据的性能提示:源码注释明确指出,由于 V5 资源的元数据(含名称)是加密的,每个资源都必须先解密才能过滤,即使不匹配也会被解密,在秘密数量较大时可能影响性能;
- 正则会作用于解密后的名称,因此
regexp: ".*"匹配所有秘密,每个匹配项会以 Passbolt 资源 ID 为键写入目标 Secret。
六、自定义字段(Custom Fields)
Passbolt 资源除了标准属性外,还可以携带任意自定义字段。ESO 通过custom_fields.<name>属性语法暴露这些字段,其中<name>是字段在 Passbolt 中配置的显示名称。
示例见 docs/snippets/passbolt-external-secret-custom-fields.yaml:
apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: passbolt-custom-fields-example spec: refreshInterval: "1h0m0s" secretStoreRef: name: passbolt kind: SecretStore target: name: passbolt-custom-fields data: # Fetch a single custom field by its display name (metadata_key). # The property value is the literal prefix "custom_fields." followed by # the name of the field as configured in Passbolt. - secretKey: api_token remoteRef: key: e22487a8-feb8-4591-95aa-14b193930cb4 # Replace with the ID of an existing Passbolt secret property: custom_fields.api-token - secretKey: deploy_key remoteRef: key: e22487a8-feb8-4591-95aa-14b193930cb4 property: custom_fields.deploy-key # Omitting property returns the full secret as JSON, with custom_fields # included as a nested object keyed by the field display name. - secretKey: full_secret remoteRef: key: e22487a8-feb8-4591-95aa-14b193930cb4其产生的 Kubernetes Secret 形态如下(见 docs/snippets/passbolt-secret-custom-fields-example.yaml):
apiVersion: v1 kind: Secret metadata: name: passbolt-custom-fields data: api_token: my-api-token-value deploy_key: ssh-ed25519-AAAA... full_secret: '{"name":"my-service","username":"deploy","password":"supersecretpassword","uri":"https://example.com","description":"","custom_fields":{"api-token":"my-api-token-value","deploy-key":"ssh-ed25519-AAAA..."}}' type: Opaque关于自定义字段的官方说明与源码印证:
- 不指定
property时返回完整 JSON:custom_fields键只要资源至少包含一个命名自定义字段就会出现在该 JSON 对象中,键名为字段显示名称; - 名称与值均可加密存储:Passbolt 会按字段配置把名称或值存放在明文元数据侧或加密机密侧,ESO 在读取前对两侧都进行解密,因此无论哪种存储方式,字段都可以用显示名称寻址。这在
Secret结构体注释中有明确说明(providers/v1/passbolt/passbolt.go); - 属性解析逻辑:
GetProp中通过strings.CutPrefix(key, "custom_fields.")识别自定义字段前缀,若前缀匹配但字段不存在则返回custom field not found: <name>,否则返回字段值(providers/v1/passbolt/passbolt.go); - 字段组装:自定义字段最终由
helper.ParseCustomFields(metaFields, secretFields).Map()填充到Secret.CustomFields中(providers/v1/passbolt/passbolt.go)。
七、实战小结与注意事项
| 场景 | 使用的 CRD 字段 | 说明 |
|---|---|---|
| 建立连接 | SecretStore.spec.provider.passbolt.host/auth | host 必须 HTTPS;凭据存于 K8s Secret |
| 信任私有 CA | caBundle或caProvider | 不配置则使用系统根证书 |
| 按 ID 同步 | ExternalSecret.spec.data[].remoteRef.key | key 为 Passbolt 资源 UUID |
| 只取单属性 | remoteRef.property | 支持五个标准属性与custom_fields.<name> |
| 按名称查找 | ExternalSecret.spec.dataFrom[].find.name.regexp | 正则匹配解密后的资源名称 |
| 读取自定义字段 | property: custom_fields.<name> | name 为 Passbolt 中的字段显示名称 |
需要记住的限制:
- Passbolt Provider 是只读的,
PushSecret、DeleteSecret等写入操作未实现,相关用法请参考仓库中其他支持读写的 Provider(如 docs/provider/aws-secrets-manager.md); find.name.regexp为必填项,缺失会直接报错;- 按名称查找会对所有资源解密后再过滤,V5 资源较多时需评估性能开销;
- 更完整的 SecretStore / ExternalSecret 通用字段说明可参阅 docs/api/secretstore.md 与 docs/api/externalsecret.md。
本文所有配置示例均可在仓库 docs/snippets 目录中找到对应 YAML,Provider 实现与测试代码位于 providers/v1/passbolt,类型定义位于 apis/externalsecrets/v1/secretsstore_passbolt_types.go,便于读者进一步深入验证。
【免费下载链接】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),仅供参考