mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
feat(ql3): add short-lived security administration command
This commit is contained in:
@@ -6,10 +6,12 @@
|
||||
- 目标版本:QingLong 3.x
|
||||
- 作者:QingLong Maintainers
|
||||
- 创建日期:2026-07-17
|
||||
- 最后更新:2026-08-24
|
||||
- 最后更新:2026-08-25
|
||||
- 讨论范围:架构与演进路线,不包含最终 UI 视觉方案
|
||||
|
||||
最新增量证据(2026-08-24):
|
||||
最新增量证据(2026-08-25):
|
||||
|
||||
- D-405/ADR-0500(已验收):Cluster Identity/API Credential/Security Audit 现在有首个受审产品入口。`ql3-security-admin` 由既有 `ql3-cluster-admin security` facade 到达同一安装内的固定 target,不新建单文件 package,也不进入常驻 `cluster-control`;每次只执行一个 exact-shape 的 identity register/enable/disable、credential issue/rotate/revoke 或最多 200 条的 keyset audit query,随后关闭单连接 admin PostgreSQL authority。命令使用独立 `ql3-security-administration+jwt` type、`security-administration` purpose、audience 与 generation/revocation ledger,拒绝其他管理面的 assertion;command、assertion、keyset 与 pepper 均来自显式有界文件,不发现 home、ambient Kubernetes context 或默认 credential。issue/rotate token 只经私有目录内 `0600`、`fsync`、hard-link no-replace 文件交付,stdout 仅含 basename 与 SHA-256;精确重放的 `token=null` 不重新生成或发布 secret。默认 PostgreSQL `verify-full`,Pool 最大一个连接,无 listener、daemon、timer、watcher 或后台 retention。`cluster-admin` 完整回归为 `448 total / 445 pass / 3 conditional skip / 0 fail`,新增聚焦测试 19/19、产品 facade 4/4、backend product contract 4/4;18-package clean build/test 退出 0,backend 为 `1575 total / 1573 pass / 2 conditional skip / 0 fail`。真实 arm64 Admin 镜像在 non-root、read-only、无网络/能力、128 MiB/0.25 CPU 下通过 12-command live contract;PostgreSQL 18.6 arm64 physical HA timeline 1→2 的 147 gates 全部通过,报告 SHA-256 为 `8fbb606773080dae15de5e31db5726abb8700862b51e4616b5f3f50e0b8374f3`。package、Cluster dependency、122-module Edge import、部署、镜像与 service bridge 审计全部 compatible,Edge/Standalone package、依赖和常驻资源不变。本切片关闭 ADR-0050“只有 application service、没有受审产品入口”的缺口,但不冒充远程 HTTP/API/UI、双人复核/break-glass、pepper rotation、audit retention/export/alert 或完整 Kubernetes Job ceremony。
|
||||
|
||||
- D-404/ADR-0499(已验收):Cluster Worker 现在有可选的直接外部 Secret custody adapter,而不再只能依赖 Kubernetes Secret value projection。`vault-kv-v2` 位于既有 `@qinglong/cluster-control` Remote Execution 子域,只有显式选择 provider 的 Cluster 进程才动态加载;基础部署继续使用 `mounted-files`,Edge/Standalone 不新增 package、依赖、daemon、timer、watcher、连接池或常驻内存。adapter 只在 durable Run/Attempt/Lease/Worker Session/execution digest/SecretRef authority 通过后,用 `SHA-256(canonical SecretRef)` 路径读取 KV v2;只接受显式私有 CA 的 TLS 1.3、每次重新打开的短期 orphan/non-renewable service token 和唯一精确 policy,不跟随 redirect、不使用系统 CA 回退、不缓存值或 token。Kubernetes overlay 删除 value Secret projection,只挂载 CA 与 token;普通 Secret、opaque environment bundle 和总响应仍受原 16/96/256 KiB 边界约束,空 Secret 保持合法,异常 envelope、metadata、digest、token、TLS 或 Vault availability 均失败关闭且不回退。真实 arm64 Vault 1.21.4 gate 已完成 3-share/2-threshold init、两个普通 Secret 与一个 bundle、value/token 原子轮换、旧 accessor revoke、缺失 material、不可信 CA、seal/unseal 与同持久存储容器替换;capability-free `0600` content-free 报告 SHA-256 为 `281fe542e1bf6078132216b9701a28e76f36dcbccc95367997d8457773a9c210`,audit 为 `compatible=true/findings=[]`。live fixture 以非 root、read-only rootfs、`cap-drop=ALL`、零新增 capability、`no-new-privileges` 与 `memory-swappiness=0` 运行,并为原生 hosted runner 的 capability/lock 差异显式关闭 fixture mlock;生产 Vault host 的 swap/mlock 硬化不由该门冒充。Cluster Control 为 `279 total / 277 pass / 2 conditional skip / 0 fail`,backend 为 `1574 total / 1572 pass / 2 conditional skip / 0 fail`,18-package clean build/test 退出 0;package、Cluster dependency、122-module Edge import、部署和 14 档 Local artifact 审计全部 compatible,基础 Edge/Standalone 仍为 `2,669,390 / 2,669,468 bytes`、325 files、58 modules。共享 CI 新增原生 x64/arm64 live matrix。该 fixture 关闭 QingLong 直接 custody adapter/data-boundary 门,不冒充生产 Vault HA、KMS/HSM seal、审计设备或灾备证明;ADR-0491 现在只剩固定低性能物理 Edge 的真实空间、RSS/I/O、写放大、ENOSPC 与断电恢复门。
|
||||
|
||||
@@ -10074,7 +10076,7 @@ PR-8 的本机最新增量由 ADR-0075/0076/0077/0078/0079/0080/0081/0082/0083/0
|
||||
|
||||
ADR-0087 Owner package 更新同样适用于上段 PR-8 累计描述:现行产品 CLI 只能经 console facade 到达其内部 bootstrap/credential-recovery,三个历史 ceremony package 名都只表示旧切片;`ql3-owner-gc` 由 maintenance 直接提供,不再拥有独立 importer。
|
||||
|
||||
PR-8 的 cluster ADR-0049 未完成项由 ADR-0050/0051 部分收敛:Identity register/enable/disable、credential issue/rotate/revoke、mutation ledger、强 actor、同事务 audit、有界 audit query,以及常驻 `/api/v3` 的认证前 overload shield 已孵化完成,但仍默认没有远程管理入口。当前 Gate 仍要求受审 CLI/API/UI、部署 ceremony、管理入口独立 rate limit、双人复核或 break-glass、pepper rotation、audit retention/export/alert;不得把 application service 或 process-local HTTP shield 的存在解释为 cluster-control 已获得管理 authority 或全局 quota。
|
||||
PR-8 的 cluster ADR-0049 未完成项由 ADR-0050/0051/0500 继续收敛:Identity register/enable/disable、credential issue/rotate/revoke、mutation ledger、强 actor、同事务 audit、有界 audit query,以及常驻 `/api/v3` 的认证前 overload shield 已孵化完成;D-405 新增默认无 listener、一次只执行一个命令并关闭单连接 admin authority 的 `ql3-security-admin` 产品 CLI,credential token 只向私有 no-replace 文件交付。当前 Gate 仍要求远程 API/UI 或完整 Kubernetes Job ceremony、管理入口独立 rate limit、双人复核或 break-glass、pepper rotation、audit retention/export/alert;不得把短生命周期 CLI、application service 或 process-local HTTP shield 的存在解释为 cluster-control 已获得管理 authority 或全局 quota。
|
||||
|
||||
未进入当前孵化切片的代码在通过对应 Gate 前必须保持不可达:不得仅因 schema、service 或 Primary 编排器已存在,就让旧 Controller、Scheduler、gRPC callback 或 Shell 脚本直接写入新状态表或调用新 Executor。已接入的 Shadow 观察只能通过默认关闭的 Feature Flag 和 origin owner 决策到达,不得调用 Executor、再次 spawn 或改变 Legacy 返回结果。manual `runSingle` 只增加 owner selection seam;默认没有 router。HTTP bootstrap 每次启动只读取一次 manifest,缺失、禁用、拒绝或非 primary 时保持 Legacy 且不加载重组件;显式 accepted manual primary 会在恢复门禁通过后安装唯一 owner,选中后禁止回退双跑。ADR-0445 已让 `ScheduleService.runTask` 的 subscription/system/script 在显式 origin flag 下只观察同一个 Legacy ChildProcess;system crond 的 `scheduled_system`、once/boot/grpc 与这些来源的 Primary owner 切换仍须独立门禁。
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# ADR-0500:短生命周期 Cluster Security Administration 产品命令
|
||||
|
||||
- 状态:Accepted
|
||||
- 日期:2026-08-25
|
||||
- 决策:D-405
|
||||
- 关联:ADR-0049、ADR-0050、ADR-0051、ADR-0276、ADR-0301
|
||||
|
||||
## 背景
|
||||
|
||||
ADR-0050 已把 Identity register/enable/disable、API credential issue/rotate/revoke、不可变 mutation ledger、强 actor、同事务 audit 与有界 audit query 收口到独立的 `@qinglong/cluster-admin` authority,并明确禁止常驻 `cluster-control` 获得 admin 数据库角色。但原切片只有 application service,没有受审产品入口。部署用户只能自行编写 composition root,容易重新引入常驻高权限进程、宽泛数据库连接池、secret stdout 或未经隔离的认证用途。
|
||||
|
||||
QingLong 3.0 同时面向低性能路由设备和 Cluster 节点。该入口只属于 Cluster Admin 镜像;不能进入 Edge/Standalone 制品,也不能为了操作便利新增 listener、daemon、timer、watcher 或常驻连接池。
|
||||
|
||||
## 决策
|
||||
|
||||
### 1. 在既有 Cluster Admin package 内增加短生命周期命令
|
||||
|
||||
`@qinglong/cluster-admin` 增加 `ql3-security-admin`,并由 `ql3-cluster-admin security` facade 到达同一安装内的固定目标。它继续位于 ADR-0301 已确定的 `security-administration/` 领域目录,不为一个共享相同镜像、依赖、升级和故障生命周期的 composition root 新建 workspace package。纯命令协议/runner 与 POSIX/PostgreSQL production runtime 分成两个领域内文件,避免再形成根目录平铺或单个超大 composition 文件。
|
||||
|
||||
命令不监听端口,也不读取 home、ambient Kubernetes context 或默认凭据。每次调用只执行一个 exact-shape JSON operation,然后关闭 PostgreSQL authority:
|
||||
|
||||
- `identity.register`、`identity.enable`、`identity.disable`;
|
||||
- `credential.issue`、`credential.rotate`、`credential.revoke`;
|
||||
- `audit.list`。
|
||||
|
||||
未知字段、相对路径、非规范路径、无界 audit query 和 operation/request shape 混淆都在获得数据库 authority 前失败关闭。
|
||||
|
||||
### 2. 独立强身份用途
|
||||
|
||||
管理命令只接受 dedicated JWT assertion profile:
|
||||
|
||||
- `typ=ql3-security-administration+jwt`;
|
||||
- `purpose=security-administration`;
|
||||
- 独立 audience 与 keyset generation/revocation ledger;
|
||||
- 复用既有强认证规则,只接受当前、未撤销且满足 assurance 的 principal。
|
||||
|
||||
Plugin Package、Worker Credential、Automation、Approval、Run、Provider Credential 或其他管理面的 assertion 不能跨用途复用。assertion、keyset、pepper 与 command 都必须是显式绝对文件;私有材料拒绝 group/world 权限,读取时拒绝 symlink、大小越界和读中变更。
|
||||
|
||||
### 3. Admin 数据库 authority 保持一次性和最小化
|
||||
|
||||
命令使用独立的 `QL3_POSTGRES_ADMIN_*` 配置,只打开 admin role,Pool 上限为一个连接,并设置短 connection/idle/lifetime 边界。默认要求 PostgreSQL `verify-full` 与显式 DNS server name;只有同时指定 `TLS_MODE=disable` 和明确的不安全 opt-in 才允许测试环境禁用 TLS。
|
||||
|
||||
启动先验证 ADR-0050 admin schema/readiness,随后复用既有 Repository 与 `ClusterAdministrationService`。成功、拒绝、异常和 readiness 失败都关闭数据库 authority。runtime、migration 与 admin credential 仍不得混用。
|
||||
|
||||
### 4. Credential 只交付到私有 no-replace 文件
|
||||
|
||||
issue/rotate 的一次性 bearer token 不写 stdout、stderr、日志、audit 或命令结果。调用者必须提供一个尚不存在的 delivery path,其父目录必须是真实私有目录。publisher 在同目录创建 `0600` 临时文件,完整写入并 `fsync`,再以 hard-link no-replace 发布并同步父目录;已有目标永不覆盖。
|
||||
|
||||
stdout 只返回 delivery basename 与内容 SHA-256。语义重放仍遵循 ADR-0050:数据库返回 `token=null` 时不得重新生成或重新发布材料。若首次成功后的交付响应丢失,操作者只能以新的 mutation ID 执行 rotate,不能恢复旧 secret。
|
||||
|
||||
### 5. 本切片不是远程管理平面
|
||||
|
||||
该命令关闭“已有 administration service 但没有受审产品入口”的缺口,但不增加 HTTP/API/UI、远程 listener、全局 quota 或后台 retention/export。生产使用仍应由一次性 Job、受控运维工作站或等价的短生命周期执行环境注入 assertion、keyset、pepper 与 admin database credential,并在完成后销毁运行环境。
|
||||
|
||||
双人复核、break-glass、pepper rotation、audit retention/export/alert 和完整 Kubernetes Job ceremony 仍是后续门禁,不能由本命令的存在推断为已经完成。
|
||||
|
||||
## 被拒绝的替代方案
|
||||
|
||||
### 把命令加入 Cluster Control HTTP API
|
||||
|
||||
拒绝。它会让常驻业务进程获得 admin 数据库权限,并把 credential 签发和 audit 读取暴露到远程攻击面。
|
||||
|
||||
### 新建一个 workspace package 或常驻 Admin Deployment
|
||||
|
||||
拒绝。该 composition root 与 `cluster-admin` 共享镜像、依赖、权限和升级生命周期;拆包只会制造单文件 package,常驻 Deployment 则扩大高权限驻留时间和资源占用。
|
||||
|
||||
### 把 token 返回 stdout 或保存进 PostgreSQL
|
||||
|
||||
拒绝。shell history、日志采集、CI 输出和数据库备份都会成为 bearer custody 面,且破坏 ADR-0050 的不可恢复语义。
|
||||
|
||||
### 自动发现默认 keyset、pepper 或数据库凭据
|
||||
|
||||
拒绝。ambient authority 会让同一命令在工作站、Pod 和路由设备上取得不同且不可审计的权限来源。
|
||||
|
||||
## 验证
|
||||
|
||||
- 新命令测试覆盖强身份注入、authority 必关闭、敏感 Buffer 清零、credential 私有交付、精确重放不再发布、有界 audit query、widened shape 拒绝,以及真实 `0600` no-replace 文件语义。
|
||||
- keyset 测试证明 Security Administration 的 type、purpose 与 audience 不能和其他管理面混用。
|
||||
- product facade 与 admin-image live contract 冻结固定 target、无 shell delegation、`--help` 和无网络/只读容器运行边界。
|
||||
- `@qinglong/cluster-admin` 完整回归为 448 total / 445 pass / 3 conditional skip / 0 fail。
|
||||
- 18-package clean build/test 退出 0;backend 为 1575 total / 1573 pass / 2 conditional skip / 0 fail。
|
||||
- 真实 arm64 Cluster Admin 镜像在 non-root、read-only rootfs、`network=none`、`cap-drop=ALL`、`no-new-privileges`、32 PID、128 MiB 和 0.25 CPU 下通过 12 个产品命令 live contract。PostgreSQL 18.6 arm64 physical HA 在 timeline 1→2 promotion 后通过 147 gates,content-free report SHA-256 为 `8fbb606773080dae15de5e31db5726abb8700862b51e4616b5f3f50e0b8374f3`。
|
||||
- package boundary 保持 18 packages、`singleSourcePackages=[]`、`shallowSourcePackages=[]`;Cluster dependency、122-module Edge import、Cluster deployment、image release 与 service-manager bridge 审计全部 compatible。
|
||||
|
||||
## 影响与剩余门禁
|
||||
|
||||
D-405 提供了第一个可直接使用、默认无 listener 的 Cluster Identity/API Credential/Audit 管理入口,同时不改变 Edge/Standalone package、依赖或常驻资源。它只关闭 ADR-0050 的产品入口缺口;远程管理 API/UI、双人复核或 break-glass、pepper 生命周期、audit retention/export/alert 和生产部署 ceremony 仍需后续 ADR 独立证明。
|
||||
@@ -503,6 +503,7 @@
|
||||
| [ADR-0497](./ADR-0497-atomic-cluster-legacy-env-migration-application.md) | Cluster Legacy Env 的原子 Task/Trigger 迁移与只追加回执 | Accepted |
|
||||
| [ADR-0498](./ADR-0498-cluster-legacy-env-application-ha-replay.md) | Cluster Legacy Env Application 的 HA Promotion 后精确重放 | Accepted |
|
||||
| [ADR-0499](./ADR-0499-direct-vault-kv-worker-secret-custody.md) | 直接 Vault KV Worker Secret 外部托管 | Accepted |
|
||||
| [ADR-0500](./ADR-0500-short-lived-cluster-security-administration-command.md) | 短生命周期 Cluster Security Administration 产品命令 | Accepted |
|
||||
|
||||
## 规则
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# QingLong 3.0 Cluster Security Administration
|
||||
|
||||
该命令是 Cluster Identity、API Credential 和 Security Audit 的短生命周期管理入口,不适用于 Edge/Standalone,也不会启动 HTTP listener。每次进程只执行一个命令,最多打开一个 admin PostgreSQL 连接,然后退出。
|
||||
|
||||
## 私有输入
|
||||
|
||||
准备一个仅当前操作者可访问的目录;command、assertion、keyset 和 pepper 必须使用规范绝对路径,私有文件不能向 group/world 开放。credential issue/rotate 还需要一个已存在的私有 delivery 目录,但目标文件必须不存在。
|
||||
|
||||
keyset 使用既有 generation/revocation 协议,例如:
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"generation": 1,
|
||||
"issuer": "https://identity.example.test/",
|
||||
"audience": "qinglong3-security-administration",
|
||||
"keys": [
|
||||
{
|
||||
"alg": "EdDSA",
|
||||
"crv": "Ed25519",
|
||||
"kid": "REPLACE_WITH_KEY_ID",
|
||||
"kty": "OKP",
|
||||
"use": "sig",
|
||||
"x": "REPLACE_WITH_ED25519_PUBLIC_JWK_X"
|
||||
}
|
||||
],
|
||||
"revokedKids": [],
|
||||
"assuranceMappings": [
|
||||
{
|
||||
"acr": "urn:example:mfa",
|
||||
"assurance": "multi_factor",
|
||||
"requiredAmr": ["pwd", "otp"]
|
||||
},
|
||||
{
|
||||
"acr": "urn:example:hardware",
|
||||
"assurance": "hardware",
|
||||
"requiredAmr": ["hwk"]
|
||||
}
|
||||
],
|
||||
"constraints": {
|
||||
"maxAssertionBytes": 8192,
|
||||
"maxLifetimeMs": 300000,
|
||||
"maxAuthenticationAgeMs": 300000,
|
||||
"clockSkewMs": 5000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
JWT header 必须使用 `typ=ql3-security-administration+jwt`;payload 的 issuer/audience 必须匹配 keyset,并包含 `ql3_purpose=security-administration`。只接受当前、未撤销的强认证 principal。其他管理面即使使用同一签名 key,也会因 type/purpose/audience 不同而被拒绝。
|
||||
|
||||
pepper 是现有 API credential digest authority 要求的 32-byte canonical base64url 值。它必须与已存 credential 的 pepper authority 一致,不要为了单次命令临时生成新值,也不要写入 command JSON。
|
||||
|
||||
## 精确命令
|
||||
|
||||
注册 Identity:
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"operation": "identity.register",
|
||||
"request": {
|
||||
"mutationId": "123e4567-e89b-42d3-a456-426614174301",
|
||||
"requestId": "security-identity-register-20260825-1",
|
||||
"expectedCurrentVersion": 0,
|
||||
"subject": { "type": "api_app", "id": "automation-client" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`identity.enable` 和 `identity.disable` 使用相同 request shape,只需修改 operation、mutationId、requestId 和 expectedCurrentVersion。
|
||||
|
||||
签发 API Credential:
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"operation": "credential.issue",
|
||||
"request": {
|
||||
"mutationId": "123e4567-e89b-42d3-a456-426614174302",
|
||||
"requestId": "security-credential-issue-20260825-1",
|
||||
"expectedCurrentVersion": 0,
|
||||
"credentialId": "automation-primary",
|
||||
"subject": { "type": "api_app", "id": "automation-client" },
|
||||
"notBeforeAtMs": 1787596800000,
|
||||
"expiresAtMs": 1787683200000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`credential.rotate` 使用同一完整 shape 和新的 mutationId;`credential.revoke` 必须删除 `notBeforeAtMs`、`expiresAtMs`,且不提供 `--delivery`。所有 mutation 都必须携带当前版本 fence。
|
||||
|
||||
有界查询 Security Audit:
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"operation": "audit.list",
|
||||
"request": {
|
||||
"limit": 25,
|
||||
"filter": { "outcome": "allowed" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
limit 范围为 1–200。可选 filter 只有 projectId、subject 和 outcome;翻页使用上一页的 exact `{occurredAtMs,eventId}` 作为 `before`,不支持 offset、自由文本或无界导出。
|
||||
|
||||
## 执行
|
||||
|
||||
生产默认要求 TLS hostname verification:
|
||||
|
||||
```sh
|
||||
export QL3_POSTGRES_ADMIN_URL='postgresql://ql3_admin:REDACTED@postgres.example.test:5432/qinglong'
|
||||
export QL3_POSTGRES_ADMIN_TLS_SERVERNAME='postgres.example.test'
|
||||
export QL3_POSTGRES_ADMIN_TLS_CA_FILE='/secure/qinglong3/postgres-ca.pem'
|
||||
|
||||
ql3-cluster-admin security \
|
||||
--command=/secure/qinglong3/security-command.json \
|
||||
--assertion=/secure/qinglong3/security-assertion.jwt \
|
||||
--keyset=/secure/qinglong3/security-keyset.json \
|
||||
--pepper=/secure/qinglong3/api-credential-pepper \
|
||||
--delivery=/secure/qinglong3/delivery/new-api-credential.json
|
||||
```
|
||||
|
||||
也可以直接调用同镜像内的 `ql3-security-admin`。测试环境只有同时设置 `QL3_POSTGRES_ADMIN_TLS_MODE=disable` 与 `QL3_POSTGRES_ADMIN_ALLOW_INSECURE=true` 才能关闭 TLS;生产禁止这样部署。
|
||||
|
||||
成功签发或轮换时,stdout 只包含 delivery 文件名和 SHA-256。token 只存在于新建的 `0600` delivery 文件。目标已存在时命令失败且绝不覆盖。精确重放返回 `status=existing` 且不重新发布 token;如果首次响应丢失,先检查原 delivery 文件,确实丢失时使用新的 mutationId 执行 rotate,不能尝试恢复旧 token。
|
||||
|
||||
## 当前边界
|
||||
|
||||
本入口没有远程 API/UI、双人复核或 break-glass、pepper rotation、audit retention/export/alert,也没有默认安装的 Kubernetes Job。生产部署应把它放在受控工作站或自行审查的一次性 Job 中,并确保 admin database credential 不进入常驻 Cluster Control。完整安全决策见 [ADR-0500](../adr/ADR-0500-short-lived-cluster-security-administration-command.md)。
|
||||
Reference in New Issue
Block a user