Files
SIEM---Wazuh/inc/wazuhapi.class.php
2025-08-27 21:17:28 +02:00

515 lines
16 KiB
PHP

<?php
/*
* Plugin SIEM-Wazuh pour GLPI
* Classe de gestion de l'API Wazuh
*/
if (!defined('GLPI_ROOT')) {
die("Sorry. You can't access this file directly");
}
class PluginSiemWazuhAPI {
private $server;
private $token;
private $config;
const API_VERSION = 'v1';
const TIMEOUT = 30;
const MAX_RETRIES = 3;
/**
* Constructor
*/
public function __construct(PluginSiemWazuhServer $server) {
$this->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;
}
}