From 3b95aa598fa6174e91b998ef1908807a2485d9ba Mon Sep 17 00:00:00 2001 From: mmetc <92726601+mmetc@users.noreply.github.com> Date: Sun, 20 Jul 2025 11:42:28 +0200 Subject: [PATCH] crowdsec: migrate bootgrid -> UIBootGrid (#4816) * bootgrid -> UIBootGrid * server-side filtering, pagination etc. * version bump * add some field defaults; lint --- security/crowdsec/Makefile | 2 +- security/crowdsec/pkg-descr | 5 + .../OPNsense/CrowdSec/AlertsController.php | 18 + .../CrowdSec/Api/AlertsController.php | 67 ++- .../CrowdSec/Api/AppsecconfigsController.php | 47 ++ .../CrowdSec/Api/AppsecrulesController.php | 47 ++ .../CrowdSec/Api/BouncersController.php | 25 +- .../CrowdSec/Api/CollectionsController.php | 47 ++ .../CrowdSec/Api/DecisionsController.php | 103 +++- .../OPNsense/CrowdSec/Api/HubController.php | 33 -- .../CrowdSec/Api/MachinesController.php | 26 +- .../CrowdSec/Api/ParsersController.php | 47 ++ .../CrowdSec/Api/PostoverflowsController.php | 47 ++ .../CrowdSec/Api/ScenariosController.php | 47 ++ .../CrowdSec/Api/ServiceController.php | 32 +- .../CrowdSec/Api/VersionController.php | 3 +- .../CrowdSec/AppsecconfigsController.php | 18 + .../CrowdSec/AppsecrulesController.php | 18 + .../OPNsense/CrowdSec/BouncersController.php | 18 + .../CrowdSec/CollectionsController.php | 18 + .../OPNsense/CrowdSec/DecisionsController.php | 18 + .../OPNsense/CrowdSec/GeneralController.php | 2 +- .../OPNsense/CrowdSec/MachinesController.php | 18 + .../OPNsense/CrowdSec/OverviewController.php | 2 +- .../OPNsense/CrowdSec/ParsersController.php | 18 + .../CrowdSec/PostoverflowsController.php | 18 + .../OPNsense/CrowdSec/ScenariosController.php | 18 + .../app/library/OPNsense/CrowdSec/Util.php | 15 + .../app/models/OPNsense/CrowdSec/General.xml | 2 +- .../models/OPNsense/CrowdSec/Menu/Menu.xml | 11 +- .../app/views/OPNsense/CrowdSec/alerts.volt | 62 +++ .../OPNsense/CrowdSec/appsecconfigs.volt | 36 ++ .../views/OPNsense/CrowdSec/appsecrules.volt | 36 ++ .../app/views/OPNsense/CrowdSec/bouncers.volt | 44 ++ .../views/OPNsense/CrowdSec/collections.volt | 36 ++ .../views/OPNsense/CrowdSec/decisions.volt | 51 ++ .../app/views/OPNsense/CrowdSec/machines.volt | 43 ++ .../app/views/OPNsense/CrowdSec/overview.volt | 246 --------- .../app/views/OPNsense/CrowdSec/parsers.volt | 36 ++ .../OPNsense/CrowdSec/postoverflows.volt | 36 ++ .../views/OPNsense/CrowdSec/scenarios.volt | 36 ++ .../conf/actions.d/actions_crowdsec.conf | 31 +- .../opnsense/www/js/CrowdSec/crowdsec-misc.js | 47 ++ .../src/opnsense/www/js/CrowdSec/crowdsec.js | 473 ------------------ 44 files changed, 1195 insertions(+), 808 deletions(-) create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AlertsController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AppsecconfigsController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AppsecrulesController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/CollectionsController.php delete mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/HubController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ParsersController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/PostoverflowsController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ScenariosController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AppsecconfigsController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AppsecrulesController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/BouncersController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/CollectionsController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/DecisionsController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/MachinesController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/ParsersController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/PostoverflowsController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/ScenariosController.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/library/OPNsense/CrowdSec/Util.php create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/alerts.volt create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/appsecconfigs.volt create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/appsecrules.volt create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/bouncers.volt create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/collections.volt create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/decisions.volt create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/machines.volt delete mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/overview.volt create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/parsers.volt create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/postoverflows.volt create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/scenarios.volt create mode 100644 security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec-misc.js delete mode 100644 security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec.js diff --git a/security/crowdsec/Makefile b/security/crowdsec/Makefile index f61c6c0a0..4de446dd3 100644 --- a/security/crowdsec/Makefile +++ b/security/crowdsec/Makefile @@ -1,5 +1,5 @@ PLUGIN_NAME= crowdsec -PLUGIN_VERSION= 1.0.10 +PLUGIN_VERSION= 1.0.11 PLUGIN_DEPENDS= crowdsec PLUGIN_COMMENT= Lightweight and collaborative security engine PLUGIN_MAINTAINER= marco@crowdsec.net diff --git a/security/crowdsec/pkg-descr b/security/crowdsec/pkg-descr index 7629c6495..4fb585fff 100644 --- a/security/crowdsec/pkg-descr +++ b/security/crowdsec/pkg-descr @@ -8,6 +8,11 @@ WWW: https://crowdsec.net/ Plugin Changelog ================ +1.0.11 + + * convert tables to UIBootGrid (required for opnsense 25.7) + * separate page for each table + 1.0.10 * changed alias names crowdsec*blacklists -> crowdsec*blocklists diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AlertsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AlertsController.php new file mode 100644 index 000000000..b37b60660 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AlertsController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class AlertsController + * @package OPNsense\CrowdSec + */ +class AlertsController extends \OPNsense\Base\IndexController +{ + public function indexAction(): void + { + $this->view->pick('OPNsense/CrowdSec/alerts'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AlertsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AlertsController.php index eb11d2440..ae648a793 100644 --- a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AlertsController.php +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AlertsController.php @@ -6,7 +6,6 @@ namespace OPNsense\CrowdSec\Api; use OPNsense\Base\ApiControllerBase; -use OPNsense\CrowdSec\CrowdSec; use OPNsense\Core\Backend; /** @@ -14,6 +13,48 @@ use OPNsense\Core\Backend; */ class AlertsController extends ApiControllerBase { + /** + * Format scope and value as "scope:value" + * + * @param array $source Array with 'scope' and 'value' keys (can be a decision) + * @return string Formatted string + */ + private function formatScopeValue(array $source): string + { + $scope = $source['scope'] ?? ''; + if ($source['value'] !== '') { + $scope = $scope . ':' . $source['value']; + } + return $scope; + } + + /** + * Summarize decision types as "type1:count1 type2:count2 ..." + * + * @param array $decisions List of decision arrays + * @return string Summary string + */ + private function formatDecisions(array $decisions): string + { + $counts = []; + + foreach ($decisions as $decision) { + if (!isset($decision['type'])) { + continue; + } + + $type = $decision['type']; + $counts[$type] = ($counts[$type] ?? 0) + 1; + } + + $parts = []; + foreach ($counts as $type => $count) { + $parts[] = "{$type}:{$count}"; + } + + return implode(' ', $parts); + } + /** * Retrieve list of alerts * @@ -21,13 +62,27 @@ class AlertsController extends ApiControllerBase * @throws \OPNsense\Base\ModelException * @throws \ReflectionException */ - public function getAction() + public function searchAction(): array { $result = json_decode(trim((new Backend())->configdRun("crowdsec alerts-list")), true); - if ($result !== null) { - // only return valid json type responses - return $result; + if ($result === null) { + return ["message" => "unable to retrieve data"]; } - return ["message" => "unable to list alerts"]; + + $rows = []; + foreach ($result as $alert) { + $source = $alert['source'] ?? []; + $rows[] = [ + 'id' => $alert['id'], + 'value' => $this->formatScopeValue($source ?? []), + 'reason' => $alert['scenario'] ?? '', + 'country' => $source['cn'] ?? '', + 'as' => $source['as_name'] ?? '', + 'decisions' => $this->formatDecisions($alert['decisions'] ?? []), + 'created' => $alert['created_at'] ?? '', + ]; + } + + return $this->searchRecordsetBase($rows); } } diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AppsecconfigsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AppsecconfigsController.php new file mode 100644 index 000000000..aa43e862e --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AppsecconfigsController.php @@ -0,0 +1,47 @@ + + +namespace OPNsense\CrowdSec\Api; + +use OPNsense\Base\ApiControllerBase; +use OPNsense\CrowdSec\Util; + +use OPNsense\Core\Backend; + +/** + * @package OPNsense\CrowdSec + */ +class AppsecconfigsController extends ApiControllerBase +{ + /** + * Retrieve the installed appsec-configs + * + * @return dictionary of items, by type + * @throws \OPNsense\Base\ModelException + * @throws \ReflectionException + */ + public function searchAction(): array + { + $result = json_decode(trim((new Backend())->configdRun("crowdsec appsec-configs-list")), true); + if ($result === null) { + return ["message" => "unable to retrieve data"]; + } + + $items = $result["appsec-configs"]; + + $rows = []; + foreach ($items as $item) { + $rows[] = [ + 'name' => $item['name'], + 'status' => $item['status'] ?? '', + 'local_version' => $item['local_version'] ?? '', + 'local_path' => Util::trimLocalPath($item['local_path'] ?? ''), + 'description' => $item['description'] ?? '', + ]; + } + + return $this->searchRecordsetBase($rows); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AppsecrulesController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AppsecrulesController.php new file mode 100644 index 000000000..067788dab --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AppsecrulesController.php @@ -0,0 +1,47 @@ + + +namespace OPNsense\CrowdSec\Api; + +use OPNsense\Base\ApiControllerBase; +use OPNsense\CrowdSec\Util; + +use OPNsense\Core\Backend; + +/** + * @package OPNsense\CrowdSec + */ +class AppsecrulesController extends ApiControllerBase +{ + /** + * Retrieve the installed appsec-rules + * + * @return dictionary of items, by type + * @throws \OPNsense\Base\ModelException + * @throws \ReflectionException + */ + public function searchAction(): array + { + $result = json_decode(trim((new Backend())->configdRun("crowdsec appsec-rules-list")), true); + if ($result === null) { + return ["message" => "unable to retrieve data"]; + } + + $items = $result["appsec-rules"]; + + $rows = []; + foreach ($items as $item) { + $rows[] = [ + 'name' => $item['name'], + 'status' => $item['status'] ?? '', + 'local_version' => $item['local_version'] ?? '', + 'local_path' => Util::trimLocalPath($item['local_path'] ?? ''), + 'description' => $item['description'] ?? '', + ]; + } + + return $this->searchRecordsetBase($rows); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/BouncersController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/BouncersController.php index 5fbc29524..ecaadbea4 100644 --- a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/BouncersController.php +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/BouncersController.php @@ -6,7 +6,6 @@ namespace OPNsense\CrowdSec\Api; use OPNsense\Base\ApiControllerBase; -use OPNsense\CrowdSec\CrowdSec; use OPNsense\Core\Backend; /** @@ -21,13 +20,27 @@ class BouncersController extends ApiControllerBase * @throws \OPNsense\Base\ModelException * @throws \ReflectionException */ - public function getAction() + public function searchAction(): array { $result = json_decode(trim((new Backend())->configdRun("crowdsec bouncers-list")), true); - if ($result !== null) { - // only return valid json type responses - return $result; + if ($result === null) { + return ["message" => "unable to retrieve data"]; } - return ["message" => "unable to list bouncers"]; + + $rows = []; + foreach ($result as $bouncer) { + $rows[] = [ + 'name' => $bouncer['name'], + 'type' => $bouncer['type'] ?? '', + 'version' => $bouncer['version'] ?? '', + 'created' => $bouncer['created_at'] ?? '', + 'valid' => ($bouncer['revoked'] ?? false) !== true, + 'ip_address' => $bouncer['ip_address'] ?? '', + 'last_seen' => $bouncer['last_pull'] ?? '', + 'os' => $bouncer['os'] ?? '', + ]; + } + + return $this->searchRecordsetBase($rows); } } diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/CollectionsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/CollectionsController.php new file mode 100644 index 000000000..7b9878994 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/CollectionsController.php @@ -0,0 +1,47 @@ + + +namespace OPNsense\CrowdSec\Api; + +use OPNsense\Base\ApiControllerBase; +use OPNsense\CrowdSec\Util; + +use OPNsense\Core\Backend; + +/** + * @package OPNsense\CrowdSec + */ +class CollectionsController extends ApiControllerBase +{ + /** + * Retrieve the installed collections + * + * @return dictionary of items, by type + * @throws \OPNsense\Base\ModelException + * @throws \ReflectionException + */ + public function searchAction(): array + { + $result = json_decode(trim((new Backend())->configdRun("crowdsec collections-list")), true); + if ($result === null) { + return ["message" => "unable to retrieve data"]; + } + + $items = $result["collections"]; + + $rows = []; + foreach ($items as $item) { + $rows[] = [ + 'name' => $item['name'], + 'status' => $item['status'] ?? '', + 'local_version' => $item['local_version'] ?? '', + 'local_path' => Util::trimLocalPath($item['local_path'] ?? ''), + 'description' => $item['description'] ?? '', + ]; + } + + return $this->searchRecordsetBase($rows); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/DecisionsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/DecisionsController.php index e49754fa1..199063d01 100644 --- a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/DecisionsController.php +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/DecisionsController.php @@ -6,14 +6,62 @@ namespace OPNsense\CrowdSec\Api; use OPNsense\Base\ApiControllerBase; -use OPNsense\CrowdSec\CrowdSec; use OPNsense\Core\Backend; + +function unrollDecisions(array $alerts): array +{ + $result = []; + + foreach ($alerts as $alert) { + if (!isset($alert['decisions']) || !is_array($alert['decisions'])) { + continue; + } + + foreach ($alert['decisions'] as $decision) { + // ignore deleted decisions + if (isset($decision['duration']) && str_starts_with($decision['duration'], '-')) { + continue; + } + + $row = $decision; + + // Add parent alert fields with prefix + foreach ($alert as $key => $value) { + if ($key === 'decisions') { + continue; // skip nested array + } + $row["alert_" . $key] = $value; + } + + $result[] = $row; + } + } + + return $result; +} + + /** * @package OPNsense\CrowdSec */ class DecisionsController extends ApiControllerBase { + /** + * Format scope and value as "scope:value" + * + * @param array $source Array with 'scope' and 'value' keys + * @return string Formatted string + */ + private function formatScopeValue(array $source): string + { + $scope = $source['scope'] ?? ''; + if ($source['value'] !== '') { + $scope = $scope . ':' . $source['value']; + } + return $scope; + } + /** * Retrieve list of decisions * @@ -21,29 +69,50 @@ class DecisionsController extends ApiControllerBase * @throws \OPNsense\Base\ModelException * @throws \ReflectionException */ - public function getAction() + public function searchAction(): array { $result = json_decode(trim((new Backend())->configdRun("crowdsec decisions-list")), true); - if ($result !== null) { - // only return valid json type responses - return $result; + if ($result === null) { + return ["message" => "unable to retrieve data"]; } - return ["message" => "unable to list decisions"]; + + $decisions = unrollDecisions($result); + + $rows = []; + foreach ($decisions as $dec) { + $alert_source = $dec['alert_source'] ?? []; + + $rows[] = [ + 'id' => $dec['id'], + 'source' => $dec['origin'] ?? '', + 'scope_value' => $this->formatScopeValue($dec), + 'reason' => $dec['scenario'] ?? '', + 'action' => $dec['type'] ?? '', + 'country' => $alert_source['cn'] ?? '', + 'as' => $alert_source['as_name'] ?? '', + 'events_count' => $dec['alert_events_count'] ?? '', + 'expiration' => $dec['duration'] ?? '', + 'alert_id' => $dec['alert_id'], + ]; + } + + return $this->searchRecordsetBase($rows); } - public function deleteAction($decision_id) + public function delAction($decision_id): array { - if ($this->request->isDelete()) { - $result = (new Backend())->configdRun("crowdsec decisions-delete ${decision_id}"); - if ($result !== null) { - // why does the action return \n\n for empty output? - if (trim($result) === '') { - return ["message" => "OK"]; - } - // TODO handle error - return ["message" => result]; + if ($this->request->isPost()) { + $result = (new Backend())->configdRun("crowdsec decisions-delete {$decision_id}"); + if ($result === null) { + return ["result" => "deleted"]; } - return ["message" => "OK"]; + + // why does the action return \n\n for empty output? + if (trim($result) === '') { + return ["result" => "deleted"]; + } + // TODO assume not found, should handle other errors + return ["result" => "not found"]; } else { $this->response->setStatusCode(405, "Method Not Allowed"); $this->response->setHeader("Allow", "DELETE"); diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/HubController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/HubController.php deleted file mode 100644 index 1bc0fcb89..000000000 --- a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/HubController.php +++ /dev/null @@ -1,33 +0,0 @@ - - -namespace OPNsense\CrowdSec\Api; - -use OPNsense\Base\ApiControllerBase; -use OPNsense\CrowdSec\CrowdSec; -use OPNsense\Core\Backend; - -/** - * @package OPNsense\CrowdSec - */ -class HubController extends ApiControllerBase -{ - /** - * Retrieve the registered hub items - * - * @return dictionary of items, by type - * @throws \OPNsense\Base\ModelException - * @throws \ReflectionException - */ - public function getAction() - { - $result = json_decode(trim((new Backend())->configdRun("crowdsec hub-items")), true); - if ($result !== null) { - // only return valid json type responses - return $result; - } - return ["message" => "unable to list hub items"]; - } -} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/MachinesController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/MachinesController.php index 98e37400b..714a5c252 100644 --- a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/MachinesController.php +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/MachinesController.php @@ -6,7 +6,6 @@ namespace OPNsense\CrowdSec\Api; use OPNsense\Base\ApiControllerBase; -use OPNsense\CrowdSec\CrowdSec; use OPNsense\Core\Backend; /** @@ -15,19 +14,32 @@ use OPNsense\Core\Backend; class MachinesController extends ApiControllerBase { /** - * Retrieve list of registered machines + * Retrieve list of machines * * @return array of machines * @throws \OPNsense\Base\ModelException * @throws \ReflectionException */ - public function getAction() + public function searchAction(): array { $result = json_decode(trim((new Backend())->configdRun("crowdsec machines-list")), true); - if ($result !== null) { - // only return valid json type responses - return $result; + if ($result === null) { + return ["message" => "unable to retrieve data"]; } - return ["message" => "unable to list machines"]; + + $rows = []; + foreach ($result as $machine) { + $rows[] = [ + 'name' => $machine['machineId'], + 'ip_address' => $machine['ipAddress'] ?? '', + 'version' => $machine['version'] ?? '', + 'validated' => $machine['isValidated'] ?? false, + 'created' => $machine['created_at'] ?? '', + 'last_seen' => $machine['last_heartbeat'] ?? '', + 'os' => $machine['os'] ?? '', + ]; + } + + return $this->searchRecordsetBase($rows); } } diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ParsersController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ParsersController.php new file mode 100644 index 000000000..92ad8d112 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ParsersController.php @@ -0,0 +1,47 @@ + + +namespace OPNsense\CrowdSec\Api; + +use OPNsense\Base\ApiControllerBase; +use OPNsense\CrowdSec\Util; + +use OPNsense\Core\Backend; + +/** + * @package OPNsense\CrowdSec + */ +class ParsersController extends ApiControllerBase +{ + /** + * Retrieve the installed parsers + * + * @return dictionary of items, by type + * @throws \OPNsense\Base\ModelException + * @throws \ReflectionException + */ + public function searchAction(): array + { + $result = json_decode(trim((new Backend())->configdRun("crowdsec parsers-list")), true); + if ($result === null) { + return ["message" => "unable to retrieve data"]; + } + + $items = $result["parsers"]; + + $rows = []; + foreach ($items as $item) { + $rows[] = [ + 'name' => $item['name'], + 'status' => $item['status'] ?? '', + 'local_version' => $item['local_version'] ?? '', + 'local_path' => Util::trimLocalPath($item['local_path'] ?? ''), + 'description' => $item['description'] ?? '', + ]; + } + + return $this->searchRecordsetBase($rows); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/PostoverflowsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/PostoverflowsController.php new file mode 100644 index 000000000..0bd818fcb --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/PostoverflowsController.php @@ -0,0 +1,47 @@ + + +namespace OPNsense\CrowdSec\Api; + +use OPNsense\Base\ApiControllerBase; +use OPNsense\CrowdSec\Util; + +use OPNsense\Core\Backend; + +/** + * @package OPNsense\CrowdSec + */ +class PostoverflowsController extends ApiControllerBase +{ + /** + * Retrieve the installed postoverflows + * + * @return dictionary of items, by type + * @throws \OPNsense\Base\ModelException + * @throws \ReflectionException + */ + public function searchAction(): array + { + $result = json_decode(trim((new Backend())->configdRun("crowdsec postoverflows-list")), true); + if ($result === null) { + return ["message" => "unable to retrieve data"]; + } + + $items = $result["postoverflows"]; + + $rows = []; + foreach ($items as $item) { + $rows[] = [ + 'name' => $item['name'], + 'status' => $item['status'] ?? '', + 'local_version' => $item['local_version'] ?? '', + 'local_path' => Util::trimLocalPath($item['local_path'] ?? ''), + 'description' => $item['description'] ?? '', + ]; + } + + return $this->searchRecordsetBase($rows); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ScenariosController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ScenariosController.php new file mode 100644 index 000000000..31f6b87f3 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ScenariosController.php @@ -0,0 +1,47 @@ + + +namespace OPNsense\CrowdSec\Api; + +use OPNsense\Base\ApiControllerBase; +use OPNsense\CrowdSec\Util; + +use OPNsense\Core\Backend; + +/** + * @package OPNsense\CrowdSec + */ +class ScenariosController extends ApiControllerBase +{ + /** + * Retrieve the installed scenarios + * + * @return dictionary of items, by type + * @throws \OPNsense\Base\ModelException + * @throws \ReflectionException + */ + public function searchAction(): array + { + $result = json_decode(trim((new Backend())->configdRun("crowdsec scenarios-list")), true); + if ($result === null) { + return ["message" => "unable to retrieve data"]; + } + + $items = $result["scenarios"]; + + $rows = []; + foreach ($items as $item) { + $rows[] = [ + 'name' => $item['name'], + 'status' => $item['status'] ?? '', + 'local_version' => $item['local_version'] ?? '', + 'local_path' => Util::trimLocalPath($item['local_path'] ?? ''), + 'description' => $item['description'] ?? '', + ]; + } + + return $this->searchRecordsetBase($rows); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ServiceController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ServiceController.php index eaa3a84d3..40403a915 100644 --- a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ServiceController.php +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ServiceController.php @@ -16,8 +16,10 @@ class ServiceController extends ApiControllerBase { /** * reconfigure CrowdSec + * + * @return array Status result */ - public function reloadAction() + public function reloadAction(): array { $status = "failed"; if ($this->request->isPost()) { @@ -36,7 +38,11 @@ class ServiceController extends ApiControllerBase /** * Retrieve status of crowdsec * - * @return array + * @return array{ + * status: string, + * crowdsec-status: string, + * crowdsec-firewall-status: string + * } * @throws \Exception */ public function statusAction() @@ -44,24 +50,30 @@ class ServiceController extends ApiControllerBase $backend = new Backend(); $response = $backend->configdRun("crowdsec crowdsec-status"); - $status = "unknown"; - if (strpos($response, "not running") > 0) { - $status = "stopped"; - } elseif (strpos($response, "is running") > 0) { - $status = "running"; + $crowdsec_status = "unknown"; + if (strpos($response, "not running") !== false) { + $crowdsec_status = "stopped"; + } elseif (strpos($response, "is running") !== false) { + $crowdsec_status = "running"; } $response = $backend->configdRun("crowdsec crowdsec-firewall-status"); $firewall_status = "unknown"; - if (strpos($response, "not running") > 0) { + if (strpos($response, "not running") !== false) { $firewall_status = "stopped"; - } elseif (strpos($response, "is running") > 0) { + } elseif (strpos($response, "is running") !== false) { $firewall_status = "running"; } + $status = "unknown"; + if ($crowdsec_status == $firewall_status) { + $status = $crowdsec_status; + } + return [ - "crowdsec-status" => $status, + "status" => $status, + "crowdsec-status" => $crowdsec_status, "crowdsec-firewall-status" => $firewall_status, ]; } diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/VersionController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/VersionController.php index d6f09e0c1..cea95d0d7 100644 --- a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/VersionController.php +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/VersionController.php @@ -6,7 +6,6 @@ namespace OPNsense\CrowdSec\Api; use OPNsense\Base\ApiControllerBase; -use OPNsense\CrowdSec\CrowdSec; use OPNsense\Core\Backend; /** @@ -21,7 +20,7 @@ class VersionController extends ApiControllerBase * @throws \OPNsense\Base\ModelException * @throws \ReflectionException */ - public function getAction() + public function getAction(): string { return (new Backend())->configdRun("crowdsec version"); } diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AppsecconfigsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AppsecconfigsController.php new file mode 100644 index 000000000..d63e2f6a7 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AppsecconfigsController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class AppsecconfigsController + * @package OPNsense\CrowdSec + */ +class AppsecconfigsController extends \OPNsense\Base\IndexController +{ + public function indexAction(): void + { + $this->view->pick('OPNsense/CrowdSec/appsecconfigs'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AppsecrulesController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AppsecrulesController.php new file mode 100644 index 000000000..ad06ba30a --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/AppsecrulesController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class AppsecrulesController + * @package OPNsense\CrowdSec + */ +class AppsecrulesController extends \OPNsense\Base\IndexController +{ + public function indexAction(): void + { + $this->view->pick('OPNsense/CrowdSec/appsecrules'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/BouncersController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/BouncersController.php new file mode 100644 index 000000000..beb79086d --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/BouncersController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class BouncersController + * @package OPNsense\CrowdSec + */ +class BouncersController extends \OPNsense\Base\IndexController +{ + public function indexAction(): void + { + $this->view->pick('OPNsense/CrowdSec/bouncers'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/CollectionsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/CollectionsController.php new file mode 100644 index 000000000..814c1ebe6 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/CollectionsController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class CollectionsController + * @package OPNsense\CrowdSec + */ +class CollectionsController extends \OPNsense\Base\IndexController +{ + public function indexAction(): void + { + $this->view->pick('OPNsense/CrowdSec/collections'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/DecisionsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/DecisionsController.php new file mode 100644 index 000000000..6cd08cc03 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/DecisionsController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class DecisionsController + * @package OPNsense\CrowdSec + */ +class DecisionsController extends \OPNsense\Base\IndexController +{ + public function indexAction(): void + { + $this->view->pick('OPNsense/CrowdSec/decisions'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/GeneralController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/GeneralController.php index 115ca686c..fc3ea58a3 100644 --- a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/GeneralController.php +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/GeneralController.php @@ -11,7 +11,7 @@ namespace OPNsense\CrowdSec; */ class GeneralController extends \OPNsense\Base\IndexController { - public function indexAction() + public function indexAction(): void { $this->view->pick('OPNsense/CrowdSec/general'); $this->view->generalForm = $this->getForm("general"); diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/MachinesController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/MachinesController.php new file mode 100644 index 000000000..efa867450 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/MachinesController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class MachinesController + * @package OPNsense\CrowdSec + */ +class MachinesController extends \OPNsense\Base\IndexController +{ + public function indexAction(): void + { + $this->view->pick('OPNsense/CrowdSec/machines'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/OverviewController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/OverviewController.php index 6dbd461d5..f15ca5218 100644 --- a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/OverviewController.php +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/OverviewController.php @@ -11,7 +11,7 @@ namespace OPNsense\CrowdSec; */ class OverviewController extends \OPNsense\Base\IndexController { - public function indexAction() + public function indexAction(): void { $this->view->pick('OPNsense/CrowdSec/overview'); } diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/ParsersController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/ParsersController.php new file mode 100644 index 000000000..adb1c6c14 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/ParsersController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class ParsersController + * @package OPNsense\CrowdSec + */ +class ParsersController extends \OPNsense\Base\IndexController +{ + public function indexAction(): void + { + $this->view->pick('OPNsense/CrowdSec/parsers'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/PostoverflowsController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/PostoverflowsController.php new file mode 100644 index 000000000..d20de2e82 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/PostoverflowsController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class PostoverflowsController + * @package OPNsense\CrowdSec + */ +class PostoverflowsController extends \OPNsense\Base\IndexController +{ + public function indexAction() + { + $this->view->pick('OPNsense/CrowdSec/postoverflows'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/ScenariosController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/ScenariosController.php new file mode 100644 index 000000000..d1bdbd4a0 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/ScenariosController.php @@ -0,0 +1,18 @@ + + +namespace OPNsense\CrowdSec; + +/** + * Class ScenariosController + * @package OPNsense\CrowdSec + */ +class ScenariosController extends \OPNsense\Base\IndexController +{ + public function indexAction(): void + { + $this->view->pick('OPNsense/CrowdSec/scenarios'); + } +} diff --git a/security/crowdsec/src/opnsense/mvc/app/library/OPNsense/CrowdSec/Util.php b/security/crowdsec/src/opnsense/mvc/app/library/OPNsense/CrowdSec/Util.php new file mode 100644 index 000000000..e0eb5a0db --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/library/OPNsense/CrowdSec/Util.php @@ -0,0 +1,15 @@ + //OPNsense/crowdsec/general CrowdSec general configuration - 1.0.10 + 1.0.11 diff --git a/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/Menu/Menu.xml b/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/Menu/Menu.xml index c50b8cd43..99e4d4e33 100644 --- a/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/Menu/Menu.xml +++ b/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/Menu/Menu.xml @@ -2,7 +2,16 @@ - + + + + + + + + + + diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/alerts.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/alerts.volt new file mode 100644 index 000000000..ea9f4214c --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/alerts.volt @@ -0,0 +1,62 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + + + + + + + + + + + + + + + + + +
IDValueReasonCountryASDecisionsCreated
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/appsecconfigs.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/appsecconfigs.volt new file mode 100644 index 000000000..53dcfd0ae --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/appsecconfigs.volt @@ -0,0 +1,36 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + + + + + + + + + + + + + + + +
NameStatusVersionPathDescription
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/appsecrules.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/appsecrules.volt new file mode 100644 index 000000000..8e6ce811b --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/appsecrules.volt @@ -0,0 +1,36 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + + + + + + + + + + + + + + + +
NameStatusVersionPathDescription
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/bouncers.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/bouncers.volt new file mode 100644 index 000000000..43d4e0e7f --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/bouncers.volt @@ -0,0 +1,44 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + + + + + + + + + + + + + + + + + + +
NameTypeVersionCreatedValidIP AddressLast SeenOS
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/collections.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/collections.volt new file mode 100644 index 000000000..ca85d2e7b --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/collections.volt @@ -0,0 +1,36 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + + + + + + + + + + + + + + + +
NameStatusVersionPathDescription
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/decisions.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/decisions.volt new file mode 100644 index 000000000..0eb4bc197 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/decisions.volt @@ -0,0 +1,51 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + +Note: the decisions coming from the CAPI (signals collected by the CrowdSec users) do not appear here. +To show them, use cscli decisions list -a in a shell. + + + + + + + + + + + + + + + + + + + + + + + + +
IDSourceScope:ValueReasonActionCountryASEventsExpirationAlert IDCommands
+ + +
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/machines.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/machines.volt new file mode 100644 index 000000000..2b4b81d1d --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/machines.volt @@ -0,0 +1,43 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + + + + + + + + + + + + + + + + + +
NameVersionValidated?IP AddressCreatedLast SeenOS
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/overview.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/overview.volt deleted file mode 100644 index 93f0f2004..000000000 --- a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/overview.volt +++ /dev/null @@ -1,246 +0,0 @@ -{# SPDX-License-Identifier: MIT #} -{# SPDX-FileCopyrightText: © 2021 CrowdSec #} - - - - - - - - -
- Service status: crowdsec ... - firewall bouncer ... -
- - - -
- -
- - - - - - - - - - - - - - - - -
NameIP AddressLast UpdateValidated?Version
-
- -
- - - - - - - - - - - - - - - - - -
NameIP AddressValidLast API PullTypeVersion
-
- -
- - - - - - - - - - - - - - - - -
CollectionStatusVersionPathDescription
-
- -
- - - - - - - - - - - - - - - - -
ScenarioStatusVersionPathDescription
-
- -
- - - - - - - - - - - - - - - - -
ParserStatusVersionPathDescription
-
- -
- - - - - - - - - - - - - - - - -
PostoverflowStatusVersionPathDescription
-
- -
- - - - - - - - - - - - - - - - - - -
IDValueReasonCountryASDecisionsCreated At
-
- -
- Note: the decisions coming from the CAPI (signals collected by the CrowdSec users) do not appear here. - To show them, use cscli decisions list -a in a shell. - - - - - - - - - - - - - - - - - - - - - - -
IDSourceScope:ValueReasonActionCountryASEventsExpirationAlert ID
-
- - - - -
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/parsers.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/parsers.volt new file mode 100644 index 000000000..0ae9c2576 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/parsers.volt @@ -0,0 +1,36 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + + + + + + + + + + + + + + + +
NameStatusVersionPathDescription
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/postoverflows.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/postoverflows.volt new file mode 100644 index 000000000..9008ff224 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/postoverflows.volt @@ -0,0 +1,36 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + + + + + + + + + + + + + + + +
NameStatusVersionPathDescription
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/scenarios.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/scenarios.volt new file mode 100644 index 000000000..a42cd7a62 --- /dev/null +++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/scenarios.volt @@ -0,0 +1,36 @@ +{# SPDX-License-Identifier: MIT #} +{# SPDX-FileCopyrightText: © 2021 CrowdSec #} + + + + + + + + + + + + + + + + + + + +
NameStatusVersionPathDescription
diff --git a/security/crowdsec/src/opnsense/service/conf/actions.d/actions_crowdsec.conf b/security/crowdsec/src/opnsense/service/conf/actions.d/actions_crowdsec.conf index 371cf485d..020fc4669 100644 --- a/security/crowdsec/src/opnsense/service/conf/actions.d/actions_crowdsec.conf +++ b/security/crowdsec/src/opnsense/service/conf/actions.d/actions_crowdsec.conf @@ -57,10 +57,35 @@ parameters:--id %s type:script_output message:crowdsec decisions delete -[hub-items] -command:/usr/local/bin/cscli hub list -o json +[collections-list] +command:/usr/local/bin/cscli collections list -o json type:script_output -message:crowdsec hub list +message:crowdsec collections list + +[scenarios-list] +command:/usr/local/bin/cscli scenarios list -o json +type:script_output +message:crowdsec scenarios list + +[parsers-list] +command:/usr/local/bin/cscli parsers list -o json +type:script_output +message:crowdsec parsers list + +[postoverflows-list] +command:/usr/local/bin/cscli postoverflows list -o json +type:script_output +message:crowdsec postoverflows list + +[appsec-rules-list] +command:/usr/local/bin/cscli appsec-rules list -o json +type:script_output +message:crowdsec appsec-rules list + +[appsec-configs-list] +command:/usr/local/bin/cscli appsec-configs list -o json +type:script_output +message:crowdsec appsec-configs list [machines-list] command:/usr/local/bin/cscli machines list -o json diff --git a/security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec-misc.js b/security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec-misc.js new file mode 100644 index 000000000..23468edbd --- /dev/null +++ b/security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec-misc.js @@ -0,0 +1,47 @@ +/* global moment, $ */ +/* exported CrowdSec */ +/* eslint no-undef: "error" */ +/* eslint semi: "error" */ + +const CrowdSec = (function () { + 'use strict'; + + function _humanizeDate(text) { + return moment(text).fromNow(); + } + + const formatters = { + yesno: function(column, row) { + const val = row[column.id]; + if (val) { + return ''; + } else { + return ''; + } + }, + + datetime: function (column, row) { + const val = row[column.id]; + const parsed = moment(val); + if (!val) { + return ''; + } + if (!parsed.isValid()) { + console.error('Cannot parse timestamp: %s', val); + return '???'; + } + return $('
') + .attr({ + 'data-toggle': 'tooltip', + 'data-placement': 'left', + title: parsed.format(), + }) + .text(_humanizeDate(val)) + .prop('outerHTML'); + }, + }; + + return { + formatters: formatters, + }; +})(); diff --git a/security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec.js b/security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec.js deleted file mode 100644 index 45149807e..000000000 --- a/security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec.js +++ /dev/null @@ -1,473 +0,0 @@ -/* global moment, $ */ -/* exported CrowdSec */ -/* eslint no-undef: "error" */ -/* eslint semi: "error" */ - -const CrowdSec = (function () { - 'use strict'; - - const crowdsec_path = '/usr/local/etc/crowdsec/'; - const _refreshTemplate = - ''; - - const _dataFormatters = { - yesno: function (column, row) { - return _yesno2html(row[column.id]); - }, - - delete: function (column, row) { - const val = row.id; - if (isNaN(val)) { - return ''; - } - return ( - '' - ); - }, - - duration: function (column, row) { - const duration = row[column.id]; - if (!duration) { - return 'n/a'; - } - return $('
') - .attr({ - 'data-toggle': 'tooltip', - 'data-placement': 'left', - title: duration, - }) - .text(_humanizeDuration(duration)) - .prop('outerHTML'); - }, - - datetime: function (column, row) { - const dt = row[column.id]; - const parsed = moment(dt); - if (!dt) { - return ''; - } - if (!parsed.isValid()) { - console.error('Cannot parse timestamp: %s', dt); - return '???'; - } - return $('
') - .attr({ - 'data-toggle': 'tooltip', - 'data-placement': 'left', - title: parsed.format(), - }) - .text(_humanizeDate(dt)) - .prop('outerHTML'); - }, - }; - function _decisionsByType(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; - } - - function _updateFreshness(selector, timestamp) { - const $freshness = $(selector).find('.actionBar .freshness'); - if (timestamp) { - $freshness.data('refresh_timestamp', timestamp); - } else { - timestamp = $freshness.data('refresh_timestamp'); - } - const howlongHuman = '???'; - if (timestamp) { - const howlongms = moment() - moment(timestamp); - howlongHuman = moment.duration(howlongms).humanize(); - } - $freshness.text(howlongHuman + ' ago'); - } - - function _addFreshness(selector) { - // this creates one timer per tab - const freshnessTemplate = - 'Last refresh: '; - $(selector).find('.actionBar').prepend(freshnessTemplate); - setInterval(function () { - _updateFreshness(selector); - }, 5000); - } - - function _refreshTab(selector, url, dataCallback) { - $.ajax({ - url: url, - cache: false, - success: dataCallback, - complete: function () { - _updateFreshness(selector, moment()); - }, - }); - } - - function _parseDuration(duration) { - const re = /(-?)(?:(?:(\d+)h)?(\d+)m)?(\d+).\d+(m?)s/m; - const matches = duration.match(re); - let seconds = 0; - - if (!matches.length) { - throw new Error( - 'Unable to parse the following duration: ' + duration + '.', - ); - } - if (typeof matches[2] !== 'undefined') { - seconds += parseInt(matches[2], 10) * 3600; // hours - } - if (typeof matches[3] !== 'undefined') { - seconds += parseInt(matches[3], 10) * 60; // minutes - } - if (typeof matches[4] !== 'undefined') { - seconds += parseInt(matches[4], 10); // seconds - } - if (parseInt(matches[5], 10) === 'm') { - // units in milliseconds - seconds *= 0.001; - } - if (parseInt(matches[1], 10) === '-') { - // negative - seconds = -seconds; - } - return seconds; - } - - function _humanizeDate(text) { - return moment(text).fromNow(); - } - - function _humanizeDuration(text) { - return moment.duration(_parseDuration(text), 'seconds').humanize(); - } - - function _yesno2html(val) { - if (val) { - return ''; - } else { - return ''; - } - } - - function _initTab(selector, url, dataCallback) { - const $tab = $(selector); - if ($tab.find('table.bootgrid-table').length) { - return; - } - $tab - .find('table') - .on('initialized.rs.jquery.bootgrid', function () { - $(_refreshTemplate) - .on('click', function () { - _refreshTab(selector, url, dataCallback); - }) - .insertBefore($tab.find('.actionBar .actions .dropdown:first')); - _addFreshness(selector); - _refreshTab(selector, url, dataCallback); - }) - .bootgrid({ - caseSensitive: false, - formatters: _dataFormatters, - }); - } - - function _initStatusMachines() { - const url = '/api/crowdsec/machines/get'; - const id = '#machines'; - const dataCallback = function (data) { - const rows = []; - data.map(function (row) { - rows.push({ - name: row.machineId, - ip_address: row.ipAddress || ' ', - last_update: row.updated_at || ' ', - validated: row.isValidated, - version: row.version || ' ', - }); - }); - $(id + ' table') - .bootgrid('clear') - .bootgrid('append', rows); - }; - _initTab(id, url, dataCallback); - } - - function _initStatusCollections() { - const url = '/api/crowdsec/hub/get'; - const id = '#collections'; - const dataCallback = function (data) { - const rows = []; - data.collections.map(function (row) { - rows.push({ - name: row.name, - status: row.status, - local_version: row.local_version || ' ', - local_path: row.local_path - ? row.local_path.replace(crowdsec_path, '') - : ' ', - description: row.description || ' ', - }); - }); - $(id + ' table') - .bootgrid('clear') - .bootgrid('append', rows); - }; - _initTab(id, url, dataCallback); - } - - function _initStatusScenarios() { - const url = '/api/crowdsec/hub/get'; - const id = '#scenarios'; - const dataCallback = function (data) { - const rows = []; - data.scenarios.map(function (row) { - rows.push({ - name: row.name, - status: row.status, - local_version: row.local_version || ' ', - local_path: row.local_path - ? row.local_path.replace(crowdsec_path, '') - : ' ', - description: row.description || ' ', - }); - }); - $(id + ' table') - .bootgrid('clear') - .bootgrid('append', rows); - }; - _initTab(id, url, dataCallback); - } - - function _initStatusParsers() { - const url = '/api/crowdsec/hub/get'; - const id = '#parsers'; - const dataCallback = function (data) { - const rows = []; - data.parsers.map(function (row) { - rows.push({ - name: row.name, - status: row.status, - local_version: row.local_version || ' ', - local_path: row.local_path - ? row.local_path.replace(crowdsec_path, '') - : ' ', - description: row.description || ' ', - }); - }); - $(id + ' table') - .bootgrid('clear') - .bootgrid('append', rows); - }; - _initTab(id, url, dataCallback); - } - - function _initStatusPostoverflows() { - const url = '/api/crowdsec/hub/get'; - const id = '#postoverflows'; - const dataCallback = function (data) { - const rows = []; - data.postoverflows.map(function (row) { - rows.push({ - name: row.name, - status: row.status, - local_version: row.local_version || ' ', - local_path: row.local_path - ? row.local_path.replace(crowdsec_path, '') - : ' ', - description: row.description || ' ', - }); - }); - $(id + ' table') - .bootgrid('clear') - .bootgrid('append', rows); - }; - _initTab(id, url, dataCallback); - } - - function _initStatusBouncers() { - const url = '/api/crowdsec/bouncers/get'; - const id = '#bouncers'; - const dataCallback = function (data) { - const rows = []; - data.map(function (row) { - // TODO - remove || ' ' later, it was fixed for 1.3.3 - rows.push({ - name: row.name, - ip_address: row.ip_address || ' ', - valid: row.revoked ? false : true, - last_pull: row.last_pull, - type: row.type || ' ', - version: row.version || ' ', - }); - }); - $(id + ' table') - .bootgrid('clear') - .bootgrid('append', rows); - }; - _initTab(id, url, dataCallback); - } - - function _initStatusAlerts() { - const url = '/api/crowdsec/alerts/get'; - const id = '#alerts'; - const dataCallback = function (data) { - const rows = []; - data.map(function (row) { - rows.push({ - id: row.id, - value: - row.source.scope + (row.source.value ? ':' + row.source.value : ''), - reason: row.scenario || ' ', - country: row.source.cn || ' ', - as: row.source.as_name || ' ', - decisions: _decisionsByType(row.decisions) || ' ', - created_at: row.created_at, - }); - }); - $(id + ' table') - .bootgrid('clear') - .bootgrid('append', rows); - }; - _initTab(id, url, dataCallback); - } - - function _initStatusDecisions() { - const url = '/api/crowdsec/decisions/get'; - const id = '#decisions'; - const dataCallback = function (data) { - const rows = []; - data.map(function (row) { - row.decisions.map(function (decision) { - // ignore deleted decisions - if (decision.duration.startsWith('-')) { - return; - } - rows.push({ - // search will break on empty values when using .append(). so we use spaces - delete: '', - id: decision.id, - source: decision.origin || ' ', - scope_value: - decision.scope + (decision.value ? ':' + decision.value : ''), - reason: decision.scenario || ' ', - action: decision.type || ' ', - country: row.source.cn || ' ', - as: row.source.as_name || ' ', - events_count: row.events_count, - // XXX pre-parse duration to seconds, and integer type, for sorting - expiration: decision.duration || ' ', - alert_id: row.id || ' ', - }); - }); - }); - $(id + ' table') - .bootgrid('clear') - .bootgrid('append', rows); - }; - _initTab(id, url, dataCallback); - } - - function initService() { - $.ajax({ - url: '/api/crowdsec/service/status', - cache: false, - success: function (data) { - let crowdsecStatus = data['crowdsec-status']; - if (crowdsecStatus === 'unknown') { - crowdsecStatus = 'Unknown'; - } else { - crowdsecStatus = _yesno2html(crowdsecStatus === 'running'); - } - $('#crowdsec-status').html(crowdsecStatus); - - let crowdsecFirewallStatus = data['crowdsec-firewall-status']; - if (crowdsecFirewallStatus === 'unknown') { - crowdsecFirewallStatus = 'Unknown'; - } else { - crowdsecFirewallStatus = _yesno2html( - crowdsecFirewallStatus === 'running', - ); - } - $('#crowdsec-firewall-status').html(crowdsecFirewallStatus); - }, - }); - } - - function deleteDecision(decisionId) { - const $modal = $('#remove-decision-modal'); - $modal.find('.modal-title').text('Delete decision #' + decisionId); - $modal.find('.modal-body').text('Are you sure?'); - $modal.find('#remove-decision-confirm').on('click', function () { - $.ajax({ - // XXX handle errors - url: '/api/crowdsec/decisions/delete/' + decisionId, - method: 'DELETE', - success: function (result) { - if (result && result.message === 'OK') { - $('#decisions table').bootgrid('remove', [decisionId]); - $modal.modal('hide'); - } - }, - }); - }); - $modal.modal('show'); - } - - function init() { - initService(); - - $('#machines_tab').on('click', _initStatusMachines); - $('#collections_tab').on('click', _initStatusCollections); - $('#scenarios_tab').on('click', _initStatusScenarios); - $('#parsers_tab').on('click', _initStatusParsers); - $('#postoverflows_tab').on('click', _initStatusPostoverflows); - $('#bouncers_tab').on('click', _initStatusBouncers); - $('#alerts_tab').on('click', _initStatusAlerts); - $('#decisions_tab').on('click', _initStatusDecisions); - - $('[data-toggle="tooltip"]').tooltip(); - - if (window.location.hash) { - // activate a tab from the hash, if it exists - $(window.location.hash + '_tab').click(); - } else { - // otherwise, machines - $('#machines_tab').click(); - } - - $(window).on('hashchange', function (e) { - $(window.location.hash + '_tab').click(); - }); - - // navigation - if (window.location.hash !== '') { - $('a[href="' + window.location.hash + '"]').click(); - } - $('.nav-tabs a').on('shown.bs.tab', function (e) { - history.pushState(null, null, e.target.hash); - }); - } - - return { - deleteDecision: deleteDecision, - init: init, - }; -})();