Skip to content

Feature: cifrado en reposo de los datos confidenciales

belen.varsi requested to merge pft/feature/cifrado-datos-confidenciales into dev

Qué

Los datos confidenciales se guardan cifrados con AES-256-GCM:

  • Estudiante: información de salud, sistema de salud y motivo de derivación.
  • Comentarios confidenciales de estudiante y de instancia (contenido).

Se cifran al guardar y se descifran al leer con un AttributeConverter de JPA, así que la API y el front no cambian.

Por qué

Pendiente del checklist: cifrado en reposo de datos confidenciales. No se usa el mismo mecanismo que la contraseña porque eso es un hash de una vía (BCrypt) y estos datos se tienen que poder volver a leer.

Cambios

  • CifradorDatosConfidenciales: AES-256-GCM con IV aleatorio. Guarda enc:v1: + Base64(IV + texto cifrado + tag). Detecta valores alterados o una clave equivocada.
  • DatoConfidencialConverter: se aplica con @Convert en los campos de Estudiante y en contenido de ComentarioConfidencialEstudiante / ComentarioConfidencialInstancia (no en ComentarioBase, que comparten los comentarios normales).
  • MigracionCifradoRunner: al iniciar, cifra lo que haya quedado en claro. Es idempotente, usa una transacción por tabla y un savepoint por fila. Apaga el trigger de updated_at solo dentro de la transacción para no marcar los comentarios como "editado". Se puede desactivar con CIFRADO_MIGRAR_AL_INICIAR=false.
  • Mientras tanto, los valores viejos en claro se siguen leyendo.
  • sis_salud pasa a TEXT sin el CHECK de formato (el valor cifrado es más largo). La validación de máx. 80 caracteres y del formato sigue en los DTOs.
  • Nueva variable DATA_ENCRYPTION_KEY (documentada en .env.example). Los tests usan una clave fija y no corren la migración.

Cambios en BD

Correr en cada base existente ANTES de desplegar:

ALTER TABLE proyecto.estudiantes DROP CONSTRAINT IF EXISTS chk_sis_salud_valido;
ALTER TABLE proyecto.estudiantes ALTER COLUMN sis_salud TYPE TEXT;

proyecto_schema.sql ya trae el cambio para las bases nuevas.

Variables de entorno

  • DATA_ENCRYPTION_KEY (obligatoria): openssl rand -base64 32. Cada ambiente tiene la suya. No se puede perder ni cambiar una vez que hay datos cifrados.
  • Para correr los tests de integración, exportar la misma clave que usa la app contra esa base.

Cómo probar

  1. Correr el script SQL y agregar DATA_ENCRYPTION_KEY al .env.
  2. Levantar la app: en el log aparece [AUDIT] Cifrado en reposo: N filas de ... cifradas.
  3. En la BD, inf_esta_salud, sis_salud, motivo, obs_confidencial y comentario (confidenciales) empiezan con enc:v1:.
  4. En la app, la ficha de salud y los comentarios confidenciales se ven normal y los que no se habían editado no aparecen como "editado".
  5. Crear o editar un comentario confidencial: queda cifrado en la BD.
  6. Reiniciar: la migración no vuelve a tocar nada.

Notas

  • La auditoría registra un UPDATE por fila migrada, sin el contenido (el trigger ya omitía estas columnas).
  • Una fila que no cumple algún CHECK queda en claro y se registra con su id. Hoy pasa con estudiantes que superan los 100 años por chk_edad_rango (bug aparte).
  • Los informes médicos (archivos en disco) quedan fuera de este MR.

Merge request reports

Loading