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

218
docs/seguridad.md Normal file
View file

@ -0,0 +1,218 @@
# Repaso de seguridad
Revisión del montaje completo — arquitectura, hardware y el código Ansible de
[`02_device_lab/ansible/`](../02_device_lab/ansible/). Fecha: 2026-08-08.
Incluye los fallos que introduce el propio código de este proyecto, no solo los del entorno.
## Resumen
| | Hallazgo | Severidad | Estado |
|---|---|---|---|
| **S1** | adb sin autenticación + contenedor privilegiado → root del anfitrión | **Crítico** | Mitigado a medias |
| **S2** | Credenciales por defecto del CMC / iDRAC (`root`/`calvin`) | **Alto** | Sin verificar |
| **S3** | macvlan mete 45 Android en la LAN de producción | **Alto** | Por diseño, hay que aislar |
| **S4** | `host_key_checking = False` en `ansible.cfg` | Medio | Aceptado, documentado |
| **S5** | SSH como root en el inventario de ejemplo | Medio | Documentado |
| **S6** | Imagen de Android por tag mutable | Medio | Abierto |
| **S7** | Sin gestión de secretos | Medio | Abierto |
| **S8** | Estado persistente en `/data` sin cifrar | Medio | Abierto |
| **S9** | `fleet install` no verifica el APK | Bajo | Abierto |
| **S10** | Microcódigo antiguo en CPUs de 2011-2016 | Bajo | Aceptado |
| **S11** | Infraestructura de doble uso | Contextual | Ver [legal_ue.md](../01_investigacion/legal_ue.md) |
---
## S1 · adb sin autenticación sobre contenedor privilegiado — **crítico**
El fallo más serio, y es una cadena de tres eslabones que por separado parecen asumibles:
1. **adb no tiene autenticación** cuando escucha en TCP. Quien llega al puerto, entra.
2. **Las imágenes de redroid son `userdebug`**, así que `adb root` funciona: shell de root dentro
del Android.
3. **El contenedor es `privileged: true`** — redroid lo exige para acceder a binder. Un privilegiado
ve los devices del anfitrión y puede montar su sistema de ficheros.
Encadenado: **cualquiera que alcance el puerto 5555 de un blade acaba siendo root en ese blade.**
No hace falta ningún exploit, solo `adb connect`.
Y con 45 dispositivos son 45 puertas.
**Qué se ha corregido hoy.** El compose ataba adb a `0.0.0.0` — todas las interfaces. Ahora hay
`redroid_adb_bind_ip`, así que se puede atar solo a la interfaz interna del chasis:
```yaml
blade-02: { ansible_host: 10.0.0.12, redroid_adb_bind_ip: 10.0.0.12 }
```
**Qué falta, y es lo que de verdad cierra el agujero:**
- **Red de laboratorio aislada.** VLAN propia para la granja, sin ruta hacia la LAN de casa ni
hacia internet salvo lo imprescindible. Es la medida que importa; el resto son parches.
- **Cortafuegos en cada blade** (nftables) que solo acepte 5555+ desde la IP del coordinador.
- **Nunca exponer estos puertos a internet.** Ni con reenvío de puertos, ni con VPN mal segmentada.
En modo `macvlan` el problema es peor: adb queda escuchando en la IP de LAN del propio Android,
donde `redroid_adb_bind_ip` no aplica. Ahí la única defensa es la VLAN.
---
## S2 · Credenciales por defecto del CMC y de los iDRAC — **alto**
Los Dell PowerEdge salen de fábrica con **`root` / `calvin`** en iDRAC, y el CMC del M1000e con las
mismas. Es la forma clásica en que se toma uno de estos chasis: no hay que romper nada, se entra.
El M1000e es de 2016 o anterior, así que el firmware de gestión arrastra años de CVEs conocidas y
TLS antiguo.
Antes de enchufarlo a nada:
- Cambiar las credenciales del CMC **y de cada uno de los 16 iDRAC**, no solo del CMC.
- Actualizar firmware de CMC e iDRAC hasta donde Dell lo permita para esa generación.
- **VLAN de gestión separada**, sin salida a internet. La gestión de un chasis nunca va en la
misma red que el resto.
- Desactivar IPMI sobre LAN si no se usa (`cipher 0` es un clásico de acceso sin autenticar).
Esto es independiente de la granja: aplica al hierro desde el momento en que se encienda.
---
## S3 · macvlan expone 45 Android en la LAN — **alto**
El modo `macvlan` es el que hace falta para el caso de los dos APKs de `oasis_mobile`, pero pone
cada Android **con IP real en la red**, cada uno con S1 encima.
- VLAN dedicada para la granja, con el coordinador como único puente.
- Reservar el rango de IPs en el DHCP, o habrá colisiones con máquinas reales.
- No usar la LAN de casa. Nunca.
---
## S4 · `host_key_checking = False` — medio
Lo puse yo en `ansible.cfg` por comodidad de arranque. El coste: si alguien se hace pasar por un
blade, Ansible se conecta igual y le entrega **comandos de root y, si se usa, la contraseña de
sudo**.
Es asumible en el primer aprovisionamiento de una red aislada. Después:
```bash
ssh-keyscan -H 10.0.0.11 10.0.0.12 ... >> ~/.ssh/known_hosts
```
y poner `host_key_checking = True`.
---
## S5 · SSH como root — medio
`inventory/hosts.yml.example` trae `ansible_user: root`. Funciona, pero:
- Usuario dedicado con sudo en vez de root directo
- **Solo clave, sin contraseña**: `PasswordAuthentication no`
- `PermitRootLogin no` en los blades
- Clave separada para la granja, no la personal
---
## S6 · Imagen de Android por tag mutable — medio
`redroid/redroid:13.0.0-latest` es un tag que **se mueve**. Consecuencias reales:
- Dos blades provisionados con una semana de diferencia acaban con imágenes distintas, y depurar
eso es un infierno.
- Cadena de suministro: si la cuenta del registro se compromete, el siguiente `docker compose pull`
se trae otra cosa. Y entra en 45 contenedores privilegiados.
Fijar por digest:
```yaml
redroid_image: "redroid/redroid@sha256:<digest>"
```
Con `docker buildx imagetools inspect redroid/redroid:13.0.0-latest` se saca el digest actual.
Se actualiza a mano cuando se quiera subir de versión, que es justo lo que se quiere.
---
## S7 · Sin gestión de secretos — medio
El inventario de ejemplo menciona `ansible_become_password` sin montar `ansible-vault`. Si se pone
una contraseña ahí en claro, queda en el disco y en cualquier copia del proyecto.
```bash
ansible-vault create group_vars/granja/vault.yml
ansible-playbook site.yml --ask-vault-pass
```
Mejor aún: sudo sin contraseña para el usuario de despliegue, restringido a los comandos que
necesita, y ninguna contraseña en ningún fichero.
---
## S8 · Estado persistente sin cifrar — medio
Cada Android guarda su `/data` en `{{ redroid_base_dir }}/data/device-NN` del blade, sin cifrar.
Si en las pruebas se entra con cuentas reales — cuentas SSB, credenciales de Gitea, lo que sea —
esas credenciales quedan en el disco del blade en claro.
`destroy.yml` **conserva los datos por defecto**, a propósito, así que sobreviven a un
redespliegue y a un cambio de manos del hardware.
- Cuentas de prueba desechables, nunca las reales.
- Cifrado de disco en los blades si van a manejar algo sensible.
- Al dar de baja un blade: `destroy.yml -e confirmar=si -e borrar_datos=si` **y** borrado del disco.
---
## S9 · `fleet install` no verifica nada — bajo
`fleet install app.apk` mete el APK en los 45 dispositivos sin comprobar firma ni hash. Como los
APK son propios (`oasis_mobile`), el riesgo es bajo, pero el patrón es malo: un fichero equivocado
se propaga a toda la flota de una vez.
Comprobar el hash antes, y a ser posible la firma con `apksigner verify --print-certs`.
---
## S10 · Microcódigo antiguo — bajo
CPUs de 20112016 con microcódigo sin actualizar: las mitigaciones de Spectre/Meltdown dependen de
microcódigo + kernel. En un laboratorio aislado que solo corre código propio, el riesgo es bajo.
Subiría si algún día se corre software de terceros no confiable en los Android.
`apt install intel-microcode` en los blades, y aun así esa generación no recibe todo.
---
## S11 · Infraestructura de doble uso — contextual
Técnicamente, esto es indistinguible de una granja de cuentas: 45 Android automatizados, con
control central y despliegue masivo de APKs. Lo que separa un laboratorio de QA de una granja de
fraude no es la infraestructura, son **tres cosas que no están aquí**: SIMs, proxies residenciales
y cuentas de plataforma.
Para que siga siendo lo que es:
- No conectar la granja a proveedores de SIM ni de números por API.
- No meter proxies residenciales o móviles.
- Documentar el propósito con evidencia — este repositorio ya lo hace.
El contexto legal está en [legal_ue.md](../01_investigacion/legal_ue.md). Y recuerda el hallazgo de
la investigación: estos Android **son detectables como emulador**, así que para las plataformas
sociales no servirían aunque se quisiera.
---
## Mínimos antes de encender
Por orden:
1. Cambiar credenciales del **CMC y de los 16 iDRAC** (S2)
2. **VLAN aislada** para granja y gestión, separadas entre sí (S1, S2, S3)
3. `redroid_adb_bind_ip` a la IP interna de cada blade (S1)
4. Claves SSH, sin root, sin contraseñas (S5)
5. Poblar `known_hosts` y `host_key_checking = True` (S4)
6. Fijar la imagen por digest (S6)
Los tres primeros no son opcionales. Sin ellos, la granja es 45 puertas abiertas a la red.