621e8895c9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
275 lines
18 KiB
Markdown
275 lines
18 KiB
Markdown
---
|
|
name: agentes-dispositivos-mesh
|
|
id: 0009
|
|
status: pending
|
|
created: 2026-05-23
|
|
updated: 2026-05-23
|
|
priority: high
|
|
risk: high
|
|
related_issues: [0134, 0135, 0136, 0137, 0138, 0139, 0140, 0141, 0142, 0143]
|
|
apps: [agents_dashboard, agents_and_robots, wg_hub, device_agent]
|
|
projects: [element_agents]
|
|
vaults: []
|
|
capability_groups: [wireguard, device-agent, docker-agent]
|
|
trigger: manual
|
|
schedule: ""
|
|
expected_runtime_s: 300
|
|
tags: [mesh, wireguard, matrix, e2ee, agents, devices, docker, sandboxing]
|
|
---
|
|
|
|
## Goal
|
|
|
|
Hablar desde Element con dispositivos completos (PCs, moviles, raspberry, IoT) y con
|
|
contenedores Docker como si fueran agentes Matrix. Cada device/container ejecuta sus
|
|
capabilities declaradas (shell/fs/camera/docker/sensores) bajo:
|
|
|
|
1. **Mesh WireGuard** anclado en `organic-machine.com` — sin abrir puertos en los devices.
|
|
2. **Matrix E2EE** como bus de control y chat — un room por device/container.
|
|
3. **Capability manifest firmado** ed25519 — el device rechaza lo que no este firmado.
|
|
|
|
## Pre-requisitos
|
|
|
|
- VPS `organic-machine.com` con root SSH (alias `vps` en `~/.ssh/config`).
|
|
- `agents_and_robots` y `agents_dashboard` desplegados (ya OK).
|
|
- `pass` con clave operador ed25519 (`pass insert operator/ed25519` — crear si falta).
|
|
- `apt-get install wireguard wireguard-tools` permitido en el VPS.
|
|
- Devices Linux/WSL: sudo sin password para `wg`, `wg-quick`, `systemctl`.
|
|
- Devices Android: Termux + WireGuard app + `pkg install golang openssh-client`.
|
|
|
|
## Funciones del registry recomendadas
|
|
|
|
| Rol | Funcion candidata | Estado |
|
|
|---|---|---|
|
|
| WG install (host) | `wg_install_bash_infra` | FALTA: crear |
|
|
| WG keygen | `wg_keygen_go_infra` | FALTA: crear |
|
|
| WG hub setup | `wg_hub_setup_bash_infra` | FALTA: crear |
|
|
| WG peer add (hub) | `wg_peer_add_go_infra` | FALTA: crear |
|
|
| WG peer remove (hub) | `wg_peer_remove_go_infra` | FALTA: crear |
|
|
| WG peer revoke (kill switch) | `wg_peer_revoke_go_infra` | FALTA: crear |
|
|
| WG client config gen | `wg_client_config_go_infra` | FALTA: crear |
|
|
| WG client install (device) | `wg_client_install_bash_infra` | FALTA: crear |
|
|
| WG status (parse `wg show`) | `wg_status_bash_infra` | FALTA: crear |
|
|
| Docker list (host) | `docker_container_list_go_infra` | FALTA: crear |
|
|
| Docker exec capability | `docker_container_exec_go_infra` | FALTA: crear |
|
|
| Docker logs tail | `docker_container_logs_go_infra` | FALTA: crear |
|
|
| Docker container enroll | `docker_container_enroll_go_infra` | FALTA: crear |
|
|
| Capability sign | `capability_manifest_sign_go_infra` | FALTA: crear |
|
|
| Capability verify | `capability_manifest_verify_go_infra` | FALTA: crear |
|
|
| Enrollment token gen | `enrollment_token_create_go_infra` | FALTA: crear |
|
|
| Enrollment token verify | `enrollment_token_verify_go_infra` | FALTA: crear |
|
|
| Matrix room per device | `matrix_room_for_device_py_browser` (extender) | OK base, EXTENDER |
|
|
| Provision hub pipeline | `provision_wg_hub_bash_pipelines` | FALTA: crear |
|
|
| Enroll device pipeline | `enroll_device_bash_pipelines` | FALTA: crear |
|
|
| Sink audit log | `device_audit_append_go_infra` | FALTA: crear |
|
|
| Notify approval | `matrix_send_message_py_browser` (existente) | OK |
|
|
|
|
## Apps tocadas
|
|
|
|
- `agents_dashboard` (cockpit ImGui) — panel "Mesh" + "Devices" + "Containers" + approval queue.
|
|
- `agents_and_robots` (hub Matrix VPS) — listener Matrix por device/container.
|
|
- `wg_hub` (nuevo service Go en VPS) — enrollment endpoint, peer CRUD, SSE stream.
|
|
- `device_agent` (nuevo binario per-host) — capability dispatcher con sandbox.
|
|
- `container_agent_sidecar` (opcional, nuevo) — sidecar para containers que necesitan WG-peer propio.
|
|
|
|
## Projects relacionados
|
|
|
|
- `element_agents` (parent project — agents Matrix).
|
|
|
|
## Vaults / storage
|
|
|
|
- `apps/wg_hub/operations.db` — tabla `wg_peers`, `wg_enrollment_tokens`, `device_audit`.
|
|
- `apps/agents_dashboard/local_files/agents_dashboard.db` — cache devices + capabilities.
|
|
- `pass operator/ed25519` — clave maestra del operador (firma manifests).
|
|
- `pass wg/preshared/<device_id>` — PSK por peer.
|
|
|
|
## Capability groups consultados
|
|
|
|
- `wireguard` (nuevo, ver `docs/capabilities/wireguard.md`).
|
|
- `device-agent` (nuevo, capability dispatcher + sandbox + audit).
|
|
- `docker-agent` (nuevo, capabilities sobre containers locales).
|
|
|
|
## Flow
|
|
|
|
### Fase A — registry primero (delegar a fn-constructor en paralelo)
|
|
|
|
1. `function: wg_install_bash_infra` (delegada).
|
|
2. `function: wg_keygen_go_infra` (delegada).
|
|
3. `function: wg_hub_setup_bash_infra` (delegada).
|
|
4. `function: wg_peer_add_go_infra` (delegada).
|
|
5. `function: wg_peer_remove_go_infra` (delegada).
|
|
6. `function: wg_peer_revoke_go_infra` (delegada).
|
|
7. `function: wg_client_config_go_infra` (delegada).
|
|
8. `function: wg_client_install_bash_infra` (delegada).
|
|
9. `function: wg_status_bash_infra` (delegada).
|
|
10. `cmd: ./fn index` — registra las 9 nuevas.
|
|
11. `cmd: fn doctor unused | grep wg_` — confirma que estan listas y no huerfanas (se usan en pasos C).
|
|
|
|
### Fase C — POC manual end-to-end
|
|
|
|
12. `function: wg_install_bash_infra` (sobre `organic-machine.com` via SSH).
|
|
13. `function: wg_keygen_go_infra` → key par hub.
|
|
14. `function: wg_hub_setup_bash_infra` — wg0, 10.42.0.1/24, ufw 51820/udp, persistencia.
|
|
15. `function: wg_keygen_go_infra` → key par device `home-wsl`.
|
|
16. `function: wg_peer_add_go_infra` (en hub) → asigna 10.42.0.10.
|
|
17. `function: wg_client_config_go_infra` → genera client.conf.
|
|
18. `function: wg_client_install_bash_infra` (en `home-wsl`).
|
|
19. `cmd: ping -c3 10.42.0.1` desde `home-wsl` — verifica handshake.
|
|
20. `cmd: curl http://10.42.0.1:8080/healthz` — agents_and_robots accesible por IP privada.
|
|
21. Repetir 15-19 para `pc-aurgi`.
|
|
|
|
### Fase B — spec + capability manifest + bot Matrix
|
|
|
|
22. Issue 0134 spec protocol: envelope JSON `{request_id, capability, args, signature, nonce}`,
|
|
error model, approval flow, audit chain hash.
|
|
23. `function: capability_manifest_sign_go_infra` (operator firma).
|
|
24. `function: capability_manifest_verify_go_infra` (device verifica antes de aceptar request).
|
|
25. `function: enrollment_token_create_go_infra` (token QR firmado, TTL 10min).
|
|
26. `function: enrollment_token_verify_go_infra` (hub valida en `/enroll`).
|
|
27. Implementar `apps/device_agent/` (Go cross-compile) — Matrix client + capability dispatcher + sandbox firejail.
|
|
28. Panel "Devices" en `agents_dashboard` — lista + capability matrix + approval queue + boton revoke.
|
|
29. Bot Matrix por device: cuando hablas en el room `#dev-aurgi:organic-machine.com`,
|
|
`agents_and_robots` parsea, valida capability, despacha a device_agent, devuelve resultado al room.
|
|
|
|
### Fase D — agentes-contenedores docker
|
|
|
|
30. `function: docker_container_list_go_infra` — corre en host con docker socket access.
|
|
31. `function: docker_container_exec_go_infra` — exec en container con whitelist binarios.
|
|
32. `function: docker_container_logs_go_infra` — tail logs SSE.
|
|
33. Modo "light": container expuesto via host's `device_agent` capability `docker.*`.
|
|
Element room: `#host-aurgi:organic-machine.com` con comando `!docker exec mycontainer ps`.
|
|
34. Modo "deep": container = peer WG propio. `container_agent_sidecar` corre WG dentro del container
|
|
(privileged) o sidecar gluetun-wg. Manifest firmado mapea `agent_X` → container_id.
|
|
35. Sub-bot Matrix por container: `#cont-mycontainer:organic-machine.com` (opcional, modo deep).
|
|
|
|
## Acceptance
|
|
|
|
- [ ] 9 funciones `wg_*` creadas + indexadas + sin huerfanas.
|
|
- [ ] Hub WG corriendo en `organic-machine.com`, `wg show` muestra interface wg0.
|
|
- [ ] `home-wsl` y `pc-aurgi` con IP estable 10.42.0.10/11, `ping` OK.
|
|
- [ ] `agents_and_robots` accesible solo desde subnet 10.42.0.0/24 (publico = DROP en :8080).
|
|
- [ ] `agents_dashboard` panel "Mesh" muestra peers vivos via SSE.
|
|
- [ ] Chat en `#dev-aurgi` ejecuta capability (ej. `!ls /home/lucas`) y devuelve resultado.
|
|
- [ ] Capability fuera del manifest rechazada con error en room.
|
|
- [ ] Capability `requires_approval=true` espera confirmacion en `#operator-approvals` antes de ejecutar.
|
|
- [ ] `docker.container.list` invocado desde Element devuelve containers del host.
|
|
- [ ] `docker.container.exec` con binario fuera de whitelist rechazado.
|
|
- [ ] Revoke device desde `agents_dashboard` → device pierde acceso en <5s.
|
|
- [ ] Audit log append-only inviolable (hash chain) sobrevive reinicio.
|
|
|
|
## Definition of Done
|
|
|
|
Triada obligatoria (ver `.claude/rules/dod_quality.md`). Sin las 3 capas + 0 anti-criterios el flow NO se mueve a `completed/`.
|
|
|
|
### Mecanica (pre-requisito)
|
|
|
|
- [ ] **Build device_agent**: `cd apps/device_agent && CGO_ENABLED=0 GOOS=linux go build -o device_agent .` exit 0; cross-compile `GOOS=windows` + `GOOS=android GOARCH=arm64` tambien verdes.
|
|
- [ ] **Build agents_and_robots + agents_dashboard**: `./fn run redeploy_cpp_app_windows agents_dashboard apps/agents_dashboard --build` + Go build de `agents_and_robots` exit 0.
|
|
- [ ] **Tests unitarios funciones nuevas verdes**: `CGO_ENABLED=1 go test -tags fts5 -count=1 ./functions/infra/...` cubriendo wg_*, capability_*, enrollment_*, device_audit_*. Lista de IDs en `## Notas`.
|
|
- [ ] **`./fn index`** sin warnings nuevos tras anadir las ~20 funciones.
|
|
- [ ] **`./fn doctor unused --json | jq '.[]|select(.id|startswith("wg_"))'`** vacio (las wg_* tienen consumidores reales).
|
|
- [ ] **`./fn doctor uses-functions`** verde para `apps/device_agent/app.md`, `apps/wg_hub/app.md`, `apps/agents_dashboard/app.md`.
|
|
- [ ] **`./fn doctor services-spec`** verde para `wg_hub.service` y `device_agent.service` con bloque service: completo.
|
|
|
|
### Cobertura de comportamiento
|
|
|
|
Minimo: golden + 8 edge/error documentados aqui con assert ejecutable. Cada uno deja entry en `e2e_runs` de la app afectada (`apps/device_agent/operations.db`, `apps/wg_hub/operations.db`).
|
|
|
|
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|
|
|---|---|---|---|
|
|
| Golden: comando whitelist OK | e2e | Element `!exec ls /home/lucas` en `#dev-home-wsl` | output `ls` en <3s, entry en `device_audit` con hash valido |
|
|
| Edge: comando NO whitelist rechazado | e2e | Element `!exec rm -rf /` | reply `capability rejected: shell.exec.rm not in manifest`; entry `device_audit` status=`rejected_capability` |
|
|
| Edge: capability fuera de manifest | e2e | Element `!camera.snapshot` en device sin esa capability | reply `capability not in manifest`; alerta a `#operator-approvals` |
|
|
| Edge: replay nonce viejo | e2e | reenviar mismo envelope con nonce ya visto (cmd test: `device_agent --replay-test <envelope.json>`) | rechazo + log `nonce_replay`; entry `device_audit` status=`rejected_nonce` |
|
|
| Edge: ed25519 manifest invalido | e2e | servir manifest firmado por clave que no es operator; `device_agent` lo recibe en enrollment | `device_agent` rechaza + no instala wg_peer; hub log muestra `manifest_invalid_signature` |
|
|
| Edge: token enrollment expirado | e2e | `enrollment_token_create` con TTL=1s, esperar 5s, `POST /enroll` | hub responde 401 `token_expired`; cmd `curl ...` exit != 0 |
|
|
| Approval flow honrado | e2e | Element `!fs.write /tmp/x hello` (requires_approval=true); operador hace 👍 en `#operator-approvals` | exec ocurre SOLO tras approval; sin approval no escribe; entry `device_audit` con `approval_msg_id` |
|
|
| Approval flow no se salta | e2e | Forzar via API directa salto del approval queue (test negativo: cmd `curl --data ...` directo al device) | device rechaza + log; sin approval_msg_id en envelope = rechazo |
|
|
| Mesh-down handled | e2e | `wg-quick down wg0` en hub mientras device manda comando | device entra en `degraded`, comando encolado o respuesta `mesh_unreachable`; al volver hub: handshake reanuda, cola se vacia |
|
|
| Dos devices simultaneos sin interferencia | e2e | `home-wsl` y `pc-aurgi` ejecutan capabilities en paralelo (script python con 2 threads) | cada audit chain es independiente, sin cross-contamination; `device_audit` muestra 2 chains separadas, hash chain valido en cada una |
|
|
| Audit chain valida tras restart | e2e | matar `device_agent` mid-flight (`kill -9`) + relanzar; `cmd: device_audit_verify_chain --device home-wsl` | chain integra, hash anterior coincide, sin huecos |
|
|
| Revoke device <5s | e2e | desde `agents_dashboard` panel "Mesh" boton "Revoke home-wsl"; medir tiempo hasta `wg show` no liste peer | peer ausente en <5s; siguientes comandos a `#dev-home-wsl` -> `peer_revoked` |
|
|
|
|
**Regla**: cada fila genera `e2e_check` en `app.md` correspondiente (issue 0068). `fn-analizador` los corre periodicamente.
|
|
|
|
### Vida util validada
|
|
|
|
| Metrica | Umbral | Donde se observa | Ventana |
|
|
|---|---|---|---|
|
|
| Peers vivos en mesh | `>=2` constantes (home-wsl + pc-aurgi) | `agents_dashboard` panel "Mesh" (last_handshake < 3min) | 7 dias |
|
|
| Crashes `device_agent` | `0` | `journalctl --user -u device_agent.service` en cada device | 7 dias |
|
|
| Crashes `wg_hub` | `0` | `ssh vps journalctl -u wg_hub.service` | 7 dias |
|
|
| Huecos en audit chain | `0` | `cmd: device_audit_verify_chain --all` | continuo |
|
|
| Rollback de wg config | `0 ocurrencias` | hub: `git -C /etc/wireguard status` debe ser clean; sin restore manual | 7 dias |
|
|
| Handshake fail rate | `<5%` | `wg show all dump` parseado por `agents_dashboard` | 7 dias |
|
|
| Approval queue stuck | `0 pendientes >24h` | `agents_dashboard` panel "Approvals" | continuo |
|
|
| Comandos exec latencia p95 | `<3s` | `call_monitor.function_stats` para `capability.shell.exec` | 7 dias |
|
|
| Replay attacks bloqueados | `>=1 detectado y bloqueado` (pen-test real) | `device_audit` status=`rejected_nonce` count | 30 dias |
|
|
|
|
### User-facing (reforzado)
|
|
|
|
- [ ] **User-facing surface**: humano abre Element en movil/web (`element.organic-machine.com`), entra a `#dev-<nombre>` y escribe comandos. Output en el mismo room. NO en una BD, NO en un log.
|
|
- [ ] **User-facing usage real**: el operador (humano) usa Element con `home-wsl` Y `pc-aurgi` (>=2 maquinas reales), **>=1 sesion/dia durante >=7 dias consecutivos**, **>=20 comandos totales** repartidos entre devices.
|
|
- [ ] **User-facing variado**: cubre capabilities de **>=4 tipos**: read (`!fs.read`, `!ls`), write (`!fs.write`), exec (`!exec`), approval-required (`!fs.write` en path sensible), docker (`!docker exec`).
|
|
- [ ] **User-facing onboarding**: parrafo en `## Notas` con pasos numerados: abrir Element -> entrar a room -> `!help` -> ejemplo de comando. Sin leer el flow entero.
|
|
- [ ] **User-facing latencia**: tras enviar mensaje en Element, output visible en <3s (read/exec) o <5s (con approval) — medido y registrado en `## Notas`.
|
|
|
|
### Anti-criterios (invalidan DoD aunque checkboxes verdes)
|
|
|
|
- [ ] **Solo-en-home-wsl**: el flow funciona en mi WSL pero falla en `pc-aurgi` u otro device fisico.
|
|
- [ ] **device_agent muere cada noche**: cualquier crash recurrente del proceso device_agent en los 7 dias de validacion.
|
|
- [ ] **Approval flow se salta**: alguna entrada en `device_audit` con capability `requires_approval=true` ejecutada sin `approval_msg_id` valido.
|
|
- [ ] **Audit chain rota**: `device_audit_verify_chain` reporta huecos o hash mismatch en algun device.
|
|
- [ ] **wg config drift**: cambios manuales en `/etc/wireguard/wg0.conf` del hub sin pasar por `wg_peer_add/remove/revoke`. Git status muestra cambios sin trackear.
|
|
- [ ] **Dashboard fantasma**: `agents_dashboard` declarado pero el operador no lo abre durante la ventana de 7 dias. Telemetria muerta.
|
|
- [ ] **Pen-test no ejercitado**: replay attack / capability fuera de manifest / token expirado declarados pero sin entry real en `device_audit` con status `rejected_*` en los 7 dias.
|
|
- [ ] **Silent-fail**: peer cae >24h y nadie se entera (sin alerta a `#operator-approvals` ni badge rojo en dashboard).
|
|
- [ ] **Secrets en repo**: cualquier hit de `git grep -E 'PrivateKey|PSK|operator/ed25519' -- ':!*.md'` en cualquier rama.
|
|
|
|
### Custom (security-specific, deben tener evidencia en `device_audit`)
|
|
|
|
- [ ] _(custom)_ Pen-test capability fuera de manifest: entry `device_audit` status=`rejected_capability` ejercitado intencionalmente >=1 vez.
|
|
- [ ] _(custom)_ Pen-test replay: entry `device_audit` status=`rejected_nonce` ejercitado >=1 vez con cmd reproducible.
|
|
- [ ] _(custom)_ Stale device: forzar `home-wsl` offline >24h, verificar badge `stale` en `agents_dashboard` + mensaje en `#operator-approvals`.
|
|
- [ ] _(custom)_ Operator key rotation: ejecutar rollover de la clave ed25519 maestra + revoke-all + re-enroll, sin perder audit chain historica. Documentado en `## Notas`.
|
|
|
|
## Telemetria esperada
|
|
|
|
- `call_monitor.calls`: cada `wg_*`, `capability.*`, `docker.*` con duration_ms, success.
|
|
- `apps/wg_hub/operations.db`: tabla `wg_peers` + `device_audit` (hash-chained append-only).
|
|
- `apps/agents_and_robots/operations.db`: tabla `matrix_capability_dispatches`.
|
|
- `apps/agents_dashboard/local_files/agents_dashboard.db`: cache devices + approval queue.
|
|
- Dashboards visibles: `agents_dashboard` panel "Mesh" (peers vivos + last handshake + bytes rx/tx).
|
|
- Matrix room `#operator-approvals` recibe cada approval_request.
|
|
- Element en movil aprueba/rechaza con reacciones (👍/👎) o comando `!approve <id>`.
|
|
|
|
## Riesgos / gotchas
|
|
|
|
- **VPS UDP/51820**: firewall del proveedor del VPS puede bloquearlo. Verificar con `nc -u -v vps 51820`.
|
|
- **NAT carrier-grade (4G/5G)**: device tras NAT estricto → `PersistentKeepalive = 25` obligatorio.
|
|
- **Sleep laptop / android doze**: handshake muere. Auto-reconnect via `systemd-networkd-wait-online` + script.
|
|
- **Privilegio sudo**: `wg-quick` requiere root. Devices necesitan sudo-NOPASSWD para `wg-quick@wg0`.
|
|
- **Clock skew**: tokens enrollment + nonces dependen de NTP. Forzar `chrony` en VPS y devices.
|
|
- **Container privileged**: modo "deep" docker requiere `--cap-add NET_ADMIN`. Riesgo si container compromised.
|
|
Mitigacion: solo modo "deep" para containers de tu propio control (ej. `agents_and_robots` self-hosted), no third-party.
|
|
- **Operator key compromise**: si tu ed25519 leaks → cualquiera firma manifests. Plan B: rotacion + revoke-all + re-enroll.
|
|
- **Matrix homeserver compromise**: chat E2EE protege contenido, pero metadata (quien habla con quien) leak.
|
|
Aceptable porque homeserver es tuyo en `organic-machine.com`.
|
|
|
|
## Notas
|
|
|
|
(rellenar tras ejecutar fases A/C/B/D)
|
|
|
|
### Para hablar con un device desde Element (onboarding)
|
|
|
|
1. Abre Element en movil o web (`element.organic-machine.com`).
|
|
2. Entra al room `#dev-<nombre>` (un room por device).
|
|
3. Escribe `!help` → bot del room (`agents_and_robots`) responde con capability matrix del device.
|
|
4. Escribe comando, ej. `!exec ls /home/lucas` o `!fs.read /var/log/syslog`.
|
|
5. Si capability requiere approval, te llega notification a `#operator-approvals` → reaccionas 👍 → ejecuta.
|
|
6. Output aparece en el mismo room del device.
|
|
|
|
### Para hablar con un container docker
|
|
|
|
1. Si el host del container ya esta en la mesh: room `#dev-<host>` con `!docker exec <container> <cmd>`.
|
|
2. Modo deep: room dedicado `#cont-<container>` (solo containers enrolled).
|