Skip to content

fix(deploy): let the edge read logs under /home with ProtectHome=read-only - #470

Open
Dragonzz27 wants to merge 4 commits into
ongridio:mainfrom
Dragonzz27:fix/edge-protect-home-readonly
Open

Dragonzz27 wants to merge 4 commits into
ongridio:mainfrom
Dragonzz27:fix/edge-protect-home-readonly

Conversation

@Dragonzz27

@Dragonzz27 Dragonzz27 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

#163 报的两个 bug,Bug 1 已经在主干修好了,本 PR 处理 Bug 2 以及"存量节点怎么拿到这个修复":

  • Bug 1(external_labels.device_id 用了 frontier 的 edge_id)已由现有实现解决:internal/edgeagent/plugins/config_tunnel.go:145-150 明确写了"尽管线上字段名是 EdgeID,它的值是 Manager 从 edge_devices(type=host) 解析出的 host device_id,绝不能用 ONGRID_EDGE_ID 顶替",并且 internal/edgeagent/plugins/config_tunnel_test.go:124 有断言 EdgeID label = ..., want manager-resolved device_id 100。所以本 PR 不再改动这部分 —— 若你确认 Bug 1 可判定关闭,这个 PR 合入后 修复 Promtail device_id 标签与 /home 访问 (Correct Promtail device labels and /home access) #163 两条都有着落。(修复 Promtail device_id 标签与 /home 访问 (Correct Promtail device labels and /home access) #163 当时把采集器写成 Promtail;日志采集现已迁移到 otelcol-contrib 的 filelog receiver,字段名与这条结论不变。)

  • Bug 2(本 PR 修复):edge 的 systemd 单元用 ProtectHome=true,它会在服务的 mount namespace 里用空 tmpfs 覆盖 /home、/root、/run/user。logs 插件是以子进程方式拉起 otelcol-contrib(filelog receiver) 的,它继承这个 namespace,于是 /home 下的路径一律不可见 —— 表现就是报告里那种"Adding target 之后永远不打开文件"的静默失效。

改成 ProtectHome=read-only:真正需要的保护(agent 不能写用户主目录)完全保留,只是让这些路径重新可读,而日志采集只需要读。

  • 存量节点的升级路径(按 review 意见补齐):一键升级走的是 ADR-024 bundle,apply-pending-upgrade.sh 只按 MANIFEST.txt 换文件,从不重写 /etc/systemd/system/ongrid-edge.service;升级包清单里也没有 service 文件。所以旧节点哪怕升级多次,ProtectHome=true 会一直留着,本 PR 的两处模板改动只有新装/重装才生效。现在 apply-pending-upgrade.sh 里加了 ensure_protect_home,与既有 ensure_log_groups 同一套路:它是 ongrid-edge.service 的 Wants= + After= 前置 oneshot,每次 agent 启动前都会跑,所以这个迁移既不需要重装、也不需要运维手工改 unit,节点只要跑过这份 bundle 之后再启动一次 agent 就会收敛(具体时序边界见下一段)。

    迁移的范围刻意收窄:只改这个 unit 里 ProtectHome= 这一行(true|yes|on|1,按 systemd 的大小写与空格习惯匹配),其余指令与用户自己的 drop-in 一律不动;ongrid-edge.service.d/ 里若有 drop-in 仍然钉着 true(它的优先级高于主 unit),只在日志里报出来,不替用户改他自己的文件。改完 best-effort systemctl daemon-reload,agent 随即按新配置启动。幂等:值已经是 read-only 就不匹配、不重写、不 reload;unit 不存在直接返回。

    需要如实说明一处时序边界:跑这次升级的仍是节点上旧版的 apply-pending-upgrade.sh(新脚本是在这次运行里才被换到磁盘上的),所以装上这份 bundle 的那一次启动还不会触发迁移;迁移发生在这之后的任意一次 agent 启动前(Restart=always 的重启、重启机器、下一次升级,或运维手动 systemctl restart ongrid-edge),无需重装、也不需要手工改 unit。要立刻收敛就手动重启一次 edge。

三处一起改:随包发布的模板 deploy/install/edge/ongrid-edge.service、install.sh 安装时实际写出的那份副本,以及新增的升级前置迁移。全仓库只有这三处涉及 ProtectHome(已扫过 deploy/ 与 helm/k8s 目录)。

