# 密钥管理

把数据库密码、媒介 Token、大模型密钥挪出配置文件。

最后校对于 2026-10-10

这页办完你会得到：一份「哪些凭证能挪、怎么挪、剩下的放在哪」的准确清单。 先说清产品到底提供了什么，因为这块最容易按想象来配。

## 产品提供的两套机制[​](#产品提供的两套机制 "产品提供的两套机制的直接链接")

覆盖面不重叠，各管一半：

| 机制                                | 覆盖什么                           | 密钥在哪                  |
| ----------------------------------- | ---------------------------------- | ------------------------- |
| 配置文件字段加密（`--crypto-key`）  | `etc/*.toml` 里的**六个**字段      | 启动参数给的那个字符串    |
| 加密变量（**系统配置 → 变量设置**） | 通知媒介、SMTP、单点登录的配置正文 | 库里自动生成的 RSA 密钥对 |

**下面这些不存在，别去找：**

* 没有「用环境变量覆盖任意配置项」的机制。没有 `N9E_DB_DSN` 这种东西， 配置只从 `etc/` 下的 toml 文件读，详见[环境变量](https://n9e.github.io/zh/docs/reference/environment-variables.md)。
* 没有对接外部密钥管理系统（Vault、云 KMS）的口子。
* **数据源的账号密码和大模型 API Key 两套机制都覆盖不到**，它们只存在各自的表里。

## 1. 把配置文件里的六个字段变成密文[​](#1-把配置文件里的六个字段变成密�文 "1. 把配置文件里的六个字段变成密文的直接链接")

能存密文的就这六个。别的字段写成 `{{cipher}}` 开头也不会被解开，会被当成字面值用：

```
[DB]                              DSN

[Redis]                           Password

[HTTP.APIForService.BasicAuth]    每一个值

[HTTP.APIForAgent.BasicAuth]      每一个值

[[Pushgw.Writers]]                BasicAuthPass

[EmbeddedTSDB]                    BasicAuthPass
```

密文的形状是 `{{cipher}}` 前缀加一段 base64，启动时用 `--crypto-key` 给的密钥解开。 **没有前缀的值原样使用**，所以同一个文件里明文和密文可以混着写，一次只改一项也行。

生成密文只有一条产品自带的路径，而且它挂在 `[HTTP.APIForService]` 这组接口下面—— **这组默认是关的**，也没有对应的命令行子命令。所以顺序是：

1. 临时打开这组接口，把配置文件里自带的那组示例账号密码换成你自己的 （那组是公开的），并把 `[HTTP] Host` 临时绑到 `127.0.0.1`，重启进程：

   ```
   [HTTP]

   Host = "127.0.0.1"

   [HTTP.APIForService]

   Enable = true

   [HTTP.APIForService.BasicAuth]

   <你的用户名> = "<你自己生成的口令>"
   ```

2. 在这台机器上调一次加密接口。`key` 的长度必须是 **16、24 或 32 字节**， 别的长度直接返回 400：

   ```
   curl -u '<你的用户名>:<你的口令>' \

     -X POST http://127.0.0.1:17000/v1/n9e/conf-prop/encrypt \

     -H 'Content-Type: application/json' \

     -d '{"data":"<要加密的明文>","key":"<16/24/32 字节的密钥>"}'
   ```

3. 响应里的 `encrypt` 字段就是要写回配置文件的值：

   ```
   {"src":"...","key":"...","encrypt":"{{cipher}}rBkS0m....=="}
   ```

4. 把它填回 `etc/config.toml`，启动时带上**同一个**密钥：

   ```
   ./n9e --crypto-key '<第 2 步用的那个密钥>'
   ```

5. 决定 `[HTTP.APIForService]` 要不要关回去。边缘模式、独立部署的 alert 进程、 以及变量解密都需要它常开；单机部署就关掉，并把 `[HTTP] Host` 改回去。

预期结果：进程正常启动并连上数据库。密钥不对时启动直接失败，日志里是 `failed to decrypt the db dsn`——不会退回明文，也不会静默跑起来。

四个二进制（`n9e`、`n9e-alert`、`n9e-pushgw`、`n9e-edge`）都认 `--crypto-key`， 拆开部署时它们必须用同一个密钥。

**密钥本身在哪儿**：`--crypto-key` 是命令行参数，所以它一定会出现在 `ps` 的输出里， 同机器上任何用户都看得到。这一点绕不过去，能做的是限制谁能登上这台机器， 以及别让密钥进版本库和 shell 历史——让 systemd unit 或容器启动脚本从只有 root 能读的文件里取出来再拼进命令行。这一层产品不管，是部署侧的事。

## 2. 界面里配的凭证：加密变量[​](#2-界面里配的凭证加密变量 "2. 界面里配的凭证：加密变量的直接链接")

通知媒介的 Token、SMTP 口令、单点登录的 ClientSecret 都是在界面里填的，不在配置文件里， 所以走另一套：在 **系统配置 → 变量设置** 里建一条加密变量， 然后在对应的配置里用 `{{.变量名}}` 引用它。

值在**浏览器里**就用 RSA 公钥加密好才发出去，之后界面上再也读不出来（列表显示 `******`）， 渲染进日志和通知记录时会被打成 `***`。完整规则和限制见[站点与用户变量](https://n9e.github.io/zh/docs/security/variables.md)。

它的边界要说清楚：**RSA 私钥和密文存在同一张 `configs` 表里**。 所以这层加密防的是「有系统配置权限的人顺手看见凭证」和「凭证写进日志」， 防不住能读数据库的人。

## 3. 剩下的凭证放在哪儿[​](#3-剩下的凭证放在哪儿 "3. 剩下的凭证放在哪儿的直接链接")

| 凭证                               | 存在哪             | 能不能加密                                                                                     |
| ---------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------- |
| 用户密码                           | `users` 表         | 加盐哈希，不可逆。但全站共用一个盐，弱口令仍然扛不住离线爆破                                   |
| 个人 Token                         | `user_token` 表    | 明文 UUID，靠删除吊销，见 [Token 与凭据轮换](https://n9e.github.io/zh/docs/security/tokens.md) |
| 数据源账号密码、证书、请求头       | `datasource` 表    | 两套机制都覆盖不到。非管理员调接口拿到的记录里这些字段已被抹掉                                 |
| 大模型 API Key                     | `ai_llm_config` 表 | 同上                                                                                           |
| 通知媒介的 Token、SMTP 口令        | 媒介配置           | 可以改成引用加密变量                                                                           |
| 单点登录的 ClientSecret / BindPass | `sso_config` 表    | 同上                                                                                           |
| RSA 私钥、JWT 签名密钥、口令盐     | `configs` 表       | 自动生成，不用管，但它们决定了库的敏感级别                                                     |

看这张表最该记住的一件事：**能加密的是少数，绝大部分凭证的实际边界是数据库本身。**

## 4. 部署侧要补的三件事[​](#4-部署侧要补的三件事 "4. 部署侧要补的三件事的直接链接")

产品覆盖不到的部分只能在部署上补，这三件的收益最高：

1. **配置文件的权限。** 即使六个字段全加密了，文件里还留着数据库地址、 Redis 地址、写入目标这类拓扑信息。`chmod 600`，属主是跑进程的那个用户。
2. **别把配置文件提交进版本库。** 要版本化就只存模板，值在部署时注入； 多环境用 `N9E_CONFIGS`（或 `--configs`）指向不同目录，是最省事也最好审计的做法， 见[环境变量](https://n9e.github.io/zh/docs/reference/environment-variables.md)。
3. **把数据库当成真正的边界。** 密码哈希、Token、数据源凭证、RSA 私钥全在里面。 数据库账号给最小权限、网络上只对夜莺开放、备份落盘时加密—— 见[备份与恢复](https://n9e.github.io/zh/docs/operations/backup-restore.md)。

## 下一步[​](#下一步 "下一步的直接链接")

* 加密变量的完整规则：[站点与用户变量](https://n9e.github.io/zh/docs/security/variables.md)
* 凭证怎么轮换：[Token 与凭据轮换](https://n9e.github.io/zh/docs/security/tokens.md)
* 上线前逐项过一遍：[安全检查清单](https://n9e.github.io/zh/docs/security/checklist.md)
