架构
Mistgate 的组件、面板与节点 Agent 如何通信、数据存放位置,以及各部分落入不当人员手中时能做什么、不能做什么。
Mistgate 由两个 Go 程序组成:面板(mistgate)和节点 Agent(mistgate-node)。本页介绍它们的组成部分和边界、两者之间的协议、面板的 HTTP 接口与存储、协议插件契约,以及简要的安全模型。要了解相关概念,请从概览开始。
组件#
面板模块#
面板是一个进程。cmd/mistgate 将各模块连接起来;模块之间只通过小型接口交互。
模块(internal/panel/...) |
职责 |
|---|---|
httpserver |
监听器、伪装站点、管理后台、公共挂载点与 Agent 接入端点之间的路由、速率限制、TLS(静态文件或 Let’s Encrypt)、安全响应头。 |
auth |
管理员与角色、通行密钥(WebAuthn)、密码加身份验证器验证码、会话、再次验证、API 令牌、所有者审批、Turnstile 人机验证、审计日志。 |
instance |
品牌信息(名称、徽标、强调色、语言),以及 设置 → 域名 中显示的只读地址。 |
fleet |
面板 CA、节点注册、Agent 流、存活检测、统计数据与事件的接收、期望状态的修订与增量、发往节点的命令,以及管理后台的节点和概览 API。 |
access |
协议配置、节点上的服务器、分组、用户、设备和凭据;用户获取内容的规则;订阅视图。 |
protocols |
协议插件(Hysteria2、VLESS、AmneziaWG)的注册表,以及由配置结构驱动的设置。 |
subs, subsettings, dns |
公共订阅端点和用户页面、订阅设置与 User-Agent 规则、DNS 预设。 |
health |
客户端视角检测、告警、节点诊断报告与修复、数据保留。 |
update |
<data-dir>/dist 中的签名发布包及其从 GitHub 下载的流程、节点 Agent 的分阶段滚动更新和定时更新、面板自身的更新。 |
provision |
通过 SSH 安装节点(持久化安装任务及其向导),以及节点已保存的 SSH 凭据和密码轮换。 |
backup |
将面板加密备份到 Cloudflare R2,以及 mistgate backup 使用的离线部分。 |
warp |
节点的 Cloudflare WARP 账户。 |
mcp |
基于同一管理 API 提供的 MCP 服务器。 |
store, vault, pagepass |
数据库、使用主密钥加密封存、由主密钥派生的用户页面密码。 |
节点 Agent#
部分(internal/node/...) |
职责 |
|---|---|
agent |
节点注册、与面板保持的单一流及其重连、负责将每项变更应用到引擎和主机的单一工作进程、可靠发件队列、命令、最近 5000 条记录的内存日志。 |
engine, hysteria2, vless, awg |
每种协议对应一个引擎。Hysteria2 在进程内运行官方核心;VLESS 在进程内运行 xray-core;AmneziaWG 在用户态(amneziawg-go)或内核模块中运行。 |
hostctl |
Agent 所管理的唯一主机状态:它的 nftables 表、sysctl 和 journald 基线、隧道防火墙、其服务器在已启用 UFW 时使用的 UDP 端口(只管理由它自己标记的规则,不会改动你的规则;ufw 在 Agent 沙箱之外运行)、主机信息和指标。 |
torrentguard |
BT 下载拦截:根据协议特征识别客户端发起的明文 BitTorrent 流量,不按端口识别。Hysteria2 会检查它代理的流;对 AmneziaWG,主机会将隧道流量交给 Agent,在内核中拦截匹配项。参见 BT 下载拦截。 |
warp, egress |
节点的 WARP 隧道,以及引擎使用的出口(直连或 WARP)。 |
certs |
节点上服务器使用的 Let’s Encrypt 证书和自签名证书。 |
doctor |
主机检查和六种安全修复。 |
update, awgprep |
使用签名发布包进行自更新;按需构建 AmneziaWG 内核模块。 |
speedtest |
带宽测试:通过并行下载和上传测试最近的 Ookla 服务器(Cloudflare 等作为备用),并限制字节数和耗时。 |
引擎不会直接操作 nftables、sysctl、解析器或证书;它们从 Agent 获取所需内容。
节点注册与面板 CA#
面板有自己的证书颁发机构(面板 CA),这是一个有效期为 10 年的 ECDSA P-256 CA,在首次启动时创建,其密钥使用主密钥加密封存在数据库中。它会签发两类证书:
- 节点证书:有效期为 30 天的客户端证书。通过证书中的 URI 识别节点,绝不根据节点在消息中写入的内容识别。
- Agent 接入端点的服务器证书:有效期为 90 天,仅保存在内存中,为私密 TLS 名称签发。整个过程不涉及公共 CA,因此该名称不会出现在 Certificate Transparency 日志中。
注册流程:
- 添加节点会创建节点和一个一次性令牌(256 位,默认有效期为一小时,以 SHA-256 哈希值存储)。安装命令中包含面板地址、私密 TLS 名称、CA 指纹和令牌。通过 SSH 安装时会自行生成令牌,并通过 SSH 运行相同命令。
mistgate-node enroll会在节点上生成密钥和证书请求,通过 TLS 1.3 和私密名称建立连接,并且只接受证书链最终连接到指纹已固定的 CA 的服务器。- 面板检查令牌、签发证书,并将证书和 CA 一并返回。如果在 10 分钟内使用相同密钥重复调用,会返回同一张证书,以应对响应丢失的情况。同一地址在一分钟内失败 10 次后,会暂时拒绝请求(IPv6 按一个 /64 计算)。
- 此后,每次调用都使用双向 TLS。面板会在每次握手时检查证书序列号(已关闭会话票据,因此每次握手都是完整握手),并在运行中的流上每 30 秒检查一次:证书被吊销或节点已退役时,会立即断开连接。
- 剩余有效期不足 10 天时,Agent 会使用新密钥续期。续期后旧证书仍有 3 小时有效期,因此未收到响应的 Agent 可以再次续期(每小时重试一次)。重新注册节点或将节点退役时,会立即吊销其证书。每个节点每小时最多获得 4 张证书。
Agent 通过面板的公共端口访问接入端点:使用私密名称进行 TLS 握手时会连接到 Agent 接入端点;使用其他名称时会进入常规公共站点。使用 serve --agent-listen 可以为 Agent 单独提供一个监听器。
Agent 流#
每个节点始终只保留一个由节点主动建立的双向 Connect 流,运行在 HTTP/2 上。同一节点的新流会替换旧流。
- 问候消息。 Agent 连接时会发送版本、能力、主机信息、当前运行内容的修订号和哈希,以及可靠消息的位置。面板会返回自己的时钟、节点设置和已收到的内容。
- 存活检测。 节点发来的任何消息都算活动。即使没有数据,统计信息也每 10 秒发送一次,因此同时充当心跳。节点在 90 秒内没有消息时,面板会关闭该流(按节点配置);没有流的时间少于 10 分钟时,节点状态为「主机闪断」,时间更长则为「无法连接」。Agent 会按 1 到 60 秒的退避间隔重连。
- 能力。 新功能采用增量方式添加。Agent 会列出它支持的功能(
doctor/1、update/1、awg/1、warp/1等),面板只会向列出相应能力的 Agent 发送功能。新面板可以继续与旧 Agent 配合使用;管理后台会显示需要更新 Agent 的情况。
期望状态与已应用哈希#
面板掌握最终状态。面板会为每个节点计算期望状态:节点上的每台服务器及其完整设置、所有可使用该服务器的用户凭据,以及节点的 WARP 配置。
- 全量状态会列出所有内容;未列出的内容都会被移除。增量会将一个修订版变更为下一个修订版,因此添加用户时不必重新发送所有内容。
- 双方会计算相同的状态哈希(共享代码位于
internal/statehash)。应用后,Agent 会计算引擎实际持有内容的哈希并返回。哈希相同表示同步;不同则会触发state_drift事件和全量重发,若问题持续存在还会产生告警。 - 仅凭据变更时,只会替换正在运行的服务器的凭据集;设置变更时,只会重启该服务器。
- 节点只会收到它必须验证的内容。Hysteria2 接收每个用户令牌的 SHA-256 哈希,而不是令牌本身;AmneziaWG 接收每台设备的公钥和预共享密钥,绝不会接收私钥。
- Agent 会将最近应用的状态保存在磁盘上,并在启动时运行该状态,因此即使面板无法访问,节点也会继续提供服务。
- 对于设有截止日期的凭据,即使面板不可用,Agent 也会在日期过后自行删除。
统计数据与事件#
统计数据批次(每个凭据的流量、打开的会话、主机指标、每台服务器的运行状况)和事件都属于可靠消息:每条消息都有序列号,Agent 会一直保留,直到面板确认收到。面板会在单个事务中保存每条消息和最后的序列号,因此重连后重新发送的批次不会被重复计数。面板无法访问时,Agent 最多排队保留 6 小时的批次(等待超过 360 个时,会合并同一小时的批次),若必须丢弃较早批次,则会报告 stats_dropped 事件。流量按小时统计。用户的流量配额一旦被某个批次超出,就会在该批次处理后立即被断开。
诊断报告是快照,不属于可靠消息:始终以最新报告为准。Agent 还会执行命令(重启服务器、断开会话、退役、运行诊断、应用修复、更新、回滚、准备 AmneziaWG 内核模块、测量带宽、流式传输日志),并且每个命令只返回一个结果。
更新、SSH 安装与备份#
- 节点 Agent从
<data-dir>/dist中的签名发布包更新。你可以将发布包复制到该目录;或者在安装的密钥(release.pub)验证成功时,面板每 10 分钟从最新的稳定版 GitHub Release 下载更新。分阶段滚动更新、单节点更新或定时更新会将已签名清单发送给 Agent;Agent 通过自己的双向 TLS 连接从面板获取二进制文件,使用编译在自身程序中的密钥验证,将文件放在当前程序旁边并重启。面板的健康检查决定更新是否通过;更新失败时会回滚;systemd 单元也会在程序反复崩溃时恢复之前的二进制文件。参见更新。 - 面板每 10 分钟检查最新的稳定版 GitHub Release,只有在所有者确认(并再次验证身份)且面板清单由编译在当前运行面板中的密钥签名并列出了该二进制文件时,才会安装更新。面板沙箱外的辅助程序会以 root 权限再次下载并验证发布包、停止面板、归档数据目录、替换二进制文件并启动面板(面板以 root 运行时使用临时单元,否则使用固定单元
mistgate-panel-update.service)。如果面板未能持续运行 45 秒,辅助程序会恢复之前的二进制文件和数据。 - SSH 安装使用所有者输入的密码,以 root 身份或具有免密 sudo 权限的用户身份连接,并且只连接到所有者已确认的主机密钥。面板会在已启用的主机防火墙(UFW 或 firewalld)中开放 SSH、80/tcp、443/tcp 和 443/udp,上传可信发布包中的 Agent,通过标准输入使用新令牌注册 Agent,启动服务单元并等待节点连接。密码会使用主密钥加密封存在数据库中,作为节点已保存的凭据(可轮换密码,也可在再次验证身份后查看密码);即使节点已退役,这些凭据仍会保留,直到所有者清除。参见通过 SSH 安装节点。
- 备份按计划(每 1 到 168 小时)或按需运行:面板会对数据库创建快照,将其与其他数据文件及主密钥打包,用 age 加密归档到所有者的公开接收方,然后上传到 Cloudflare R2。私钥不会到达面板;
mistgate backup restore会在离线状态下解密。参见备份。
Cloudflare 版将面板数据存储在 D1 中,并通过 mistgate edge deploy 安装(参见安装 Cloudflare 版)。目前尚不支持隧道检测;计划将检测迁移到节点上(ADR 0004)。节点的 SSH 安装在第二个 Worker 脚本中运行,并由一个 Durable Object 持有套接字(ADR 0008);对于 Worker 无法连接的服务器,仍需使用手动安装命令。Worker 中无法使用基于文件的面板备份和面板更新操作;该版本的异地备份需要 R2 绑定(第 2 阶段)。mistgate edge deploy 会设置 SETUP_TOKEN_SHA256 密钥(它选择的设置令牌的 SHA-256),因此 Worker 日志只记录该令牌的过期时间,不会记录设置链接;若手动部署时未设置该密钥,则会记录一次设置链接,参见 edge/worker/README.md。
面板的 HTTP 接口#
| 监听器 | 提供的内容 |
|---|---|
公共(--listen) |
伪装站点;位于私密订阅前缀下的公共挂载点(订阅、用户页面、对应的自助服务调用、品牌徽标);位于私密前缀或私密主机名下的管理后台;处理私密 TLS 名称的 Agent 接入端点。 |
管理(--admin-listen,可选) |
仅管理后台,使用普通 HTTP,供单独监听器模式使用。 |
Agent(--agent-listen,可选) |
仅 Agent 接入端点,使用 TLS。 |
ACME HTTP(--acme-http,需配置 --acme-domain) |
Let’s Encrypt HTTP-01 验证,以及重定向到 HTTPS。 |
公共监听器按以下顺序判断请求去向:使用私密 Agent 名称的 TLS 连接进入 Agent 接入端点;请求私密管理主机名或位于私密管理前缀下的请求进入管理后台;位于订阅前缀下的请求交给订阅处理器;其他请求进入伪装站点。非规范路径、错误前缀和未知主机名最终都会得到伪装站点的两种响应之一(页面或 404)。每个 404 响应至少延迟 8 ms,因此无法通过计时区分未知订阅令牌(一次数据库读取)和未知路径。即使订阅处理器内部生成 404 或 405,也会替换为伪装站点的 404。
管理后台由单页应用、位于 <admin>/api/ 下的 Connect API、位于 <admin>/mcp 的 MCP 服务器,以及位于 <admin>/nodes/install 的服务端渲染 SSH 安装管理器组成。API 会先检查会话:需要会话 Cookie(__Host-sid,Secure、HttpOnly、SameSite=Strict),或 Authorization: Bearer 请求头中的 API 令牌。管理后台响应带有严格的 Content-Security-Policy、X-Frame-Options: DENY、no-store、noindex,并在 TLS 上启用 HSTS;拒绝来自其他源的状态变更请求。
每个客户端(一个 IPv4 地址或一个 IPv6 /64)在公共、管理和 Agent 三侧分别有请求额度。超出额度时,客户端会收到伪装站点样式的 429 响应。订阅端点还单独设有限制:一个网络在一分钟内尝试 20 个未知令牌后,15 分钟内只能看到伪装站点;一个令牌每小时最多可获取 60 次;一个链接在一天内被 8 个以上网络使用时,会产生「链接共享」事件。
存储#
- SQLite通过
modernc.org/sqlite使用(纯 Go,无 cgo),运行在 WAL 模式下,包含一个写连接和一个由四个只读连接组成的连接池。数据表为 STRICT;ID 是带类型前缀(nod_、usr_等)的 128 位随机字符串;时间使用 Unix 秒数。 - 迁移是嵌入二进制文件的 goose SQL 文件(
internal/panel/store/migrations),每次打开数据库时都会应用。已应用的迁移绝不修改;变更通过新增文件实现。 - 密钥使用主密钥进行 XChaCha20-Poly1305 加密封存,并将行 ID 作为关联数据,因此密封值不能移到其他行。密码存储为 argon2id 哈希;API、注册和订阅令牌通过其 SHA-256 哈希查找。用户页面密码由主密钥和链接派生,绝不存储。
- 文件:数据目录布局参见配置。
协议插件契约#
协议的两部分共享一套术语(internal/plugin):节点上的服务器规格、用户凭据、流量增量、会话和运行状况。
面板部分(internal/panel/protocols)负责描述和呈现协议:
| 方法 | 用途 |
|---|---|
ID, DisplayName |
hysteria2 / 「Hysteria2」、vless / 「VLESS」、awg / 「AmneziaWG」。 |
SettingsSchema, DefaultSettings, Validate, Summary |
管理后台据此生成协议配置表单的 JSON Schema、带有自动生成密钥的默认值、各字段验证,以及协议配置卡片上的单行摘要。 |
BuildInbound |
将协议配置设置、节点信息和每节点覆盖项转换为节点运行的规格。 |
IssueCredential |
生成设备凭据:客户端提交的密钥,以及发送到节点的验证值。 |
Clients, Render |
可使用该协议的应用及格式,以及该协议在指定格式订阅中的内容。 |
Doctor |
集群诊断使用的协议专属检查。 |
可选接口用于处理部分协议才需要的内容:每台服务器的密钥材料(InboundInitializer,AmneziaWG 用它生成服务器密钥对)、每台设备和每个协议配置各一份凭据(PerDevice)、最低客户端版本(ClientRequirements),以及验证前的设置修复(SettingsNormalizer)。具体签名见 internal/panel/protocols/protocol.go。
节点部分(internal/node/engine)负责运行协议。引擎会使用凭据应用规格(操作幂等;凭据可在不重启的情况下替换)、移除服务器、收集流量增量和会话、断开会话、报告其持有内容(供计算状态哈希)及其运行状况。Agent 会向引擎提供证书、出口(直连或 WARP)、节点 DNS,以及用于处理未认证请求的伪装站点处理器。
两个注册表都内置在二进制文件中(internal/panel/protocols/builtin 和 cmd/mistgate-node/wire.go);程序运行时不会加载插件。外部协议插件目前处于计划中。
安全模型简述#
更详细的登录、角色、再次验证、会话和审计日志说明见安全。
API 令牌被盗
- 只能调用令牌权限级别允许的方法:只读权限只能读取;操作员还可以管理用户(创建、更改、启用、续期、重置流量、吊销设备)并运行检测;管理员还可以读取审计日志,以及节点已保存的 SSH 端点和登录名。
- 不会获得订阅链接、设备密钥、页面密码或服务器密码:令牌可调用的方法均不会返回这些内容。
- 无法通过再次验证,也无法创建或吊销令牌、修改登录设置、管理节点或协议配置。应用诊断修复,启动、暂停或取消滚动更新,安排节点更新,通过 SSH 安装节点或更改其 SSH 密码,都只能通过 MCP 执行,并且必须由所有者在管理后台批准计划。
- 令牌会过期(默认 90 天,最长 365 天),并受速率限制。令牌所做的变更会以令牌名称记录在审计日志中;读取操作每分钟最多记录一次,并附上来源地址。可在 集成 中吊销令牌。
订阅链接被盗
- 可访问该用户原本能访问的内容:其服务器配置和用户页面;如果启用了自助服务,还包括 AmneziaWG 密钥。启用页面密码时,密码会保护用户页面和自助服务,但不会保护订阅本身。
- 无法获取其他用户、管理后台或节点信息(节点的公共地址除外)。
- 每个获取链接的应用都会显示在该用户的设备列表中;链接从许多网络使用时会产生「链接共享」事件。生成一个生成新链接(旧链接立即失效),然后删除无法识别的设备:它们的凭据会从每个节点中移除。
节点遭入侵
- 可获取该节点所需的信息:其服务器的协议配置密钥(混淆密码、自签名密钥、AmneziaWG 服务器密钥)、WARP 密钥,以及可使用该节点的用户验证值。它可以看到连接到自身的 Hysteria2 客户端令牌。
- 只能以自身身份通过 Agent 接入端点与面板通信:报告自身服务器的流量、事件和运行状况(平均超过 10 Gbit/s 的批次会被丢弃)。它无法调用管理 API、读取其他节点的密钥,也看不到未使用该节点的用户。
- 从集群退役会立即吊销该节点的证书;之后应更改它运行过的协议配置密钥。
面板或其数据目录遭入侵
- 主密钥掌握所有密钥;它可以解密已存储的所有密钥,而面板决定每个节点运行什么。
- 面板保存着通过 SSH 安装的节点的 SSH 凭据,并使用主密钥加密封存:同时拿到面板和主密钥的人可以用这些密码登录服务器,无论是否有签名。通过安装命令安装的节点不会保存 SSH 凭据。
- 仍然无法让节点 Agent 运行未签名的代码:Agent 只接受由编译在其中的发布公钥验证通过的更新;私有发布密钥离线保存,绝不存放在面板上。被篡改的数据目录也无法让 root 更新辅助程序安装未经你签名的面板:它使用编译在二进制文件中的密钥验证,绝不使用
release.pub。发往节点的命令是固定列表;诊断修复仅包含六种安全操作,其中包括只运行已安装二进制文件及其内置模板的单元刷新;节点会拒绝小于 1024 或高于其 sshd 端口的端口跳跃范围。