> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arupa.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Secret Manager

> 通过 配置、HTTP API 和插件消息接口与 secret-manager 核心插件集成。

`secret-manager` 存储加密的应用程序密钥，并将其暴露给经过身份验证的用户和显式授权的插件。
它是一个 WASM 插件，因此它使用与其他 WASM 插件相同的插件契约和主机回调。

## 插件标识

| 属性      | 值                        |
| ------- | ------------------------ |
| 插件名称    | `secret-manager`         |
| 类型      | `wasm`                   |
| 契约版本    | `1`                      |
| 导航入口    | `/keys/pages/index.html` |
| HTTP 前缀 | `/keys`                  |

插件名称用于插件配置、插件消息的目标以及各个密钥的访问策略。

## 配置

标准插件设置在 `[Plugins.secret-manager]` 下配置：

```toml theme={null}
[Plugins.secret-manager]
Restart = "always"
RunAsUser = ""
[Plugins.secret-manager.Params]
"secretmgr.identity" = "AGE-SECRET-KEY-ABCD"
"secretmgr.secret.server1" = "ABCDEFG"
"secretmgr.policy.server1" = "[\"ssh\"]"
"secretmgr.meta.server1" = "{
    \"name\":\"server1\",
    \"allowed_plugins\":[\"ssh\"],
    \"updated_at\":\"2026-07-17T11:47:31Z\",
    \"encryption\":\"scrypt\"
}"
```

`Params` 通过插件的 `RegisterRequest` 传递给插件。插件使用以下参数名称：

| 参数                        | 格式           | 含义                                                 |
| ------------------------- | ------------ | -------------------------------------------------- |
| `secretmgr.identity`      | X25519 身份字符串 | 用于使用 `identity` 加密的密钥的私有身份。如果不存在，插件将生成一个并将其持久化到主机。 |
| `secretmgr.secret.<name>` | Base64 文本    | 名为 `<name>` 的密钥的 Age 加密密文。                         |
| `secretmgr.policy.<name>` | JSON 字符串数组   | 允许通过 IPC 检索 `<name>` 的已排序、去重的插件名称。                 |
| `secretmgr.meta.<name>`   | JSON 对象      | `<name>` 的显示和加密元数据。                                |

插件会将更改保存到其自身的显式 `Params` 部分。它不会将这些值写入共享的 KV 存储。宿主程序会将参数补丁应用到当前配置，并刷新插件管理器的配置快照；插件也会更新其内存中的副本。

请将 `secretmgr.identity` 和所有 `secretmgr.secret.*` 值视为敏感信息。

identity 是一个私钥，而 secret 参数包含加密的秘密信息，这些信息不应在日志或常规配置诊断中暴露。

### 通过环境变量提供 identity

identity 参数支持内核的环境变量引用语法。为了避免将私钥写入配置文件，请将私钥的值存储在环境变量中，并在 `Params` 中引用它：

```toml theme={null}
[Plugins.secret-manager.Params]
"secretmgr.identity" = "env://SECRET_KEY"
```

内核会在将 `Params` 传递给插件之前解析 `env://SECRET_KEY`。
引用时必须不带尾随的 `?`，这样如果未设置 `SECRET_KEY`，
参数解析将失败，并且 `secret-manager` 无法启动。这比在加密数据已存在时静默生成新 identity 要好。

首次安装且未设置 `secretmgr.identity` 参数时，插件会生成一个 X25519 identity，并通过 `PatchParams` 将其持久化。
然后，您可以将生成的身份验证信息移动到受保护的环境变量中，设置 `secretmgr.identity = env://SECRET_KEY`，并使用 `SECRET_KEY` 启动应用程序。
请务必使用相同的 identity：使用新生成的密钥无法解密经过 identity 加密的密钥。
如果 identity 必须直接存储在配置中，则仍然可以使用 `scrypt` 加密和密码短语保护各个密钥值，这样配置中包含的将是密文而不是明文密钥值。

