server = $server; $this->config = new PluginSiemWazuhConfig(); } /** * Test connection to Wazuh server */ public function testConnection() { try { $result = $this->authenticate(); if ($result['success']) { // Test basic API call $info = $this->makeRequest('GET', '/'); if ($info['success']) { return [ 'success' => true, 'message' => __('Connection successful', 'siem-wazuh'), 'data' => $info['data'] ]; } else { return [ 'success' => false, 'message' => __('Connection failed:', 'siem-wazuh') . ' ' . $info['message'] ]; } } else { return [ 'success' => false, 'message' => __('Authentication failed:', 'siem-wazuh') . ' ' . $result['message'] ]; } } catch (Exception $e) { return [ 'success' => false, 'message' => __('Connection error:', 'siem-wazuh') . ' ' . $e->getMessage() ]; } } /** * Authenticate with Wazuh API */ private function authenticate() { if (!empty($this->token)) { return ['success' => true, 'token' => $this->token]; } $url = rtrim($this->server->fields['wazuh_url'], '/') . ':' . $this->server->fields['wazuh_port'] . '/security/user/authenticate'; $credentials = base64_encode($this->server->fields['wazuh_login'] . ':' . $this->server->getPassword()); $headers = [ 'Authorization: Basic ' . $credentials, 'Content-Type: application/json' ]; try { $response = $this->httpRequest('POST', $url, null, $headers); if ($response['success'] && isset($response['data']['data']['token'])) { $this->token = $response['data']['data']['token']; return ['success' => true, 'token' => $this->token]; } else { return [ 'success' => false, 'message' => $response['message'] ?? __('Authentication failed', 'siem-wazuh') ]; } } catch (Exception $e) { return [ 'success' => false, 'message' => $e->getMessage() ]; } } /** * Get alerts from Wazuh */ public function getAlerts($params = []) { $auth_result = $this->authenticate(); if (!$auth_result['success']) { return $auth_result; } $default_params = [ 'offset' => 0, 'limit' => $this->config->getConfiguration('max_alerts_per_sync', 100), 'sort' => '-timestamp', 'rule.level' => '>=' . $this->config->getConfiguration('min_rule_level', 5) ]; // Merge with provided parameters $params = array_merge($default_params, $params); // Construire l'URL avec les paramètres $query_string = http_build_query($params); $endpoint = '/alerts?' . $query_string; return $this->makeRequest('GET', $endpoint); } /** * Get alert by ID */ public function getAlert($alert_id) { $auth_result = $this->authenticate(); if (!$auth_result['success']) { return $auth_result; } return $this->makeRequest('GET', '/alerts/' . $alert_id); } /** * Get agents */ public function getAgents($params = []) { $auth_result = $this->authenticate(); if (!$auth_result['success']) { return $auth_result; } $default_params = [ 'offset' => 0, 'limit' => 500, 'sort' => '+name' ]; $params = array_merge($default_params, $params); $query_string = http_build_query($params); $endpoint = '/agents?' . $query_string; return $this->makeRequest('GET', $endpoint); } /** * Get agent by ID */ public function getAgent($agent_id) { $auth_result = $this->authenticate(); if (!$auth_result['success']) { return $auth_result; } return $this->makeRequest('GET', '/agents/' . $agent_id); } /** * Get rules */ public function getRules($params = []) { $auth_result = $this->authenticate(); if (!$auth_result['success']) { return $auth_result; } $default_params = [ 'offset' => 0, 'limit' => 500, 'sort' => '+id' ]; $params = array_merge($default_params, $params); $query_string = http_build_query($params); $endpoint = '/rules?' . $query_string; return $this->makeRequest('GET', $endpoint); } /** * Get decoders */ public function getDecoders($params = []) { $auth_result = $this->authenticate(); if (!$auth_result['success']) { return $auth_result; } $default_params = [ 'offset' => 0, 'limit' => 500 ]; $params = array_merge($default_params, $params); $query_string = http_build_query($params); $endpoint = '/decoders?' . $query_string; return $this->makeRequest('GET', $endpoint); } /** * Get cluster status */ public function getClusterStatus() { $auth_result = $this->authenticate(); if (!$auth_result['success']) { return $auth_result; } return $this->makeRequest('GET', '/cluster/status'); } /** * Get manager information */ public function getManagerInfo() { $auth_result = $this->authenticate(); if (!$auth_result['success']) { return $auth_result; } return $this->makeRequest('GET', '/manager/info'); } /** * Make API request */ private function makeRequest($method, $endpoint, $data = null, $extra_headers = []) { if (!$this->token) { $auth_result = $this->authenticate(); if (!$auth_result['success']) { return $auth_result; } } $url = rtrim($this->server->fields['wazuh_url'], '/') . ':' . $this->server->fields['wazuh_port'] . $endpoint; $headers = array_merge([ 'Authorization: Bearer ' . $this->token, 'Content-Type: application/json' ], $extra_headers); return $this->httpRequest($method, $url, $data, $headers); } /** * Make HTTP request with retry logic */ private function httpRequest($method, $url, $data = null, $headers = []) { $retries = 0; $last_error = null; while ($retries < self::MAX_RETRIES) { try { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => self::TIMEOUT, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers, CURLOPT_USERAGENT => 'GLPI-SIEM-Wazuh-Plugin/1.0' ]); if ($data !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($data) ? json_encode($data) : $data); } // Logging for debug mode if ($this->config->getConfiguration('debug_mode', 0)) { $this->logDebug("HTTP Request: $method $url", [ 'headers' => $headers, 'data' => $data, 'retry' => $retries ]); } $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curl_error = curl_error($ch); curl_close($ch); if ($response === false) { throw new Exception("cURL Error: $curl_error"); } $decoded_response = json_decode($response, true); if ($this->config->getConfiguration('debug_mode', 0)) { $this->logDebug("HTTP Response: $http_code", [ 'response' => $decoded_response, 'raw_response' => substr($response, 0, 1000) ]); } if ($http_code >= 200 && $http_code < 300) { return [ 'success' => true, 'data' => $decoded_response, 'http_code' => $http_code ]; } elseif ($http_code == 401) { // Token expired, clear it and retry authentication $this->token = null; if ($retries < self::MAX_RETRIES - 1) { $retries++; continue; } } $error_message = "HTTP $http_code"; if ($decoded_response && isset($decoded_response['detail'])) { $error_message .= ": " . $decoded_response['detail']; } elseif ($decoded_response && isset($decoded_response['message'])) { $error_message .= ": " . $decoded_response['message']; } throw new Exception($error_message); } catch (Exception $e) { $last_error = $e; $retries++; if ($retries < self::MAX_RETRIES) { sleep(pow(2, $retries)); // Exponential backoff continue; } $this->logError("HTTP Request failed after $retries retries", [ 'url' => $url, 'method' => $method, 'error' => $e->getMessage() ]); break; } } return [ 'success' => false, 'message' => $last_error ? $last_error->getMessage() : __('Request failed after maximum retries', 'siem-wazuh') ]; } /** * Query Wazuh Indexer (OpenSearch/Elasticsearch) */ public function queryIndexer($index, $query = [], $params = []) { if (empty($this->server->fields['indexer_url'])) { return [ 'success' => false, 'message' => __('Indexer not configured', 'siem-wazuh') ]; } $url = rtrim($this->server->fields['indexer_url'], '/') . ':' . $this->server->fields['indexer_port'] . '/' . $index . '/_search'; $headers = [ 'Content-Type: application/json' ]; if (!empty($this->server->fields['indexer_login'])) { $credentials = base64_encode($this->server->fields['indexer_login'] . ':' . $this->server->getPassword('indexer_password')); $headers[] = 'Authorization: Basic ' . $credentials; } $default_query = [ 'size' => $this->config->getConfiguration('max_alerts_per_sync', 100), 'sort' => [ ['timestamp' => ['order' => 'desc']] ] ]; $final_query = array_merge($default_query, $query); return $this->httpRequest('POST', $url, $final_query, $headers); } /** * Get recent alerts from indexer */ public function getRecentAlertsFromIndexer($minutes = 60) { $query = [ 'query' => [ 'bool' => [ 'must' => [ [ 'range' => [ 'timestamp' => [ 'gte' => 'now-' . $minutes . 'm', 'lt' => 'now' ] ] ] ] ] ] ]; $min_level = $this->config->getConfiguration('min_rule_level', 5); if ($min_level > 0) { $query['query']['bool']['must'][] = [ 'range' => [ 'rule.level' => [ 'gte' => $min_level ] ] ]; } return $this->queryIndexer('wazuh-alerts-*', $query); } /** * Log debug message */ private function logDebug($message, $context = []) { if ($this->config->getConfiguration('debug_mode', 0)) { $this->log('debug', $message, $context); } } /** * Log error message */ private function logError($message, $context = []) { $this->log('error', $message, $context); } /** * Log message */ private function log($level, $message, $context = []) { global $DB; $log_levels = ['debug', 'info', 'warning', 'error', 'critical']; $current_log_level = $this->config->getConfiguration('log_level', 'info'); $current_level_index = array_search($current_log_level, $log_levels); $message_level_index = array_search($level, $log_levels); // Only log if message level is equal or higher than configured level if ($message_level_index >= $current_level_index) { try { $DB->insert('glpi_plugin_siem_wazuh_logs', [ 'wazuh_server_id' => $this->server->getID(), 'level' => $level, 'message' => $message, 'context' => json_encode($context), 'date_creation' => $_SESSION['glpi_currenttime'] ]); } catch (Exception $e) { // Ignore logging errors to prevent recursion error_log("SIEM-Wazuh Plugin: Failed to log message - " . $e->getMessage()); } } } /** * Get API statistics */ public function getStatistics() { global $DB; $stats = []; // Requests count by server $iterator = $DB->request([ 'SELECT' => ['COUNT' => '* as count'], 'FROM' => 'glpi_plugin_siem_wazuh_logs', 'WHERE' => [ 'wazuh_server_id' => $this->server->getID(), 'date_creation' => ['>=', date('Y-m-d 00:00:00')] ] ]); $stats['requests_today'] = $iterator->current()['count']; // Error rate $iterator = $DB->request([ 'SELECT' => ['COUNT' => '* as count'], 'FROM' => 'glpi_plugin_siem_wazuh_logs', 'WHERE' => [ 'wazuh_server_id' => $this->server->getID(), 'level' => ['IN', ['error', 'critical']], 'date_creation' => ['>=', date('Y-m-d 00:00:00')] ] ]); $stats['errors_today'] = $iterator->current()['count']; $stats['error_rate'] = $stats['requests_today'] > 0 ? round(($stats['errors_today'] / $stats['requests_today']) * 100, 2) : 0; return $stats; } }