Skip to content

安全模型

本文讲系统的安全姿态:鉴权与 token、注册审批闸门、被刻意禁止的控制模型、私钥处理、写盘与容器边界。配置项见 ../reference/configuration.md,接口鉴权见 ../reference/api.md

两面同构:鉴权与账号模型在两个控制面各有一份同构实现。当前生效面为主控制面(TS 面,apps/control-server-ts/src/auth.tssrc/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 /healthzDB 探针

所有 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):

  1. agent POST /agent/register(enrollment token + requested_node_id + inventory)。
  2. 校验 enrollment token(全局或绑定、未消费、未过期)。
  3. 查审批状态:
    • rejected403(显式否决)。
    • 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=strictNoNewPrivileges=true、受限 ReadWritePaths(见 ../guides/deployment.md)。

多管理员 / RBAC

管理面凭据为**静态 admin token(机器 / 自动化)+ 管理员账号(人机登录)**双轨;账号侧无细粒度 RBAC(role 为预留字段,见 ../ROADMAP.md)。生产应妥善保管 admin token、用 TLS 终止反代(全链路无明文段:CF 代理时 SSL 模式 Full (strict),源站强制 HTTPS + HSTS)、按需收紧 CORS 白名单。