当前插件不接受使用密码短语包装的 `secretmgr.identity` 值；该参数必须解析为原始 identity。解密经过密码短语加密的密钥仍然需要 identity。

### 密钥元数据

每个 `secretmgr.meta.<name>` 值都具有以下格式：

```json theme={null}
{
  "name": "database-password",
  "description": "Password used by the database plugin",
  "allowed_plugins": ["database"],
  "updated_at": "2026-07-17T00:00:00Z",
  "encryption": "identity"
}
```

`encryption` 可以是 `identity` 或 `scrypt`。
`allowed_plugins` 和 `updated_at` 字段也保留在 HTTP API 返回的元数据中。
策略中的插件名称将与内核中经过身份验证的源名称进行比较。

### 密钥名称

密钥名称在截断后必须为 1-128 个字符。它们可以包含 ASCII 字符：

字母、数字、`/`、`_`、`-` 和 `.`，但不能包含 `..`。
空名称以及包含任何其他字符的名称将被拒绝。

## 加密

插件存储使用标准 Base64 编码的密文。

| 模式         | 选择方式     | 检索要求                          | 相对成本                               |
| ---------- | -------- | ----------------------------- | ---------------------------------- |
| `identity` | 请求不提供密码。 | 插件持久化的 `secretmgr.identity`。  | X25519 解密通常几乎是即时的。                 |
| `scrypt`   | 请求提供密码。  | 与加密期间使用的密码相同，通过 HTTP 或插件消息提供。 | 基于密码的密钥派生会占用额外的 CPU 和内存，因此耗时会明显更长。 |

密码永远不会被持久化。忘记密码会导致 `scrypt` 密钥无法恢复。Scrypt 使用故意耗时且内存密集型的计算从密码派生加密密钥。
这使得每次离线密码猜测的成本更高，因此基于密码短语的加密方式比使用存储的 X25519 身份信息要慢。当额外的延迟可以接受时，请选择这种方式。

经过身份验证的 HTTP API 和插件间的 IPC 接口都可以在调用者提供密码短语时检索 `scrypt` 加密的密钥。
如果没有正确的 `secretmgr.identity`，则无法解密使用 `identity` 加密的密钥。
密文不足以恢复密钥。
解密过程中接受的最大明文大小为 1 MiB。

## HTTP API

所有已注册的 HTTP 路由都需要经过身份验证的用户。该插件返回的 JSON 数据包含：

`Content-Type: application/json; charset=utf-8` 和 `Cache-Control: no-store`。

### 列出密钥

```http theme={null}
GET /keys
```

响应：

```json theme={null}
{
"success": true,
"keys": [
    {
    "name": "数据库密码",
    "description": "数据库插件使用的密码",
    "allowed_plugins": ["数据库"],
    "updated_at": "2026-07-17T00:00:00Z",
    "encryption": "identity"
    }
]
}
```

此列表仅包含元数据，不包含任何密钥值。条目按密钥名称排序。

### 添加密钥

```http theme={null}
POST /keys/add
Content-Type: application/json
```

请求体：

```json theme={null}
{
    "name": "database-password",
    "description": "数据库插件使用的密码",
    "value": "secret-value",
    "passphrase": "",
    "allowed_plugins": ["database"]
}
```

`passphrase` 为可选。空值表示选择 `identity` 加密。

非空值表示选择 `scrypt` 加密。`allowed_plugins` 可以为空，
这意味着任何插件都无法通过进程间通信 (IPC) 获取密钥。

成功返回 HTTP `201 Created`：

```json theme={null}
{
"success": true,
"name": "database-password"
}
```

如果 JSON 格式错误、名称无效或插件策略无效，则端点返回 `400 Bad Request`；如果密钥已存在，则返回 `409 Conflict`；如果加密或持久化失败，则返回 `500 Internal Server Error`。

### 更新密钥

```http theme={null}
POST /keys/update
Content-Type: application/json
```

