#!/usr/bin/env php getFullConfig(); // Initialiser la base de données Database::init($config['database']); $db = Database::getInstance(); echo "\n========================================\n"; echo " CRÉATION DES TABLES DE SÉCURITÉ\n"; echo "========================================\n\n"; try { // Désactiver temporairement le mode strict pour les clés étrangères $db->exec("SET FOREIGN_KEY_CHECKS = 0"); // 1. Table des alertes echo "1. Création de la table sec_alerts...\n"; $db->exec(" CREATE TABLE IF NOT EXISTS `sec_alerts` ( `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY, `alert_type` VARCHAR(50) NOT NULL COMMENT 'Type d\'alerte (BRUTE_FORCE, SQL_ERROR, etc.)', `alert_level` ENUM('INFO', 'WARNING', 'ERROR', 'CRITICAL', 'SECURITY') NOT NULL DEFAULT 'INFO', `ip_address` VARCHAR(45) DEFAULT NULL COMMENT 'Adresse IP source', `user_id` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID utilisateur si connecté', `username` VARCHAR(255) DEFAULT NULL COMMENT 'Username tenté ou utilisé', `endpoint` VARCHAR(255) DEFAULT NULL COMMENT 'Endpoint API concerné', `method` VARCHAR(10) DEFAULT NULL COMMENT 'Méthode HTTP', `details` JSON DEFAULT NULL COMMENT 'Détails additionnels en JSON', `occurrences` INT(11) DEFAULT 1 COMMENT 'Nombre d\'occurrences', `first_seen` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_seen` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `email_sent` TINYINT(1) DEFAULT 0 COMMENT 'Email d\'alerte envoyé', `email_sent_at` TIMESTAMP NULL DEFAULT NULL, `resolved` TINYINT(1) DEFAULT 0 COMMENT 'Alerte résolue', `resolved_at` TIMESTAMP NULL DEFAULT NULL, `resolved_by` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID admin qui a résolu', `notes` TEXT DEFAULT NULL COMMENT 'Notes de résolution', KEY `idx_ip` (`ip_address`), KEY `idx_type_time` (`alert_type`, `last_seen`), KEY `idx_level` (`alert_level`), KEY `idx_resolved` (`resolved`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Alertes de sécurité et monitoring' "); echo " ✓ Table sec_alerts créée\n"; // 2. Table des métriques de performance (SANS PARTITIONNEMENT) echo "2. Création de la table sec_performance_metrics...\n"; $db->exec(" CREATE TABLE IF NOT EXISTS `sec_performance_metrics` ( `id` BIGINT(20) UNSIGNED AUTO_INCREMENT PRIMARY KEY, `endpoint` VARCHAR(255) NOT NULL COMMENT 'Endpoint API', `method` VARCHAR(10) NOT NULL COMMENT 'Méthode HTTP', `response_time_ms` INT(11) NOT NULL COMMENT 'Temps de réponse total en ms', `db_time_ms` INT(11) DEFAULT 0 COMMENT 'Temps cumulé des requêtes DB en ms', `db_queries_count` INT(11) DEFAULT 0 COMMENT 'Nombre de requêtes DB', `memory_peak_mb` FLOAT DEFAULT NULL COMMENT 'Pic mémoire en MB', `memory_start_mb` FLOAT DEFAULT NULL COMMENT 'Mémoire au début en MB', `http_status` INT(11) DEFAULT NULL COMMENT 'Code HTTP de réponse', `user_id` INT(11) UNSIGNED DEFAULT NULL COMMENT 'ID utilisateur si connecté', `ip_address` VARCHAR(45) DEFAULT NULL COMMENT 'Adresse IP', `user_agent` TEXT DEFAULT NULL COMMENT 'User Agent complet', `request_size` INT(11) DEFAULT NULL COMMENT 'Taille de la requête en octets', `response_size` INT(11) DEFAULT NULL COMMENT 'Taille de la réponse en octets', `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY `idx_endpoint_time` (`endpoint`, `created_at`), KEY `idx_response_time` (`response_time_ms`), KEY `idx_created` (`created_at`), KEY `idx_status` (`http_status`), KEY `idx_user` (`user_id`), KEY `idx_date_endpoint` (`created_at`, `endpoint`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Métriques de performance des requêtes' "); echo " ✓ Table sec_performance_metrics créée\n"; // 3. Table des tentatives de login échouées echo "3. Création de la table sec_failed_login_attempts...\n"; $db->exec(" CREATE TABLE IF NOT EXISTS `sec_failed_login_attempts` ( `id` INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY, `username` VARCHAR(255) DEFAULT NULL COMMENT 'Username tenté', `encrypted_username` VARCHAR(255) DEFAULT NULL COMMENT 'Username chiffré si trouvé', `ip_address` VARCHAR(45) NOT NULL COMMENT 'Adresse IP', `user_agent` TEXT DEFAULT NULL COMMENT 'User Agent', `attempt_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `error_type` VARCHAR(50) DEFAULT NULL COMMENT 'Type d\'erreur (invalid_password, user_not_found, etc.)', `country_code` VARCHAR(2) DEFAULT NULL COMMENT 'Code pays de l\'IP (si géoloc activée)', KEY `idx_ip_time` (`ip_address`, `attempt_time`), KEY `idx_username` (`username`), KEY `idx_encrypted_username` (`encrypted_username`), KEY `idx_time` (`attempt_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Tentatives de connexion échouées' "); echo " ✓ Table sec_failed_login_attempts créée\n"; // 4. Table des IPs bloquées echo "4. Création de la table sec_blocked_ips...\n"; $db->exec(" CREATE TABLE IF NOT EXISTS `sec_blocked_ips` ( `ip_address` VARCHAR(45) NOT NULL PRIMARY KEY COMMENT 'Adresse IP bloquée', `reason` VARCHAR(255) NOT NULL COMMENT 'Raison du blocage', `details` JSON DEFAULT NULL COMMENT 'Détails additionnels', `blocked_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `blocked_until` TIMESTAMP NOT NULL COMMENT 'Bloqué jusqu\'à', `blocked_by` VARCHAR(50) DEFAULT 'system' COMMENT 'Qui a bloqué (system ou user ID)', `permanent` TINYINT(1) DEFAULT 0 COMMENT 'Blocage permanent', `unblocked_at` TIMESTAMP NULL DEFAULT NULL COMMENT 'Date de déblocage effectif', `unblocked_by` INT(11) UNSIGNED DEFAULT NULL COMMENT 'Qui a débloqué', `block_count` INT(11) DEFAULT 1 COMMENT 'Nombre de fois bloquée', KEY `idx_blocked_until` (`blocked_until`), KEY `idx_permanent` (`permanent`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='IPs bloquées temporairement ou définitivement' "); echo " ✓ Table sec_blocked_ips créée\n"; // 5. Créer les vues echo "5. Création des vues...\n"; // Vue pour les alertes actives $db->exec(" CREATE OR REPLACE VIEW sec_active_alerts AS SELECT a.*, u.encrypted_name as user_name, r.encrypted_name as resolver_name FROM sec_alerts a LEFT JOIN users u ON a.user_id = u.id LEFT JOIN users r ON a.resolved_by = r.id WHERE a.resolved = 0 OR (a.resolved = 1 AND a.resolved_at >= DATE_SUB(NOW(), INTERVAL 24 HOUR)) ORDER BY CASE a.alert_level WHEN 'SECURITY' THEN 1 WHEN 'CRITICAL' THEN 2 WHEN 'ERROR' THEN 3 WHEN 'WARNING' THEN 4 WHEN 'INFO' THEN 5 END, a.last_seen DESC "); echo " ✓ Vue sec_active_alerts créée\n"; // Vue pour les IPs suspectes $db->exec(" CREATE OR REPLACE VIEW sec_suspicious_ips AS SELECT ip_address, COUNT(*) as total_attempts, COUNT(DISTINCT username) as unique_usernames, MIN(attempt_time) as first_attempt, MAX(attempt_time) as last_attempt, TIMESTAMPDIFF(MINUTE, MIN(attempt_time), MAX(attempt_time)) as timespan_minutes FROM sec_failed_login_attempts WHERE attempt_time >= DATE_SUB(NOW(), INTERVAL 1 HOUR) GROUP BY ip_address HAVING total_attempts >= 5 OR unique_usernames >= 3 ORDER BY total_attempts DESC "); echo " ✓ Vue sec_suspicious_ips créée\n"; // 6. Créer les index additionnels echo "6. Création des index additionnels...\n"; // Index pour les requêtes fréquentes $db->exec("CREATE INDEX IF NOT EXISTS idx_sec_metrics_recent ON sec_performance_metrics(created_at DESC, endpoint)"); $db->exec("CREATE INDEX IF NOT EXISTS idx_sec_alerts_recent ON sec_alerts(last_seen DESC, alert_level)"); $db->exec("CREATE INDEX IF NOT EXISTS idx_sec_failed_recent ON sec_failed_login_attempts(attempt_time DESC, ip_address)"); echo " ✓ Index créés\n"; // 7. Créer la procédure de nettoyage echo "7. Création de la procédure de nettoyage...\n"; $db->exec("DROP PROCEDURE IF EXISTS sec_cleanup_old_data"); $db->exec(" CREATE PROCEDURE sec_cleanup_old_data(IN days_to_keep INT) BEGIN -- Nettoyer les métriques de performance DELETE FROM sec_performance_metrics WHERE created_at < DATE_SUB(NOW(), INTERVAL days_to_keep DAY); -- Nettoyer les tentatives de login DELETE FROM sec_failed_login_attempts WHERE attempt_time < DATE_SUB(NOW(), INTERVAL days_to_keep DAY); -- Nettoyer les alertes résolues DELETE FROM sec_alerts WHERE resolved = 1 AND resolved_at < DATE_SUB(NOW(), INTERVAL days_to_keep DAY); -- Retourner le nombre de lignes supprimées SELECT ROW_COUNT() as deleted_rows; END "); echo " ✓ Procédure sec_cleanup_old_data créée\n"; // Réactiver les clés étrangères $db->exec("SET FOREIGN_KEY_CHECKS = 1"); // 8. Vérifier que tout est créé echo "\n8. Vérification finale...\n"; $tables = ['sec_alerts', 'sec_performance_metrics', 'sec_failed_login_attempts', 'sec_blocked_ips']; $allOk = true; foreach ($tables as $table) { $stmt = $db->query("SELECT COUNT(*) as count FROM $table"); if ($stmt) { $result = $stmt->fetch(PDO::FETCH_ASSOC); echo " ✓ Table $table : OK ({$result['count']} enregistrements)\n"; } else { echo " ✗ Table $table : ERREUR\n"; $allOk = false; } } if ($allOk) { echo "\n========================================\n"; echo "✅ TOUTES LES TABLES ONT ÉTÉ CRÉÉES AVEC SUCCÈS\n"; echo "========================================\n\n"; echo "Le système de sécurité est maintenant prêt à être utilisé.\n"; echo "Vous pouvez tester avec : php test_security.php\n\n"; } else { echo "\n⚠️ Certaines tables n'ont pas pu être créées.\n"; echo "Vérifiez les erreurs ci-dessus.\n\n"; } } catch (PDOException $e) { echo "\n❌ ERREUR SQL : " . $e->getMessage() . "\n\n"; echo "Code d'erreur : " . $e->getCode() . "\n"; echo "Vérifiez les permissions et la configuration de la base de données.\n\n"; exit(1); } catch (Exception $e) { echo "\n❌ ERREUR : " . $e->getMessage() . "\n\n"; exit(1); }