diff --git a/README.md b/README.md index 308b795..94fad49 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,9 @@ spec: 更完整的资源见 [`config/samples`](config/samples),初始架构和安全边界见 [`docs/architecture.md`](docs/architecture.md)。 +系统已批准的规范性行为、验收标准和设计决策见 +[`docs/specification.md`](docs/specification.md)。 + 分支、提交、PR 和 CI 约定见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。 Docker/devcontainer、PostgreSQL、OpenBao、envtest 与 Kind 的启动顺序见 [`docs/development.md`](docs/development.md)。 diff --git a/docs/specification.md b/docs/specification.md new file mode 100644 index 0000000..97cfb3b --- /dev/null +++ b/docs/specification.md @@ -0,0 +1,479 @@ +# PostgreSQL Tenant Operator 系统规格说明书 + +| 项目 | 内容 | +| --- | --- | +| 状态 | Approved | +| 目标 API | `database.ddupan.top/v1alpha1` | +| 最后更新 | 2026-09-10 | +| 批准日期 | 2026-09-10 | +| 规范范围 | 首次注册外部 PostgreSQL 实例并创建一个应用租户 | + +本文档定义系统对用户和外部依赖呈现的行为,是 API、测试和实现共同遵守的合同。 +实现若需要改变本文合同,必须先修改规格并重新获得批准。 + +文中的“必须”“禁止”“应当”“可以”分别对应强制要求、强制限制、推荐行为和可选 +行为。 + +## 1. 背景 + +homelab 中的大部分应用共享一个运行在独立 VM 上的 PostgreSQL DBMS。应用需要各自 +独立的 database、owner role、login role 和密码,但不需要独立 PostgreSQL 实例。 +目前这些资源依靠人工 SQL 和人工 Secret 管理,难以重复、审计和检测漂移。 + +本系统使用 Kubernetes CRD 作为声明式 API,持续协调外部 PostgreSQL 与 OpenBao: + +```text +PostgreSQLInstance / PostgreSQLTenant + | + v + postgresql-tenant-operator + | | + v v + PostgreSQL catalog OpenBao KV v2 +``` + +## 2. 目标 + +v1alpha1 必须实现以下目标: + +1. 注册一个已经存在的外部 PostgreSQL 实例并报告连接状态。 +2. 为一个应用租户创建独立 database 和一个同时作为 database owner 的 login role。 +3. 根据实例 allowlist 安装租户申请的 PostgreSQL extension。 +4. 首次生成高强度长期密码,并只把凭据明文写入 OpenBao KV v2。 +5. 为 Kubernetes 应用创建 ExternalSecret,由 ESO 将凭据投射到同 namespace Secret。 +6. 同时输出 OpenBao API URL,使 Kubernetes 外的应用可以直接读取凭据。 +7. 同时输出 PostgreSQL DNS hostname 和 IP address,不假定所有消费者都能使用集群内 + DNS。 +8. 持续检测并修正由本系统管理的非破坏性漂移。 +9. 通过 Kubernetes Condition 报告进度、成功和可操作的失败原因。 +10. 重复 reconcile、controller 重启及外部依赖暂时失败不得重复创建或破坏资源。 +11. 删除 Tenant CR 时默认保留外部资源;显式选择 `Delete` 时提供完整清理路径。 + +## 3. 非目标 + +v1alpha1 不负责: + +- 创建、升级、备份或高可用运行 PostgreSQL DBMS/VM; +- 创建或运维 OpenBao; +- 直接写入包含凭据明文的 Kubernetes Secret;Secret 必须由 ESO 投射; +- 动态凭据、定时或自动密码轮换; +- Web UI、独立 REST API 或 Backstage 插件; +- 跨实例迁移 database; +- schema/table 级别租户、多 login role 或跨租户 grant; +- 删除不属于本系统管理的 database、role、extension 或 OpenBao Secret; +- 接管不是由本系统创建的外部资源; +- 提供生产环境 SLA。 + +## 4. 参与者与事实来源 + +| 对象 | 事实来源 | 说明 | +| --- | --- | --- | +| 期望状态 | Kubernetes CR `spec` | 用户声明的合同 | +| 最近观察结果 | Kubernetes CR `status` | 可以丢失并重建,不是外部事实来源 | +| database/role/grant/extension | PostgreSQL catalog | 每轮 reconcile 必须重新读取 | +| 受管资源所有权与协调阶段 | PostgreSQL controller registry | 与受管 DBMS 一起备份和恢复 | +| 应用凭据 | OpenBao KV v2 | Kubernetes API 中不得出现明文 | +| Kubernetes 凭据投射 | External Secrets Operator | ExternalSecret 由本 controller 管理 | +| PostgreSQL 管理凭据 | OpenBao KV v2 | 由 `PostgreSQLInstance` 引用 | + +平台管理员管理 `PostgreSQLInstance`、controller 部署配置、OpenBao policy 和 +PostgreSQL 管理 role。应用或 GitOps 流程在获得 namespace RBAC 后管理 +`PostgreSQLTenant`。 + +## 5. 资源模型 + +### 5.1 PostgreSQLInstance + +`PostgreSQLInstance` 是 cluster-scoped 资源,表示一个已经存在、可由 controller +管理的 PostgreSQL server。 + +它必须声明: + +- PostgreSQL host、port 和管理连接使用的 database; +- PostgreSQL host address,供无法解析 DNS 的消费者使用; +- TLS mode; +- PostgreSQL 管理凭据在 OpenBao 中的位置和字段名; +- 租户允许申请的 extension 集合。 + +实例 Ready 不代表 PostgreSQL 数据有备份或高可用,只表示 controller 当前可以安全 +建立管理连接、读取 server metadata、访问 controller registry 并使用所需管理能力。 + +### 5.2 PostgreSQLTenant + +`PostgreSQLTenant` 是 namespaced 资源。v1alpha1 中,一个 Tenant 精确对应: + +- 一个 `PostgreSQLInstance`; +- 一个 database; +- 一个同时作为 database owner、供应用使用的 `LOGIN` role; +- 零个或多个 extension; +- 一个 OpenBao KV v2 凭据位置。 +- 一个同 namespace ExternalSecret 及其目标 Kubernetes Secret。 + +Tenant 的 namespace 用于 Kubernetes RBAC 和身份识别,不代表 PostgreSQL schema。 +同一 Instance 中的 database 和 role 名称全局唯一。 + +## 6. 标识与默认值 + +以下是 v1alpha1 的标识合同: + +| 字段 | 默认值 | 约束 | +| --- | --- | --- | +| Instance port | `5432` | 1–65535 | +| Instance host address | 无 | 必须是合法 IPv4 或 IPv6 address | +| 管理 database | `postgres` | 合法 PostgreSQL identifier | +| TLS mode | `verify-full` | 禁止隐式降级 | +| Tenant database | `metadata.name` | 同一 Instance 全局唯一 | +| login role | `metadata.name` | 同一 Instance 全局唯一 | +| deletion policy | `Retain` | `Retain` 或 `Delete` | + +database 和 role 名称必须作为 PostgreSQL identifier 参数安全引用,禁止通过字符串 +拼接执行。名称校验必须拒绝空字符串、NUL 和超过 PostgreSQL identifier 长度限制的 +值,并统一限制为小写字母、数字和下划线。 + +Tenant 首次成功后,`instanceRef`、database、login role 和凭据位置必须 +不可变。修改这些字段不是 rename 或 migration,API 必须拒绝或报告明确的 +`ImmutableField`。 + +## 7. PostgreSQL 权限合同 + +建议的 v1alpha1 权限模型如下: + +1. database 必须由 login role 拥有。 +2. login role 必须是 `LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION`。 +3. 必须撤销 `PUBLIC` 对租户 database 的连接权限,再显式允许 login role 连接。 +4. controller 不得修改其他 database 或无关 role 的权限。 +5. controller 只保证请求的 extension 存在;移除 extension 不得自动执行 + `DROP EXTENSION`。 + +这意味着应用可以在自己的 database 内执行 schema migration,但不能创建其他 +database、role 或访问其他租户。v1alpha1 不创建只有形式意义、却未隔离运行时权限的 +额外 `NOLOGIN` owner。若以后应用能分别使用 migration 和 runtime 凭据,再通过新的 +权限 profile 引入 owner/migrator/runtime 角色模型。 + +## 8. OpenBao 凭据合同 + +### 8.1 Controller 自身认证 + +controller 必须使用 Kubernetes auth 登录 OpenBao。controller 使用的 OpenBao API +address、提供给消费者的 OpenBao API address、auth mount、auth role 和 KV v2 mount +属于部署配置,不属于任何 CR。两个 API address 可以相同;若 controller 使用集群内 +地址而外部消费者不能解析,则必须单独配置 consumer address。生产部署的 KV mount +默认为 `kv`;开发环境可以配置为 OpenBao dev server 默认的 `secret`。长期 OpenBao +Token 禁止写入 Deployment、CR 或镜像。 + +### 8.2 管理凭据 + +`PostgreSQLInstance` 只引用 PostgreSQL 管理用户名和密码所在的 OpenBao KV v2 +mount-relative path。controller 对该路径只需要读取权限。 + +### 8.3 租户凭据 + +Tenant 不声明凭据 path。controller 根据部署级 KV mount、base path 和 Tenant identity +推导唯一的 mount-relative path。推导结果禁止以 `/` 开头,禁止包含空路径段、`.`、 +`..`,也禁止把 KV v2 HTTP API 的 `data` 或 `metadata` 层编码进路径。 + +新 Tenant 的凭据建立顺序必须可从任意中断点恢复: + +1. 验证 Instance、名称、extension 和目标 OpenBao 路径; +2. 确认目标 database、role 和 OpenBao 记录不存在,或能够验证为同一 Tenant + 已创建的部分状态; +3. 生成密码; +4. 先创建带 controller 所有权 metadata 的 OpenBao KV v2 记录; +5. 从 OpenBao 重新读取凭据; +6. 使用该凭据创建作为 database owner 的 login role 和其他 PostgreSQL 资源; +7. 用 login role 实际连接目标 database; +8. 全部验证成功后将 Tenant 标记 Ready。 + +若第 4 步成功、后续 PostgreSQL 操作失败,下一轮必须读取同一份 OpenBao 凭据继续, +不得生成第二个密码。若 PostgreSQL 先存在而 OpenBao 记录不存在,controller 必须报告 +Conflict,不得擅自重置已有 role 密码。 + +controller 必须在 PostgreSQL 管理 database 的专用 registry schema 中持久保存可验证的 +Instance UID、Tenant UID 与 namespace/name 关联,不能只依赖会丢失的 CR status 判断 +资源所有权。registry 必须可回读且不得改变数据库授权语义;database 或 role COMMENT +不能作为权威所有权记录。 + +默认写入字段固定为: + +```text +username +password +database +host +hostaddr +port +sslmode +``` + +这些字段是 controller 的规范化输出合同。controller 不生成包含密码的 URI、JDBC URL +或应用专用键名。应用通过 ExternalSecret template、Helm values 或自身配置把原子字段 +映射为 `DATABASE_URL`、独立环境变量或配置文件;因此 URI escaping 和应用特有格式也 +由消费方负责。`host` 是 DNS 名称,`hostaddr` 是可直接连接的 IP;消费者自行选择其 +支持且可达的连接目标。PostgreSQL server 证书必须同时包含与 `host` 匹配的 DNS SAN +和与 `hostaddr` 匹配的 IP SAN,使两种目标都能在 `verify-full` 下独立完成身份验证。 + +### 8.4 凭据输出与 ExternalSecret + +controller 必须根据部署级 base path 推导 Tenant 的 KV path,Tenant 不能选择 mount 或 +任意远端路径。Tenant 可以指定目标 Kubernetes Secret 名称;省略时默认为 +`-postgresql`。 + +controller 必须创建同 namespace ExternalSecret,从固定的 ClusterSecretStore 读取七个 +原子字段。ExternalSecret 及目标 Secret 的名称通过 Tenant status 暴露。controller +不得直接读取 OpenBao 密码后写入 Kubernetes Secret。 + +Tenant status 还必须提供完整、可由外部消费者使用的 OpenBao KV v2 API URL。URL 可以 +包含 consumer API address、mount 和 secret path,但不得包含 Token、密码或其他认证 +信息。默认 `kubectl get` 表格显示目标 Secret 名称;完整 OpenBao URL 通过 +`kubectl get postgresqltenant -o yaml` 获取,避免表格列过长。 + +OpenBao metadata 必须能够标识 Tenant UID、namespace/name 和 Instance,使 controller +区分自己的残留记录与外部记录。任何凭据值都不得进入日志、Event、Condition、metric +label、trace、CR spec/status 或测试快照。 + +## 9. Reconcile 行为 + +系统采用最终一致性模型。Kubernetes、PostgreSQL 和 OpenBao 可以短暂处于不同阶段; +controller 不尝试实现跨系统事务,而是通过 PostgreSQL 中持久化的协调阶段、幂等外部 +操作和每轮回读验证最终收敛。 + +每个 Tenant 的 registry 记录至少经历以下单向阶段: + +```text +Planned -> CredentialCreated -> RoleCreated -> DatabaseCreated + -> ExternalSecretCreated -> CredentialProjected -> Ready +``` + +阶段用于恢复进度,但不能替代实际状态检查。controller 重启后必须同时检查 registry、 +PostgreSQL catalog 和 OpenBao,再决定继续、保持 Ready 或报告 Conflict。 + +每轮 Tenant reconcile 必须按以下逻辑执行: + +```text +读取 Tenant + -> 读取 Instance + -> 校验不可变字段与输入 + -> 检查 Instance Ready + -> 读取 OpenBao 与 PostgreSQL 实际状态 + -> 检测冲突或部分完成状态 + -> 执行非破坏性补齐 + -> 使用应用凭据验证登录 + -> 创建并验证 ExternalSecret/Secret 投射 + -> 回读实际状态 + -> 更新 status +``` + +要求: + +- 所有步骤必须幂等; +- 每个外部写入前必须先持久化足够的操作意图,写入后必须回读并推进 registry 阶段; +- 暂时性网络、锁和依赖错误必须重试; +- 输入错误、资源冲突和禁止操作不得忙循环重试,只在 generation 或依赖状态变化后 + 重试; +- 未知外部资源不得被修改、接管或删除; +- 用户从 `spec.extensions` 移除 extension 时不得执行卸载,必须报告该字段在 v1alpha1 + 中只允许追加; +- controller 重启不得影响已经签发的应用密码; +- `status` 丢失后必须可以从 PostgreSQL 和 OpenBao 重建。 + +## 10. Condition 合同 + +两个资源都必须提供唯一的 `Ready` Condition。可以增加辅助 Condition,但调用方只需 +依赖 `Ready`。 + +| 状态 | 含义 | +| --- | --- | +| `Ready=Unknown` | 正在首次观察或 reconcile,尚无结论 | +| `Ready=False` | 当前 generation 未达到合同要求 | +| `Ready=True` | 当前 generation 已回读验证成功 | + +Condition 必须带正确的 `observedGeneration`。资源自身的 +`status.observedGeneration` 只在当前 generation 完成一次有结论的 reconcile 后更新。 + +最低 Reason 集合: + +| Reason | 适用资源 | 含义 | +| --- | --- | --- | +| `Reconciling` | 两者 | 尚在处理 | +| `Ready` | 两者 | 当前 generation 已验证 | +| `InvalidSpec` | 两者 | 输入不满足规格 | +| `DependencyUnavailable` | 两者 | PostgreSQL 或 OpenBao 暂时不可用 | +| `AuthenticationFailed` | Instance | 管理凭据或 TLS 验证失败 | +| `InsufficientPrivileges` | Instance | 管理 role 缺少必要权限 | +| `InstanceNotReady` | Tenant | 引用的 Instance 未 Ready | +| `Conflict` | Tenant | 目标名称或 OpenBao 路径已被其他主体占用 | +| `ProvisioningFailed` | Tenant | 可重试的创建/验证失败 | +| `CredentialProjectionFailed` | Tenant | ESO 或目标 Secret 未达到期望状态 | + +Condition message 必须适合人类排障,但禁止包含连接串密码、Token 或完整 Secret 数据。 + +## 11. 删除与保留 + +### 11.1 Retain + +`Retain` 是默认策略: + +- 删除 Tenant CR 不得删除 database、role、extension 或 OpenBao 记录; +- controller 不得因外部依赖不可用而永久阻止 Retain CR 删除; +- 保留资源必须继续携带原 Tenant UID 和 namespace/name 的所有权记录,但在 CR 删除后 + 明确处于 unmanaged 状态; +- 重新创建同名 Tenant 会产生新的 UID,必须因已有资源不属于新 UID 而报告 Conflict; +- v1alpha1 不提供重新关联、import 或 adoption;恢复管理必须使用第 12 节的迁移流程, + 或等待后续版本定义显式纳管协议。 + +### 11.2 Delete + +用户在创建 Tenant 时显式设置 `deletionPolicy: Delete`,表示删除 CR 时授权永久清理 +该 Tenant 的外部资源。controller 必须使用 finalizer,并按以下顺序处理: + +1. 再次验证 database、role 和 OpenBao 记录都属于当前 Tenant UID; +2. 删除 ExternalSecret,并确认目标 Kubernetes Secret 已删除; +3. 禁止该 login role 建立新连接; +4. 终止该 database 的现有连接; +5. 删除 database,database 内 extension 随之删除; +6. 删除 login role; +7. 删除 OpenBao KV 记录及其可恢复版本; +8. 回读确认外部资源均不存在; +9. 删除 controller registry 记录; +10. 移除 finalizer,允许 Kubernetes 删除 CR。 + +任一步失败都必须保持 finalizer 并从安全检查开始重试。controller 禁止使用 +`CASCADE` 删除无法证明属于该 Tenant 的依赖对象。若 Instance 或 OpenBao 永久丢失, +管理员可以在核实外部状态后手工移除 finalizer;该逃生操作必须在运维 runbook 中明确 +标记为可能遗留资源。 + +v1alpha1 不自动检查备份,也不承诺恢复被 `Delete` 删除的数据。显式选择 Delete 的 +用户承担数据销毁语义;默认 Retain 用于避免普通误删。 + +## 12. 现有环境迁移 + +v1alpha1 不接管现有 database 或 role,但必须提供可重复、可回滚的迁移 runbook。对每 +个现有应用租户,推荐的停机迁移顺序是: + +1. 盘点 database、role、owner、grant 和 extension,并完成可恢复备份; +2. 创建逻辑备份,必须使用可映射到新 owner 的格式,避免恢复旧 role ownership; +3. 停止应用写入并确认没有活动写事务; +4. 完成最终逻辑备份; +5. 将旧 database 和 role 重命名为带迁移时间戳的保留名称,释放最终名称; +6. 创建 `PostgreSQLTenant`,由 controller 创建最终 database、role 和 OpenBao 凭据; +7. 等待 Tenant Ready; +8. 以新 owner 恢复逻辑备份,并验证 row count、schema、extension 和应用权限; +9. 让 ESO 投射新凭据,重启或重新部署应用; +10. 验证应用读写后结束维护窗口; +11. 保留旧 database、role 和备份直到回滚窗口结束,再由管理员手工清理。 + +回滚时停止新应用写入、恢复原名称或连接配置,并重新使用旧凭据。迁移工具不得把旧 +密码、管理凭据或 dump 文件提交到 Git。真实命令、锁定方式和各现有应用验证项在实现 +首个可用版本前写入独立 `docs/migration.md` 并通过临时 PostgreSQL 实例演练。 + +## 13. 安全要求 + +1. 所有 PostgreSQL 与 OpenBao 网络访问必须支持超时和 context cancellation。 +2. homelab 部署必须通过 Deployment 挂载的共享 CA bundle 验证 TLS server identity; + 该 bundle 的信任根来自 OpenBao PKI,但不得包含 CA 私钥。Instance 默认使用 + `verify-full`,其 host 必须与服务器证书名称匹配。开发环境可以显式使用 `disable` + 明文连接。 +3. PostgreSQL 管理 role 应使用满足本规格的最小权限,不应使用 PostgreSQL + superuser;若 extension 安装需要额外权限,必须单独记录例外。 +4. OpenBao policy 必须限制为:读取已登记的管理凭据范围,以及创建/读取本 controller + 管理的租户 KV 范围。 +5. namespace 用户不得修改 cluster-scoped Instance。 +6. 所有 identifier、extension name 和引用字段必须在发起外部调用前校验。 +7. controller 不得通过 shell 或 `psql` 子进程执行用户输入。 +8. 错误包装、结构化日志和 tracing 必须经过 Secret 泄露测试。 + +详细威胁模型和部署 policy 将在 `docs/security.md` 中定义。 + +## 14. 可观测性要求 + +v1alpha1 至少必须提供: + +- Kubernetes Events:开始 provisioning、成功及需要人工处理的失败; +- 结构化日志:resource namespace/name、Instance、generation、阶段和错误类别; +- controller-runtime 默认 reconcile metrics; +- 不包含 database、role、OpenBao path 等无界用户输入的低基数失败分类 metric。 + +日志和 metrics 的存在不能代替 Condition;Condition 是 API 使用者判断状态的主要方式。 + +## 15. 验收标准 + +实现 v1alpha1 第一条完整纵向切片前,测试必须覆盖: + +1. 有效 Instance 可以建立 TLS 管理连接并变为 Ready。 +2. PostgreSQL 或 OpenBao 暂时不可用时 Ready=False,恢复后自动变为 Ready。 +3. 有效 Tenant 创建 database、作为 owner 的 login、grant、extension 和 OpenBao + 记录。 +4. 应用凭据可以实际连接且不能创建其他 database/role。 +5. 相同 generation 重复 reconcile 不改变密码、不重复创建资源。 +6. controller 在每个外部写入步骤后中断,重启后都能继续并得到相同最终状态。 +7. 预先存在且不属于当前 Tenant UID 的 database、role 或 OpenBao path 导致 + Conflict,且不修改已有资源。 +8. 未在 allowlist 的 extension 在任何外部写入前被拒绝。 +9. status 被清空后可以从两个外部事实来源重建。 +10. 删除 Retain Tenant 后外部资源仍存在且不再受管;重新创建同名 Tenant 报告 + Conflict。 +11. 日志、Event、Condition、metric 和 CR 中不存在生成的密码或管理凭据。 +12. 两个 namespace 对同一 Instance 申请相同名称时,只有第一个成功,第二个报告 + Conflict。 +13. 删除 Delete Tenant 时,任一步骤失败都可重试,且最终删除 database、login role、 + OpenBao KV 历史和 finalizer。 +14. 使用迁移 runbook 可以把一个现有 database 转移到新建的受管 database,并在回滚 + 窗口内恢复旧服务。 +15. Tenant 只有在 ExternalSecret Ready、目标 Secret 存在且应用凭据实际可登录后才 + Ready。 +16. Tenant status 同时提供 Kubernetes Secret reference 和不含认证信息的 OpenBao API + URL。 +17. DNS 不可用时,使用输出的 `hostaddr` 可以连接 PostgreSQL;server 证书同时覆盖 + `host` 的 DNS SAN 和 `hostaddr` 的 IP SAN,两种连接目标均可通过 `verify-full`。 + +单元测试验证纯决策逻辑,adapter 集成测试使用 Docker PostgreSQL/OpenBao,controller +集成测试使用 envtest,完整网络路径使用 Kind E2E。 + +## 16. 已确认决策 + +- v1alpha1 使用一个同时作为 database owner 的 login role,不创建额外 NOLOGIN owner。 +- v1alpha1 不接管任意现有资源,但必须提供并演练 dump/restore 迁移路径。 +- v1alpha1 同时实现默认 `Retain` 和显式 `Delete`;Delete 必须有 finalizer、所有权验证 + 和完整清理路径。 +- OpenBao KV v2 mount 和 base path 是 controller 部署配置,mount 默认 `kv`;Tenant + 不能选择 mount 或任意远端 path,controller 根据 Tenant identity 推导记录路径。 +- 租户 KV 记录固定写入 `username/password/database/host/hostaddr/port/sslmode` 七个 + 原子字段; + controller 不生成连接 URI,应用负责映射和拼装自身配置。 +- PostgreSQL TLS 使用 controller Deployment 挂载的共享 CA bundle。OpenBao PKI 是 + CA 权威并继续签发、续期 PostgreSQL server 证书;controller 只消费公开 trust + bundle,不接触 CA 私钥。bundle 可以由 ConfigMap 或现有证书同步机制投射,不允许 + Tenant 或 Instance 选择其他 CA;开发环境可以显式使用 `sslMode: disable`。 +- 每个 PostgreSQLInstance 在其管理 database 中维护 controller 专用 registry schema。 + registry 是受管资源所有权和协调阶段的权威记录;Tenant status 只是观察缓存, + Instance status 不聚合 Tenant 清单。 +- PostgreSQL database 和 role identifier 必须匹配 `^[a-z][a-z0-9_]{0,62}$`,不支持 + 需要双引号的大小写或特殊字符名称。 +- External Secrets Operator 是 v1alpha1 的运行依赖。controller 管理同 namespace + ExternalSecret,但不直接写明文 Secret;Tenant status 同时输出目标 Secret reference + 和供非 Kubernetes 消费者使用的 OpenBao API URL。 +- PostgreSQLInstance 同时声明 DNS `host` 和 IP `hostaddr`;PostgreSQL server 证书必须 + 同时包含对应 DNS SAN 和 IP SAN,消费者自行选择连接目标。 + +## 17. 批准状态 + +具体设计决策和本文整体已于 2026-09-10 获得批准,可以进入 API reference、测试和 +实现阶段。 + +## 18. 与当前脚手架的已知差异 + +当前 API skeleton 至少需要以下调整: + +- 删除 Tenant 自选 OpenBao path 的能力,改由部署级 mount、base path 和 Tenant + identity 推导,并修正当前包含 `kv/` 前缀的示例; +- 增加 controller 部署级 OpenBao KV mount 和 TLS 配置; +- 增加部署级 OpenBao consumer address、ClusterSecretStore 和 KV base path 配置; +- 删除独立 `ownerRole` 字段,使 login role 成为 database owner; +- 为 Instance 增加 `hostaddr`,为 Tenant 增加目标 Secret 配置及凭据输出 status; +- 按已确认的 identifier 合同收紧校验; +- 增加 PostgreSQL controller registry,记录基于 UID 的所有权和最终一致性协调阶段; +- 修正凭据 type 中遗留的 rotation 注释; +- 使 Condition、不可变字段和 extension 追加语义具备 API 校验或明确的 reconcile + 结果。 + +这些是规格批准后的实现工作,不属于本规格本身。