mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
246 lines
8.6 KiB
Markdown
246 lines
8.6 KiB
Markdown
# `ql3-identity` 本机 Identity/Credential 管理
|
||
|
||
`ql3-identity` 是一次命令、一次进程的 Owner-only 管理入口。它不启动管理服务,也不把
|
||
credential token 写入 argv、stdout、stderr、command JSON 或 SQLite。
|
||
|
||
## 前置条件
|
||
|
||
- 已完成 Fresh Setup,当前 credential 属于 `default` Project 的 active Owner;
|
||
- deployment root、其子目录、SQLite、Owner credential presentation 与 command
|
||
file 均由当前 real/effective UID 拥有;
|
||
- 目录权限为 `0700`,文件权限为 `0600`;
|
||
- Owner pepper keyring 中存在与 SQLite active pepper 完全匹配的材料;
|
||
- managed credential delivery 使用独立、规范化、非 symlink 的 `0700` 目录。
|
||
|
||
## 实例 Authority Project
|
||
|
||
Identity 与 API credential 是实例级对象,不属于任意单个业务 Project。
|
||
`ql3-identity` 只接受实例 authority Project 的当前 Owner:
|
||
|
||
- 新部署以全库最早成功消费的 Owner bootstrap challenge 所属 Project 为 authority;
|
||
- 没有任何 consumed bootstrap challenge 的旧库兼容回退到迁移内建的 `default`;
|
||
- 后续给其他 Project 授予 Owner 不会转移该 authority;
|
||
- secondary Project Owner 使用自己的 `projectId` 调用时,返回统一 authorization
|
||
error,并记录 `instance_authority_project_required`;
|
||
- 当前没有 authority Project 转移命令,不要通过直接编辑数据库模拟转移。
|
||
|
||
command 中的 `projectId` 必须填写该 authority Project。它会在服务授权和最终 SQLite
|
||
事务各验证一次;直接调用 repository 不能绕过。
|
||
|
||
所有示例中的 UUID、subject 和路径都必须替换。command file 创建完成后执行:
|
||
|
||
```sh
|
||
ql3-identity run --command-file /srv/qinglong3/commands/identity.json
|
||
```
|
||
|
||
## 查询当前版本
|
||
|
||
后续 mutation 所需的 `expectedCurrentVersion` 必须通过受支持的 inspect 命令取得,
|
||
不要直接查询 SQLite。Identity 精确查询示例:
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"operation": "identity.inspect",
|
||
"options": {
|
||
"deploymentRoot": "/srv/qinglong3",
|
||
"databasePath": "/srv/qinglong3/qinglong3.sqlite",
|
||
"profile": "edge",
|
||
"ownerPepperKeyringDirectory": "/srv/qinglong3/owner-keys",
|
||
"credentialFilePath": "/srv/qinglong3/owner-credential.json"
|
||
},
|
||
"request": {
|
||
"projectId": "default",
|
||
"target": { "type": "agent", "id": "agent-planner" },
|
||
"requestId": "identity-inspect-agent-planner",
|
||
"auditEventId": "10111111-1111-4111-8111-111111111111"
|
||
}
|
||
}
|
||
```
|
||
|
||
Credential 精确查询把 operation 改为 `credential.inspect`,request 改为:
|
||
|
||
```json
|
||
{
|
||
"projectId": "default",
|
||
"credentialId": "agent-planner-primary",
|
||
"requestId": "credential-inspect-agent-planner",
|
||
"auditEventId": "10222222-2222-4222-8222-222222222222"
|
||
}
|
||
```
|
||
|
||
成功命中只返回 subject、status/state、version 和时间窗;不存在返回:
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"operation": "credential.inspect",
|
||
"projectId": "default",
|
||
"found": false
|
||
}
|
||
```
|
||
|
||
`found:false` 只会在 Owner authorization 与事务内 credential/Project/RoleBinding
|
||
围栏全部通过后返回。输出不会包含 secret digest、pepper key ID、token、数据库路径
|
||
或交付路径。每次人工查询使用新的 `auditEventId`;inspect 不是 mutation,也不使用
|
||
`mutationId`、`failureAuditEventId` 或 `expectedCurrentVersion`。
|
||
|
||
## 注册 Identity
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"operation": "identity.register",
|
||
"options": {
|
||
"deploymentRoot": "/srv/qinglong3",
|
||
"databasePath": "/srv/qinglong3/qinglong3.sqlite",
|
||
"profile": "edge",
|
||
"ownerPepperKeyringDirectory": "/srv/qinglong3/owner-keys",
|
||
"credentialFilePath": "/srv/qinglong3/owner-credential.json"
|
||
},
|
||
"request": {
|
||
"projectId": "default",
|
||
"target": { "type": "agent", "id": "agent-planner" },
|
||
"expectedCurrentVersion": 0,
|
||
"mutationId": "11111111-1111-4111-8111-111111111111",
|
||
"requestId": "identity-register-agent-planner",
|
||
"failureAuditEventId": "11111111-1111-4111-8111-111111111112"
|
||
}
|
||
}
|
||
```
|
||
|
||
`identity.enable` 和 `identity.disable` 使用相同结构,并把
|
||
`expectedCurrentVersion` 设为当前 Identity version。仍有 active Owner binding 的
|
||
User Identity 不能被禁用。
|
||
|
||
## 签发或轮换 Credential
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"operation": "credential.issue",
|
||
"options": {
|
||
"deploymentRoot": "/srv/qinglong3",
|
||
"databasePath": "/srv/qinglong3/qinglong3.sqlite",
|
||
"profile": "edge",
|
||
"ownerPepperKeyringDirectory": "/srv/qinglong3/owner-keys",
|
||
"credentialFilePath": "/srv/qinglong3/owner-credential.json",
|
||
"credentialDeliveryDirectory": "/srv/qinglong3/managed-credentials"
|
||
},
|
||
"request": {
|
||
"projectId": "default",
|
||
"target": { "type": "agent", "id": "agent-planner" },
|
||
"credentialId": "agent-planner-primary",
|
||
"expectedCurrentVersion": 0,
|
||
"lifetimeMs": 86400000,
|
||
"mutationId": "22222222-2222-4222-8222-222222222221",
|
||
"requestId": "credential-issue-agent-planner",
|
||
"failureAuditEventId": "22222222-2222-4222-8222-222222222222"
|
||
}
|
||
}
|
||
```
|
||
|
||
轮换时把 operation 改为 `credential.rotate`,并使用当前 credential version。
|
||
`lifetimeMs` 范围为 60 秒至两年。
|
||
|
||
成功输出示例:
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"operation": "credential.issue",
|
||
"status": "inserted",
|
||
"projectId": "default",
|
||
"target": { "type": "agent", "id": "agent-planner" },
|
||
"credentialId": "agent-planner-primary",
|
||
"version": 1,
|
||
"state": "active",
|
||
"expiresAtMs": 1780000000000,
|
||
"delivery": {
|
||
"fileName": "managed-credential-22222222-2222-4222-8222-222222222221.ready.json",
|
||
"digest": "64-char-lowercase-sha256"
|
||
}
|
||
}
|
||
```
|
||
|
||
输出没有绝对路径和 token。ready 文件位于 command 指定的 delivery directory,
|
||
权限为 `0600`,其 JSON 可以直接作为后续 `credentialFilePath`。如果 CLI 在 commit
|
||
后中断,必须原样重放同一个 command;不要更换 mutation、request、target、version
|
||
或 lifetime。
|
||
|
||
## 确认交付
|
||
|
||
consumer 已复制、安装并真实验证 ready credential 后,提交确认:
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"operation": "credential.delivery.acknowledge",
|
||
"options": {
|
||
"deploymentRoot": "/srv/qinglong3",
|
||
"databasePath": "/srv/qinglong3/qinglong3.sqlite",
|
||
"profile": "edge",
|
||
"ownerPepperKeyringDirectory": "/srv/qinglong3/owner-keys",
|
||
"credentialFilePath": "/srv/qinglong3/owner-credential.json",
|
||
"credentialDeliveryDirectory": "/srv/qinglong3/managed-credentials"
|
||
},
|
||
"request": {
|
||
"projectId": "default",
|
||
"credentialMutationId": "22222222-2222-4222-8222-222222222221",
|
||
"expectedDeliveryDigest": "replace-with-issue-output-digest",
|
||
"mutationId": "33333333-3333-4333-8333-333333333331",
|
||
"requestId": "credential-delivery-ack-agent-planner",
|
||
"failureAuditEventId": "33333333-3333-4333-8333-333333333332"
|
||
}
|
||
}
|
||
```
|
||
|
||
数据库 acknowledgement 先提交,随后 pending/ready 文件才删除。响应丢失时原样
|
||
重放;`cleanup:"absent"` 表示数据库已经确认且文件已在前一次执行中安全删除。
|
||
|
||
不要在 consumer 尚未验证前 acknowledge,也不要把唯一正在用于本命令认证的
|
||
credential presentation 提前删除。
|
||
|
||
## 撤销 Credential
|
||
|
||
`credential.revoke` 不需要 delivery directory 或 lifetime:
|
||
|
||
```json
|
||
{
|
||
"schemaVersion": 1,
|
||
"operation": "credential.revoke",
|
||
"options": {
|
||
"deploymentRoot": "/srv/qinglong3",
|
||
"databasePath": "/srv/qinglong3/qinglong3.sqlite",
|
||
"profile": "edge",
|
||
"ownerPepperKeyringDirectory": "/srv/qinglong3/owner-keys",
|
||
"credentialFilePath": "/srv/qinglong3/owner-credential.json"
|
||
},
|
||
"request": {
|
||
"projectId": "default",
|
||
"target": { "type": "agent", "id": "agent-planner" },
|
||
"credentialId": "agent-planner-primary",
|
||
"expectedCurrentVersion": 1,
|
||
"mutationId": "44444444-4444-4444-8444-444444444441",
|
||
"requestId": "credential-revoke-agent-planner",
|
||
"failureAuditEventId": "44444444-4444-4444-8444-444444444442"
|
||
}
|
||
}
|
||
```
|
||
|
||
撤销 Owner 当前使用的 credential 前,必须先签发并验证另一把有效 credential。
|
||
数据库会拒绝撤销 active Owner 的最后一把凭据。
|
||
|
||
## 运维规则
|
||
|
||
- 先使用 exact inspect 取得当前 version,再创建 mutation command;不要把数据库表当作
|
||
产品 API;
|
||
- command file 是不可变 mutation 载体;响应不确定时只允许原样重放;
|
||
- 不编辑 pending/ready 文件,不按 mtime 自动清理;
|
||
- `mutation_conflict` 表示相同 mutation ID 已用于不同语义,必须人工调查;
|
||
- `owner_continuity_required` 表示操作会造成 Owner 无法接管;
|
||
- `credential_or_policy_fence_rejected` 表示认证后 authority 已变化,应重新认证并使用
|
||
新 mutation;
|
||
- managed delivery 上限是 64 个条目;长期未确认项需要人工核对,不得用通用目录
|
||
GC 删除。
|