ETAPA_CALIDAD: Nota de discrepancia: las 5 clases de P1 usan var(--color-primary), no var(--color-re-teal) como decía el prompt — ambas resuelven al mismo hex #43BBC8. Hice el reemplazo por nombre de clase (igual intención: texto teal → carbón), no por el literal del grep.
This commit is contained in:
124
sprints/sprint-5/ETAPA_AUDIT_LOG_PROMPT.md
Normal file
124
sprints/sprint-5/ETAPA_AUDIT_LOG_PROMPT.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 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.
|
||||
```
|
||||
Reference in New Issue
Block a user