验证

  • bash -n deploy/install/edge/install.sh、bash -n deploy/install/edge/install-edge.sh、bash -n deploy/install/apply-pending-upgrade.sh 通过。

  • 仓库自带的两个安装器守卫脚本通过:

    • scripts/test-edge-installer-connectivity-guard.sh → edge installer connectivity guard: ok
    • scripts/test-edge-metrics-env.py → install.sh / install-edge.sh: metrics address install/reinstall checks passed

    需要说清楚这两个脚本证明的是什么:它们抽取安装脚本里的连通性判断与 env 保留代码块来执行,不会解析 [Service] 单元内容,所以它们能确认的是"我没有改坏安装脚本本身",不构成对本改动的行为验证。

  • 迁移逻辑走真实升级入口验证,而不是只校验安装脚本语法:scripts/test-apply-pending-upgrade.sh 新增一段,用临时 unit 目录 + systemctl/logger shim 直接调用整个 apply-pending-upgrade.sh(不是把函数抄出来单测),逐条断言:

    • ProtectHome=true 被改成 read-only,而 ProtectSystem=strict、LimitNOFILE=65536、Environment=ONGRID_EDGE_CUSTOM=keep-me 逐字保留;
    • 用户 drop-in 里的 ProtectHome=true 原样保留,同时日志里出现"still pins ProtectHome=true"的提示;
    • 只发生一次 daemon-reload;紧接着再跑一次升级,unit 字节级不变且不再 reload(幂等);
    • ProtectHome=false 的 unit 与根本没有该指令的 unit 都字节不变、不 reload;
    • ProtectHome = True(带空格、首字母大写)同样被匹配并改写;
    • unit 文件不存在时是 no-op,不报错、不创建文件。
  • 自证保护有效:把上面这段断言指向迁移前的 apply-pending-upgrade.sh,同一套测试退出码 1(改动前是红的);指向改动后的版本退出码 0。

  • 三处 ProtectHome 现均为 read-only,注释一致。

未验证项(如实说明):ProtectHome 的真实挂载效果需要 Linux + systemd,本机是 macOS、systemd-analyze 不存在,CI 对安装脚本也只有 bash -n 这一层。新增的自动化证明的是"迁移只改这一行、幂等、会 reload、不动用户配置",不含"/home 在真机上确实变为可读且不可写"这一挂载语义(read-only 是 systemd 文档里的标准取值,语义为"可读写目录改为只读挂载")。请报告人用这两条自查确认:

systemctl show ongrid-edge -p ProtectHome        # 期望 ProtectHome=read-only
nsenter -t $(pgrep -x ongrid-edge) -m -- ls /home # 期望能看到内容且不可写

影响面

只影响 edge 的 systemd 单元安全性配置,不涉及 Go 代码、API、.proto、数据库、前端。对已装节点的可观察变化是:升级或重启后 unit 里的 ProtectHome= 一行被规范化,其余配置与 drop-in 不变。如果将来有插件确实需要写用户主目录(目前没有),需要显式加 ReadWritePaths=,而不是回退到 true。

回滚

revert 本提交即恢复模板与安装脚本里的 ProtectHome=true,无数据迁移;已迁移过的节点把该行改回 true 后 systemctl daemon-reload 即可(迁移只在看到 true|yes|on|1 时才动手,回滚后的值不会被再次改写)。

Fixes #163

Author confirmation

…-only

ProtectHome=true masks /home, /root and /run/user with an empty tmpfs inside the
service mount namespace. The logs plugin spawns promtail as a subprocess, so it
inherits that namespace and cannot see any file under /home: promtail reports
"Adding target" and then never opens the path, which makes log collection fail
silently for any application logging into a home directory.

read-only keeps the protection that actually matters here, since those paths
stay unwritable to the agent, while leaving them readable, which is all log
scraping needs.

Both unit definitions change together: the template shipped as
ongrid-edge.service and the copy install.sh writes out at install time.
@Dragonzz27
Dragonzz27 requested a review from singchia as a code owner October 6, 2026 18:24
@youzi-1122

Copy link
Copy Markdown
Member

这个修复需要同时支持已有 Linux Edge 的正常升级流程,不能只在新安装或重新运行安装脚本时生效。

目前两处 ProtectHome=read-only 的改动方向正确,但存量节点通过控制台一键升级后仍会保留旧的 ProtectHome=true:

  • 在线、离线安装脚本会写入新的 unit,并执行 daemon-reload 和重启,所以新装/重装可以生效。
  • 升级包清单只包含二进制和升级脚本,没有 service 文件;升级应用逻辑也没有迁移旧 unit。仅升级 Manager 的 Compose 部署同样不会更新远端 Edge 的 unit。

请在合并前补齐:

  1. 让正常的一键升级自动迁移旧的默认配置、完成 daemon-reload,并确保重启后的 Edge 使用新配置;用户不应需要重新安装或手动修改 unit。迁移应可重复执行,并保留用户其他自定义 unit / drop-in 配置。
  2. 补充从旧 ProtectHome=true 节点升级的验证:升级后实际配置为 read-only,当前日志采集器能读取 /home 下的测试日志,同时仍不能写入该目录。请覆盖实际升级路径,而不只是安装脚本的语法检查。
  3. 将 PR 说明和新增注释中的 Promtail 更新为当前的 otelcol-contrib / filelog。日志采集已经迁移到 Contrib;目录访问限制仍然适用,但验证应基于现在使用的采集器。

完成这些后,才能确保已有用户通过正常升级获得 #163 的修复。

install.sh and install-edge.sh used to write ProtectHome=true, and a bundle
upgrade only swaps the files listed in MANIFEST.txt, so an existing node kept
that line through every upgrade: the log collector still could not read files
under /home, which is the failure issue ongridio#163 reports.

