Files
unibus/dev/issues/0007-jetstream-encryption-at-rest.md
T

4.9 KiB

issue, title, status, created, domain, scope
issue title status created domain scope
0007 Cifrado at-rest del control plane (JetStream KV / SQLite en disco) spec 2026-06-07 security unibus (pkg/embeddednats, cmd/membershipd, deploy/cluster) + procedimiento de migración del store existente

Objetivo

Cifrar en reposo el almacenamiento del plano de control para que un nodo comprometido (root en el VPS) o un disco robado no exponga los metadatos de control en claro.

Estado actual (auditado el 07/06/2026, report 0012 y siguientes):

  • Contenido de los mensajes: cifrado E2E por room (megolm/olm). El servidor nunca ve el plaintext; no vive en el plano de control. No es el objeto de este issue.
  • Claves de room (UNIBUS_room_keys): guardadas selladas (sealed box X25519, cifradas para cada miembro). El servidor las almacena y reparte pero no puede abrirlas. Ya protegidas.
  • Metadatos de control (UNIBUS_rooms, UNIBUS_members, UNIBUS_rooms_by_member, UNIBUS_users): se serializan con json.Marshal y se escriben en claro en el store. En cluster ese store es el directorio local_files/jetstream/ de cada nodo; en single-node es el archivo SQLite local_files/unibus.db. Hoy no hay cifrado at-rest: con root en un nodo se pueden leer subjects de salas, la pertenencia (quién está en qué sala con qué rol), los handles y roles de los usuarios, y las claves públicas (signPub/kexPub). No se exponen mensajes (E2E) ni se pueden descifrar salas (claves selladas), pero sí toda la topología.

Tras este issue, los buckets/archivos del control plane quedan cifrados en disco con una clave por nodo gestionada fuera de git. El modelo de amenaza pasa de "root del nodo ve la topología" a "root del nodo necesita además la clave at-rest (que puede vivir en un secreto separado / TPM / variable de entorno inyectada) para leer cualquier cosa".

Contexto técnico

  • NATS Server / JetStream soporta encryption at-rest nativo: se configura una cifra (aes o chacha20) y una clave; JetStream cifra los ficheros de los streams/KV en disco. El bus usa un NATS embebido (pkg/embeddednats), así que la activación es por opciones del servidor embebido, no por un nats-server.conf externo.
  • Para el backend SQLite (single-node) el equivalente sería SQLCipher o cifrado a nivel de archivo/FS; queda como sub-tarea de menor prioridad porque el despliegue real es cluster (KV).

Tareas

  1. Confirmar la API de encryption-at-rest del NATS embebido en la versión usada (opción de servidor para cipher + clave; cómo se pasa la clave de forma que no quede en argv ni en git).
  2. Activar el cifrado en pkg/embeddednats detrás de una opción de configuración. La clave se inyecta por archivo (--jetstream-encryption-key-file, 0600, junto a las claves TLS del nodo) o variable de entorno desde el unit systemd; nunca en argv ni commiteada.
  3. cmd/membershipd: flag/env para la clave + reflejar el estado en la posture publicada en /healthz (p.ej. "at_rest":true) para que el monitor lo verifique.
  4. deploy/cluster: provisionar la clave at-rest por nodo (generación + pass/secrets gitignored) y cablearla en cluster.env + el unit. Documentar en el runbook.
  5. Migración del store existente (gotcha crítico): JetStream no re-cifra retroactivamente los datos ya escritos en claro. Diseñar y documentar el procedimiento seguro para el cluster en producción (probable: backup → exportar snapshot del control plane → parar nodo → recrear el store con la clave activa → re-importar; o rotación nodo a nodo aprovechando la replicación R3). Respetar la regla de migraciones (aditivo, sin pérdida de datos).
  6. Tests: arrancar un nodo con clave at-rest, escribir un user/room, y verificar que el fichero en disco no contiene en claro un subject/handle conocido (grep negativo), y que el nodo sigue leyéndolos con la clave. Verificar que sin la clave el store no se abre.

Definition of Done

  • Cifrado at-rest activo en los 3 nodos del cluster; /healthz lo refleja en la posture.
  • Evidencia ejecutable: un valor conocido (subject de sala / handle de usuario) no aparece en claro al hacer grep sobre local_files/jetstream/; el nodo lo sigue sirviendo con la clave.
  • Procedimiento de migración probado sobre datos reales sin pérdida (snapshot/restore verificado).
  • La clave at-rest nunca está en git ni en argv; vive en archivo 0600 / secreto inyectado.
  • No baja ninguna otra capa de seguridad (enforce + ACL + TLS + E2E + sealed keys intactas).

Notas

Aditivo y ortogonal al resto de la seguridad: TLS protege en tránsito, E2E el contenido, las claves de room van selladas; este issue cierra el último hueco (metadatos de control en claro en disco) para el modelo de amenaza "VPS comprometido / disco robado". Prioridad media: el despliegue ya es seguro frente a ataques de red (enforce+TLS+ACL); esto endurece frente a compromiso físico/root del host. Relacionado con el endurecimiento de los issues 0004/0005/0006.