From a72cee5344ab34c1c08fb61036ff18b3f9d82f36 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Tue, 9 Jun 2026 16:31:39 -0700 Subject: [PATCH] =?UTF-8?q?fedac/native:=20fix=20ssh=20key=20auth=20?= =?UTF-8?q?=E2=80=94=20dedicated=20/root=20home=20for=20authorized=5Fkeys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dropbear rejected the (correct, matching) baked key with 'Permission denied (publickey)' because root's home was '/' — dropbear refuses authorized_keys under a loosely-permed home dir. Move root's home to a dedicated /root (0700) with ~/.ssh 0700 + authorized_keys 0600, baked there instead of /.ssh. Interactive shells are unaffected (they use HOME=/tmp). js_start_ssh now checks /root/.ssh/authorized_keys and logs which auth mode it picked. --- fedac/native/docker-build.sh | 30 ++++++++++++++++++++---------- fedac/native/src/js-bindings.c | 12 +++++++----- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/fedac/native/docker-build.sh b/fedac/native/docker-build.sh index 52bc1d6ab6..55401f338c 100644 --- a/fedac/native/docker-build.sh +++ b/fedac/native/docker-build.sh @@ -202,13 +202,21 @@ ln -s busybox "$IROOT/bin/wget" 2>/dev/null || true # notepat auto-starts sshd once wifi connects (system.startSSH in # js-bindings.c) and shows "ssh root@" on screen. OTA builds are # key-only: js_start_ssh disables password auth whenever -# /.ssh/authorized_keys exists (root's home is "/"), which is baked here -# from fedac/native/keys/authorized_keys. Without that file the daemon -# falls back to -B (blank-password root) — local dev images only. +# /root/.ssh/authorized_keys exists, baked here from +# fedac/native/keys/authorized_keys. Without that file the daemon falls +# back to -B (blank-password root) — local dev images only. +# +# IMPORTANT: dropbear looks up authorized_keys via the passwd home dir +# (getpwnam, NOT $HOME — everything else on the device uses HOME=/tmp) and +# REFUSES the file if the home dir or ~/.ssh is group/other-writable. So +# root's home is a dedicated /root at 0700 (the rootfs "/" is too loose), +# with ~/.ssh 0700 and authorized_keys 0600 — see the matching passwd +# entry "root:x:0:0:root:/root:/bin/sh" below. DROPBEAR_BIN=$(command -v dropbear 2>/dev/null || true) DROPBEARKEY_BIN=$(command -v dropbearkey 2>/dev/null || true) if [ -n "$DROPBEAR_BIN" ] && [ -n "$DROPBEARKEY_BIN" ]; then - mkdir -p "$IROOT/usr/sbin" "$IROOT/etc/dropbear" "$IROOT/.ssh" + mkdir -p "$IROOT/usr/sbin" "$IROOT/etc/dropbear" "$IROOT/root/.ssh" + chmod 700 "$IROOT/root" "$IROOT/root/.ssh" cp -L "$DROPBEAR_BIN" "$IROOT/usr/sbin/dropbear" cp -L "$DROPBEARKEY_BIN" "$IROOT/usr/sbin/dropbearkey" chmod +x "$IROOT/usr/sbin/dropbear" "$IROOT/usr/sbin/dropbearkey" @@ -221,9 +229,9 @@ if [ -n "$DROPBEAR_BIN" ] && [ -n "$DROPBEARKEY_BIN" ]; then done done if [ -f "$NATIVE/keys/authorized_keys" ]; then - cp "$NATIVE/keys/authorized_keys" "$IROOT/.ssh/authorized_keys" - chmod 600 "$IROOT/.ssh/authorized_keys" - log " dropbear bundled (key-only via keys/authorized_keys)" + cp "$NATIVE/keys/authorized_keys" "$IROOT/root/.ssh/authorized_keys" + chmod 600 "$IROOT/root/.ssh/authorized_keys" + log " dropbear bundled (key-only via /root/.ssh/authorized_keys)" else log " dropbear bundled (no keys/authorized_keys — blank-password fallback)" fi @@ -597,10 +605,12 @@ if [ -d /opt/alsa-ucm-conf-cros/ucm2 ]; then fi # ── 2m: /etc/group and /etc/passwd ── -# Full 7-field passwd entry: dropbear resolves uid 0's home ("/", so -# authorized_keys lives at /.ssh/) and login shell from here. +# Full 7-field passwd entry: dropbear resolves uid 0's home (/root, 0700, +# where authorized_keys lives) and login shell from here. Home is /root — +# NOT "/" — because dropbear refuses authorized_keys under a loosely-permed +# home; interactive shells still use HOME=/tmp (set by init + pty.c). echo "root:x:0:" > "$IROOT/etc/group" -echo "root:x:0:0:root:/:/bin/sh" > "$IROOT/etc/passwd" +echo "root:x:0:0:root:/root:/bin/sh" > "$IROOT/etc/passwd" # ── 2n: Claude Code ── # Prefer a freshly-fetched native binary from the official GCS "latest" diff --git a/fedac/native/src/js-bindings.c b/fedac/native/src/js-bindings.c index 9cd794c590..759f9cb81f 100644 --- a/fedac/native/src/js-bindings.c +++ b/fedac/native/src/js-bindings.c @@ -5919,11 +5919,13 @@ static JSValue js_start_ssh(JSContext *ctx, JSValueConst this_val, int argc, JSV } // Key-only auth when an authorized_keys file is baked in (public OTA - // builds — root's home is "/" per /etc/passwd, so dropbear reads - // /.ssh/authorized_keys). -B (blank-password root) only as a fallback - // for local dev images without baked keys. - ac_log("[ssh] starting dropbear on port 22...\n"); - if (access("/.ssh/authorized_keys", F_OK) == 0) { + // builds — root's home is /root per /etc/passwd, so dropbear reads + // /root/.ssh/authorized_keys). -B (blank-password root) only as a + // fallback for local dev images without baked keys. + int have_keys = (access("/root/.ssh/authorized_keys", F_OK) == 0); + ac_log("[ssh] starting dropbear on port 22 (authorized_keys=%s)...\n", + have_keys ? "/root/.ssh" : "none -> -B fallback"); + if (have_keys) { system("dropbear -R -s -g -p 22 -P /tmp/dropbear.pid 2>/tmp/dropbear.log &"); } else { system("dropbear -R -B -p 22 -P /tmp/dropbear.pid 2>/tmp/dropbear.log &"); -- 2.51.2