Skip to content

imagy-audit — Modelo de Datos

Base de Datos

  • Nombre: imagy_audit
  • Usuario: imagy_audit_app (NOBYPASSRLS)
  • RLS: Habilitado en todas las tablas (tenant_id)
  • Inmutabilidad: UPDATE y DELETE revocados en audit_events

Diagrama ER

Tablas

audit_events (particionada por mes)

Tabla principal de eventos de auditoria. Particionada por rango de created_at con granularidad mensual. Cada particion contiene los eventos de un mes calendario.

ColumnaTipoDescripcion
idUUID PKIdentificador unico del evento
tenant_idUUIDTenant propietario (RLS)
class_uidINTClase OCSF (ej: 3001 = Account Change)
class_nameVARCHAR(100)Nombre de la clase OCSF
category_uidINTCategoria OCSF (ej: 3 = IAM)
category_nameVARCHAR(100)Nombre de la categoria OCSF
activity_idINTActividad OCSF (1=Create, 2=Update, 3=Delete, etc.)
activity_nameVARCHAR(100)Nombre de la actividad
severity_idINTSeveridad (1=Informational, 2=Low, 3=Medium, 4=High, 5=Critical)
timeBIGINTTimestamp del evento original (epoch milliseconds)
messageTEXTDescripcion legible del evento
actor_idUUID FKReferencia al actor que ejecuto la accion
resource_idUUID FKReferencia al recurso afectado (nullable)
src_endpointJSONBEndpoint de origen (ip, name, service)
metadataJSONBMetadata OCSF (version, product, tenant_uid, profiles)
unmappedJSONBCampos de extension — datos de dominio no mapeados a OCSF
created_atTIMESTAMPTZFecha de insercion (clave de particionamiento)

Inmutabilidad: Esta tabla no permite UPDATE ni DELETE a nivel de permisos de base de datos.

audit_actors

Catalogo de actores que han generado eventos. Se actualiza con last_seen_at cada vez que el actor aparece en un nuevo evento.

ColumnaTipoDescripcion
idUUID PKIdentificador interno
tenant_idUUIDTenant (RLS)
user_uidVARCHAR(255)UID del usuario o servicio en la plataforma
user_nameVARCHAR(255)Nombre del actor
user_typeVARCHAR(50)User, System, Service, Admin
session_uidVARCHAR(255)Ultima sesion conocida (nullable)
service_nameVARCHAR(100)Nombre del servicio si es actor de tipo System/Service
first_seen_atTIMESTAMPTZPrimera aparicion
last_seen_atTIMESTAMPTZUltima aparicion

Constraint: UNIQUE(tenant_id, user_uid)

audit_resources

Catalogo de recursos que han sido referenciados en eventos de auditoria.

ColumnaTipoDescripcion
idUUID PKIdentificador interno
tenant_idUUIDTenant (RLS)
resource_uidVARCHAR(255)UID del recurso en su dominio de origen
resource_typeVARCHAR(100)Tipo de recurso (credit, application, user, document, flow)
resource_nameVARCHAR(255)Nombre legible del recurso (nullable)
domainVARCHAR(50)Dominio de origen (imaglend, imagflow, imagsign, imagid, identity)
first_seen_atTIMESTAMPTZPrimera referencia
last_seen_atTIMESTAMPTZUltima referencia

Constraint: UNIQUE(tenant_id, resource_uid, resource_type)

audit_export_jobs

Jobs de exportacion solicitados por los tenants.

ColumnaTipoDescripcion
idUUID PKIdentificador del job
tenant_idUUIDTenant (RLS)
statusVARCHAR(20)pending, processing, completed, failed
formatVARCHAR(20)ocsf_json, csv
filtersJSONBFiltros aplicados (periodo, categoria, actor, severidad)
file_urlTEXTURL de descarga (S3 presigned, nullable hasta completar)
total_eventsBIGINTCantidad de eventos exportados
requested_atTIMESTAMPTZFecha de solicitud
started_atTIMESTAMPTZInicio del procesamiento (nullable)
completed_atTIMESTAMPTZFinalizacion (nullable)
requested_byUUIDUsuario que solicito la exportacion

Campos de Extension (unmapped)

El campo unmapped de tipo JSONB almacena datos especificos de dominio que no mapean directamente a campos OCSF estandar. Esto permite extender el schema sin romper la compatibilidad.

Ejemplos por dominio:

json
// ImagLend — credito aprobado
{
  "credit_id": "credit-uuid-001",
  "product_id": "microcredito-express",
  "amount": { "value": 500000, "currency": "COP" },
  "decision": "approved",
  "pipeline_step": "disbursement"
}

// ImagSign — documento firmado
{
  "signature_id": "sig-uuid-001",
  "document_type": "credit_contract",
  "signer_document": "****7890",
  "signature_method": "electronic"
}

// ImagID — sujeto verificado
{
  "subject_id": "subj-uuid-001",
  "verification_type": "liveness",
  "provider": "onfido",
  "score": 0.97
}

Estrategia de Particionamiento

La tabla audit_events esta particionada por rango mensual sobre created_at. Las particiones se crean automaticamente con anticipacion (3 meses adelante) mediante un job programado.

