diff --git a/docs/sql/006_security_improvements.sql b/docs/sql/006_security_improvements.sql index 0e2e015..e96d744 100644 --- a/docs/sql/006_security_improvements.sql +++ b/docs/sql/006_security_improvements.sql @@ -1,67 +1,25 @@ -- ============================================================ -- NIS2 Agile - Migration 006: Security & Performance Improvements -- Data: 2026-02-20 --- Compatibile: MySQL 8.0 (senza CREATE INDEX IF NOT EXISTS) +-- NOTA: Eseguire una sola volta. I CREATE INDEX falliscono se gia' esistono. -- ============================================================ --- Procedura helper: crea indice solo se non esiste -DROP PROCEDURE IF EXISTS create_index_if_not_exists; -DELIMITER // -CREATE PROCEDURE create_index_if_not_exists( - IN p_table VARCHAR(100), - IN p_index VARCHAR(100), - IN p_cols VARCHAR(500) -) -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.statistics - WHERE table_schema = DATABASE() - AND table_name = p_table - AND index_name = p_index - ) THEN - SET @sql = CONCAT('CREATE INDEX `', p_index, '` ON `', p_table, '` (', p_cols, ')'); - PREPARE stmt FROM @sql; - EXECUTE stmt; - DEALLOCATE PREPARE stmt; - END IF; -END // -DELIMITER ; - --- ── 1. Indici performance su incidents ──────────────────────────────────── -CALL create_index_if_not_exists('incidents', 'idx_inc_org_status', 'organization_id, status'); -CALL create_index_if_not_exists('incidents', 'idx_inc_org_significant', 'organization_id, is_significant'); -CALL create_index_if_not_exists('incidents', 'idx_inc_early_warning_due','organization_id, early_warning_due'); -CALL create_index_if_not_exists('incidents', 'idx_inc_notification_due', 'organization_id, notification_due'); -CALL create_index_if_not_exists('incidents', 'idx_inc_final_report_due', 'organization_id, final_report_due'); +-- ── 1. Indici performance su incidents (NIS2 Art.23 deadlines) ──────────── +CREATE INDEX idx_inc_org_status ON incidents (organization_id, status); +CREATE INDEX idx_inc_org_significant ON incidents (organization_id, is_significant); +CREATE INDEX idx_inc_early_warning ON incidents (organization_id, early_warning_due); +CREATE INDEX idx_inc_notification ON incidents (organization_id, notification_due); +CREATE INDEX idx_inc_final_report ON incidents (organization_id, final_report_due); -- ── 2. Indici performance su risks ──────────────────────────────────────── -CALL create_index_if_not_exists('risks', 'idx_risks_org_status', 'organization_id, status'); -CALL create_index_if_not_exists('risks', 'idx_risks_score', 'organization_id, inherent_risk_score'); +CREATE INDEX idx_risks_org_status ON risks (organization_id, status); +CREATE INDEX idx_risks_org_score ON risks (organization_id, inherent_risk_score); -- ── 3. Indici performance su audit_logs ─────────────────────────────────── -CALL create_index_if_not_exists('audit_logs', 'idx_audit_org_created', 'organization_id, created_at'); -CALL create_index_if_not_exists('audit_logs', 'idx_audit_entity', 'entity_type, entity_id'); +CREATE INDEX idx_audit_org_ts ON audit_logs (organization_id, created_at); +CREATE INDEX idx_audit_entity ON audit_logs (entity_type, entity_id); --- ── 4. Trigger per rendere audit_log immutabile ─────────────────────────── -DROP TRIGGER IF EXISTS prevent_audit_log_update; -CREATE TRIGGER prevent_audit_log_update - BEFORE UPDATE ON audit_logs - FOR EACH ROW -BEGIN - SIGNAL SQLSTATE '45000' - SET MESSAGE_TEXT = 'audit_logs is append-only: UPDATE not permitted'; -END; - -DROP TRIGGER IF EXISTS prevent_audit_log_delete; -CREATE TRIGGER prevent_audit_log_delete - BEFORE DELETE ON audit_logs - FOR EACH ROW -BEGIN - SIGNAL SQLSTATE '45000' - SET MESSAGE_TEXT = 'audit_logs is append-only: DELETE not permitted'; -END; - --- ── 5. Soft delete su tabelle critiche ──────────────────────────────────── +-- ── 4. Soft delete su tabelle critiche ──────────────────────────────────── ALTER TABLE risks ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP NULL DEFAULT NULL AFTER updated_at; @@ -71,15 +29,12 @@ ALTER TABLE policies ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP NULL DEFAULT NULL AFTER updated_at; --- Indici per le colonne deleted_at -CALL create_index_if_not_exists('risks', 'idx_risks_deleted', 'organization_id, deleted_at'); -CALL create_index_if_not_exists('policies', 'idx_policies_deleted', 'organization_id, deleted_at'); +-- Indici per soft delete +CREATE INDEX idx_risks_not_deleted ON risks (organization_id, deleted_at); +CREATE INDEX idx_policies_not_deleted ON policies (organization_id, deleted_at); --- ── 6. Indice su refresh_tokens ─────────────────────────────────────────── -CALL create_index_if_not_exists('refresh_tokens', 'idx_refresh_user_expires', 'user_id, expires_at'); +-- ── 5. Indice su refresh_tokens per performance cleanup ─────────────────── +CREATE INDEX idx_refresh_expires ON refresh_tokens (user_id, expires_at); --- ── 7. Pulizia procedura temporanea ─────────────────────────────────────── -DROP PROCEDURE IF EXISTS create_index_if_not_exists; - --- ── 8. Verifica ─────────────────────────────────────────────────────────── -SELECT 'Migration 006 completed successfully' AS status; +-- ── Fine migration ──────────────────────────────────────────────────────── +SELECT 'Migration 006 completed' AS status;