密钥管理
把数据库密码、媒介 Token、大模型密钥挪出配置文件。
这页办完你会得到:一份「哪些凭证能挪、怎么挪、剩下的放在哪」的准确清单。 先说清产品到底提供了什么,因为这块最容易按想象来配。
产品提供的两套机制
覆盖面不重叠,各管一半:
| 机制 | 覆盖什么 | 密钥在哪 |
|---|---|---|
配置文件字段加密(--crypto-key) | etc/*.toml 里的六个字段 | 启动参数给的那个字符串 |
| 加密变量(系统配置 → 变量设置) | 通知媒介、SMTP、单点登录的配置正文 | 库里自动生成的 RSA 密钥对 |
下面这些不存在,别去找:
- 没有「用环境变量覆盖任意配置项」的机制。没有
N9E_DB_DSN这种东西, 配置只从etc/下的 toml 文件读,详见环境变量。 - 没有对接外部密钥管理系统(Vault、云 KMS)的口子。
- 数据源的账号密码和大模型 API Key 两套机制都覆盖不到,它们只存在各自的表里。
1. 把配置文件里的六个字段变成密文
能存密文的就这六个。别的字段写成 {{cipher}} 开头也不会被解开,会被当成字面值用:
[DB] DSN
[Redis] Password
[HTTP.APIForService.BasicAuth] 每一个值
[HTTP.APIForAgent.BasicAuth] 每一个值
[[Pushgw.Writers]] BasicAuthPass
[EmbeddedTSDB] BasicAuthPass
密文的形状是 {{cipher}} 前缀加一段 base64,启动时用 --crypto-key 给的密钥解开。
没有前缀的值原样使用,所以同一个文件里明文和密文可以混着写,一次只改一项也行。
生成密文只有一条产品自带的路径,而且它挂在 [HTTP.APIForService] 这组接口下面——
这组默认是关的,也没有对应的命令行子命令。所以顺序是:
-
临时打开这组接口,把配置文件里自带的那组示例账号密码换成你自己的 (那组是公开的),并把
[HTTP] Host临时绑到127.0.0.1,重启进程:[HTTP]Host = "127.0.0.1"[HTTP.APIForService]Enable = true[HTTP.APIForService.BasicAuth]<你的用户名> = "<你自己生成的口令>" -
在这台机器上调一次加密接口。
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 字节的密钥>"}' -
响应里的
encrypt字段就是要写回配置文件的值:{"src":"...","key":"...","encrypt":"{{cipher}}rBkS0m....=="} -
把它填回
etc/config.toml,启动时带上同一个密钥:./n9e --crypto-key '<第 2 步用的那个密钥>' -
决定
[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. 界面里配的凭证:加密变量
通知媒介的 Token、SMTP 口令、单点登录的 ClientSecret 都是在界面里填的,不在配置文件里,
所以走另一套:在 系统配置 → 变量设置 里建一条加密变量,
然后在对应的配置里用 {{.变量名}} 引用它。
值在浏览器里就用 RSA 公钥加密好才发出去,之后界面上再也读不出来(列表显示 ******),
渲染进日志和通知记录时会被打成 ***。完整规则和限制见站点与用户变量。
它的边界要说清楚:RSA 私钥和密文存在同一张 configs 表里。
所以这层加密防的是「有系统配置权限的人顺手看见凭证」和「凭证写进日志」,
防不住能读数据库的人。
3. 剩下的凭证放在哪儿
| 凭证 | 存在哪 | 能不能加密 |
|---|---|---|
| 用户密码 | users 表 | 加盐哈希,不可逆。但全站共用一个盐,弱口令仍然扛不住离线爆破 |
| 个人 Token | user_token 表 | 明文 UUID,靠删除吊销,见 Token 与凭据轮换 |
| 数据源账号密码、证书、请求头 | datasource 表 | 两套机制都覆盖不到。非管理员调接口拿到的记录里这些字段已被抹掉 |
| 大模型 API Key | ai_llm_config 表 | 同上 |
| 通知媒介的 Token、SMTP 口令 | 媒介配置 | 可以改成引用加密变量 |
| 单点登录的 ClientSecret / BindPass | sso_config 表 | 同上 |
| RSA 私钥、JWT 签名密钥、口令盐 | configs 表 | 自动生成,不用管,但它们决定了库的敏感级别 |
看这张表最该记住的一件事:能加密的是少数,绝大部分凭证的实际边界是数据库本身。
4. 部署侧要补的三件事
产品覆盖不到的部分只能在部署上补,这三件的收益最高:
- 配置文件的权限。 即使六个字段全加密了,文件里还留着数据库地址、
Redis 地址、写入目标这类拓扑信息。
chmod 600,属主是跑进程的那个用户。 - 别把配置文件提交进版本库。 要版本化就只存模板,值在部署时注入;
多环境用
N9E_CONFIGS(或--configs)指向不同目录,是最省事也最好审计的做法, 见环境变量。 - 把数据库当成真正的边界。 密码哈希、Token、数据源凭证、RSA 私钥全在里面。 数据库账号给最小权限、网络上只对夜莺开放、备份落盘时加密—— 见备份与恢复。
下一步
- 加密变量的完整规则:站点与用户变量
- 凭证怎么轮换:Token 与凭据轮换
- 上线前逐项过一遍:安全检查清单