外观
安全模型
本文讲系统的安全姿态:鉴权与 token、注册审批闸门、被刻意禁止的控制模型、私钥处理、写盘与容器边界。配置项见 ../reference/configuration.md,接口鉴权见 ../reference/api.md。
两面同构:鉴权与账号模型在两个控制面各有一份同构实现。当前生效面为主控制面(TS 面,
apps/control-server-ts/src/auth.ts、src/services/adminAccounts.ts);文中引用的 Python 路径对应温备控制面(apps/control-server),语义一致。
鉴权边界
| 接口面 | 鉴权 | 说明 |
|---|---|---|
Admin API(/api/v1/admin/*、/api/v1/ui/*) | Authorization: Bearer <凭据>(静态 DN42_CONTROL_ADMIN_TOKEN 或账号会话令牌) | fail-closed:未配置 admin token 时全部 403(含账号登录);凭据错 / 会话过期或被吊销 401 |
账号登录(POST /api/v1/auth/login) | 无 Bearer;用户名 + 密码 + Turnstile 服务端硬校验 | Turnstile 校验失败 400(app/api/v1/auth.py:122-126);凭据错统一 401 invalid credentials(防枚举);连续失败限速 429;见 ../reference/api.md |
Agent HTTP(/api/v1/agent/*,除 register) | Authorization: Bearer <agent token> | token 绑定 node_id |
Agent WebSocket(/agent/ws/{node_id}) | Bearer,且 principal.node_id == URL node_id | 不符 4403、无效 4401 |
POST /agent/register | 请求体内 enrollment token | 唯一不需 Bearer 的写接口 |
GET /healthz | 无 | DB 探针 |
所有 admin 写操作(含鉴权失败)由中间件记入 admin_audit_log,见 ../reference/database.md。
token 哈希模型
- 数据库只存 sha256 哈希,不存明文。
- Agent token:格式
<id>.<secret>(id为非密查找键)或固定字面量(literal_token_id从哈希派生 id)。明文只在签发响应里出现一次,不落日志、不入库。resolve()校验未撤销、未过期。rotate()撤旧签新。 - Enrollment token:同哈希模型。
node_id为空 = 任意节点可用,非空 = 仅该节点;一次性(used_at标记消费)。 - 账号会话令牌(
admin_sessions):登录签发secrets.token_urlsafe(32),DB 只存 sha256;TTL 默认 24h,改密吊销该用户全部存活会话。账号密码(admin_users)只存 argon2id 散列;不存在的用户名也跑一次对照校验,抹平时间侧信道。
注册审批闸门
未知节点无法自助上线,必须经管理员放行(agent_http.py register + PendingRegistrationStore):
- agent
POST /agent/register(enrollment token + requested_node_id + inventory)。 - 校验 enrollment token(全局或绑定、未消费、未过期)。
- 查审批状态:
rejected→ 403(显式否决)。pending→ 返回PENDING_APPROVAL,不消费 enrollment token,agent 周期重试。- 未知节点 → 记为 pending 注册申请。
- 已落库且有 generation → 签发 agent token,标记 enrollment 已用。
"approve 只是放行名单,provision 才下发 DesiredState"——两步分离避免误放行即生效。流程见 ../guides/node-onboarding.md。
私钥处理(WireGuard escrow)
- 节点 WireGuard 私钥在节点本地生成,0600 存于状态目录,永不写进渲染产物(占位
secret://),永不上传明文。 - 公钥上报控制面,作为内部对端公钥派生的单一真源。
- 私钥用控制面下发的离线恢复公钥 RSA-OAEP 封存(escrow)后上报,供灾难恢复。恢复私钥永不在控制面/节点出现,只在离线
dn42-recover工具里使用。 - 控制面对上报公钥做一致性严格校验:不符返回 409 中止 apply。
完整 escrow 与恢复演练见 ../guides/secret-recovery.md。
禁止的控制模型
系统刻意不提供以下能力,这是核心安全设计而非缺失:
- ❌ 远程 shell / 任意命令执行接口。
- ❌ 控制面直接 push 命令到节点执行。
- ❌ 节点接收并执行控制面下发的脚本片段。
控制面能表达的只有声明式期望状态(DesiredState)。怎么达成完全由节点本地 Agent 基于本机观测决定(见 architecture.md)。即使控制面被攻陷,攻击者也只能改"想要什么",无法直接在节点上执行任意命令——节点渲染的是受 schema 约束、经模板生成的配置,不是可执行指令流。
写盘与容器边界
- Agent 渲染文件路径强校验(禁
..、绝对路径、NUL、Windows 盘符,见dn42_runtime),原子写盘。 - 容器由受管 label(
dn42.managed等)标识;Agent 只动带本节点 label 的容器,不碰其它容器。 - systemd 单元用
ProtectSystem=strict、NoNewPrivileges=true、受限ReadWritePaths(见 ../guides/deployment.md)。
多管理员 / RBAC
管理面凭据为**静态 admin token(机器 / 自动化)+ 管理员账号(人机登录)**双轨;账号侧无细粒度 RBAC(role 为预留字段,见 ../ROADMAP.md)。生产应妥善保管 admin token、用 TLS 终止反代(全链路无明文段:CF 代理时 SSL 模式 Full (strict),源站强制 HTTPS + HSTS)、按需收紧 CORS 白名单。