sql
-- Tabla padre (particionada)
CREATE TABLE audit_events (
    id UUID NOT NULL DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL,
    class_uid INT NOT NULL,
    class_name VARCHAR(100) NOT NULL,
    category_uid INT NOT NULL,
    category_name VARCHAR(100) NOT NULL,
    activity_id INT NOT NULL,
    activity_name VARCHAR(100) NOT NULL,
    severity_id INT NOT NULL DEFAULT 1,
    time BIGINT NOT NULL,
    message TEXT,
    actor_id UUID REFERENCES audit_actors(id),
    resource_id UUID REFERENCES audit_resources(id),
    src_endpoint JSONB,
    metadata JSONB NOT NULL,
    unmapped JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);

-- Particiones mensuales (auto-creadas)
CREATE TABLE audit_events_2026_01 PARTITION OF audit_events
    FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

CREATE TABLE audit_events_2026_02 PARTITION OF audit_events
    FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');

-- ... se crean automaticamente via pg_cron

Ciclo de Vida de Particiones

  • Hot (0-90 dias): Particiones en tablespace SSD, indices completos, consulta directa.
  • Warm (91-365 dias): Particiones en tablespace HDD, indices reducidos, consulta mas lenta.
  • Cold (> 365 dias): Datos exportados a S3 Glacier, particion eliminada de PostgreSQL. Acceso solo via exportacion bajo demanda.

Inmutabilidad

La inmutabilidad se garantiza a nivel de base de datos revocando permisos de modificacion:

sql
-- Revocar UPDATE y DELETE en audit_events para el usuario de aplicacion
REVOKE UPDATE, DELETE ON audit_events FROM imagy_audit_app;

-- Trigger de seguridad adicional (defense in depth)
CREATE OR REPLACE FUNCTION prevent_audit_modification()
RETURNS TRIGGER AS $$
BEGIN
    RAISE EXCEPTION 'audit_events is immutable: % operations are not allowed', TG_OP;
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_prevent_update
    BEFORE UPDATE ON audit_events
    FOR EACH ROW EXECUTE FUNCTION prevent_audit_modification();

CREATE TRIGGER trg_prevent_delete
    BEFORE DELETE ON audit_events
    FOR EACH ROW EXECUTE FUNCTION prevent_audit_modification();

RLS Policies

sql
ALTER TABLE audit_events ENABLE ROW LEVEL SECURITY;
ALTER TABLE audit_actors ENABLE ROW LEVEL SECURITY;
ALTER TABLE audit_resources ENABLE ROW LEVEL SECURITY;
ALTER TABLE audit_export_jobs ENABLE ROW LEVEL SECURITY;

-- Cada tenant solo ve sus propios eventos
CREATE POLICY tenant_isolation ON audit_events
    FOR ALL USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation ON audit_actors
    FOR ALL USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation ON audit_resources
    FOR ALL USING (tenant_id = get_current_tenant_id());

CREATE POLICY tenant_isolation ON audit_export_jobs
    FOR ALL USING (tenant_id = get_current_tenant_id());

-- Control plane para platform admin (acceso cross-tenant)
CREATE POLICY control_plane ON audit_events
    FOR SELECT USING (
        current_setting('app.current_tenant_id', true) IS NULL
        OR current_setting('app.current_tenant_id', true) = ''
    );

Indices

sql
-- Consulta por tenant y rango de tiempo (query mas comun)
CREATE INDEX idx_audit_events_tenant_time
    ON audit_events(tenant_id, created_at DESC);

-- Consulta por actor
CREATE INDEX idx_audit_events_actor
    ON audit_events(tenant_id, actor_id, created_at DESC);

-- Consulta por recurso
CREATE INDEX idx_audit_events_resource
    ON audit_events(tenant_id, resource_id, created_at DESC);

-- Consulta por severidad (alertas, findings)
CREATE INDEX idx_audit_events_severity
    ON audit_events(tenant_id, severity_id, created_at DESC)
    WHERE severity_id >= 3;

-- Consulta por categoria y clase OCSF
CREATE INDEX idx_audit_events_category
    ON audit_events(tenant_id, category_uid, class_uid, created_at DESC);

-- Consulta por time (epoch) para rangos exactos
CREATE INDEX idx_audit_events_time
    ON audit_events(tenant_id, time DESC);

-- Actores
CREATE UNIQUE INDEX idx_audit_actors_uid
    ON audit_actors(tenant_id, user_uid);

-- Recursos
CREATE UNIQUE INDEX idx_audit_resources_uid
    ON audit_resources(tenant_id, resource_uid, resource_type);

-- Export jobs por estado
CREATE INDEX idx_audit_exports_status
    ON audit_export_jobs(tenant_id, status, requested_at DESC);

Tablas de Soporte

idempotency_keys

Previene el procesamiento duplicado de eventos consumidos desde RabbitMQ.

ColumnaTipoDescripcion
keyVARCHAR(255) PKClave de idempotencia (event_id del mensaje)
created_atTIMESTAMPTZFecha de registro
expires_atTIMESTAMPTZExpiracion (TTL de 7 dias)

processed_events

Registro de eventos de dominio ya procesados para garantizar exactly-once semantics.

ColumnaTipoDescripcion
event_idUUID PKID del evento de dominio
sourceVARCHAR(100)Servicio de origen
routing_keyVARCHAR(255)Routing key del mensaje
processed_atTIMESTAMPTZFecha de procesamiento

Reimagine Tech LLC — Documentacion Interna