fix(firecracker): make the launch path work end-to-end
First real end-to-end launch (there was no firecracker integration test)
surfaced three bugs in the control/provision path, all now verified fixed
by tests/integration/test_firecracker_launch:
1. Guest SSH rejected the correct key. The rootless rootfs build
(`docker export | tar` as a non-root user) can't preserve uid 0, so
every path — including /root — is owned by the build uid (node in
guest). dropbear refuses root's authorized_keys when /root isn't
root-owned, so auth fell back to password → denied. bb-init now
`chown -R 0:0 /root`.
2. The SSH client (newer OpenSSH) didn't reliably present the -i key
against the operator's ~/.ssh config; add `IdentitiesOnly=yes` to
ssh_base_argv so only the per-bottle key is offered.
3. cp_in double-wrapped the remote command as `sh -c <remote>`, but ssh
space-joins everything after the host into one string for the guest
shell, collapsing `sh -c mkdir -p X && …` to `mkdir` with no operand
("missing operand"). Pass the command as a single arg and let the
guest login shell run it (stdin still carries the tar).
Also stop discarding dropbear's stderr (`-E` now reaches the host-side
console.log) so future guest-auth issues are debuggable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
@@ -151,8 +151,13 @@ class FirecrackerBottle(Bottle):
|
|||||||
["tar", "-C", host_parent, "-cf", "-", host_base],
|
["tar", "-C", host_parent, "-cf", "-", host_base],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
|
# Pass the command as one arg: ssh space-joins everything after
|
||||||
|
# the host into a single string for the guest's login shell, so a
|
||||||
|
# `sh -c <remote>` split would drop everything past the first word
|
||||||
|
# (the guest shell runs `<remote>` directly; stdin carries the
|
||||||
|
# tar).
|
||||||
ssh = subprocess.run(
|
ssh = subprocess.run(
|
||||||
[*self._ssh(tty=False), "--", "sh", "-c", remote],
|
[*self._ssh(tty=False), "--", remote],
|
||||||
stdin=tar.stdout, capture_output=True, text=True, check=False,
|
stdin=tar.stdout, capture_output=True, text=True, check=False,
|
||||||
)
|
)
|
||||||
tar.wait()
|
tar.wait()
|
||||||
|
|||||||
@@ -268,6 +268,10 @@ def ssh_base_argv(private_key: Path, guest_ip: str) -> list[str]:
|
|||||||
return [
|
return [
|
||||||
"ssh",
|
"ssh",
|
||||||
"-i", str(private_key),
|
"-i", str(private_key),
|
||||||
|
# Only offer the per-bottle key — don't let an agent or the
|
||||||
|
# operator's ~/.ssh/config inject other identities (newer
|
||||||
|
# OpenSSH otherwise may not reliably present the -i key).
|
||||||
|
"-o", "IdentitiesOnly=yes",
|
||||||
"-o", "StrictHostKeyChecking=no",
|
"-o", "StrictHostKeyChecking=no",
|
||||||
"-o", "UserKnownHostsFile=/dev/null",
|
"-o", "UserKnownHostsFile=/dev/null",
|
||||||
"-o", "LogLevel=ERROR",
|
"-o", "LogLevel=ERROR",
|
||||||
@@ -301,9 +305,17 @@ if [ -n "$KEY" ]; then
|
|||||||
chown -R node:node /home/node/.ssh 2>/dev/null || true
|
chown -R node:node /home/node/.ssh 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# The rootless rootfs build (`docker export | tar` as a non-root user)
|
||||||
|
# can't preserve uid 0, so every path lands owned by the build uid,
|
||||||
|
# which maps to `node` (uid 1000) in-guest — including /root. dropbear
|
||||||
|
# (like OpenSSH) refuses root's authorized_keys unless the home dir is
|
||||||
|
# owned by root, so restore root's ownership of its own home.
|
||||||
|
chown -R 0:0 /root 2>/dev/null || true
|
||||||
|
|
||||||
mkdir -p /etc/dropbear /run
|
mkdir -p /etc/dropbear /run
|
||||||
# -R: generate host keys on demand. -E: log to stderr (guest console).
|
# -R: generate host keys on demand. -E: log auth failures to stderr,
|
||||||
/bb-dropbear -R -E -p 22 2>/dev/null &
|
# captured in the host-side console.log for debugging.
|
||||||
|
/bb-dropbear -R -E -p 22 &
|
||||||
|
|
||||||
# Reap zombies as PID 1. dropbear is always a child, so `wait` blocks
|
# Reap zombies as PID 1. dropbear is always a child, so `wait` blocks
|
||||||
# rather than busy-looping.
|
# rather than busy-looping.
|
||||||
|
|||||||
Reference in New Issue
Block a user