Skip to content

fix: agent 改用固定系统用户,修复部分 LXC 容器启动失败 - #8

Merged
stqfdyr merged 2 commits into
mainfrom
fix/agent-static-user
Sep 16, 2026
Merged

stqfdyr merged 2 commits into
mainfrom
fix/agent-static-user

Conversation

@stqfdyr

@stqfdyr stqfdyr commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

问题

没开 nesting 的 LXC 容器里,agent 启动失败:

Failed to set up mount namespacing
status=226/NAMESPACE

systemd 建不了挂载命名空间时,会按单元配置决定能否降级:单元写的是静态 User=,就跳过 ProtectSystem=、ProtectHome=、PrivateTmp=、PrivateDevices= 照常启动;单元写了 DynamicUser=,则拒绝启动。判断在 systemd 的 insist_on_sandboxing(),v245 至 v257 一致。

修改

  • install.sh 先建系统用户 monitor-agent(useradd --system,与 install-hub.sh 建 monitor 的写法相同),已存在则跳过。这一步排在下载与注册之前,失败的主机保持原有 agent 继续运行、也不会白花一个注册键;以 $INIT 守住,因为 Alpine 没有 useradd
  • 单元中 DynamicUser=yes 换为 User=monitor-agent,并明写 RestrictSUIDSGID=yes

DynamicUser= 隐含 RestrictSUIDSGID= 与 RemoveIPC=,换成静态 User= 后两者一并失效(systemd-analyze security --offline=true 对两版单元的差异恰好只有这两项,7.3 → 7.5)。RestrictSUIDSGID= 明写补回:它由 seccomp 实现,不经挂载命名空间,不会把 226/NAMESPACE 带回来。RemoveIPC= 对不建 IPC 对象的 agent 没有作用,不再保留。其余隔离项逐字不变。

普通主机上的差别只剩 uid 由启动时临时分配改为固定,agent 不写文件,没有实际影响。受限容器中挂载类隔离被跳过,agent 仍以非 root 运行,NoNewPrivileges、RestrictSUIDSGID、地址族与内存限制保留。

未采用 issue 中的 User=nobody:该用户由多个服务共用,systemd 会对它给出警告。

install.sh 由 hub 以 include_str! 内嵌下发,所以这个修复要先随 hub 发一版、主控升级之后,节点重跑安装命令才会拿到新单元;在那之前老 hub 仍在下发老脚本。文档见 monitor-probe/monitor-document 同名分支的 PR。

测试

docker 模拟受限容器(Debian 13,systemd 257,seccomp 拒绝 unshare),除身份一行外与安装脚本写出的单元相同:

结果
DynamicUser=yes status=226/NAMESPACE,Operation not supported
User=monitor-agent active,uid 997

真实 KVM 主机(Debian 13,systemd 257),用本分支脚本覆盖安装原有 agent:

  • 进程以 monitor-agent(uid 995)运行,NRestarts=0,hub 日志显示节点重新连上
  • agent 进程的挂载命名空间与 PID 1 不同,隔离仍然生效
  • 再运行一次安装脚本,已有用户被跳过,安装成功

单元加固项离线比对:systemd-analyze security --offline=true 两版逐项 diff,只有 RemoveIPC= 与 RestrictSUIDSGID= 变化,补上后者后只剩 RemoveIPC=。

未在真实 LXC 中复现,issue 报告者在该环境下以同样思路(关闭 DynamicUser、换为静态用户)验证可行。

Fixes #5

没开 nesting 的 LXC 容器建不了挂载命名空间。单元写的是静态 User= 时,
systemd 会跳过 ProtectSystem= 等挂载类隔离照常启动;写的是 DynamicUser=
时则拒绝启动,报 226/NAMESPACE。

install.sh 先建系统用户 monitor-agent,单元里 DynamicUser=yes 换成
User=monitor-agent,其余隔离项不变。已安装的节点重跑安装命令即可换上新单元。

Fixes #5
CarlJia referenced this pull request in CarlJia/monitor Sep 15, 2026
- #2/P1: housekeeping 首轮跳过离线判定,消除重启后假离线告警
- #3/P1: 宿主函数加墙钟 deadline,堵住 host 调用循环绕过 fuel/timeout 的沙箱缺口
- #4: host_http_post 有界读取响应体(64 KiB 硬上限)
- #5: db_restore 后重建插件 Registry
- #7: 插件删除行+kv 清理单事务化
- #8: enable 失败回滚 enabled 并修面板徽标优先级
- #10: delete_node 级联清理 notification_log
- #12: 解压炸弹防线补齐 4 类测试
- #13: settings GET 回显通知阈值默认值,读改写回环可闭环
- #16: http 响应写回抽纯函数并补 5 分支测试
- #17: 新增 DELETE kv 路由,前端 KvDialog 去除清空临时方案
- #18: DispatchEntry.result 枚举与文档对齐
- #19: 日志卡片选中插件被删后不再弹假 404
DynamicUser= 隐含 RestrictSUIDSGID= 与 RemoveIPC=,换成静态 User= 后两者
一并失效(systemd-analyze security 由 7.3 降到 7.5)。RestrictSUIDSGID=
明写补回,它由 seccomp 实现,不经挂载命名空间,不会把 226/NAMESPACE 带回来。
RemoveIPC= 对不建 IPC 对象的 agent 没有作用,不再保留。

