Rebelión en la granja — investigación y laboratorio de dispositivos

Investigación sobre granjas de dispositivos y automatización completa para
levantar un laboratorio Android sobre un chasis de blades Debian.

Conclusión que ordena el repositorio: el cuello de botella nunca fue el
cómputo sino la identidad. Desde que la atestación de dispositivo pasó de
estadística a criptográfica, un servidor no puede hacerse pasar por un
teléfono — le falta una clave grabada en fábrica que ningún software produce.

Contenido:

- 01_investigacion/ — capa de identidad (verificación telefónica, eSIM,
  reputación de IP, atestación por hardware, correlación entre cuentas),
  mercado y players, stack técnico, marco legal en la UE
- 02_device_lab/ansible/ — Ansible para 16 blades: preflight de solo lectura
  que identifica el hardware por DMI, despliegue de redroid en Docker y
  control de flota por adb. Solo ansible.builtin, sin colecciones externas
- docs/ — requisitos, costes y repaso de seguridad con 11 hallazgos
- 03_charla/ — guion para HackMadrid / Hackmeeting València

Estado: la automatización está escrita pero SIN ejecutar contra hardware.
YAML validado, plantillas renderizadas en ambos modos de red y fleet.sh
comprobado con bash -n. El modelo de blade sigue sin confirmar.
This commit is contained in:
s1to 2026-08-08 19:42:14 +02:00
commit 74bfbe9437
42 changed files with 2989 additions and 0 deletions

View file