此请求使用与 `/keys/add` 相同的字段。`name` 用于标识现有的密钥，且无法更改。
要在更改元数据或访问策略的同时保留现有密文，请将 `value` 设置为单个字符`*`，并将 `passphrase` 留空。
如果提供带有此标记的密码短语，则请求将被拒绝。

成功返回 HTTP `200 OK`，格式与 `{ "success": true, "name": "..." }` 相同。
未知密钥返回 `404 Not Found`；格式错误的输入或无效的策略返回 `400 Bad Request`。

### 揭示密钥

```http theme={null}
POST /keys/reveal
Content-Type: application/json
```

请求体：

```json theme={null}
{
"name": "database-password",
"passphrase": ""
}
```

当密钥使用 `scrypt` 加密时，必须填写密码短语；对于 `identity` 密钥，密码短语必须为空。成功返回明文：

```json theme={null}
{
"success": true,
"name": "database-password",
"value": "secret-value"
}
```

如果输入格式错误或缺少/无效的 scrypt 密码，则端点返回 `400 Bad Request`。

如果缺少密钥或其他解密失败，则返回 `404 Not Found`。

### 删除密钥

```http theme={null}
POST /keys/delete
Content-Type: application/json
```

请求体：

```json theme={null}
{
"name": "database-password"
}
```

成功返回 HTTP `200 OK`：

```json theme={null}
{
"success": true,
"name": "database-password"
}
```

删除操作会移除密文、策略和元数据参数。删除不存在的名称不会造成任何损害。无效名称会返回 `400 Bad Request` 错误。

## 插件消息 API

插件间消息检索使用内核的同步插件消息传输。目标插件名称为 `secret-manager`，支持的主题为：

```text theme={null}
secret-manager.secret.get
```

有效负载为 UTF-8 JSON：

```json theme={null}
{
"name": "database-password",
"passphrase": ""
}
```

发送插件必须列在所请求密钥的 `allowed_plugins` 列表中。对于 `identity` 密钥，密码短语可以为空；对于 `scrypt` 密钥，密码短语是必需的。

内核会将消息的 `source` 设置为发送者的注册名称；调用者无法伪造此字段。

成功后，回复会将明文内容放入 `message` 中：

```json theme={null}
{
"error": "",
"message": "secret-value"
}
```

失败时，回复会在 `error` 中放置描述，并且不会返回密钥值：

```json theme={null}
{
"error": "插件无权访问此密钥",
"message": ""
}
```

消息接口会拒绝不支持的主题、无效的 JSON、无效的密钥名称、未经授权的来源、缺失的密钥、不支持的加密元数据、

缺失的 `scrypt` 密码短语以及无效的密码短语。

有关通用消息信封和主机认证来源的行为，请参阅
[插件消息](../../../arupa/v0/zh-CN/plugin/ipc-message)。

## KV 和其他插件接口

`secret-manager` 不使用共享的 KV 存储来存储密钥数据。
使用者不得在 KV 命名空间中查找密钥；请使用 HTTP。 API 或插件上述消息主题。

该插件未声明 Socket.IO 命名空间或事件。其与宿主的交互仅限于：

* 在注册期间接收 `Params`；
* 使用宿主的 `PatchParams` 回调来持久化身份和密钥的更改；
* 处理已认证的 HTTP 请求；
* 接收已认证的插件消息。

其静态页面加载共享的前端资源和标准主题的 Web SDK，但这些浏览器资源是 UI 依赖项，而非密钥存储接口。

## 安全注意事项

* 仅将 HTTP 端点用于已认证的管理访问。
* 优先使用插件消息进行服务间访问，并将每个密钥仅授予需要它的插件名称。
* 将插件消息回复视为明文密钥材料。
* 为每次 `scrypt` 检索提供密码短语，并且绝不将其记录或持久化到调用插件中。
* 不要在日志中附加请求正文、密码短语、解密值、身份或密文参数。
