gioser

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1471Shell 173Rust 105Markdown 943545 Sutichaykuna

⎇ 466972e3010bda77ec242cf40c53abc43c948992 scripts/ssh-e2e-test.sh Shell · 193 siq'ikuna · 9.5 KB Ñawpa willay ch'uya

takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4 476168bb0 · Sergio ·

1#!/usr/bin/env bash
2# ssh-e2e-test.sh — SPIKE de validación (sucio, temporal) del Paso 1: probar el handshake SSH REAL
3# contra un takana booteado en QEMU, con sshd supervisado por arje-zero como genesis card.
4#
5# Esto NO es plomería de producto: arma un `work/rootfs-ssh-test` desechable = el 4/4 limpio del
6# store + openssh + netup (todos PRECOMPILADOS de la caché del lab, no rebuild) + una seed con un
7# card `sshd`, y lo bootea como initramfs. La red es QEMU slirp con hostfwd :2222->:22, así que el
8# host hace `ssh -p 2222 root@localhost` SIN tocar firewall/bridge (la lección de netup). El chroot
9# de privsep —que el bwrap anidado no pudo— acá corre como root REAL del guest.
10#
11# Quema tres incógnitas que el sandbox de bwrap dejó abiertas:
12#   1. el handshake completo (post-chroot, post-setuid sshd-user) ocurre,
13#   2. arje maneja el ciclo de vida del daemon (Restart) como cualquier card Native,
14#   3. los fds de red de sshd no segfaultean contra el musl del target.
15#
16# Variables: KERNEL (def store/*-linux/boot/bzImage) MEM (def 2048) KVM (1 si /dev/kvm)
17#            PORT (hostfwd, def 2222) DEADLINE (def 150s)
18set -euo pipefail
19ROOT="$(cd "$(dirname "$0")/.." && pwd)"
20cd "$ROOT"
21
22KERNEL="${KERNEL:-$(ls store/*-linux/boot/bzImage 2>/dev/null | head -1)}"
23MEM="${MEM:-2048}"
24KVM="${KVM:-1}"
25PORT="${PORT:-2222}"
26DEADLINE="${DEADLINE:-150}"
27WORK="$ROOT/work/ssh-e2e"
28RFS="$ROOT/work/rootfs-ssh-test"
29LOG="$WORK/console.log"
30
31pick() { ls -d store/*-"$1" 2>/dev/null | head -1; }
32MUSL=$(pick musl); BB=$(pick busybox); ARJE="store/69e2cab8ac00c33cde7680eb393952e2c7f9ae9af24c180c6593d7a86482efb9-arje-zero"
33NETUP=$(pick netup); OSSH=$(pick openssh)
34for v in KERNEL MUSL BB ARJE NETUP OSSH; do
35  p="${!v}"; [ -n "$p" ] && [ -e "$p" ] || { echo "falta $v ($p)"; exit 1; }
36done
37echo "==> kernel $KERNEL"
38echo "==> componentes: musl busybox arje-zero netup openssh (precompilados del store)"
39
40# ── 1) ensamblar el rootfs desechable ───────────────────────────────────────────────────────────
41rm -rf "$RFS" "$WORK"; mkdir -p "$RFS" "$WORK"
42for src in "$MUSL" "$BB" "$ARJE" "$NETUP" "$OSSH"; do
43  cp -a "$src"/. "$RFS"/ 2>/dev/null || true
44done
45chmod -R u+w "$RFS"            # el store está sellado read-only; el staging debe ser escribible
46rm -rf "$RFS/.hammer"
47mkdir -p "$RFS"/{proc,sys,dev,dev/pts,run,tmp,etc,etc/ssh,root,root/.ssh,var,var/empty,ente,sbin,bin,usr/bin}
48chmod 0711 "$RFS/var/empty"
49ln -sf /usr/bin/arje-zero "$RFS/sbin/init"
50# busybox a /bin/sh + applets que la getty/seed necesitan (por si el cp no dejó symlinks en /bin)
51[ -e "$RFS/bin/busybox" ] || cp -a "$BB"/bin/busybox "$RFS/bin/busybox"
52for a in sh getty; do ln -sf busybox "$RFS/bin/$a"; done
53
54# usuarios: root (login por llave) + sshd (privsep). shells reales para que sshd ejecute el comando.
55cat > "$RFS/etc/passwd" <<'EOF'
56root:x:0:0:root:/root:/bin/sh
57sshd:x:74:74:sshd privsep:/var/empty:/bin/false
58EOF
59cat > "$RFS/etc/group" <<'EOF'
60root:x:0:
61sshd:x:74:
62EOF
63cat > "$RFS/etc/ssh/sshd_config" <<'EOF'
64Port 22
65ListenAddress 0.0.0.0
66PermitRootLogin yes
67PubkeyAuthentication yes
68PasswordAuthentication no
69AuthorizedKeysFile /root/.ssh/authorized_keys
70StrictModes no
71Subsystem sftp /usr/libexec/sftp-server
72EOF
73
74# ── 2) llave de cliente desechable (host) → authorized_keys del guest ────────────────────────────
75"$OSSH/usr/bin/ssh-keygen" -t ed25519 -N '' -f "$WORK/clientkey" -q
76cat "$WORK/clientkey.pub" > "$RFS/root/.ssh/authorized_keys"
77chmod 0600 "$RFS/root/.ssh/authorized_keys"
78
79# ── 3) seed card: console-getty + un card `sshd` (Native, Restart) ───────────────────────────────
80# El card sshd arranca la red (netup, DHCP slirp), genera host keys (ssh-keygen -A) y exec sshd -D.
81# Mismo esquema validado de STAGE1_SEED_CARD (takana-bootstrap); ULIDs nuevos A10/A11/A12.
82SSHD_CMD='/usr/bin/netup; /usr/bin/ssh-keygen -A; exec /usr/sbin/sshd -D -e'
83cat > "$RFS/ente/seed.card.json" <<EOF
84{
85  "schema_version": 1,
86  "id": "01HQAR53D4M2NBV8KZTYXFQA10",
87  "lineage": null,
88  "label": "hammer-ssh-spike",
89  "provides": ["Spawn", "Journal"],
90  "requires": [],
91  "permissions": { "networking": "full", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": true },
92  "soma": {
93    "namespaces": { "mount": false, "pid": false, "net": false, "uts": false, "ipc": false, "user": false, "cgroup": false },
94    "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
95    "cgroup": { "path": "arje.slice/hammer", "cpu_weight": null, "io_weight": null },
96    "cpu_affinity": null
97  },
98  "payload": "Virtual",
99  "supervision": "OneShot",
100  "lifecycle": "daemon",
101  "priority": "normal",
102  "flow": { "input": [], "output": [] },
103  "genesis": [
104    {
105      "schema_version": 1,
106      "id": "01HQAR53D4M2NBV8KZTYXFQA11",
107      "lineage": null,
108      "label": "console-getty",
109      "provides": [],
110      "requires": [],
111      "permissions": { "networking": "none", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": true },
112      "soma": {
113        "namespaces": { "mount": false, "pid": false, "net": false, "uts": false, "ipc": false, "user": false, "cgroup": false },
114        "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
115        "cgroup": { "path": "arje.slice/getty", "cpu_weight": null, "io_weight": null },
116        "cpu_affinity": null
117      },
118      "payload": { "Native": { "exec": "/bin/busybox", "argv": ["getty", "-n", "-l", "/bin/sh", "115200", "console"], "envp": [] }},
119      "supervision": { "Restart": { "initial": 200, "max": 10000 } },
120      "lifecycle": "daemon",
121      "priority": "normal",
122      "flow": { "input": [], "output": [] },
123      "genesis": []
124    },
125    {
126      "schema_version": 1,
127      "id": "01HQAR53D4M2NBV8KZTYXFQA12",
128      "lineage": null,
129      "label": "sshd",
130      "provides": [],
131      "requires": [],
132      "permissions": { "networking": "full", "filesystem": "read-write", "ipc": { "allow": [] }, "processes": true },
133      "soma": {
134        "namespaces": { "mount": false, "pid": false, "net": false, "uts": false, "ipc": false, "user": false, "cgroup": false },
135        "rlimits": { "mem_bytes": null, "nproc": null, "nofile": null },
136        "cgroup": { "path": "arje.slice/sshd", "cpu_weight": null, "io_weight": null },
137        "cpu_affinity": null
138      },
139      "payload": { "Native": { "exec": "/bin/busybox", "argv": ["sh", "-c", "${SSHD_CMD}"], "envp": [["HOME", "/root"]] }},
140      "supervision": { "Restart": { "initial": 500, "max": 20000 } },
141      "lifecycle": "daemon",
142      "priority": "normal",
143      "flow": { "input": [], "output": [] },
144      "genesis": []
145    }
146  ]
147}
148EOF
149
150# ── 4) empaquetar initramfs ──────────────────────────────────────────────────────────────────────
151# -R 0:0: el cpio corre como uid no-root; sin esto el rootfs queda con nuestro uid y sshd rechaza
152# /var/empty ("must be owned by root"). Reasigna todo a root:root dentro del archivo.
153( cd "$RFS" && find . -print0 | cpio --null -o -H newc -R 0:0 2>/dev/null | gzip -1 ) > "$WORK/initramfs.cpio.gz"
154echo "==> initramfs $(du -h "$WORK/initramfs.cpio.gz" | cut -f1)"
155
156# ── 5) boot QEMU (slirp + hostfwd) en background ─────────────────────────────────────────────────
157accel=(-cpu Broadwell)
158if [ "$KVM" = 1 ] && [ -w /dev/kvm ]; then accel=(-enable-kvm -cpu host); fi
159echo "==> boot QEMU (mem ${MEM}, kvm $([ "${accel[0]}" = -enable-kvm ] && echo sí || echo no)), hostfwd :$PORT->:22"
160qemu-system-x86_64 -m "$MEM" -no-reboot -nographic "${accel[@]}" \
161  -kernel "$KERNEL" -initrd "$WORK/initramfs.cpio.gz" \
162  -append "console=ttyS0 rdinit=/sbin/init" \
163  -netdev "user,id=n0,hostfwd=tcp::$PORT-:22" -device e1000,netdev=n0 \
164  > "$LOG" 2>&1 &
165QPID=$!
166trap 'kill $QPID 2>/dev/null || true' EXIT
167
168# ── 6) esperar a sshd y hacer el handshake real ──────────────────────────────────────────────────
169SSHOPTS=(-p "$PORT" -i "$WORK/clientkey" -o StrictHostKeyChecking=no
170         -o UserKnownHostsFile=/dev/null -o GlobalKnownHostsFile=/dev/null
171         -o PasswordAuthentication=no -o ConnectTimeout=4 -o LogLevel=ERROR)
172echo "==> esperando handshake (deadline ${DEADLINE}s)…"
173start=$(date +%s); ok=0
174while [ $(( $(date +%s) - start )) -lt "$DEADLINE" ]; do
175  if ! kill -0 $QPID 2>/dev/null; then echo "!! QEMU murió"; break; fi
176  if out=$(ssh "${SSHOPTS[@]}" root@localhost \
177        'echo HANDSHAKE_OK uid=$(id -u) user=$(id -un); uname -srm; cat /etc/os-release 2>/dev/null | head -1' 2>/dev/null); then
178    echo "================ RESPUESTA DEL GUEST ================"
179    echo "$out"
180    echo "===================================================="
181    echo "$out" | grep -q HANDSHAKE_OK && ok=1
182    break
183  fi
184  sleep 3
185done
186
187# ── 7) veredicto + diagnóstico ───────────────────────────────────────────────────────────────────
188echo "---- consola (cola) ----"; tail -25 "$LOG" 2>/dev/null
189if [ "$ok" = 1 ]; then
190  echo; echo "*** SSH E2E OK — handshake real contra hammer+arje, sshd como card ***"; exit 0
191else
192  echo; echo "*** SSH E2E FALLÓ — ver $LOG ***"; exit 1
193fi