开始
设计哲学
安全
装 agent 不扩大被监控机器的攻击面
- 不监听端口。连接由 agent 主动向 hub 发起,被监控的机器不用开端口,也不用改防火墙
- 不写文件。只读
/proc、/sys/class/net、/etc/os-release和statvfs,没有配置文件、数据库或缓存。累加流量、识别重启这类有状态的工作都在 hub 侧 - 不执行 hub 下发的东西。hub 下发的只有 TCP 探测任务(
host:port和间隔),没有任何指令能让 agent 运行命令或写文件 - 不自动更新。自更新意味着 agent 能以 root 下载并执行任意二进制。升级由你在机器上重新执行一次节点的安装命令
最小权限运行
一键脚本写出的 systemd 单元带着这些限制:
| agent | hub | |
|---|---|---|
| 身份 | 专用系统用户 monitor-agent | 专用系统用户 monitor |
| 文件系统 | ProtectSystem=strict + ProtectHome,全盘只读 | 同上,另加 ReadWritePaths 只开 data/ |
| 提权 | NoNewPrivileges、RestrictSUIDSGID | NoNewPrivileges、RestrictSUIDSGID |
| 隔离 | PrivateTmp、PrivateDevices | PrivateTmp、PrivateDevices |
| 网络 | RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK | AF_INET AF_INET6 |
| 内存 | MemoryMax=64M | MemoryMax=256M |
hub 因此改不了自己的二进制,可写的只有数据库和主题所在的那一个目录。
没开 nesting 的 LXC 容器建不了挂载命名空间,systemd 会跳过「文件系统」和「隔离」两行,服务照常启动,其余几行不受影响。agent 不用 DynamicUser 正是因为这一点:带着它的单元在这种容器里无法启动,报 226/NAMESPACE。
节点 token 写在 /opt/monitor/agent.env,权限 0600,不写进服务定义,因为服务定义会出现在
systemctl cat 和日志里。
OpenRC 上 agent 同样以专用用户 monitor-agent 运行。上表其余几行是 systemd 的单元选项,OpenRC
没有对应的开关,两边一致的只有运行身份、自动重启和 token 的存放方式。
凭证的边界
- 节点 token 是 256 位随机值,走
Authorization: Bearer头而不是 URL query,因为 query 会进反代的 access log。它只能上报自己那一个节点,读不到别的节点,也改不了任何设置 - 换发 token 立即生效,已经建立的连接也会被断开
- 会话是 256 位随机 token,数据库里只存 sha256,14 天过期。cookie 带
HttpOnly与SameSite=Lax,HTTPS 下加Secure - 面板的每个 handler 都在参数里带一个
Admin提取器,框架在进入函数体之前校验会话,不会漏掉某个接口 - 应急密码用 argon2id 存储。同一地址 15 分钟内失败 5 次即锁定;另有一道并发闸门,同一时刻只做一次密码校验
面板默认不对公网开放
一键脚本默认 --listen 127.0.0.1(--host 可改),装完后从公网打不开面板,要先配反向代理。
原因是明文 HTTP 上,会话 cookie 和节点 token 都在链路上明文传输,中间人还能替换安装命令下载的脚本和二进制,而那个脚本以 root 执行。所以 agent 和 install.sh 拒绝用明文连远程 hub,从 IP 或明文地址进面板时也不给出安装命令。
一个例外是同一内网的直连:从私网地址(10.、172.16–172.31、192.168.、IPv6 ULA,或本机)直接用 http://IP:28080 进面板,hub 会照常给出安装命令——令牌没有离开这个网络,上面那些风险不成立。判断依据是「这次请求没有声明 X-Forwarded-Proto,且 hub 看到的客户端地址是私网地址」;经反代转发的公网访客不满足,所以反代部署不会因为这个例外被绕过。
配法见反向代理。
极简
零配置文件
hub 只有四个运行参数:--listen、--db、--themes、--site。其余设置都存在 SQLite 里,在面板上改。
没有配置文件,也就没有「文件在哪、权限对不对、和数据库是否一致」这类问题。
不做的功能
| 不做 | 原因 |
|---|---|
| 负载告警 | 阈值、持续时长、节点范围是一整套规则引擎。通知只做掉线、流量、到期、登录这几件判定明确的事 |
| 远程 SSH / web terminal | 只读的观察者被攻破,和能执行命令的工具被攻破,代价差一个数量级 |
| 插件系统 | 等于把第三方代码请进 hub 的进程 |
| HTTP 探测 | 一个 URL 的成败取决于状态码、跳转、证书、正文匹配,那是另一套东西;而 ICMP 已经做了,见下 |
| agent 自动更新 | 自更新意味着 agent 能以 root 下载并执行任意二进制 |
| 跨平台 agent | 加 Windows / macOS 等于推翻整个采集实现 |
这些不在计划里。探针只报告机器的状态,不代管机器;不写的代码既不占内存,也不会出漏洞。
本分支加了 ICMP 探测,与上面这些不同:这个仓库是二次开发,不逐条沿袭上游的取舍。上游的理由是「一种够用,三种要三套超时语义、三套图例」——我们只加了一种(按任务可选,默认仍是 TCP),而且超时与图例和 TCP 共用一套,所以那句担心没有兑现。它要回答的是一个 TCP 握手答不了的问题:这台主机、这条线路通不通,与「某个服务通不通」是两件事。权限不足时不会静默成丢包,原因会显示在相关任务上,也有 monitor-agent --ping 可以先自检——详见延迟与丢包。
高效
同一台机器、同一套负载的实测数字,完整数据在性能:
| 可执行文件 | 6.3 MiB |
| 冷启动到能响应请求 | 70 ms |
| 空转内存 | 6.5 MiB |
| 200 个节点在线,每 2 秒上报 | 8.4 MiB,一颗核的 2.5% |
| 每秒 1000 次上报 | 8.6 MiB |
Rust 没有 GC,也没有语言运行时,负载上去内存基本不动。换语言只带来体积和内存上的优势,响应速度靠的是几处具体的调优,记在同一页。