403 lines
13 KiB
PL/PgSQL
403 lines
13 KiB
PL/PgSQL
-- ============================================================================
|
|
-- OrbiQuant IA - Esquema AUDIT
|
|
-- ============================================================================
|
|
-- Archivo: 07_audit_schema.sql
|
|
-- Descripción: Logs de auditoría, eventos del sistema y seguridad
|
|
-- Fecha: 2025-12-05
|
|
-- ============================================================================
|
|
|
|
SET search_path TO audit;
|
|
|
|
-- ============================================================================
|
|
-- TABLA: audit_logs
|
|
-- Descripción: Log general de acciones en el sistema
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS audit_logs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
|
|
-- Tabla afectada
|
|
table_name VARCHAR(100) NOT NULL,
|
|
record_id TEXT,
|
|
|
|
-- Acción
|
|
action VARCHAR(20) NOT NULL, -- 'INSERT', 'UPDATE', 'DELETE'
|
|
|
|
-- Datos
|
|
old_data JSONB,
|
|
new_data JSONB,
|
|
|
|
-- Usuario
|
|
user_id UUID,
|
|
user_email VARCHAR(255),
|
|
|
|
-- Contexto
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
session_id UUID,
|
|
|
|
-- Timestamp
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices para búsqueda eficiente
|
|
CREATE INDEX idx_audit_logs_table ON audit_logs(table_name);
|
|
CREATE INDEX idx_audit_logs_record ON audit_logs(table_name, record_id);
|
|
CREATE INDEX idx_audit_logs_user ON audit_logs(user_id);
|
|
CREATE INDEX idx_audit_logs_action ON audit_logs(action);
|
|
CREATE INDEX idx_audit_logs_created ON audit_logs(created_at DESC);
|
|
|
|
-- Particionamiento por mes (para producción)
|
|
-- CREATE TABLE audit_logs_2025_01 PARTITION OF audit_logs
|
|
-- FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
|
|
|
|
-- ============================================================================
|
|
-- TABLA: security_events
|
|
-- Descripción: Eventos de seguridad
|
|
-- ============================================================================
|
|
CREATE TYPE security_event_type AS ENUM (
|
|
'login_success',
|
|
'login_failed',
|
|
'logout',
|
|
'password_changed',
|
|
'password_reset_requested',
|
|
'password_reset_completed',
|
|
'2fa_enabled',
|
|
'2fa_disabled',
|
|
'2fa_failed',
|
|
'account_locked',
|
|
'account_unlocked',
|
|
'suspicious_activity',
|
|
'api_key_created',
|
|
'api_key_revoked',
|
|
'permission_denied',
|
|
'rate_limit_exceeded'
|
|
);
|
|
|
|
CREATE TYPE security_severity AS ENUM ('info', 'warning', 'error', 'critical');
|
|
|
|
CREATE TABLE IF NOT EXISTS security_events (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
|
|
-- Usuario
|
|
user_id UUID REFERENCES auth.users(id),
|
|
user_email VARCHAR(255),
|
|
|
|
-- Evento
|
|
event_type security_event_type NOT NULL,
|
|
severity security_severity DEFAULT 'info',
|
|
description TEXT,
|
|
|
|
-- Contexto
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
location JSONB, -- {country, city, lat, lon}
|
|
|
|
-- Datos adicionales
|
|
metadata JSONB,
|
|
|
|
-- Estado
|
|
acknowledged BOOLEAN DEFAULT FALSE,
|
|
acknowledged_by UUID REFERENCES auth.users(id),
|
|
acknowledged_at TIMESTAMPTZ,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_security_events_user ON security_events(user_id);
|
|
CREATE INDEX idx_security_events_type ON security_events(event_type);
|
|
CREATE INDEX idx_security_events_severity ON security_events(severity);
|
|
CREATE INDEX idx_security_events_created ON security_events(created_at DESC);
|
|
CREATE INDEX idx_security_events_unack ON security_events(acknowledged) WHERE acknowledged = FALSE;
|
|
|
|
-- ============================================================================
|
|
-- TABLA: system_events
|
|
-- Descripción: Eventos del sistema (no de usuario)
|
|
-- ============================================================================
|
|
CREATE TYPE system_event_type AS ENUM (
|
|
'service_started',
|
|
'service_stopped',
|
|
'service_error',
|
|
'database_backup',
|
|
'database_restore',
|
|
'deployment',
|
|
'config_changed',
|
|
'scheduled_job_started',
|
|
'scheduled_job_completed',
|
|
'scheduled_job_failed',
|
|
'integration_connected',
|
|
'integration_disconnected',
|
|
'integration_error',
|
|
'alert_triggered',
|
|
'maintenance_started',
|
|
'maintenance_completed'
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS system_events (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
|
|
-- Evento
|
|
event_type system_event_type NOT NULL,
|
|
service_name VARCHAR(100),
|
|
description TEXT,
|
|
|
|
-- Severidad
|
|
severity security_severity DEFAULT 'info',
|
|
|
|
-- Detalles
|
|
details JSONB,
|
|
error_message TEXT,
|
|
stack_trace TEXT,
|
|
|
|
-- Metadata
|
|
hostname VARCHAR(255),
|
|
environment VARCHAR(50), -- 'development', 'staging', 'production'
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_system_events_type ON system_events(event_type);
|
|
CREATE INDEX idx_system_events_service ON system_events(service_name);
|
|
CREATE INDEX idx_system_events_severity ON system_events(severity);
|
|
CREATE INDEX idx_system_events_created ON system_events(created_at DESC);
|
|
|
|
-- ============================================================================
|
|
-- TABLA: trading_audit
|
|
-- Descripción: Auditoría específica de operaciones de trading
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS trading_audit (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
|
|
-- Referencias
|
|
bot_id UUID,
|
|
signal_id UUID,
|
|
position_id UUID,
|
|
account_id UUID,
|
|
user_id UUID,
|
|
|
|
-- Evento
|
|
event_type VARCHAR(50) NOT NULL, -- 'signal_generated', 'order_placed', 'order_filled', etc.
|
|
description TEXT,
|
|
|
|
-- Datos de la operación
|
|
symbol VARCHAR(20),
|
|
direction VARCHAR(10),
|
|
lot_size DECIMAL(10,4),
|
|
price DECIMAL(20,8),
|
|
|
|
-- Precios
|
|
entry_price DECIMAL(20,8),
|
|
stop_loss DECIMAL(20,8),
|
|
take_profit DECIMAL(20,8),
|
|
|
|
-- Resultado
|
|
pnl DECIMAL(15,2),
|
|
outcome VARCHAR(20),
|
|
|
|
-- Contexto del modelo
|
|
model_id UUID,
|
|
confidence DECIMAL(5,4),
|
|
amd_phase VARCHAR(20),
|
|
|
|
-- Metadata
|
|
metadata JSONB,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_trading_audit_bot ON trading_audit(bot_id);
|
|
CREATE INDEX idx_trading_audit_signal ON trading_audit(signal_id);
|
|
CREATE INDEX idx_trading_audit_position ON trading_audit(position_id);
|
|
CREATE INDEX idx_trading_audit_account ON trading_audit(account_id);
|
|
CREATE INDEX idx_trading_audit_event ON trading_audit(event_type);
|
|
CREATE INDEX idx_trading_audit_symbol ON trading_audit(symbol);
|
|
CREATE INDEX idx_trading_audit_created ON trading_audit(created_at DESC);
|
|
|
|
-- ============================================================================
|
|
-- TABLA: api_request_logs
|
|
-- Descripción: Logs de requests a la API
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS api_request_logs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
|
|
-- Usuario
|
|
user_id UUID,
|
|
api_key_id UUID,
|
|
|
|
-- Request
|
|
method VARCHAR(10) NOT NULL,
|
|
path VARCHAR(500) NOT NULL,
|
|
query_params JSONB,
|
|
headers JSONB,
|
|
body_size INT,
|
|
|
|
-- Response
|
|
status_code INT,
|
|
response_time_ms INT,
|
|
response_size INT,
|
|
|
|
-- Contexto
|
|
ip_address INET,
|
|
user_agent TEXT,
|
|
|
|
-- Error (si aplica)
|
|
error_message TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Índices para análisis
|
|
CREATE INDEX idx_api_logs_user ON api_request_logs(user_id);
|
|
CREATE INDEX idx_api_logs_path ON api_request_logs(path);
|
|
CREATE INDEX idx_api_logs_status ON api_request_logs(status_code);
|
|
CREATE INDEX idx_api_logs_created ON api_request_logs(created_at DESC);
|
|
|
|
-- ============================================================================
|
|
-- TABLA: data_access_logs
|
|
-- Descripción: Log de acceso a datos sensibles
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS data_access_logs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
|
|
-- Usuario que accedió
|
|
user_id UUID NOT NULL REFERENCES auth.users(id),
|
|
|
|
-- Dato accedido
|
|
resource_type VARCHAR(100) NOT NULL, -- 'user_profile', 'kyc_document', 'wallet_balance', etc.
|
|
resource_id UUID,
|
|
resource_owner_id UUID, -- Usuario dueño del dato
|
|
|
|
-- Acción
|
|
action VARCHAR(50) NOT NULL, -- 'view', 'export', 'modify'
|
|
|
|
-- Contexto
|
|
reason TEXT, -- Justificación del acceso
|
|
ip_address INET,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_data_access_user ON data_access_logs(user_id);
|
|
CREATE INDEX idx_data_access_resource ON data_access_logs(resource_type, resource_id);
|
|
CREATE INDEX idx_data_access_owner ON data_access_logs(resource_owner_id);
|
|
CREATE INDEX idx_data_access_created ON data_access_logs(created_at DESC);
|
|
|
|
-- ============================================================================
|
|
-- TABLA: compliance_logs
|
|
-- Descripción: Logs de cumplimiento regulatorio
|
|
-- ============================================================================
|
|
CREATE TABLE IF NOT EXISTS compliance_logs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
|
|
-- Usuario
|
|
user_id UUID REFERENCES auth.users(id),
|
|
|
|
-- Evento
|
|
event_type VARCHAR(100) NOT NULL,
|
|
-- 'terms_accepted', 'risk_disclosure_accepted', 'kyc_submitted', 'kyc_approved',
|
|
-- 'aml_check_passed', 'suspicious_activity_flagged', etc.
|
|
|
|
-- Detalles
|
|
description TEXT,
|
|
document_version VARCHAR(50),
|
|
document_url TEXT,
|
|
|
|
-- Metadata
|
|
metadata JSONB,
|
|
ip_address INET,
|
|
|
|
-- Estado de revisión
|
|
requires_review BOOLEAN DEFAULT FALSE,
|
|
reviewed_by UUID REFERENCES auth.users(id),
|
|
reviewed_at TIMESTAMPTZ,
|
|
review_notes TEXT,
|
|
|
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_compliance_user ON compliance_logs(user_id);
|
|
CREATE INDEX idx_compliance_event ON compliance_logs(event_type);
|
|
CREATE INDEX idx_compliance_review ON compliance_logs(requires_review) WHERE requires_review = TRUE;
|
|
CREATE INDEX idx_compliance_created ON compliance_logs(created_at DESC);
|
|
|
|
-- ============================================================================
|
|
-- VISTAS DE ADMINISTRACIÓN
|
|
-- ============================================================================
|
|
|
|
-- Vista: Actividad reciente de usuarios
|
|
CREATE OR REPLACE VIEW admin_user_activity AS
|
|
SELECT
|
|
u.id AS user_id,
|
|
u.email,
|
|
p.first_name,
|
|
p.last_name,
|
|
u.role,
|
|
u.status,
|
|
u.last_login_at,
|
|
u.last_login_ip,
|
|
(SELECT COUNT(*) FROM audit.security_events se WHERE se.user_id = u.id AND se.created_at > NOW() - INTERVAL '24 hours') AS security_events_24h,
|
|
(SELECT COUNT(*) FROM audit.api_request_logs ar WHERE ar.user_id = u.id AND ar.created_at > NOW() - INTERVAL '24 hours') AS api_requests_24h
|
|
FROM auth.users u
|
|
LEFT JOIN public.profiles p ON p.user_id = u.id;
|
|
|
|
-- Vista: Alertas de seguridad pendientes
|
|
CREATE OR REPLACE VIEW admin_security_alerts AS
|
|
SELECT
|
|
se.*,
|
|
u.email AS user_email,
|
|
p.first_name,
|
|
p.last_name
|
|
FROM audit.security_events se
|
|
LEFT JOIN auth.users u ON u.id = se.user_id
|
|
LEFT JOIN public.profiles p ON p.user_id = se.user_id
|
|
WHERE se.acknowledged = FALSE
|
|
AND se.severity IN ('warning', 'error', 'critical')
|
|
ORDER BY se.created_at DESC;
|
|
|
|
-- Vista: Resumen de trading por bot
|
|
CREATE OR REPLACE VIEW admin_bot_trading_summary AS
|
|
SELECT
|
|
ta.bot_id,
|
|
b.name AS bot_name,
|
|
b.risk_profile,
|
|
DATE(ta.created_at) AS date,
|
|
COUNT(*) FILTER (WHERE ta.event_type = 'signal_generated') AS signals_generated,
|
|
COUNT(*) FILTER (WHERE ta.event_type = 'order_placed') AS orders_placed,
|
|
COUNT(*) FILTER (WHERE ta.event_type = 'order_filled') AS orders_filled,
|
|
SUM(ta.pnl) AS total_pnl,
|
|
COUNT(*) FILTER (WHERE ta.pnl > 0) AS winning_trades,
|
|
COUNT(*) FILTER (WHERE ta.pnl < 0) AS losing_trades
|
|
FROM audit.trading_audit ta
|
|
LEFT JOIN trading.bots b ON b.id = ta.bot_id
|
|
WHERE ta.bot_id IS NOT NULL
|
|
GROUP BY ta.bot_id, b.name, b.risk_profile, DATE(ta.created_at);
|
|
|
|
-- ============================================================================
|
|
-- FUNCIONES DE LIMPIEZA
|
|
-- ============================================================================
|
|
|
|
-- Función para limpiar logs antiguos
|
|
CREATE OR REPLACE FUNCTION cleanup_old_logs(retention_days INT DEFAULT 90)
|
|
RETURNS TABLE(
|
|
table_name TEXT,
|
|
rows_deleted BIGINT
|
|
) AS $$
|
|
DECLARE
|
|
cutoff_date TIMESTAMPTZ;
|
|
BEGIN
|
|
cutoff_date := NOW() - (retention_days || ' days')::INTERVAL;
|
|
|
|
-- API request logs (30 días por defecto)
|
|
DELETE FROM audit.api_request_logs WHERE created_at < cutoff_date;
|
|
RETURN QUERY SELECT 'api_request_logs'::TEXT, COUNT(*)::BIGINT FROM audit.api_request_logs WHERE created_at < cutoff_date;
|
|
|
|
-- Audit logs (90 días por defecto, excepto críticos)
|
|
DELETE FROM audit.audit_logs WHERE created_at < cutoff_date;
|
|
|
|
-- Security events (mantener indefinidamente los críticos)
|
|
DELETE FROM audit.security_events
|
|
WHERE created_at < cutoff_date
|
|
AND severity NOT IN ('error', 'critical');
|
|
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|