Seguridad¶
Responde: ¿quién puede hacer qué, qué mecanismo lo sostiene, y qué riesgos se aceptaron a sabiendas?
El modelo en una vista¶
flowchart LR
U["👤 Usuario"]
B["🌐 Navegador"]
subgraph front["Frontend + BFF"]
BFF["<b>BFF</b><br/>sesión del lado del servidor"]
end
IDP["<b>Supabase Auth</b><br/>emite y firma el token"]
subgraph api["Cloud API — dos superficies"]
ABM["<b>ABM</b><br/>token + tenant + RBAC"]
ING["<b>Ingesta</b><br/>clave de dispositivo"]
end
subgraph datos["Datos"]
PG[("Postgres<br/><i>aislamiento por código</i>")]
MG[("MongoDB<br/><i>aislamiento por índice</i>")]
end
ED["<b>Edge Pi Service</b><br/>planta"]
PLC["⚙️ PLC"]
U --> B
B -->|"credenciales"| IDP
IDP -->|"token"| B
B -->|"cookie de sesión"| BFF
BFF -->|"inyecta el token<br/>del lado del servidor"| ABM
ABM -->|"verifica la firma"| IDP
ABM --> PG
ABM --> MG
ED -->|"clave por dispositivo"| ING
ING --> MG
ABM -.->|"<b>sin autenticación</b><br/>solo lectura"| ED
ED -.->|"<b>sin autenticación</b><br/>solo lectura, Modbus"| PLC
classDef ok fill:#1168bd,stroke:#0b4884,color:#fff
classDef store fill:#438dd5,stroke:#2e6295,color:#fff
classDef risk fill:#c0392b,stroke:#7b241c,color:#fff
classDef ext fill:#999,stroke:#6b6b6b,color:#fff
class BFF,ABM,ING ok
class PG,MG store
class ED risk
class U,B,IDP,PLC ext
En rojo, la pieza a la que se llega sin credenciales. Las dos flechas punteadas son los dos
tramos sin autenticación del sistema, y las dos son de solo lectura. Eso no es casual: es
la consecuencia de SYS-ADR-007, que decide que el sistema
observa y no actúa.
Las dos superficies¶
La decisión estructural: dos superficies HTTP con modelos de autenticación distintos, no una con excepciones.
| ABM | Ingesta | |
|---|---|---|
| Quién la consume | el BFF del frontend | el Edge Pi Service |
| Qué la autentica | token firmado por el proveedor de identidad | clave de API por dispositivo |
| De dónde sale el tenant | de la petición, validado contra la membresía del lado del servidor | se deriva de la credencial: el cliente no lo nombra |
| Autorización | permisos por rol | ninguna: la clave habilita una sola operación |
| CORS | habilitado | deshabilitado explícitamente |
Identidad y autenticación de personas¶
La identidad la emite un proveedor externo. La API nunca ve una contraseña: recibe un token y verifica su firma contra el juego de claves públicas del proveedor, comprobando emisor, audiencia y vencimiento, y que el algoritmo de firma sea uno de los dos asimétricos admitidos.
Cuando el proveedor no responde, la API devuelve un error de servicio y nadie entra. Es una decisión de fallo cerrado, opuesta a la de Redis: ver calidad y resiliencia.
El navegador nunca llama a la API. Entre medio está el BFF, que guarda la sesión del lado del servidor e inyecta el token al reenviar. La cabecera de autorización que pone el BFF gana siempre sobre lo que traiga quien llama: el cliente no puede sustituirla.
Tres controles adicionales:
- Una cuenta revocada o deshabilitada se rechaza en cada petición, no solo al iniciar sesión.
- Una guardia obliga a cambiar la contraseña cuando la cuenta lo requiere, y deja pasar únicamente las operaciones necesarias para hacerlo.
- El primer inicio de sesión aprovisiona al usuario y activa sus invitaciones pendientes, de forma idempotente.
Autorización y aislamiento entre clientes¶
Cada cliente es un tenant. El aislamiento se sostiene en tres capas:
| Capa | Qué impone |
|---|---|
| Resolución de tenant | La petición dice sobre qué tenant opera, y el servidor verifica que quien llama tenga una membresía activa en él. Declarar un tenant no da acceso a él |
| Permisos por rol | Los permisos salen del catálogo de la base, no de un mapa en el código. La misma definición la consumen el enforcement del backend y lo que muestra el frontend, para que no diverjan |
| Almacenamiento | En Postgres, cada consulta filtra por tenant. En MongoDB, el aislamiento lo garantiza una clave compuesta por tenant |
Los roles personalizados que puede armar un administrador de la empresa cliente tienen tres reglas que impiden llegar a permisos del fabricante: un rol no puede incluir un permiso de sistema que no tenga quien lo crea o lo edita, nadie cambia su propio rol, y los permisos se validan contra el catálogo del tenant.
Lo que esas reglas no hacen es mirar hacia atrás: revisar los roles y las asignaciones ya guardados es una tarea de operación pendiente.
La excepción de plataforma¶
Hay un tenant que sí opera sobre los demás: el de la empresa que provee el sistema. Sus operadores pueden administrar tenants cliente aunque no sean miembros.
Es una excepción explícita y documentada en un ADR del Cloud, no un agujero: el camino existe solo para membresías del tenant plataforma, un usuario de un tenant cliente no tiene ninguna ruta hacia él, y hay un disparador en la base que impide asignar roles de plataforma fuera de ese tenant. Los permisos de plataforma puestos en un rol que no lo es los cubre la regla de arriba, no el disparador.
Cada acceso cross-tenant concedido deja rastro: un registro con el usuario, el rol efectivo, el tenant destino y el identificador de la petición, y un contador por rol efectivo. Que el contador exista no significa que alguien lo recolecte: ver monitoreo.
Credenciales de dispositivo¶
Cada Raspberry Pi tiene su propia clave de API, y su diseño es la parte mejor resuelta del modelo:
- Solo se persiste el hash del secreto. El texto en claro se muestra una vez y no se guarda nunca.
- El secreto tiene 32 bytes de entropía, lo que justifica un hash rápido en lugar de uno deliberadamente lento: no hay diccionario que atacar.
- La comparación es en tiempo constante, para que la latencia no filtre cuántos bytes acertó quien prueba.
- Todos los fallos devuelven el mismo error, sea la clave inexistente, revocada, vencida o de un dispositivo dado de baja. Distinguirlos le daría a un atacante un oráculo para enumerar identificadores válidos. El motivo real queda en el log del servidor.
- La verificación del secreto corre siempre y primero, incluso si la clave está revocada, para que el costo del camino de fallo no dependa del motivo.
- Soporta revocación y vencimiento, y respeta el estado del dispositivo.
- Que la base no responda no se traduce en "credencial inválida": se distingue del rechazo, para que el Edge reintente en vez de descartar.
La clave resuelve a un tenant y a un dispositivo concretos. Un dispositivo comprometido no puede escribir en otro tenant, porque no tiene forma de nombrarlo.
Riesgos aceptados¶
| Riesgo | Por qué se aceptó | ADR |
|---|---|---|
| El Cloud consulta al Edge sin credenciales | El control compensatorio es el aislamiento de red. La superficie es de solo lectura | SYS-ADR-005 |
| La lectura del PLC va sin autenticación | El protocolo industrial no la tiene. Es de solo lectura | SYS-ADR-007 |
| El límite de tasa falla abierto | Se prefiere disponibilidad sobre la limitación | sin decisión escrita |
Lo que acota a los dos primeros es el mismo hecho: no existe un camino de escritura hacia la máquina. No hay un permiso mal puesto que lo habilite, porque el código que lo haría no existe.
El control que sostiene el primero no está configurado en ningún repositorio.
SYS-ADR-005 acepta las consultas sin credenciales apoyándose en
que la Raspberry Pi solo es alcanzable desde el Cloud por una red privada, y la configuración
por defecto del empaquetado publica el servicio en todas las interfaces de red de la Pi.
Comprobar esa red es la acción de seguridad de mayor retorno pendiente, y no requiere escribir
código: requiere mirar la red de la planta.
Debilidades abiertas¶
Ninguna tiene decisión que la respalde. En orden de consecuencia.
1. El aislamiento entre clientes lo impone el código, no la base¶
Las tablas tienen control de acceso a nivel de fila habilitado y los roles anónimo y autenticado revocados. Pero el backend conecta con el rol dueño de la base, que lo bypassea.
Eso significa que la última línea de defensa del aislamiento multi-cliente es que cada consulta filtre correctamente por tenant. Es disciplina, no un mecanismo: una consulta nueva que olvide el filtro no encuentra ninguna barrera debajo.
Y ninguna prueba automatizada lo cubre: los tests que verificarían el aislamiento, y los que cubren la escalada por roles personalizados, son de integración y necesitan una base real, que el CI del Cloud no provee. Un retroceso de cualquiera de los dos pasa en verde. Ver qué verifica el CI y qué no.
2. La política de CORS devuelve un comodín fijo¶
El middleware responde con un comodín en el origen permitido, sin leer ninguna configuración: no hay forma de restringir los orígenes sin tocar el código.
El impacto real es menor de lo que sugiere: la respuesta no habilita el envío de credenciales entre orígenes, y la superficie se consume desde el BFF del lado del servidor, no desde el navegador. Un sitio de terceros no puede usar la sesión de un usuario para llamar a la API.
3. La telemetría del Edge queda fuera del secreto compartido¶
El endpoint de métricas del Edge se expone sin el secreto compartido incluso cuando éste se activa, a propósito, para que lo pueda consultar un recolector. Endurecer el resto de la superficie inbound no lo cubre, y como ningún repositorio configura un recolector, hoy es exposición sin contrapartida.
4. La URL que el Cloud consulta en planta la elige el cliente, y nadie valida su destino¶
El Cloud guarda una URL base por dispositivo Edge y hace peticiones GET contra ella para los
chequeos de estado. Esa URL la carga un usuario de la empresa cliente, y lo que se valida
es la forma, nunca el destino.
Es la clase de problema conocida como falsificación de peticiones del lado del servidor: un usuario con permiso de administración de dispositivos puede hacer que el servidor emita peticiones a una dirección que él elige, y leer el resultado con el permiso de chequeo. Lo que lo acota hoy no es un control del código sino la topología de red del servicio en la nube, que es justamente el supuesto que nadie comprobó.
Las dos mitigaciones son baratas: restringir el destino a una lista permitida o excluir los rangos privados, y validar igual al crear y al editar. El detalle está en Cloud → Edge.
5. Hoy no se pueden autenticar los chequeos hacia la planta¶
El Edge acepta un secreto compartido por cabecera. El Cloud no tiene forma de enviarlo: no hay configuración donde guardarlo ni código que lo ponga en la petición.
Como el cliente del Cloud convierte cualquier respuesta que no sea 2xx en estado de error,
activar el secreto en la Pi hace que una Pi sana se vea igual que una Pi caída en las tres
pantallas de estado del dispositivo. No es una vulnerabilidad: es la razón por la que sigue
abierto el riesgo que SYS-ADR-005 aceptó. El arreglo es del lado
del Cloud y es chico. Ver
Cloud → Edge.
6. Credenciales de prueba en los secretos del repositorio¶
El pipeline del frontend usa credenciales de usuarios reales para sus pruebas de extremo a extremo, guardadas como secretos del repositorio. Son cuentas de prueba, pero apuntan a un ambiente real y no hay staging: ver despliegues.
Gestión de secretos¶
Ningún repositorio de este sistema contiene credenciales reales: solo archivos de ejemplo con marcadores. Los valores viven repartidos en las consolas de las plataformas y en un archivo local en cada Raspberry Pi. El inventario está en despliegue.
Dos propiedades que vale destacar:
- El despliegue de la API usa federación de identidad: no hay claves de servicio de larga vida guardadas en el repositorio.
- La clave de servicio del proveedor de identidad vive en un gestor de secretos, no como variable de entorno en claro.
Fuera de esos repositorios hay un pendiente urgente: un archivo de entorno versionado en un
repositorio público, de un tercero, con una credencial de servicio dentro. El orden para
resolverlo es contraintuitivo —rotar primero, destrackear después—, porque un .gitignore
agregado después del commit no destrackea nada y borrar el archivo no revoca la credencial: el
historial de git la conserva. El repositorio es de un tercero, así que la acción es suya y hay
que avisarle.
Tres cosas que no están escritas en ningún lado¶
Y las tres importan:
- La topología de red entre el Cloud y la planta, que es el control del que depende
SYS-ADR-005. - Quién tiene acceso a las consolas de las plataformas donde viven los secretos, y con qué nivel.
- Si alguna credencial fue rotada alguna vez. No hay registro de rotación.
Ninguna se responde leyendo código: las tres se miran en las consolas, y las tres deberían quedar escritas antes del traspaso. Ver configuración y secretos.