useradd 原先排在下载、注册与停服之后,失败时节点已经停机、注册键已经花掉,
而单元尚未改写。移到 init 检测之后,并以 $INIT 守住:Alpine 没有 useradd,
无条件执行会让 OpenRC 主机全部装不上。

解释 User= 的注释一并移入单元,systemctl cat 的读者正好看得到。
@stqfdyr
stqfdyr merged commit 85f6702 into main Sep 16, 2026
1 check passed
@stqfdyr
stqfdyr deleted the fix/agent-static-user branch September 16, 2026 03:20
CarlJia referenced this pull request in CarlJia/monitor Sep 16, 2026
独立评审(7 路本地 + codex 对抗尝试)后的修复。P0/P1 全部经独立校验器确认。

阻断项:
- #1/#4 wasm 在运行时上下文里被同步调用,宿主函数里的嵌套 block_on 会
  panic;release 档 panic=abort 直接终止进程,启用财务插件即崩。
  call_hook/call_json_hook 补 block_in_place(与 run_one 同一形态),
  call_plugin_json 去掉多余的外层 block_on。新增 run_blocking:只在多线程
  运行时上包一层,纯同步/单线程路径直接跑。
- #2 SSRF 预检只覆盖首个 URL,而共享 client 默认跟随重定向(含
  https->http 降级),任一公网开放重定向即可绕过。新增 App::plugin_http
  (Policy::none()),插件出网与宿主隔离。
- #7 注册表读锁横跨插件执行,插件回头发事件重入同一把锁会死锁。
  dispatch_ticks/call_json/dispatch_one 改为只在取快照时借锁。

迁移与数据边界:
- #5 删列闸门按 plugin_id 限定(不再被第三方插件的 node: 前缀打开)。
- #15 四条 DROP COLUMN 收进一个事务,完成判据与待删清单同源。
- #16 Db::open 补 version > SCHEMA_VERSION 守卫(与 check_backup 一致)。
- #17 GATED_VERSION 用绝对常量,不再随 SCHEMA_VERSION 漂移。
- #6 删列前记一行非默认值行数,运维在日志里看得到这次丢了什么。
- #18/#23 plugin_data 配额统一按字节,检查与写入同一事务(BEGIN IMMEDIATE)。
- #20 无到期字段的插件事件按 payload 内容哈希(FNV-1a)定键,不再永久去重。
- #21/#22 SSRF 预检的 DNS 解析与请求超时都按剩余预算收缩。
- #24 data_list/nodes_query 用各自的上限,放不下报 -6 而不是静默截断。

契约与文档:
- #13/#14 插件端点先判加载状态(400)再判能力声明(404);错误体统一纯文本。
- #8/#9/#29 README 的 nodes_query 字段、data_list 格式、tick 周期按实现改写。
- #26 表单控件按字段名推断的规则写进协议(#27 同步修掉清空价格存成 0)。
- #25 破坏性 ABI/API 变更按 SemVer 升到 2.0.0,README 补升级说明。
- #28 tg-notify 错误码表补 -9 对应的 19。

测试与 CI:
- #10 删除空测试体;#11 nodes_query 测试播种节点并断言在线状态。
- #19 插件 crate 接进 CI(独立 crate,根 cargo test 覆盖不到)。
- #12 插件区分「宿主查询失败」与「确实没有节点」,清理拿不到存活集合拒删。
- #3 抽出 plugin_http_fetch,http_post/http_get 不再逐行重复。

验证:hub 166 passed;finance-stats 8;tg-notify 3;web-admin build+lint。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
stqfdyr added a commit to monitor-probe/monitor-document that referenced this pull request Sep 24, 2026
- 设计哲学:OpenRC 的提示框改为「同样以专用用户运行,但 systemd 那几行隔离项没有对应物」
- 卸载:OpenRC 的手动步骤补上删用户(Alpine 用 `deluser`,其它系统用 `userdel`,较早版本装的没有这个用户时静默跳过),删掉「不建系统用户」那句
- 设计哲学、FAQ、AI 部署提示词里「下载的二进制会以 root 跑起来」改为按实际写:以 root 执行的是安装脚本;GitHub 代理返回的内容会作为 agent 在每台节点上运行。systemd 自 monitor-probe/monitor#8 起、OpenRC 自 monitor-probe/monitor#49 起,agent 都不以 root 运行,警示的对象换成真正以 root 执行的那一环,力度不变
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

部分 lxc 容器,agent 启动会失败

1 participant