first sync
This commit is contained in:
515
inc/wazuhapi.class.php
Normal file
515
inc/wazuhapi.class.php
Normal file
@@ -0,0 +1,515 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user