57 lines
4.0 KiB
Markdown
57 lines
4.0 KiB
Markdown
# SSH/scp Task-Polling statt NFS-Freigabe (Sicherheits-Umbau 2026-08-03)
|
|
|
|
## Entscheidung
|
|
NFS-Freigabe ist OBSOLET. Alle Sub-Agenten erhalten Tasks über **SSH/scp (Key-Auth, verschlüsselt)**.
|
|
Grund: NFS (sec=sys) überträgt plaintext → Man-in-the-Middle-fähig. SSH = Ende-zu-Ende verschlüsselt,
|
|
nur eigene Tasks sichtbar (gefiltert nach `*.C.D.*`), auditierbar.
|
|
|
|
`/srv/work/tasks/` bleibt als **zentrale Ablage auf dem Master** (offen/in_arbeit/fertig/bugfix/archiv),
|
|
nur die Zugriffsart änderte sich: kein Mount, sondern pull/push per scp.
|
|
|
|
## Warum NFS hier nicht überall geht
|
|
SwitchHermes = Ubuntu 24.04 auf Nintendo-Switch (Tegra-Kernel `4.9.140-l4t`):
|
|
- `modprobe nfs` → `FATAL: Module nfsv4 not found`
|
|
- NFS-Client + FUSE/sshfs fehlen (kein Kernelmodul, wenig Speicher → kein Kernel-Ausbau gewollt)
|
|
→ SSH/scp ist die einzige portable Methode für ARM-Switch-Geräte.
|
|
|
|
## Architecture des SSH-Poller-Daemons
|
|
- Jeder Agent hat **eigenen ed25519-Key** (auf Master: `/root/.ssh/<agent>_mesh`), Public-Key in `authorized_keys`.
|
|
- Poller-Script `mesh-agent-poller.sh <meine-IP> <meine-ID> [master] [keyfile]`:
|
|
1. `scp` pull eigene Tasks `offen/<JahrMonatTag>.<C>.<D>.*.json` nach `/tmp/mesh-tasks/`
|
|
2. Atomarer Lock **per SSH auf dem Master**: `mv offen/... in_arbeit/...` (absolute Pfade!), `&& echo OK || echo LOST`
|
|
3. Bearbeitung via `hermes chat -q "<PROMPT>" --quiet --max-turns 3`
|
|
4. Ergebnis als Base64 ins JSON-Feld `result` schreiben (SSH-python), dann `mv in_arbeit/ → fertig/`, chmod 0666
|
|
- **Daemon**: systemd-user-service (`~/.config/systemd/user/mesh-poller.service`, `Restart=always`, Loop alle 10s).
|
|
NICHT nohup/Cron-@reboot als Loop — der stirbt beim SSH-Session-Ende.
|
|
- Service-Start für User-Services: `export XDG_RUNTIME_DIR=/run/user/$(id -u); systemctl --user ...`
|
|
|
|
## Wichtigste Pitfalls (alle live getroffen)
|
|
1. **`SSH_CMD` MUSS den Host enthalten**: `SSH_CMD="ssh -i $KEY ... root@172.24.196.119"`.
|
|
Vergessen → `hostname contains invalid characters` + leerer Lock-Output = endlose SKIPs, obwohl der
|
|
manuelle `mv`-Test auf dem Master klappt.
|
|
2. **Relative Pfade im remote-mv vermeiden** — `../../../in_arbeit` geht 3 Ebenen hoch statt 1. Nutze absolute Pfade.
|
|
3. **Datei-Rechte (Hauptgrund, warum Polling anfangs nie lief):** vom Master (root) erstellte/durch `mv`
|
|
bewegte Task-Dateien behalten root-Owner → Sub-Agent (uid 1000) kann nicht reinschreiben, selbst bei
|
|
0777-Dir. Fix: **`chmod 0666`** nach Task-Erstellung (`mesh-task-create.sh`) UND nach jedem `mv` im Poller
|
|
(`in_arbeit` UND `fertig`). Sonst `PermissionError: [Errno 13]`.
|
|
4. **fail2ban bannt Mesh-IPs** bei scp-Fehlschlägen → `Connection refused` auf Port 22, obwohl Ping klappt
|
|
und sshd auf `0.0.0.0:22` lauscht. Fix: `ignoreip = <alle ZT-IPs>` in `[sshd]`-Sektion von
|
|
`/etc/fail2ban/jail.local`. Verifizieren: `fail2ban-client status sshd` → IP aus `Banned IP list` nehmen.
|
|
5. **Doppelte Daemon-Prozesse** (ein nohup-Rest + ein systemd-Rest) stören sich gegenseitig beim Lock.
|
|
Nach Umbau: `systemctl --user stop`, `pkill -9 -f mesh-poller-daemon`, PID-Datei löschen, sauber `start`.
|
|
Am Ende MUSS `pgrep -f mesh-poller-daemon` genau EINEN Prozess zeigen.
|
|
6. **"No such file or directory" beim remote-scp-Glob** kann heißen, dass der Task bereits in `fertig/`
|
|
liegt (ein früherer Lauf hat ihn gezogen). Erst `ls` auf Master prüfen, bevor man den Glob verdächtigt.
|
|
|
|
## NFS-Server deaktivieren (nach Migration)
|
|
- `systemctl stop/disable nfs-kernel-server rpcbind`
|
|
- `ufw delete allow from <ip> to any port 2049/111/20048` für jede Agenten-IP
|
|
- Offene Mesh-Ports danach NUR: 22 (SSH), 8644 (Webhook), 11434 (Ollama) — alle gerichtet auf die 4 bekannten ZT-IPs.
|
|
- `/etc/exports` bleibt als Datei liegen, aber inaktiv.
|
|
|
|
## NFS-Export-Härtung (falls man doch NFS nutzen muss)
|
|
Export auf die 4 bekannten IPs statt `/16`, zur
|
|
defense-in-depth: UFW-Regel (nur bekannte IPs) UND Export (bekannte IPs) konsistent:
|
|
`/srv/work/tasks ip1(rw,sync,no_subtree_check,no_root_squash) ip2(...) ...`
|
|
`exportfs -ra` reload separat ausführen, NICHT als gateway-gebundener Befehl (wird gekillt).
|