---
title: "主密钥和签名密钥轮换及紧急访问"
description: "轮换用于保护已存储凭据的主密钥，并使用紧急访问机制。"
image: "https://docs.quirelms.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.quirelms.com/zh-Hans/llms.txt
> Use this file to discover all available pages before exploring further.

# 主密钥和签名密钥轮换及紧急访问

<span id="master-key-and-signing-key-rotation-and-break-glass-access"></span>

审计员会按名称询问 21-compliance.md 第 14 节中的控制措施。此处介绍操作流程；流程生成的记录就是审计证据。

## 密钥 <!--quire:the-keys-->

每个已存储凭据都会使用新生成的数据加密密钥（DEK）加密。DEK 由主密钥（KEK）包装；主密钥的引用会与其一起保存（`key_ref`，或打包值中的引用）。轮换主密钥时，会重新包装 DEK，但绝不会解密或重新加密凭据。

| 设置 | 含义 |
| --- | --- |
| `QUIRE_MASTER_KEY` | 当前主密钥：32 字节、base64。所有新密钥都使用它包装 |
| `QUIRE_MASTER_KEY_VERSION` | 版本标签；未设置时为 `v1`。每次更换密钥时递增 |
| `QUIRE_MASTER_KEY_RETIRED` | 仍可能用于包装已存储密钥的旧密钥，格式如 `v1=<base64>,v0=<base64>`。仅用于读取，不会写入 |

Web 层、worker 和 `bun run kek:rotate` 命令都会读取这三个设置。它们的值必须一致，否则某个组件就无法解开其他组件包装的密钥。

如果未设置 `QUIRE_MASTER_KEY`，每个子系统会保留从 `QUIRE_SECRET_KEY` 派生的密钥。这种方式可以正常工作，系统健康页面会将其标记为降级状态；设置主密钥后，该派生密钥仍可读取，因此首次轮换会将所有内容从派生密钥迁出。任何能够读取进程环境的人员都能解密所有已存储凭据，因此生产环境应设置主密钥，将其保存在密钥存储服务中，并与数据库备份分开保存。

## 轮换 <!--quire:rotating-->

主密钥的使用时长会显示在“平台控制台”>“安全”>“主密钥”页面，并以天数记录在指标 `quire.secrets.master_key.age` 中。每日运行的 `platform.key_age` 计划（UTC 03:41）会在密钥达到 365 天时向平台审计链写入提醒记录，并在此后每 30 天再次提醒，直到密钥完成轮换。收到提醒时应轮换；如果密钥可能已泄露，也应立即轮换。

1. 生成新密钥：`openssl rand -base64 32`。
2. 将 `QUIRE_MASTER_KEY` 设为新值，并将 `QUIRE_MASTER_KEY_VERSION` 设为下一个标签（`v2`）。将旧密钥移到 `QUIRE_MASTER_KEY_RETIRED`，格式为 `v1=<old base64>`。在此主机之外保存新旧密钥的副本。
3. 使用新设置部署 Web 层和 worker。现在所有新密钥都会用 `env:QUIRE_MASTER_KEY:v2` 包装；旧密钥仍可通过退休密钥读取。
4. 提交轮换请求，并说明将写入审计记录的原因：
   - 控制台中选择“安全”>“主密钥”>“轮换主密钥”；或
   - 使用相同环境的 shell 执行 `bun run kek:rotate request --reason "Annual rotation, ticket SEC-114"`。
5. Worker 每分钟重新包装一批密钥（由 scheduler 的 `platform.key_rotation` 计划执行），并会在重启后继续。要一次性完成，请执行 `bun run kek:rotate run`。使用 `bun run kek:rotate status` 查看进度。
6. 记录显示轮换完成，且**未解决数和失败数都为零**后，从 `QUIRE_MASTER_KEY_RETIRED` 中移除退休密钥并重新部署。在此之前请保留它：无法迁移的值仍由旧密钥包装。

### 任务会处理哪些内容 <!--quire:what-the-job-walks-->

任务会遍历所有存有已包装 DEK 的存储区，即 `SEALED_STORES`（`apps/worker/src/key-rotation.ts`）中的项目。控制数据库存储区会在控制数据库上遍历；组织存储区会在行级安全机制下逐个组织遍历，所用数据库取决于组织所在位置，因此固定到专用数据库的租户会在该数据库内完成轮换。新增已包装密钥列却未列入清单时，某项测试会失败；凭据审查将某个已密封列分类为需要轮换，但清单遗漏时，另一项测试也会失败。

### 记录 <!--quire:the-record-->

- `ops.key_rotation`：每次轮换对应一行，记录原因、请求人、状态和统计数据（已重新包装、已是最新、未解决、失败）。
- `ops.key_rotation_progress`：每个存储区和范围处理后记录一行，其中包含无法读取的密钥引用及每种引用对应的值数量。恢复轮换时会跳过这些记录。
- 平台审计链：`platform/key_rotation_request`（含原因）、每个存储区对应的 `platform/key_rotation_store`（含计数），以及 `platform/key_rotation_complete` 或 `platform/key_rotation_fail`；提醒会生成 `platform/key_age_reminder`。
- 指标：`quire.secrets.master_key.age` 和 `quire.secrets.rewrap.outstanding`（上次轮换无法迁移的值）。

