Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,73 @@ private function formatDecisions(array $decisions): string
return implode(' ', $parts);
}

/**
* Retrieve detailed information for a single alert
*
* @param string $alert_id Alert ID to inspect
* @return array Alert details
*/
public function getAction($alert_id): array
{
if (!ctype_digit(strval($alert_id))) {
return ["message" => "invalid alert id"];
}

$backend = new Backend();
$result = json_decode(trim($backend->configdRun("crowdsec alerts-inspect {$alert_id}")), true);
if ($result === null) {
return ["message" => "unable to retrieve alert details"];
}

$source = $result['source'] ?? [];

$decisions = [];
foreach ($result['decisions'] ?? [] as $dec) {
$decisions[] = [
'id' => $dec['id'] ?? '',
'scope' => ($dec['scope'] ?? '') . ':' . ($dec['value'] ?? ''),
'type' => $dec['type'] ?? '',
'duration' => $dec['duration'] ?? '',
'origin' => $dec['origin'] ?? '',
];
}

$events = [];
foreach ($result['events'] ?? [] as $evt) {
$meta = [];
foreach ($evt['meta'] ?? [] as $m) {
$meta[$m['key'] ?? ''] = $m['value'] ?? '';
}
$events[] = [
'timestamp' => $evt['timestamp'] ?? '',
'meta' => $meta,
];
}

return [
'alert' => [
'id' => $result['id'] ?? '',
'created_at' => $result['created_at'] ?? '',
'machine_id' => $result['machine_id'] ?? '',
'simulated' => $result['simulated'] ?? false,
'remediation' => $result['remediation'] ?? false,
'scenario' => $result['scenario'] ?? '',
'message' => $result['message'] ?? '',
'events_count' => $result['events_count'] ?? 0,
'scope_value' => $this->formatScopeValue($source),
'country' => $source['cn'] ?? '',
'as_name' => $source['as_name'] ?? '',
'as_number' => $source['as_number'] ?? '',
'ip_range' => $source['range'] ?? '',
'start_at' => $result['start_at'] ?? '',
'stop_at' => $result['stop_at'] ?? '',
'uuid' => $result['uuid'] ?? '',
'decisions' => $decisions,
'events' => $events,
],
];
}

