安全加固
状态:Roadmap
最后核对:2026-05-15
本文记录证书 pin、本地配置持久化、密钥驻留、rekey 策略、Noise 首包行为和 REALITY 密钥轮换相关的安全加固路线。
当前状态
| 领域 | 状态 | 当前行为 |
|---|---|---|
| 上游 TLS SPKI pin | 完成 | TCP upstream TLS 规则携带 upstream_tls_spki_pins_sha256。Agent 对叶子证书 SPKI DER 计算 SHA-256 并比对 pin;启用 upstream TLS 但没有 pin 的规则会被拒绝。 |
| Agent 本地配置 cache | 已移除 | Agent 不再读写本地配置 cache 文件。已应用的 L4/L3 配置只保留在进程内存中;重启后由控制面重新下发。 |
| Secret zeroization | 完成 + 规划 | 控制 PSK decode buffer、生成的 PSK 和控制通道临时私钥已使用 Zeroizing。全 runtime secret wrapper 覆盖仍是规划。 |
| Secret logging | 完成 | runtime fingerprint 不再包含私钥原文 hex;需要稳定标识时使用短 SHA-256 标识。 |
| 控制通道 rekey | 完成 | Agent 会在 NEXUS_CONTROL_REKEY_INTERVAL_SECS 后重连 secure control session,默认 6 小时。 |
| QUIC 重连周期 | 完成 | QUIC REALITY client 会在 NEXUS_QUIC_MAX_CONNECTION_AGE_SECS 后关闭旧连接并重连,默认 6 小时。 |
| Noise 首包 payload | 完成 | Noise handshake message 不再携带业务 payload。首个业务 payload 会排队,等 transport key 生成后再发送。 |
| REALITY 双 profile 轮换 | 规划 | 当前 runtime 会拒绝同一个 UDP listener 上的 QUIC REALITY route 混用不同 private key,并提示使用 dual profile/epoch 轮换。完整 active/next/retiring 生命周期尚未实现。 |
安全目标
当前加固切片目标是:
- 不在本地磁盘创建控制面 L4/L3 配置副本。
- 只要 Agent 进程还活着,控制面断开后已应用的数据面 listener 继续从内存状态运行。
- Agent 重启后必须等待控制面权威 replay,不信任本地 last-known-good cache。
- 为未来审计“本地已应用配置”和“后端期望配置”的差异提供 apply 信号。
- 普通业务 upstream TLS 不再使用 no-verify,而是使用显式 SPKI pin。
- Noise 首个 handshake packet 不携带业务 payload。
它不承诺防御已经获得运行时最高权限的攻击者。如果攻击者能读取进程内存、 ptrace、抓 core dump,Zeroizing 只能缩短暴露窗口,不能保证“内存里一无所获”。
SPKI Pin
TcpProxyRule 包含:
protobuf
repeated string upstream_tls_spki_pins_sha256 = 20;支持 hex、base64 或 base64url 编码,内容是:
text
SHA-256(subjectPublicKeyInfo DER)规则:
- upstream TLS 必须至少有一个 pin。
- 可以配置多个 pin,用于证书或密钥轮换。
- pin 必须显式配置;默认不做 TOFU。
- REALITY camouflage target 探测可以保留 probe-only no-verify 路径,但普通 upstream TLS 不可以。
已移除本地配置 Cache
之前的 Agent 磁盘 cache 已移除。Agent 不再创建、加载、迁移或更新:
agent-state.nxcagent-state.previous.nxcagent-state.json
这些 cache 环境变量不再属于运行时契约:
NEXUS_CACHE_DIRNEXUS_AGENT_CACHE_KEYNEXUS_AGENT_CACHE_KEY_FILENEXUS_AGENT_CACHE_ALLOW_INSECURE_KEY_FILE
当前运行语义:
- 如果配置已经应用,之后控制面断开,现有 forwarder 和 L3 tunnel 继续使用内存状态运行。
- 如果 Agent 进程重启且 backend 不可用,本地不会应用任何旧配置;Agent 会重连并等待控制面权威 replay。
- 可以在进程内用 hash/bytes 跳过重复配置,但这些状态不落盘。
理由:如果没有硬件背书、运维持有或外部 secret,本地 cache 加密不能解决离线磁盘泄露。 不保存本地配置 cache 更简单、更容易审计,也把恢复边界明确放在 backend 可用性/HA 上。
Rekey 和前向安全
Secure control session 使用认证的 open-session 交换和 AEAD frame。Agent 会按配置周期主动重连,重新派生 session key。
Noise UDP 行为刻意向 WireGuard 的安全模型靠拢:
- handshake packet 不携带任意业务 payload。
- 首个业务 payload 在本地排队。
- 只有 transport key 派生完成后才发送业务 payload。
这样仍能保持较快的用户感知路径,同时避免把应用数据塞进首次 handshake message 带来的弱前向安全语义。
QUIC REALITY 当前以连接生命周期作为 rekey 边界。如果未来 QUIC 库暴露显式 key update 接口,可以进一步接入;目前使用 reconnect。
REALITY 轮换
当前同端口 QUIC REALITY 凭据约束:
- 同一个 UDP listen address 上的 QUIC REALITY route 必须使用同一个 REALITY private key。
- 不同 SNI/shortId 可以在该 key 边界内共享 QUIC REALITY listener。
- 同 listener 上的 QUIC REALITY route 混用不同 REALITY private key 会被拒绝。
- 这个凭据边界不阻止同一个 UDP socket 同时承载 UDP RAW fallback 或 UDP Noise route,只要包分发信号明确。
规划中的完整轮换模型:
- REALITY profile 存储
active、next、retiring状态。 - 先部署 dual-accept profile set。
- 客户端逐步切换到 next public key 和 shortId。
- telemetry 显示旧 key 使用清空后,再 retire old profile。
- 审计 profile epoch、public-key fingerprint、节点应用状态和仍使用旧 key 的连接数。
剩余工作
- 在 protobuf/database 边界之后,把更多运行时
Vec<u8>/[u8; 32]私钥载体替换为 secret newtype。 - 为 config update 增加 backend Ed25519 签名校验。
- 增加 SPKI pin 管理 API 和 UI。
- 实现完整 REALITY active/next/retiring credential 生命周期。
- 将 pin failure、config replay/apply event、rekey event、REALITY rotation event 接入监控矩阵和 TimeDB pipeline。