Saltar a contenido

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:

  1. La topología de red entre el Cloud y la planta, que es el control del que depende SYS-ADR-005.
  2. Quién tiene acceso a las consolas de las plataformas donde viven los secretos, y con qué nivel.
  3. 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.