5.4 KiB
ETAPA AUDIT LOG — Registro de auditoría append-only (Sprint 5, SEC-002)
1. Objetivo
Implementar un registro de auditoría append-only en Postgres que loggee eventos
de configuración crítica: cambios en user_roles (altas/bajas/modificaciones de
acceso) y cambios de status en surveys (activación/desactivación de encuestas).
Opcionalmente, si la implementación es simple y sin fricción, agregar también un
trigger DDL para loggear migraciones aplicadas.
2. Contexto
- Migración de referencia para patrones:
20260627000001_auth_user_roles.sql(patrón SECURITY DEFINER + GRANT puntual a reutilizar). - Helpers RLS existentes a reusar:
public.current_app_role()ypublic.current_org_id()— no reinventar. - Gap conocido y aceptado (SEC-003): esto NO protege contra TRUNCATE/DROP ejecutado por superusuario directo en el VPS. Documentar en comentario de la migración, no resolver.
- Acceso a datos sensibles: NO loggear en esta etapa — hoy no existe código que
lea columnas
is_sensitive=true. Deferido a cuando ese código exista. - Naming de migración: después de
20260627000001— usar timestamp del día de ejecución, formatoYYYYMMDDHHMMSS.
3. DECISIONES YA TOMADAS — no consultar
- Tabla nueva:
public.audit_log, append-only real. - Ningún rol (
anon,authenticated,service_rolevía API) tiene INSERT/UPDATE/DELETE directo enaudit_log. Solo escriben funciones SECURITY DEFINER de los triggers. - Segunda barrera: trigger
BEFORE UPDATE OR DELETE ON audit_logque aborta con excepción — defensa en profundidad. - Lectura de audit_log: RLS, solo
platform_adminviacurrent_app_role(). - Patrón de función trigger: SECURITY DEFINER, propiedad de
postgres, GRANT EXECUTE solo al rol que lo necesita (no a PUBLIC). - Columnas de audit_log:
id bigserial PRIMARY KEY, occurred_at timestamptz NOT NULL DEFAULT now(), actor_role text, actor_user_id uuid, action text NOT NULL, target_table text NOT NULL, target_id text, detail jsonb - Triggers obligatorios (Prioridad 1):
- AFTER INSERT/UPDATE/DELETE en
user_roles - AFTER UPDATE en
surveyscuando cambiastatus
- AFTER INSERT/UPDATE/DELETE en
- Trigger DDL (Prioridad 2 — opcional): solo implementar si es simple y sin fricción. Si requiere extensión nueva o configuración de infraestructura, NO incluir y reportarlo.
4. Tareas
4.1 Diagnóstico previo — DDL trigger
Antes de implementar, verificar si el DDL trigger (ddl_command_end) está disponible
sin extensiones adicionales en esta versión de Postgres self-hosted:
docker exec -i supabase-db psql -U postgres -d postgres -c "
SELECT event_object_schema, trigger_name
FROM information_schema.triggers
WHERE trigger_name LIKE '%ddl%' LIMIT 5;"
Reportar si es viable sin fricción. Si requiere configuración extra, documentarlo y omitirlo de esta etapa.
4.2 Migración principal
Archivo: supabase/migrations/YYYYMMDDHHMMSS_audit_log.sql
Contenido en orden:
- Crear tabla
audit_logcon columnas definidas arriba. - RLS en
audit_log: soloplatform_adminpuede SELECT. - Trigger
BEFORE UPDATE OR DELETE ON audit_log→ función que lanza excepción (append-only enforcement). - Función
log_user_roles_change()SECURITY DEFINER → INSERT en audit_log. - Trigger AFTER INSERT/UPDATE/DELETE en
user_roles→ llama a la función. - Función
log_survey_status_change()SECURITY DEFINER → INSERT en audit_log solo cuandoNEW.status IS DISTINCT FROM OLD.status. - Trigger AFTER UPDATE en
surveys→ llama a la función. - Si DDL trigger es viable (4.1): función + trigger DDL.
- Comentario al final documentando el gap SEC-003.
4.3 Verificación en VPS
NO aplicar la migración — solo generar el archivo y reportar el SQL completo para revisión del director. El director aplica manualmente después de aprobar.
5. DoD antes de reportar
git log --name-only -1— solo el archivo de migración- SQL completo de la migración en el reporte (no resumido — el director necesita leerlo antes de aplicar)
- Diagnóstico DDL trigger reportado (4.1)
- Build sin errores:
npm run build - Commit + push a Gitea
- NO aplicado en VPS — esperar aprobación del director
6. Qué reportar
Diagnóstico DDL trigger (4.1):
- ¿Viable sin extensiones? ✓/✗
- Si no: razón y alternativa
SQL completo de la migración:
[pegar el SQL íntegro — no resumido]
Verificado:
- Build: ✓/✗
- git log --name-only -1: [lista]
- Commit: [hash]
NO aplicado en VPS — pendiente aprobación del director.
7. Qué NO hacer
- ❌ No aplicar la migración en el VPS sin aprobación explícita del director
- ❌ No implementar logging de accesos a datos sensibles (deferido)
- ❌ No agregar extensiones nuevas a Postgres
- ❌ No tocar el formulario público, las RPCs de dashboard, ni el middleware de auth
- ❌ No usar GRANT EXECUTE a PUBLIC — solo al rol específico que necesita cada función
- ❌ No omitir el comentario del gap SEC-003 en la migración
8. Versionado
feat(compliance): audit_log append-only — user_roles y surveys.status [SEC-002]
Tabla audit_log con RLS (solo platform_admin), trigger BEFORE UPDATE/DELETE
que aborta (append-only enforcement), triggers AFTER en user_roles y
surveys.status. Patrón SECURITY DEFINER reutilizado de auth_user_roles.
Gap SEC-003 (TRUNCATE por superusuario) documentado y aceptado.