@ -0,0 +1,302 @@
# Ansible — provisionar la granja Android sobre los blades Debian
Automatiza el montaje completo: de blades Debian pelados a una flota de Android
virtuales controlables por `adb` desde un único sitio.
**No hay móviles físicos en esta granja.** Cada "dispositivo" es un contenedor
[redroid](https://github.com/remote-android/redroid-doc) — Android real corriendo sobre el kernel
del blade. Es la única forma sensata de hacer esto con el hardware que hay.
## La arquitectura
```
Dell PowerEdge M1000e — 16 blades
├── blade-01 ................ COORDINADOR
│ └── adb + fleet.sh único punto de control de toda la flota
└── blade-02 .. blade-16 .... NODOS DE CARGA (15)
└── Debian 12 pelado
└── Docker
├── redroid-blade-NN-01 Android 13 ~2 GB
├── redroid-blade-NN-02 Android 13 ~2 GB
└── redroid-blade-NN-03 Android 13 ~2 GB
Con 8 GB por blade → 3 instancias → ~45 Android en total
```
Debian va **directo sobre el blade**. Sin Proxmox, sin VMs, sin LXC anidado: con 8 GB por blade,
cada capa de hipervisor se come una instancia Android entera.
---
## Requisitos
**Nodo de control** (tu sobremesa):
```bash
sudo apt install ansible
```
**Blades**:
- Debian 12 bookworm instalado
- Acceso SSH desde el nodo de control, con sudo o como root
- Salida a internet, o un mirror local de `download.docker.com` y `deb.debian.org`
- Kernel con `binder_linux.ko` (el estándar de Debian lo trae)
**Ejecuta siempre desde este directorio** (`ansible/`) — `ansible.cfg` y las rutas de roles son
relativas a él.
---
## Puesta en marcha
### 1. Inventario
```bash
cp inventory/hosts.yml.example inventory/hosts.yml
$EDITOR inventory/hosts.yml # pon las IPs reales de los blades
```
### 2. Preflight — esto va primero, siempre
```bash
ansible-playbook playbooks/preflight.yml
```
**Es de solo lectura, no toca nada.** Contesta lo que ahora mismo no sabemos:
- **Qué blade hay dentro del chasis** (lo lee de DMI — no hace falta ir a mirar el frontal)
- Si esas CPUs tienen las instrucciones que Android x86_64 necesita
- Si el kernel trae binder
- Cuántas instancias caben en cada blade según su RAM real
Deja un informe en `reports/preflight-<fecha>.md` con la tabla de toda la flota y el total de
capacidad. Si algún blade sale **NO APTO**, lo dice ahí y no cuando estés a medio despliegue.
### 3. Montar la granja
```bash
ansible-playbook site.yml
```
Idempotente: relanzable sin romper nada.
### 4. Comprobar
```bash
ansible-playbook playbooks/status.yml
```
### 5. Instalar un APK en toda la flota
```bash
ansible-playbook playbooks/apk.yml -e apk_local_path=~/COFRE/CODERS/OASIS_APK/oasis.apk
```
---
## Control diario desde el coordinador
Entra por SSH al coordinador y usa `fleet`:
```bash
fleet connect # conecta adb a toda la flota
fleet list # qué hay conectado
fleet install app.apk # instala en todos
fleet shell 'getprop ro.build.version.release'
fleet ip # IP de cada Android — útil para el caso de dos APKs
fleet reboot
```
---
## Los dos modos de red
Se controla con `redroid_network_mode` en `group_vars/all.yml`.
### `portmap` (por defecto)
Cada Android detrás de NAT de Docker; adb en `<ip_blade>:<puerto>`, empezando en 5555.
Funciona en cualquier sitio sin saber nada de la LAN. Es el modo con el que empezar.
### `macvlan` — el que quieres para `oasis_mobile`
Cada Android coge una **IP real de la LAN** a través del switch FlexIO del chasis. Los dos APKs
que se tienen que ver entre sí se ven como **pares de red de verdad**, no a través de traducciones.
Es lo más cerca de "dos móviles en la misma wifi" sin tener dos móviles.
```yaml
redroid_network_mode: macvlan
redroid_macvlan_parent: eno1
redroid_macvlan_subnet: 10.0.0.0/24
redroid_macvlan_gateway: 10.0.0.1
redroid_macvlan_ip_prefix: "10.0.0."
```
Y en el inventario, un `redroid_macvlan_ip_offset` por blade (el ejemplo ya trae 20, 30, 40…).
> **El gotcha de macvlan.** Un anfitrión **no puede hablar con sus propios contenedores macvlan**.
> Es una limitación del driver, no un fallo de configuración. Por eso el coordinador es un blade
> **distinto**: desde fuera sí se llega. Si algún día pones adb en un nodo de carga y no ve sus
> propios Android, es esto.
>
> Reserva también un rango de IPs para los Android en el DHCP de la LAN, o acabarás con colisiones.
---
## El binder, que es donde esto se atasca
Debian 12 compila binder como módulo pero **sin binderfs**, así que redroid no puede crear sus
devices solo. El rol `binder` lo resuelve: carga `binder_linux` con los tres nombres que Android
espera (`binder`, `hwbinder`, `vndbinder`) y lo deja persistente para los siguientes arranques.
Los contenedores de un blade **comparten ese binder del kernel anfitrión**. Si no está, no arranca
ni una instancia.
**Si el playbook falla con "faltan devices binder"**: el módulo estaba ya cargado y en uso, así que
no se pudo recargar con los parámetros nuevos. La configuración persistente ya quedó escrita —
reinicia ese blade y relanza. Es un fallo de una sola vez.
---
## Ajustes que vas a querer tocar
Todo en `group_vars/all.yml`.
| Variable | Por defecto | Para qué |
|---|---|---|
| `redroid_image` | `redroid/redroid:13.0.0-latest` | Versión de Android. Hay 11, 12, 13 y 14 |
| `redroid_instances` | `0` (auto) | Instancias por blade. 0 = calculado desde la RAM real |
| `redroid_mem_mb` | `2048` | RAM por Android |
| `redroid_host_reserve_mb` | `1500` | RAM que se le deja a Debian + Docker |
| `redroid_gpu_mode` | `guest` | Sin GPU en los blades → render por software |
| `redroid_network_mode` | `portmap` | `portmap` o `macvlan` |
Cambiar la versión de Android en toda la flota, sin editar nada:
```bash
ansible-playbook playbooks/devices.yml -e redroid_image=redroid/redroid:14.0.0-latest
```
### La palanca real: la RAM
El cuello de botella son **los 8 GB por blade, no la CPU**. Si los blades resultan ser M620
(DDR3 ECC), ampliar a 32 GB cuesta poco en segunda mano:
```bash
ansible-playbook playbooks/devices.yml -e redroid_instances=14
```
De ~45 Android a **~210**, por menos de lo que cuesta un mes de luz del chasis.
---
## Playbooks
| Playbook | Qué hace | Toca algo |
|---|---|---|
| `playbooks/preflight.yml` | Reconocimiento: modelo, CPU, RAM, binder, capacidad | **No** |
| `site.yml` | La granja entera de cero | Sí |
| `playbooks/provision.yml` | Solo el hierro: base + binder + Docker | Sí |
| `playbooks/devices.yml` | Solo los contenedores Android | Sí |
| `playbooks/coordinator.yml` | Regenera el listado de flota y `fleet` | Sí |
| `playbooks/apk.yml` | Instala un APK en toda la flota | Sí |
| `playbooks/status.yml` | Estado real de la granja | **No** |
| `playbooks/destroy.yml` | Para y borra los contenedores | **Sí, destructivo** |
`destroy.yml` exige `-e confirmar=si`, y conserva los `/data` salvo que añadas `-e borrar_datos=si`.
---
## Probarlo antes de tocar el chasis
`inventory/hosts.yml.example` trae un bloque comentado de **piloto de un nodo**: el sobremesa hace
de coordinador y de nodo de carga a la vez. Descoméntalo, comenta el bloque `granja`, y tienes el
flujo entero contra una sola máquina. Con 62 GB de RAM salen unas 8 instancias (tope
`redroid_max_instances`).
Es la forma sensata de empezar: valida todo el pipeline sin encender 2 kW.
---
## Estructura
```
ansible/
├── ansible.cfg
├── site.yml playbook maestro
├── inventory/hosts.yml.example 16 blades + piloto de un nodo
├── group_vars/all.yml los mandos del operador
├── playbooks/
│ ├── preflight.yml reconocimiento (solo lectura)
│ ├── provision.yml base + binder + docker
│ ├── devices.yml contenedores Android
│ ├── coordinator.yml listado de flota + fleet
│ ├── apk.yml instalar APK en la flota
│ ├── status.yml estado (solo lectura)
│ ├── destroy.yml desmontar (destructivo)
│ └── templates/preflight-report.md.j2
├── roles/
│ ├── preflight/ DMI, flags de CPU, binder, capacidad
│ ├── base/ paquetes, hora
│ ├── binder/ los tres devices, persistentes
│ ├── docker/ Docker CE + compose v2
│ ├── redroid/ genera el compose y levanta Android
│ └── coordinator/ adb + devices.txt + fleet.sh
└── reports/ informes de preflight (se genera solo)
```
---
## Estado de verificación
Escrito el 2026-08-08. **No se ha ejecutado contra hardware real** — el chasis todavía no está
accesible y el sobremesa no tiene Docker instalado.
Lo que sí está comprobado:
- Los 18 ficheros YAML parsean correctamente
- La plantilla de `docker-compose` renderiza YAML válido **en los dos modos de red**, con los
puertos y las IPs bien asignados
- `fleet.sh` pasa `bash -n` una vez renderizado, sin Jinja pendiente
Lo que **no** está comprobado y solo dirá la realidad:
- Que las imágenes x86_64 de Android arranquen en la generación de CPU de estos blades
(el riesgo real si resultan ser M610 — preflight lo detecta antes de gastar tiempo)
- El comportamiento de macvlan sobre los switches FlexIO concretos del chasis
- Que 3 instancias por blade sean cómodas con 8 GB en carga real, no solo en el papel
---
## Siguiente capa: GADS
[GADS](https://github.com/shamanec/GADS) da UI web y Appium integrado encima de esto. No está
automatizado aquí a propósito: montar un despliegue de Node que no puedo probar sería añadir
complejidad sin verificar. Merece la pena cuando la flota pase de una decena de dispositivos o
cuando haga falta lanzar tests automatizados en vez de trastear con `fleet shell`.
---
## Seguridad — lee esto antes de encender
El repaso completo está en **[../../docs/seguridad.md](../../docs/seguridad.md)** (11 hallazgos).
Lo que no se puede saltar:
**adb no tiene autenticación**, las imágenes de redroid permiten `adb root`, y los contenedores son
`privileged` porque redroid lo exige. Encadenado: **quien alcance el puerto 5555 de un blade acaba
siendo root en ese blade**, sin exploit ninguno. Con 45 dispositivos son 45 puertas.
Mitigación parcial ya en el código — ata adb solo a la interfaz interna del chasis:
```yaml
blade-02: { ansible_host: 10.0.0.12, redroid_adb_bind_ip: 10.0.0.12 }
```
Lo que lo cierra de verdad es una **VLAN aislada** para la granja. En modo `macvlan`,
`redroid_adb_bind_ip` no aplica (adb escucha en la IP de LAN del propio Android) y la VLAN es la
única defensa.
Y antes de nada: **cambiar las credenciales de fábrica del CMC y de los 16 iDRAC** (`root`/`calvin`).