### 存在未解决值时 <!--quire:when-values-are-unresolved-->

未解决值是指由本安装不持有的密钥引用包装的值，或其格式与列定义不符的值。进度记录会指出密钥引用（例如 `env:QUIRE_MASTER_KEY:v0 (unreadable)`）。将该密钥重新加入 `QUIRE_MASTER_KEY_RETIRED` 并再次运行轮换；如果密钥永久丢失，则请组织管理员重新填写凭据，系统会使用当前密钥重新包装。如果轮换失败，记录中会显示错误；请修复原因后重新提交请求。

## 签名密钥 <!--quire:signing-keys-->

组织使用各自的 RSA 密钥为 OpenID Connect token 和 LTI 消息签名，并在 `/.well-known/jwks.json` 发布公钥。此项工作不需要运维人员操作。每小时运行的 `platform.signing_keys` 计划会在当前密钥的 90 天有效期结束前七天发布后继密钥；一周后，后继密钥开始签名，旧密钥进入退役状态；再过 90 天，旧密钥会被删除并从密钥集中移除。每个步骤都会作为 `platform/signing_key_advance` 写入平台审计链。

如需提前更换某组织的密钥（例如发生泄露）：

- 在控制台中选择“安全”>“主密钥”>“发布新的签名密钥”（需要 `platform/keys_manage`）；或
- 在使用 worker 环境的 shell 中执行以下命令：`bun run kek:rotate signing-keys rotate
  --tenant <slug or id> --reason "Key exposed, INC-3310"`. `bun run kek:rotate
  signing-keys status` 会按阶段列出所有组织的密钥。

新密钥会立即发布，并在七天后开始签名，此时当前密钥会退役。特意保留这一周的间隔，是因为依赖方会缓存密钥集，重叠期太短会让所有工具同时失败。退役密钥会继续保留在密钥集中 90 天，以便已使用该密钥签名的 token 继续通过验证。如果泄露情况要求更早停止信任该密钥，可由运维人员使用自己的数据库访问权限、依据变更记录删除对应行（紧急访问机制是只读的）；此后由该密钥签名的 token 将无法通过验证。强制轮换会以含原因的 `platform/signing_key_rotate` 记录写入审计链。Worker 必须使用与 Web 层相同的 `QUIRE_MASTER_KEY` 设置才能包装新密钥；运行 `bun run kek:rotate` 轮换主密钥时，也会重新包装签名密钥及其他密钥（`oauth_signing_key` 位于 `SEALED_STORES` 中）。

## 生产环境紧急访问 <!--quire:break-glass-production-access-->

没有人拥有生产环境的常驻访问权限。遇到无法等待的情况时，由所有者签发紧急访问授权：在“平台控制台”>“安全”>“紧急访问”中操作。

- 授权范围可以是一家组织或平台注册表；原因至少 20 个字符，且需注明事故或工单；有效期为 5 到 240 分钟。授权会自动过期，每条语句执行时都会检查时钟。
- 可签发给签发授权的所有者本人，也可签发给另一位所有者（双人流程）。只有授权指定的人员可以使用。签发需要 `platform/break_glass_issue`，使用需要 `platform/break_glass_use`；默认情况下这两项权限仅授予所有者。
- 语句通过网关运行，而非直接使用数据库登录：只读、逐条执行、范围限定为指定组织或控制注册表、超时为五秒，最多返回 500 行。二进制值只显示大小。
- 平台审计链会记录签发（含原因）、撤销、每条语句（执行前记录，事件为 `platform/break_glass_statement`；拒绝的语句结果为 `denied`）和每条结果（`platform/break_glass_result`）。`ops.break_glass_statement` 会保存审计记录 ID，因此授权记录可关联到对应审计条目。
- 不提供写入操作。如果某项更改无法等到发布，应在产品之外使用运维人员自己的数据库访问权限，并建立专属变更记录；记录中应引用此处使用的事故编号。

为什么不直接签发数据库凭据：Postgres 登录权限会在申请该权限的会话结束后继续有效，会绕过应用依赖的行级安全，也无法写入本产品的审计链；因此，相关语句只能在有人交付服务器日志的范围内审计。网关让审计记录成为访问流程本身的一部分，而不是依赖流程外的惯例。

处理审计请求时，请列出该时间段的授权（“紧急访问”页面），打开某项授权的历史以查看语句和审计记录 ID，再到平台审计链读取这些记录（运行 `bun run audit:verify --platform` 可证明审计链完整）。

Source: https://docs.quirelms.com/zh-Hans/ops/key-rotation/index.mdx