Re-assert just that directive in the pre-start upgrade hook, next to the
existing group-membership re-assertion. The hook runs before every agent start,
so a node converges without reinstalling and without hand-editing its unit.
Only the ProtectHome line of the unit this project installs is rewritten,
matching the spellings systemd reads as true; every other directive and any
operator drop-in is left untouched, and a drop-in that still pins true is
reported in the log instead of being overridden. The step is idempotent,
preserves the unit's mode and ownership across an atomic rename, and reloads
systemd best-effort so the very next start uses the migrated unit.

Extend scripts/test-apply-pending-upgrade.sh to drive the real hook with a
temporary unit directory plus systemctl and logger shims, covering the
migration itself, idempotency, preserved operator lines, the untouched drop-in,
units that must not change and a missing unit. Pointing the same assertions at
the pre-migration hook fails, so the suite is a real guard rather than a syntax
check.
Log collection moved from Promtail to the OpenTelemetry Contrib collector with
its filelog receiver, but both ProtectHome notes still described a promtail
subprocess. Name the collector that is actually started today so the reason the
mount namespace matters stays traceable; the restriction on directory access is
unchanged.
@youzi-1122

Copy link
Copy Markdown
Member

复查了最新提交 81f5692403731651761d903828d840d139237056。两处 unit 模板的修改方向正确,但新增迁移还没有满足前面提出的“一次正常升级后生效”要求,另外测试存在宿主机路径隔离问题。建议合并前修正以下两项。

  1. [P1] 第一次正常升级仍不会执行迁移。

    ensure_protect_home 只在新 hook 启动时调用,但已有节点应用这份 bundle 时运行的仍是旧 hook。新脚本只是被安装到磁盘,本轮不会重新执行它。

    在隔离 Linux 容器中,用 PR 基线的旧 hook 应用包含新 Agent 和新 hook 的 bundle,结果是:

    第一次升级:新 Agent、新 hook 已安装;ProtectHome=true;未调用 daemon-reload
    额外启动一次:ProtectHome=read-only;调用 daemon-reload
    

    PR 说明已承认这个时序边界,但依赖用户再手动重启或等待未来重启,仍不满足正常一键升级的验收要求。请让一次升级流程自动完成迁移、reload 及必要重启,并增加“旧 hook → 新 bundle”的回归测试,不能只直接运行新 hook。仍需补充真实 systemd 下的验证:升级完成后的 Edge/otelcol-contrib 能读取 /home 中的测试日志,同时不能写入该目录。

  2. [P2] 新增测试会越过临时目录,操作宿主机的 Edge 文件。

    run_hook_with_unit()只设置了 unit、drop-in 和命令替身相关变量,漏掉 ONGRID_EDGE_UPGRADE_STAGE_DIR、ONGRID_EDGE_UPGRADE_BIN_DIR、ONGRID_EDGE_UPGRADE_LIB_DIR、ONGRID_EDGE_UPGRADE_LEGACY_TARGET,因此会进入默认的真实升级目录。反过来,前面的 run_hook() 虽然隔离了升级目录,却没有隔离新迁移使用的 unit/drop-in 路径,而且在命令替身建立前就已运行。

    在一次性容器中放置模拟宿主机的 unit、Agent 和默认目录下的 pending 后,运行未经修改的测试脚本:它输出 apply-pending-upgrade tests passed,但同时把 /etc/systemd/system/ongrid-edge.service 改成了 read-only,并把待升级文件应用到了 /usr/local/bin/ongrid-edge。这些操作全部发生在测试容器内,没有操作真实 Edge;但说明以 root 在已安装 Edge 的开发或测试机器上运行该脚本会产生副作用。

    请在首次调用 hook 前统一初始化全部临时路径和命令替身,并复用同一个隔离入口;增加断言,确保默认宿主机路径不被读取或修改。

验证范围:GitHub 当前检查全部通过;隔离 Linux 容器中的 test-apply-pending-upgrade.sh、test-edge-upgrade-hook-guard.sh、test-edge-installer-connectivity-guard.sh 和 test-edge-metrics-env.py 均通过,上述两个遗漏场景也已单独复现。尚未验证真实 systemd 挂载行为及 otelcol/Loki 端到端采集,因此目前不能据此认定存量节点升级问题已完整解决。

The ProtectHome migration cases overrode only the unit and drop-in paths, so the
bundle-level runs kept the hook's defaults and pointed the migration at the host
unit under the systemd system directory, and the earliest runs started before the
command shims existed. On a machine that really has an edge installed, running
this suite as root would rewrite that host's unit and apply a staged bundle into
the real binary and library directories.

Route every invocation through one isolated entry point that sets the upgrade
stage, binary and library dirs, the legacy target, the unit and drop-in paths and
the systemctl and logger shims, and install the shims before the first run.
Bundle-level runs are pointed at a unit path that never exists, and the suite now
asserts that phase reloaded nothing, created no unit, never mentioned the
migration and never resolved the host unit path. Pointing one bundle run back at
a real unit makes the suite fail, so the new guard is not vacuous.
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.

修复 Promtail device_id 标签与 /home 访问 (Correct Promtail device labels and /home access)

2 participants