treg安全架构深度剖析:Fernet加密、write-only密钥与审计日志的三层防线
【免费下载链接】tregOpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn项目地址: https://gitcode.com/GitHub_Trending/treg/treg
treg 是一个面向 Agent 的工具调用代理平台(可理解为"Agent 工具版 OpenRouter"),代理 3000+ 工具端点并按次计费。正因为所有凭证都汇聚到一台服务器,treg 的安全设计尤为关键:Fernet 静态加密保证密钥落盘即密文、write-only 密钥保证 API Key 只显示一次、fire-and-forget 审计日志保证每笔调用可追溯。本文带你完整看懂这套三层防线。
一、Fernet 静态加密:密钥落盘即密文
1. 核心原则:永远不存明文
treg 的第一条安全决策写在代码注释里:secrets are never stored plaintext(密钥绝不明文落盘)。所有上传的 API Key、OAuth Token 在写入数据库前都会经过 Fernet 对称加密。
核心实现集中在 src/treg/crypto.py,整个模块只有几十个行,却定义了完整的加密契约:
encrypt()/decrypt():使用环境变量TREG_SECRET_KEY派生的 Fernet 密钥加解密;new_key():生成一把全新的 urlsafe 密钥,供部署时配置;new_token()/hash_token():生成高熵随机 token 并计算其 SHA-256 哈希。
2. 一个巧妙的"开发环境警报器"
在开发环境如果忘记配置TREG_SECRET_KEY,treg 会临时生成一把只在内存中存活一把一次性密钥。后果是:重启进程后所有密钥都无法解密——这是一种"大声的失败",逼着运维人员意识到"你该配置密钥了",而不是默默用明文运行。
3. 解密只发生在调用瞬间
解密动作只在一个位置发生:代理转发请求前的内存中。密钥被解出、注入请求头、发往上游,然后立即丢弃。明文密钥永远不会返回给任何客户端,任何 API 响应的密钥视图都是脱敏的元数据(名称、所有者、健康状态),而非密钥值本身。
二、write-only 密钥:API Key 只显示一次
1. 数据库里根本没有你的 Key
这是 treg 密钥管理最值得学习的设计。当你创建一个 API Key(形如treg_abc123...)时:
mint_secret()生成明文,同时计算前 12 位安全前缀和SHA-256 哈希;- 明文在响应中恰好返回一次,此后永远不再出现;
- 数据库只保存哈希和短前缀——哈希用于登录时查找匹配,前缀用于在控制台辨认"这是哪把 Key"。
这意味着即使整库被拖走,攻击者拿到的也只有不可逆的 SHA-256 摘要。由于 token 本身就是高熵随机串(非密码),treg 选择快速的 SHA-256 而非慢速密码哈希——因为攻击者无法暴力枚举高熵空间,只能做哈希碰撞,而这在计算上不可行。
密钥模型定义见 src/treg/models.py(ApiKey表:key_hash唯一索引、safe_prefix、state状态机active | disabled | revoked),生命周期规则在 src/treg/domain/identity/api_keys.py。
2. 原子轮换:一次只能换一把
轮换(rotate)是最容易出并发事故的运维操作。treg 用一条条件 UPDATE 语句作为跨进程锁:
UPDATE api_keys SET state='revoked' WHERE id=? AND state IN ('active','disabled') AND replacement_key_id IS NULL先"赢得"这条更新的一方(rowcount=1)才有资格插入新 Key;并发请求拿到 rowcount=0,直接收到409 Conflict,而不是创建出两把替换密钥或泄露数据库错误。旧 Key 被标记revoked且永久失效,但审计行、事件与 Activity 快照全部保留——撤销是永久的,只是隐藏,不是删除。
另外两个贴心细节:
- 响应头防缓存:任何返回完整凭证的响应都带
Cache-Control: no-store,密钥不会落进浏览器或代理缓存; last_used_at不阻塞认证:最后使用时间只是展示元数据,认证事务提交后才在 5 分钟窗口内异步更新,高并发下请求不会挤在一行写锁上。
三、审计日志:fire-and-forget 也要有纪律
1. 设计目标:审计绝不能拖慢业务
审计模块 src/treg/audit.py 的模块注释开宗明义:never block the proxied response——写审计行入队后立即返回,响应流不等待落盘。
但"随手一丢"是有纪律的,关键参数一目了然:
| 常量 | 值 | 作用 |
|---|---|---|
_MAX_CONCURRENT_WRITES | 1 | 每进程单写者,省连接池 |
_BATCH | 200 | 每 200 行一次 INSERT 往返 |
_MAX_PENDING | 5000 | 队列超限即丢审计行,绝不无限膨胀 |
极端突发流量下宁可丢几行审计(并记录"已丢弃"计数),也不允许审计把内存吃爆、把真实业务卡死——审计是尽力而为,业务才是第一优先级。
2. 每笔调用都留下身份指纹
record_call()写入的CallRecord不只有 URL 和状态码,还包括调用者身份三元组:api_key_id(Key 的数据库 ID)、api_key_name(人类可读名称)、api_key_prefix(短前缀)。注意——审计表里依然没有完整密钥,只有足以定位"谁"的指纹。
3. 密钥管理事件:只增不改的账本
ApiKeyEvent是一张append-only(只追加)的审计表:谁、什么时间、对哪把 Key、做了什么(rotated、revoked、disabled……)。模型注释强调:It never contains complete secret material——审计表永远不包含完整密钥材料,审计本身也不能成为泄密渠道。
团队控制台对密钥的禁用、轮换、撤销入口见 src/treg/web/ 下的管理页面,危险操作(撤销、移除成员)有独立的确认界面。
四、边界防御:密钥的所有权围栏
三层防线之外,treg 还有一道横向的所有权边界:
- 成员只能绑定自己上传的密钥(
domain/tools/bindings.py的validate_bindings强制检查),防止把队友的 Key 洗进自己控制的工具再经代理base_url外传; base_url双重 SSRF 防护:注册时校验 + 调用时重新解析主机,屏蔽回环、内网、链路本地与云元数据地址(含 DNS 重绑定),且禁止把工具指向 treg 自身;- 本地运行的密钥特许(local-run grant)是全系统唯一允许取回密钥值的路径,且仅限会员 + 工具所有者开启 + 全程审计,OAuth 场景下只放出短命的 access token,
refresh_token与client_secret永远不出服务器。
五、部署自检清单
把 treg 的安全设计提炼为可执行的 5 条检查项:
- 配置
TREG_SECRET_KEY——用new_key()生成;未配置时密钥重启即失效,是刻意的告警机制; - 创建 Key 后立刻保存明文——write-only 意味着界面里再也找不到它;
- 轮换遇到 409 不要重试轰炸——说明另一请求已赢得轮换,检查 Key 列表即可;
- 关注审计丢弃计数——队列超过 5000 会丢行,持续丢弃说明写入端(连接池/数据库)需要扩容;
- Webhook 告警地址走公网——健康检查的凭证失效通知会拒绝回环/内网/CGNAT 地址,防止被当作 SSRF 跳板。
结语
treg 的安全架构没有花哨的组件,却处处是"失败要响亮、泄露要不可能"的取舍:Fernet 让密文成为唯一落盘形态,write-only 让数据库拖库等于拖走一堆哈希,审计日志用单写者 + 批量 + 背压保证"记录一切"与"绝不拖垮业务"可以共存。这三层防线的设计思路,对任何要托管用户凭证的代理类项目都是很好的范本。
延伸阅读:docs/context/architecture/auth-secrets.md(认证与密钥完整设计)、tests/test_audit.py(审计纪律的回归测试)、tests/test_api_keys.py(密钥生命周期测试)。
【免费下载链接】tregOpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn项目地址: https://gitcode.com/GitHub_Trending/treg/treg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考