/**
* Retrieve list of alerts
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ public function searchAction(): array

public function delAction($decision_id): array
{
if (!ctype_digit(strval($decision_id))) {
return ["result" => "invalid decision id"];
}

if ($this->request->isPost()) {
$result = (new Backend())->configdRun("crowdsec decisions-delete {$decision_id}");
if ($result === null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,119 @@
{# SPDX-License-Identifier: MIT #}
{# SPDX-FileCopyrightText: © 2021 CrowdSec <info@crowdsec.net> #}

<style>
#alertDetailBody .detail-label { width: 150px; }
#alertDetailBody .detail-value-wrap { word-break: break-all; }
#alertDetailBody h4 { margin-top: 20px; padding-left: 8px; }
#alertDetailBody h5 { padding-left: 8px; }
</style>

<script src="/ui/js/moment-with-locales.min.js"></script>
<script src="/ui/js/CrowdSec/crowdsec-misc.js"></script>
<script>
"use strict";

$(function() {
const decisionsByType = function(decisions) {
const dectypes = {};
if (!decisions) {
return '';
}
decisions.map(function (decision) {
// TODO ignore negative expiration?
dectypes[decision.type] = dectypes[decision.type]
? dectypes[decision.type] + 1
: 1;
});
let ret = '';
for (const type in dectypes) {
if (ret !== '') {
ret += ' ';
}
ret += type + ':' + dectypes[type];
}
return ret;
};

$("#cscli_alerts").UIBootgrid({
search: '/api/crowdsec/alerts/search/',
options: {
selection: false,
multiSelect: false,
formatters: {
"created": CrowdSec.formatters.datetime,
"commands": function(column, row) {
return '<button type="button" class="btn btn-xs btn-default alert-inspect" ' +
'data-alert-id="' + row.id + '" title="Inspect">' +
'<span class="fa fa-fw fa-info-circle"></span></button>';
},
},
}
});

$(document).on('click', '.alert-inspect', function() {
var alertId = $(this).data('alert-id');
var $body = $('#alertDetailBody');

$body.html('<div class="text-center"><i class="fa fa-spinner fa-spin fa-2x"></i></div>');
$('#alertDetailModal').modal('show');

$.getJSON('/api/crowdsec/alerts/get/' + alertId, function(data) {
if (data.message) {
$body.html('<div class="alert alert-danger">' + data.message + '</div>');
return;
}

var a = data.alert;

var html = '<table class="table table-condensed table-striped">' +
'<tbody>' +
'<tr><td class="detail-label"><strong>ID</strong></td><td>' + a.id + '</td></tr>' +
'<tr><td><strong>Date</strong></td><td>' + a.created_at + '</td></tr>' +
'<tr><td><strong>Machine</strong></td><td>' + a.machine_id + '</td></tr>' +
'<tr><td><strong>Simulation</strong></td><td>' + (a.simulated ? 'true' : 'false') + '</td></tr>' +
'<tr><td><strong>Remediation</strong></td><td>' + (a.remediation ? 'true' : 'false') + '</td></tr>' +
'<tr><td><strong>Reason</strong></td><td>' + a.scenario + '</td></tr>' +
'<tr><td><strong>Events Count</strong></td><td>' + a.events_count + '</td></tr>' +
'<tr><td><strong>Scope:Value</strong></td><td>' + a.scope_value + '</td></tr>' +
'<tr><td><strong>Country</strong></td><td>' + a.country + '</td></tr>' +
'<tr><td><strong>AS</strong></td><td>' + a.as_name + (a.as_number ? ' (AS' + a.as_number + ')' : '') + '</td></tr>' +
'<tr><td><strong>IP Range</strong></td><td>' + a.ip_range + '</td></tr>' +
'<tr><td><strong>Begin</strong></td><td>' + a.start_at + '</td></tr>' +
'<tr><td><strong>End</strong></td><td>' + a.stop_at + '</td></tr>' +
'<tr><td><strong>UUID</strong></td><td>' + a.uuid + '</td></tr>' +
'</tbody></table>';

if (a.decisions && a.decisions.length > 0) {
html += '<h4>Active Decisions</h4>' +
'<table class="table table-condensed table-striped">' +
'<thead><tr>' +
'<th>ID</th><th>Scope:Value</th><th>Action</th><th>Expiration</th><th>Origin</th>' +
'</tr></thead><tbody>';

for (var i = 0; i < a.decisions.length; i++) {
var d = a.decisions[i];
html += '<tr>' +
'<td>' + d.id + '</td>' +
'<td>' + d.scope + '</td>' +
'<td>' + d.type + '</td>' +
'<td>' + d.duration + '</td>' +
'<td>' + d.origin + '</td>' +
'</tr>';
}

html += '</tbody></table>';
}

if (a.events && a.events.length > 0) {
html += '<h4>Events</h4>';

for (var i = 0; i < a.events.length; i++) {
var evt = a.events[i];
var meta = evt.meta || {};

if (a.events.length > 1) {
html += '<h5>Event ' + (i + 1) + ' — ' + $('<span>').text(evt.timestamp).html() + '</h5>';
}

html += '<table class="table table-condensed table-striped"><tbody>';

var keys = Object.keys(meta).sort();
for (var j = 0; j < keys.length; j++) {
var escaped = $('<span>').text(meta[keys[j]]).html();
html += '<tr><td class="detail-label"><strong>' + keys[j] + '</strong></td>' +
'<td class="detail-value-wrap">' + escaped + '</td></tr>';
}

html += '</tbody></table>';
}
}

$body.html(html);
}).fail(function() {
$body.html('<div class="alert alert-danger">Failed to retrieve alert details.</div>');
});
});

updateServiceControlUI('crowdsec');
});
</script>
Expand All @@ -53,10 +128,27 @@
<th data-column-id="as">AS</th>
<th data-column-id="decisions">Decisions</th>
<th data-column-id="created" data-formatter="created">Created</th>
<th data-column-id="commands" data-formatter="commands" data-sortable="false">Details</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
</tfoot>
</table>

<div class="modal fade" id="alertDetailModal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span></button>
<h4 class="modal-title">Alert Details</h4>
</div>
<div class="modal-body" id="alertDetailBody">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ command:/usr/local/bin/cscli alerts list -l 0 -o json
type:script_output
message:crowdsec alerts list

[alerts-inspect]
command:/usr/local/bin/cscli alerts inspect -o json
parameters:%s
type:script_output
message:crowdsec alerts inspect

[bouncers-list]
command:/usr/local/bin/cscli bouncers list -o json
type:script_output
Expand Down