跳到主要内容

密钥管理

把数据库密码、媒介 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] 这组接口下面—— 这组默认是关的,也没有对应的命令行子命令。所以顺序是:

  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. 界面里配的凭证:加密变量​

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

值在浏览器里就用 RSA 公钥加密好才发出去,之后界面上再也读不出来(列表显示 ******), 渲染进日志和通知记录时会被打成 ***。完整规则和限制见站点与用户变量。

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

3. 剩下的凭证放在哪儿​

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

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

4. 部署侧要补的三件事​

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

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

下一步​