Premier commit
This commit is contained in:
171
app/Services/CortexXdrService.php
Normal file
171
app/Services/CortexXdrService.php
Normal file
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CortexXdrService
|
||||
{
|
||||
protected ?string $apiKey;
|
||||
protected ?string $apiKeyId;
|
||||
protected ?string $baseUrl;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apiKey = config('services.cortex.key');
|
||||
$this->apiKeyId = config('services.cortex.id');
|
||||
$this->baseUrl = config('services.cortex.url');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate headers.
|
||||
* Switched to Standard Authentication (Direct Key) based on documentation screenshot.
|
||||
*/
|
||||
protected function getHeaders()
|
||||
{
|
||||
// STANDARD AUTHENTICATION
|
||||
return [
|
||||
'x-xdr-auth-id' => $this->apiKeyId,
|
||||
'Authorization' => $this->apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Incidents
|
||||
* Using pagination to fetch up to $limit incidents.
|
||||
*/
|
||||
public function getIncidents($limit = 1000)
|
||||
{
|
||||
return $this->fetchAll('incidents/get_incidents', 'incidents', $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Endpoints
|
||||
* Using pagination to fetch up to $limit endpoints.
|
||||
*/
|
||||
public function getEndpoints($limit = 1000)
|
||||
{
|
||||
return $this->fetchAll('endpoints/get_endpoint', 'endpoints', $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic fetch method with pagination.
|
||||
*/
|
||||
private function fetchAll($endpoint, $dataKey, $limit)
|
||||
{
|
||||
if (empty($this->apiKey) || empty($this->apiKeyId) || empty($this->baseUrl)) {
|
||||
Log::warning('Cortex XDR credentials missing.');
|
||||
return [];
|
||||
}
|
||||
|
||||
$allResults = [];
|
||||
$batchSize = 100; // API Limit
|
||||
$offset = 0;
|
||||
|
||||
try {
|
||||
while ($offset < $limit) {
|
||||
// Adjust batch size for last page if needed
|
||||
$currentBatchSize = min($batchSize, $limit - $offset);
|
||||
|
||||
// API confusingly uses search_to as count? Or offset + count?
|
||||
// Documentation: "search_to: Integer representing the ending offset" ?
|
||||
// Error message: "0 < search_size <= 100".
|
||||
// Wait, request_data documentation usually says:
|
||||
// search_from: integer
|
||||
// search_to: integer (exclusive end index?) or count?
|
||||
// Let's assume standard Cortex: offset based.
|
||||
// If search_from=0, search_to=100 -> gets 0 to 99 (100 items).
|
||||
|
||||
$response = Http::withHeaders($this->getHeaders())
|
||||
->post("{$this->baseUrl}/public_api/v1/{$endpoint}/", [
|
||||
'request_data' => [
|
||||
'search_from' => $offset,
|
||||
'search_to' => $offset + $currentBatchSize,
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->successful()) {
|
||||
$items = $response->json()['reply'][$dataKey] ?? [];
|
||||
|
||||
if (empty($items)) {
|
||||
break; // No more items
|
||||
}
|
||||
|
||||
$allResults = array_merge($allResults, $items);
|
||||
$offset += count($items);
|
||||
|
||||
// If we got fewer items than requested, we are done
|
||||
if (count($items) < $currentBatchSize) {
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
$err = "Cortex API Error ({$endpoint}): " . $response->status() . ' - ' . $response->body();
|
||||
Log::error($err);
|
||||
throw new \Exception($err);
|
||||
}
|
||||
}
|
||||
|
||||
return $allResults;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Dashboard Summary
|
||||
*/
|
||||
public function getSummary()
|
||||
{
|
||||
try {
|
||||
$incidents = $this->getIncidents(1000); // Limit incidents to 1000 recent
|
||||
$endpoints = $this->getEndpoints(1000); // Limit endpoints to 2000
|
||||
} catch (\Exception $e) {
|
||||
// In dashboard context, we verify configuration separately.
|
||||
// Returning empty arrays here avoids crashing the page if just one call fails.
|
||||
$incidents = [];
|
||||
$endpoints = [];
|
||||
}
|
||||
|
||||
// Calculations
|
||||
$endpointsTotal = count($endpoints);
|
||||
$endpointsConnected = collect($endpoints)->filter(fn($e) => strtolower($e['endpoint_status'] ?? '') === 'connected')->count();
|
||||
$endpointsDisconnected = collect($endpoints)->filter(fn($e) => strtolower($e['endpoint_status'] ?? '') === 'disconnected')->count();
|
||||
|
||||
// Filter only Active Incidents (New or Under Investigation)
|
||||
$activeIncidents = collect($incidents)->filter(fn($i) => in_array($i['status'] ?? '', ['new', 'under_investigation']));
|
||||
|
||||
//die(var_dump($activeIncidents));
|
||||
|
||||
$incidentsCritical = $activeIncidents->where('severity', 'critical')->count();
|
||||
$incidentsHigh = $activeIncidents->where('severity', 'high')->count();
|
||||
$incidentsMedium = $activeIncidents->where('severity', 'medium')->count();
|
||||
$incidentsLow = $activeIncidents->where('severity', 'low')->count();
|
||||
|
||||
// Endpoint Types (Workstation, Server, etc.)
|
||||
$endpointTypes = collect($endpoints)->groupBy(function ($e) {
|
||||
return ucfirst(strtolower($e['endpoint_type'] ?? 'Unknown'));
|
||||
})->map->count();
|
||||
|
||||
return [
|
||||
'incidents_total' => $activeIncidents->count(),
|
||||
'incidents_critical' => $incidentsCritical,
|
||||
'incidents_high' => $incidentsHigh,
|
||||
'incidents_medium' => $incidentsMedium,
|
||||
'incidents_low' => $incidentsLow,
|
||||
'endpoints_total' => $endpointsTotal,
|
||||
'endpoints_connected' => $endpointsConnected,
|
||||
'endpoints_disconnected' => $endpointsDisconnected,
|
||||
'endpoints_types' => $endpointTypes, // New Data
|
||||
'recent_incidents' => collect($incidents)
|
||||
->filter(fn($i) => !in_array($i['status'] ?? '', ['resolved_true_positive', 'resolved_false_positive']))
|
||||
->slice(0, 10)
|
||||
->values()
|
||||
->all()
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user