#!/usr/bin/env bash # ssh-e2e-test.sh — SPIKE de validación (sucio, temporal) del Paso 1: probar el handshake SSH REAL # contra un hammer booteado en QEMU, con sshd supervisado por arje-zero como genesis card. # # Esto NO es plomería de producto: arma un `work/rootfs-ssh-test` desechable = el 4/4 limpio del # store + openssh + netup (todos PRECOMPILADOS de la caché del lab, no rebuild) + una seed con un # card `sshd`, y lo bootea como initramfs. La red es QEMU slirp con hostfwd :2222->:22, así que el # host hace `ssh -p 2222 root@localhost` SIN tocar firewall/bridge (la lección de netup). El chroot # de privsep —que el bwrap anidado no pudo— acá corre como root REAL del guest. # # Quema tres incógnitas que el sandbox de bwrap dejó abiertas: # 1. el handshake completo (post-chroot, post-setuid sshd-user) ocurre, # 2. arje maneja el ciclo de vida del daemon (Restart) como cualquier card Native, # 3. los fds de red de sshd no segfaultean contra el musl del target. # # Variables: KERNEL (def store/*-linux/boot/bzImage) MEM (def 2048) KVM (1 si /dev/kvm) # PORT (hostfwd, def 2222) DEADLINE (def 150s) set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" KERNEL="${KERNEL:-$(ls store/*-linux/boot/bzImage 2>/dev/null | head -1)}" MEM="${MEM:-2048}" KVM="${KVM:-1}" PORT="${PORT:-2222}" DEADLINE="${DEADLINE:-150}" WORK="$ROOT/work/ssh-e2e" RFS="$ROOT/work/rootfs-ssh-test" LOG="$WORK/console.log" pick() { ls -d store/*-"$1" 2>/dev/null | head -1; } MUSL=$(pick musl); BB=$(pick busybox); ARJE="store/69e2cab8ac00c33cde7680eb393952e2c7f9ae9af24c180c6593d7a86482efb9-arje-zero" NETUP=$(pick netup); OSSH=$(pick openssh) for v in KERNEL MUSL BB ARJE NETUP OSSH; do p="${!v}"; [ -n "$p" ] && [ -e "$p" ] || { echo "falta $v ($p)"; exit 1; } done echo "==> kernel $KERNEL" echo "==> componentes: musl busybox arje-zero netup openssh (precompilados del store)" # ── 1) ensamblar el rootfs desechable ─────────────────────────────────────────────────────────── rm -rf "$RFS" "$WORK"; mkdir -p "$RFS" "$WORK" for src in "$MUSL" "$BB" "$ARJE" "$NETUP" "$OSSH"; do cp -a "$src"/. "$RFS"/ 2>/dev/null || true done chmod -R u+w "$RFS" # el store está sellado read-only; el staging debe ser escribible rm -rf "$RFS/.hammer" mkdir -p "$RFS"/{proc,sys,dev,dev/pts,run,tmp,etc,etc/ssh,root,root/.ssh,var,var/empty,ente,sbin,bin,usr/bin} chmod 0711 "$RFS/var/empty" ln -sf /usr/bin/arje-zero "$RFS/sbin/init" # busybox a /bin/sh + applets que la getty/seed necesitan (por si el cp no dejó symlinks en /bin) [ -e "$RFS/bin/busybox" ] || cp -a "$BB"/bin/busybox "$RFS/bin/busybox" for a in sh getty; do ln -sf busybox "$RFS/bin/$a"; done # usuarios: root (login por llave) + sshd (privsep). shells reales para que sshd ejecute el comando. cat > "$RFS/etc/passwd" <<'EOF' root:x:0:0:root:/root:/bin/sh sshd:x:74:74:sshd privsep:/var/empty:/bin/false EOF cat > "$RFS/etc/group" <<'EOF' root:x:0: sshd:x:74: EOF cat > "$RFS/etc/ssh/sshd_config" <<'EOF' Port 22 ListenAddress 0.0.0.0 PermitRootLogin yes PubkeyAuthentication yes PasswordAuthentication no AuthorizedKeysFile /root/.ssh/authorized_keys StrictModes no Subsystem sftp /usr/libexec/sftp-server EOF # ── 2) llave de cliente desechable (host) → authorized_keys del guest ──────────────────────────── "$OSSH/usr/bin/ssh-keygen" -t ed25519 -N '' -f "$WORK/clientkey" -q cat "$WORK/clientkey.pub" > "$RFS/root/.ssh/authorized_keys" chmod 0600 "$RFS/root/.ssh/authorized_keys" # ── 3) seed card: console-getty + un card `sshd` (Native, Restart) ─────────────────────────────── # El card sshd arranca la red (netup, DHCP slirp), genera host keys (ssh-keygen -A) y exec sshd -D. # Mismo esquema validado de STAGE1_SEED_CARD (hammer-bootstrap); ULIDs nuevos A10/A11/A12. SSHD_CMD='/usr/bin/netup; /usr/bin/ssh-keygen -A; exec /usr/sbin/sshd -D -e' cat > "$RFS/ente/seed.card.json" </dev/null | gzip -1 ) > "$WORK/initramfs.cpio.gz" echo "==> initramfs $(du -h "$WORK/initramfs.cpio.gz" | cut -f1)" # ── 5) boot QEMU (slirp + hostfwd) en background ───────────────────────────────────────────────── accel=(-cpu Broadwell) if [ "$KVM" = 1 ] && [ -w /dev/kvm ]; then accel=(-enable-kvm -cpu host); fi echo "==> boot QEMU (mem ${MEM}, kvm $([ "${accel[0]}" = -enable-kvm ] && echo sí || echo no)), hostfwd :$PORT->:22" qemu-system-x86_64 -m "$MEM" -no-reboot -nographic "${accel[@]}" \ -kernel "$KERNEL" -initrd "$WORK/initramfs.cpio.gz" \ -append "console=ttyS0 rdinit=/sbin/init" \ -netdev "user,id=n0,hostfwd=tcp::$PORT-:22" -device e1000,netdev=n0 \ > "$LOG" 2>&1 & QPID=$! trap 'kill $QPID 2>/dev/null || true' EXIT # ── 6) esperar a sshd y hacer el handshake real ────────────────────────────────────────────────── SSHOPTS=(-p "$PORT" -i "$WORK/clientkey" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null -o PasswordAuthentication=no -o ConnectTimeout=4 -o LogLevel=ERROR) echo "==> esperando handshake (deadline ${DEADLINE}s)…" start=$(date +%s); ok=0 while [ $(( $(date +%s) - start )) -lt "$DEADLINE" ]; do if ! kill -0 $QPID 2>/dev/null; then echo "!! QEMU murió"; break; fi if out=$(ssh "${SSHOPTS[@]}" root@localhost \ 'echo HANDSHAKE_OK uid=$(id -u) user=$(id -un); uname -srm; cat /etc/os-release 2>/dev/null | head -1' 2>/dev/null); then echo "================ RESPUESTA DEL GUEST ================" echo "$out" echo "====================================================" echo "$out" | grep -q HANDSHAKE_OK && ok=1 break fi sleep 3 done # ── 7) veredicto + diagnóstico ─────────────────────────────────────────────────────────────────── echo "---- consola (cola) ----"; tail -25 "$LOG" 2>/dev/null if [ "$ok" = 1 ]; then echo; echo "*** SSH E2E OK — handshake real contra hammer+arje, sshd como card ***"; exit 0 else echo; echo "*** SSH E2E FALLÓ — ver $LOG ***"; exit 1 fi