124 lines
5.4 KiB
Markdown
124 lines
5.4 KiB
Markdown
# 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()` y
|
|
`public.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, formato `YYYYMMDDHHMMSS`.
|
|
|
|
## 3. DECISIONES YA TOMADAS — no consultar
|
|
- Tabla nueva: `public.audit_log`, append-only real.
|
|
- Ningún rol (`anon`, `authenticated`, `service_role` vía API) tiene INSERT/UPDATE/DELETE
|
|
directo en `audit_log`. Solo escriben funciones SECURITY DEFINER de los triggers.
|
|
- Segunda barrera: trigger `BEFORE UPDATE OR DELETE ON audit_log` que aborta con
|
|
excepción — defensa en profundidad.
|
|
- Lectura de audit_log: RLS, solo `platform_admin` via `current_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:
|
|
```sql
|
|
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 `surveys` cuando cambia `status`
|
|
- 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:
|
|
```bash
|
|
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:
|
|
1. Crear tabla `audit_log` con columnas definidas arriba.
|
|
2. RLS en `audit_log`: solo `platform_admin` puede SELECT.
|
|
3. Trigger `BEFORE UPDATE OR DELETE ON audit_log` → función que lanza excepción
|
|
(append-only enforcement).
|
|
4. Función `log_user_roles_change()` SECURITY DEFINER → INSERT en audit_log.
|
|
5. Trigger AFTER INSERT/UPDATE/DELETE en `user_roles` → llama a la función.
|
|
6. Función `log_survey_status_change()` SECURITY DEFINER → INSERT en audit_log
|
|
solo cuando `NEW.status IS DISTINCT FROM OLD.status`.
|
|
7. Trigger AFTER UPDATE en `surveys` → llama a la función.
|
|
8. Si DDL trigger es viable (4.1): función + trigger DDL.
|
|
9. 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.
|
|
``` |