From 36d23c74b20d659ad4ba8c7914ceb1b5fe774db8 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Mon, 13 Jul 2026 22:20:57 +0200 Subject: [PATCH] Manage dropbear via a systemd path unit watching authorized_keys Debug SSH access on port 22222 currently has two lifecycle mechanisms: dropbear.service is enabled and gated by ConditionFileNotEmpty=/root/.ssh/authorized_keys at boot, and haos-config explicitly starts/stops the service when importing a CONFIG partition. Keys written through any other path - most notably os-agent's AddSSHAuthKey D-Bus method, which the Supervisor is growing an API on top of - do not start dropbear until the next reboot. Add dropbear.path, watching /root/.ssh/authorized_keys, triggering a oneshot dropbear-lifecycle.service that starts dropbear when the file is non-empty and stops it when it is absent. This makes the file the single source of truth for whether debug SSH runs, regardless of the writer, and lets haos-config drop its explicit systemctl calls. Design notes: - PathChanged= only, no PathExists=: an exists-trigger pointed at a oneshot that does not consume the file re-triggers on every deactivation until the start rate limit is hit. Boot-time presence is already handled by the enabled dropbear.service and its ConditionFileNotEmpty. - The lifecycle service is idempotent since the watch may fire for any change in the directory, including temporary files from atomic writes (rename into place is IN_MOVED_TO, which PathChanged= covers). - RequiresMountsFor=/root/.ssh orders the watch after root-.ssh.mount: inotify events do not cross mount points, so a watch armed on the shadowed rootfs inode would never fire. If the bind mount ever goes away, the directive degenerates to a no-op. - Before=haos-config.service arms the watch before the config import can write the file, closing the missed-event race on first boot. Co-Authored-By: Claude Fable 5 --- .../systemd/system/dropbear-lifecycle.service | 10 ++++++++++ .../usr/lib/systemd/system/dropbear.path | 17 +++++++++++++++++ .../rootfs-overlay/usr/sbin/haos-config | 3 --- 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 buildroot-external/rootfs-overlay/usr/lib/systemd/system/dropbear-lifecycle.service create mode 100644 buildroot-external/rootfs-overlay/usr/lib/systemd/system/dropbear.path diff --git a/buildroot-external/rootfs-overlay/usr/lib/systemd/system/dropbear-lifecycle.service b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/dropbear-lifecycle.service new file mode 100644 index 00000000000..714887b4867 --- /dev/null +++ b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/dropbear-lifecycle.service @@ -0,0 +1,10 @@ +[Unit] +Description=Start or stop dropbear based on SSH authorized_keys +RequiresMountsFor=/root/.ssh + +[Service] +# Idempotent on purpose: the path unit may fire for any change in the watched +# directory (e.g. temporary files from atomic writes), so this must converge +# rather than toggle. +Type=oneshot +ExecStart=/bin/sh -c 'if [ -s /root/.ssh/authorized_keys ]; then systemctl start dropbear.service; else systemctl stop dropbear.service; fi' diff --git a/buildroot-external/rootfs-overlay/usr/lib/systemd/system/dropbear.path b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/dropbear.path new file mode 100644 index 00000000000..e50ff1c5b6d --- /dev/null +++ b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/dropbear.path @@ -0,0 +1,17 @@ +[Unit] +Description=Watch SSH authorized_keys for debug SSH access +# The watch must be armed on the bind-mounted directory (inotify events do +# not cross mount points), and before haos-config potentially imports keys. +RequiresMountsFor=/root/.ssh +Before=haos-config.service + +[Path] +# Edge-triggered on purpose: PathExists= pointed at a oneshot unit that does +# not consume the file would re-trigger until the start rate limit is hit. +# Presence of keys at boot is instead handled by dropbear.service itself, +# which is enabled and gated by ConditionFileNotEmpty. +PathChanged=/root/.ssh/authorized_keys +Unit=dropbear-lifecycle.service + +[Install] +WantedBy=multi-user.target diff --git a/buildroot-external/rootfs-overlay/usr/sbin/haos-config b/buildroot-external/rootfs-overlay/usr/sbin/haos-config index b4b6c8066ad..54e7a0225ee 100755 --- a/buildroot-external/rootfs-overlay/usr/sbin/haos-config +++ b/buildroot-external/rootfs-overlay/usr/sbin/haos-config @@ -76,13 +76,10 @@ if [ -f "${CONFIG_DIR}/authorized_keys" ]; then cp -f ${CONFIG_DIR}/authorized_keys /root/.ssh/authorized_keys chmod 600 /root/.ssh/authorized_keys - - systemctl start dropbear > /dev/null 2>&1 else echo "[Info] Stop SSH debug access" rm -f /root/.ssh/authorized_keys - systemctl stop dropbear > /dev/null 2>&1 fi ##