From ce4469f458926917af4bb3cb40c254f1d6425d84 Mon Sep 17 00:00:00 2001
From: mmetc <92726601+mmetc@users.noreply.github.com>
Date: Tue, 10 May 2022 11:59:42 +0200
Subject: [PATCH] new plugin: security/crowdsec (#2945)
This is a plugin we developed to provide configuration and a basic UI for the crowdsec IDS and IPS. It depends on a couple of binaries recently added to ports.conf
Adding machines (servers, other firewalls) and advanced configuration are not managed by the UI but available from the command line.
---
security/crowdsec/+POST_DEINSTALL.post | 50 ++
security/crowdsec/+POST_INSTALL.post | 11 +
security/crowdsec/+PRE_DEINSTALL.pre | 8 +
security/crowdsec/LICENSE | 21 +
security/crowdsec/Makefile | 10 +
security/crowdsec/pkg-descr | 49 ++
.../crowdsec/src/etc/cron.d/oscrowdsec.cron | 9 +
.../src/etc/crowdsec/acquis.d/opnsense.yaml | 15 +
.../src/etc/inc/plugins.inc.d/crowdsec.inc | 95 ++++
security/crowdsec/src/etc/rc.d/oscrowdsec | 104 +++++
.../src/etc/rc.syshook.d/start/50-crowdsec | 6 +
.../CrowdSec/Api/AlertsController.php | 33 ++
.../CrowdSec/Api/BouncersController.php | 33 ++
.../CrowdSec/Api/CollectionsController.php | 33 ++
.../CrowdSec/Api/DecisionsController.php | 53 +++
.../CrowdSec/Api/GeneralController.php | 17 +
.../CrowdSec/Api/MachinesController.php | 33 ++
.../CrowdSec/Api/ParsersController.php | 33 ++
.../CrowdSec/Api/PostoverflowsController.php | 33 ++
.../CrowdSec/Api/ScenariosController.php | 33 ++
.../CrowdSec/Api/ServiceController.php | 78 ++++
.../CrowdSec/Api/VersionController.php | 28 ++
.../OPNsense/CrowdSec/GeneralController.php | 19 +
.../OPNsense/CrowdSec/OverviewController.php | 18 +
.../OPNsense/CrowdSec/forms/general.xml | 95 ++++
.../app/models/OPNsense/CrowdSec/ACL/ACL.xml | 9 +
.../app/models/OPNsense/CrowdSec/General.php | 12 +
.../app/models/OPNsense/CrowdSec/General.xml | 56 +++
.../models/OPNsense/CrowdSec/Menu/Menu.xml | 8 +
.../app/views/OPNsense/CrowdSec/general.volt | 142 ++++++
.../app/views/OPNsense/CrowdSec/overview.volt | 249 ++++++++++
.../scripts/OPNsense/CrowdSec/hub-upgrade.sh | 18 +
.../scripts/OPNsense/CrowdSec/reconfigure.py | 94 ++++
.../scripts/OPNsense/CrowdSec/reconfigure.sh | 33 ++
.../conf/actions.d/actions_crowdsec.conf | 94 ++++
.../templates/OPNsense/CrowdSec/+TARGETS | 4 +
.../OPNsense/CrowdSec/crowdsec.rc.conf.d | 11 +
.../CrowdSec/crowdsec_firewall.rc.conf.d | 11 +
.../OPNsense/CrowdSec/oscrowdsec.rc.conf.d | 1 +
.../templates/OPNsense/CrowdSec/settings.json | 5 +
.../src/opnsense/www/js/CrowdSec/crowdsec.js | 426 ++++++++++++++++++
41 files changed, 2090 insertions(+)
create mode 100755 security/crowdsec/+POST_DEINSTALL.post
create mode 100755 security/crowdsec/+POST_INSTALL.post
create mode 100755 security/crowdsec/+PRE_DEINSTALL.pre
create mode 100644 security/crowdsec/LICENSE
create mode 100644 security/crowdsec/Makefile
create mode 100644 security/crowdsec/pkg-descr
create mode 100644 security/crowdsec/src/etc/cron.d/oscrowdsec.cron
create mode 100644 security/crowdsec/src/etc/crowdsec/acquis.d/opnsense.yaml
create mode 100644 security/crowdsec/src/etc/inc/plugins.inc.d/crowdsec.inc
create mode 100755 security/crowdsec/src/etc/rc.d/oscrowdsec
create mode 100755 security/crowdsec/src/etc/rc.syshook.d/start/50-crowdsec
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AlertsController.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/BouncersController.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/CollectionsController.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/DecisionsController.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/GeneralController.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/MachinesController.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/Api/ServiceController.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/VersionController.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/GeneralController.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/OverviewController.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/forms/general.xml
create mode 100644 security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/ACL/ACL.xml
create mode 100644 security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/General.php
create mode 100644 security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/General.xml
create mode 100644 security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/Menu/Menu.xml
create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/general.volt
create mode 100644 security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/overview.volt
create mode 100755 security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/hub-upgrade.sh
create mode 100755 security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/reconfigure.py
create mode 100755 security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/reconfigure.sh
create mode 100644 security/crowdsec/src/opnsense/service/conf/actions.d/actions_crowdsec.conf
create mode 100644 security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/+TARGETS
create mode 100644 security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/crowdsec.rc.conf.d
create mode 100644 security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/crowdsec_firewall.rc.conf.d
create mode 100644 security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/oscrowdsec.rc.conf.d
create mode 100644 security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/settings.json
create mode 100644 security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec.js
diff --git a/security/crowdsec/+POST_DEINSTALL.post b/security/crowdsec/+POST_DEINSTALL.post
new file mode 100755
index 000000000..536a32529
--- /dev/null
+++ b/security/crowdsec/+POST_DEINSTALL.post
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+# Removing the plugin from the web interface will autoremove the dependencies
+# too, and here we have to delete the files in rc.conf.d (because they are
+# generated from templates when the configuration is saved, and the package
+# system did not keep track of them).
+
+# But.. If the plugin is removed from the command line (which does not happen
+# outside of testing conditions), the crowdsec and bouncer services will not be
+# removed. However, since we deleted the files that enabled these services,
+# they will be disabled at the next reboot.
+
+rm -f /etc/rc.conf.d/crowdsec \
+ /etc/rc.conf.d/crowdsec_firewall \
+ /etc/rc.conf.d/oscrowdsec
+
+
+# Remove aliases and with them, the rules. We don't have plugin files
+# anymore so we do that on the fly.
+
+/usr/local/bin/php <<'EOT'
+aliases->alias->iterateItems() as $index => $alias) {
+ if (strval($alias->name) == $name) {
+ if ($model->aliases->alias->del($index)) {
+ $model->serializeToConfig();
+ Config::getInstance()->save();
+ }
+ }
+ }
+}
+
+removeAlias('crowdsec_blacklists');
+removeAlias('crowdsec6_blacklists');
+EOT
+
+
+# apply the configuration changes to the packet filter
+configctl filter reload
diff --git a/security/crowdsec/+POST_INSTALL.post b/security/crowdsec/+POST_INSTALL.post
new file mode 100755
index 000000000..6a6497449
--- /dev/null
+++ b/security/crowdsec/+POST_INSTALL.post
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# the configuration file used in reconfigure (i.e. settings.json) may eventually
+# have credentials, so we create a directory to contain it -- the directory
+# permissions will be copied to the file while generating the jinja template.
+
+# shellcheck disable=SC2174
+mkdir -p -m 0700 /usr/local/etc/crowdsec/opnsense
+
+configctl crowdsec reconfigure
+
diff --git a/security/crowdsec/+PRE_DEINSTALL.pre b/security/crowdsec/+PRE_DEINSTALL.pre
new file mode 100755
index 000000000..9f7107ef0
--- /dev/null
+++ b/security/crowdsec/+PRE_DEINSTALL.pre
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# need to temporarily stop the bouncer to remove all the rules
+service crowdsec_firewall stop >/dev/null 2>&1 | :
+
+# the rest of the cleanup is done in the post-deinstall script, otherwise
+# the plugin recreates the objects during "filter reload".
+
diff --git a/security/crowdsec/LICENSE b/security/crowdsec/LICENSE
new file mode 100644
index 000000000..92d86fc27
--- /dev/null
+++ b/security/crowdsec/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020-2021 Crowdsec
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/security/crowdsec/Makefile b/security/crowdsec/Makefile
new file mode 100644
index 000000000..fb0653f19
--- /dev/null
+++ b/security/crowdsec/Makefile
@@ -0,0 +1,10 @@
+PLUGIN_NAME= crowdsec
+PLUGIN_VERSION= 0.2
+PLUGIN_DEVEL= yes
+#PLUGIN_REVISION= 1
+PLUGIN_DEPENDS= crowdsec
+PLUGIN_COMMENT= Lightweight and collaborative security engine
+PLUGIN_MAINTAINER= marco@crowdsec.net
+PLUGIN_WWW= https://crowdsec.net/
+
+.include "../../Mk/plugins.mk"
diff --git a/security/crowdsec/pkg-descr b/security/crowdsec/pkg-descr
new file mode 100644
index 000000000..2cd4f07c1
--- /dev/null
+++ b/security/crowdsec/pkg-descr
@@ -0,0 +1,49 @@
+Crowdsec is an open-source, lightweight software, detecting peers with
+aggressive behaviors to prevent them from accessing your systems. Its user
+friendly design and assistance offers a low technical barrier of entry and
+nevertheless a high security gain.
+
+WWW: https://crowdsec.net/
+
+Plugin Changelog
+================
+
+0.2
+
+* first published release
+* added options `lapi_enabled`, `crowdsec_firewall_verbose`
+* removed options `crowdsec_flags`, `crowdsec_firewall_flags`
+* changed default for `agent_enabled`, `firewall_bouncer_enabled` to 1
+
+0.1
+
+* fixed packet tags with ipv6
+* custom `crowdsec_flags`, `crosdsec_firewall_flags`
+
+0.0.9
+
+* fixed the javascript, 0.0.8 had a syntax error
+* new option: rules_tag
+* new option: lapi_manual_configuration
+* ipv4/ipv6 validation with regexp
+
+0.0.8
+
+* crowdsec update 1.3.2
+* configurable `rules_log` and LAPI address/port
+
+0.0.7
+
+* automated removal of Alias objects when the plugin is uninstalled
+
+0.0.6
+
+* crowdsec update 1.3.1.r1
+* bouncer update to 0.0.23.r1
+* automated creation of Alias and Rule objects
+
+0.0.5
+
+* fixed an issue that prevented the bouncer from banning IPs on opnsense
+* fixed support for notification plugins
+
diff --git a/security/crowdsec/src/etc/cron.d/oscrowdsec.cron b/security/crowdsec/src/etc/cron.d/oscrowdsec.cron
new file mode 100644
index 000000000..2ec072c40
--- /dev/null
+++ b/security/crowdsec/src/etc/cron.d/oscrowdsec.cron
@@ -0,0 +1,9 @@
+# DO NOT EDIT THIS FILE -- OPNsense auto-generated file
+#
+# User-defined crontab files can be loaded via /etc/cron.d
+# or /usr/local/etc/cron.d and follow the same format as
+# /etc/crontab, see the crontab(5) manual page.
+SHELL=/bin/sh
+PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
+#minute hour mday month wday who command
+0 1,13 * * * root /usr/local/opnsense/scripts/OPNsense/CrowdSec/hub-upgrade.sh
diff --git a/security/crowdsec/src/etc/crowdsec/acquis.d/opnsense.yaml b/security/crowdsec/src/etc/crowdsec/acquis.d/opnsense.yaml
new file mode 100644
index 000000000..905be2256
--- /dev/null
+++ b/security/crowdsec/src/etc/crowdsec/acquis.d/opnsense.yaml
@@ -0,0 +1,15 @@
+#
+# Before 22.1, OPNsense used circular logs under /var/log/*.log that
+# can still be around. They are old, in binary format and are not needed by crowdsec.
+#
+# For this reason we don't scan /var/log/*.log, but some plugins can write
+# their (plaintext) logs in that location, in such case add their pathnames too.
+#
+
+filenames:
+ # ssh
+ - /var/log/audit/*.log
+ # web admin
+ - /var/log/lighttpd/*.log
+labels:
+ type: syslog
diff --git a/security/crowdsec/src/etc/inc/plugins.inc.d/crowdsec.inc b/security/crowdsec/src/etc/inc/plugins.inc.d/crowdsec.inc
new file mode 100644
index 000000000..a1e2eb6b0
--- /dev/null
+++ b/security/crowdsec/src/etc/inc/plugins.inc.d/crowdsec.inc
@@ -0,0 +1,95 @@
+
+
+use OPNsense\Core\Config;
+use OPNsense\Firewall\Alias;
+use OPNsense\Firewall\Plugin;
+
+function add_alias_if_not_exist($name, $description, $proto) {
+ $model = new Alias();
+
+ if ($model->getByName($name) != null) {
+ return;
+ }
+
+ $new_alias = $model->aliases->alias->Add();
+ $new_alias->name = $name;
+ $new_alias->description = $description;
+ $new_alias->proto = $proto;
+ $new_alias->type = 'external';
+ $model->serializeToConfig();
+ Config::getInstance()->save();
+}
+
+function crowdsec_firewall(Plugin $fw)
+{
+ global $config;
+
+ $general = $config['OPNsense']['crowdsec']['general'];
+
+ $bouncer_enabled = isset($general['firewall_bouncer_enabled']) && $general['firewall_bouncer_enabled'];
+
+ if (!$bouncer_enabled) {
+ return;
+ }
+
+ $rules_log_enabled = isset($general['rules_log']) && $general['rules_log'];
+
+ $rules_tag = "";
+ if (isset($general['rules_tag'])) {
+ $rules_tag = $general['rules_tag'];
+ };
+
+ add_alias_if_not_exist('crowdsec_blacklists', 'CrowdSec (IPv4)', 'IPv4');
+
+ // https://github.com/opnsense/core/blob/master/src/opnsense/mvc/app/library/OPNsense/Firewall/FilterRule.php
+
+ $fw->registerFilterRule(
+ 1, /* priority */
+ array(
+ 'ipprotocol' => 'inet',
+ 'descr' => 'CrowdSec (IPv4)',
+ 'from' => '$crowdsec_blacklists', # $ to reference an alias
+ 'type' => 'block',
+ 'log' => $rules_log_enabled,
+ 'tag' => $rules_tag,
+ 'label' => 'blocked by crowdsec',
+ 'quick' => true
+ ),
+ null
+ );
+
+ add_alias_if_not_exist('crowdsec6_blacklists', 'CrowdSec (IPv6)', 'IPv6');
+
+ $fw->registerFilterRule(
+ 1, /* priority */
+ array(
+ 'ipprotocol' => 'inet6',
+ 'descr' => 'CrowdSec (IPv6)',
+ 'from' => '$crowdsec6_blacklists', # $ to reference an alias
+ 'type' => 'block',
+ 'log' => $rules_log_enabled,
+ 'tag' => $rules_tag,
+ 'label' => 'blocked by crowdsec',
+ 'quick' => true
+ ),
+ null
+ );
+}
+
+function crowdsec_services()
+{
+ $services[] = array(
+ 'description' => 'CrowdSec',
+ 'configd' => array(
+ 'restart' => array('crowdsec restart'),
+ 'start' => array('crowdsec start'),
+ 'stop' => array('crowdsec stop'),
+ ),
+ 'name' => 'crowdsec'
+ );
+
+ return $services;
+}
diff --git a/security/crowdsec/src/etc/rc.d/oscrowdsec b/security/crowdsec/src/etc/rc.d/oscrowdsec
new file mode 100755
index 000000000..a64dc0d38
--- /dev/null
+++ b/security/crowdsec/src/etc/rc.d/oscrowdsec
@@ -0,0 +1,104 @@
+#!/bin/sh
+#
+# $FreeBSD$
+#
+
+# PROVIDE: oscrowdsec
+# REQUIRE: NETWORKING syslogd
+# BEFORE: DAEMON
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="oscrowdsec"
+rcvar="oscrowdsec_enable"
+
+load_rc_config $name
+
+: ${oscrowdsec_enable="NO"}
+
+
+oscrowdsec_start () {
+ #
+ # Start, or stop the services according to the plugin's configuration.
+ # When starting -> error if the services are already running
+ # When stopping -> no error
+ #
+
+ if service crowdsec enabled; then
+ service crowdsec start
+ else
+ service crowdsec stop || :
+ fi
+
+ if service crowdsec_firewall enabled; then
+ service crowdsec_firewall start
+ else
+ service crowdsec_firewall stop || :
+ fi
+
+# XXX should complain if they were not stopped?
+# service crowdsec status
+# if [ $? -eq 0 ]; then
+# debug "oscrowdsec_start: crowdsec is still running"
+# return 0
+# fi
+}
+
+oscrowdsec_stop () {
+ # Always stop the services, enabled or not, running or not. No errors.
+
+ service crowdsec stop || :
+ service crowdsec_firewall stop || :
+
+ # XXX should complain if they were running and have not been stopped?
+}
+
+oscrowdsec_restart () {
+ oscrowdsec_stop || :
+ oscrowdsec_start
+}
+
+oscrowdsec_status () {
+ # return error if at least one program is not running
+ ret=0
+
+ if service crowdsec status; then
+ ret=$?
+ fi
+
+ if service crowdsec_firewall status; then
+ if [ $ret -eq 0 ]; then
+ ret=$?
+ fi
+ fi
+ return $ret
+}
+
+oscrowdsec_reload () {
+ # Here we take it easy. the bouncer does not even support reload
+ oscrowdsec_restart
+}
+
+case $1 in
+ start)
+ oscrowdsec_start
+ exit $?
+ ;;
+ stop)
+ oscrowdsec_stop
+ exit $?
+ ;;
+ restart)
+ oscrowdsec_restart
+ exit $?
+ ;;
+ status)
+ oscrowdsec_status
+ exit $?
+ ;;
+ reload)
+ oscrowdsec_reload
+ exit $?
+ ;;
+esac
diff --git a/security/crowdsec/src/etc/rc.syshook.d/start/50-crowdsec b/security/crowdsec/src/etc/rc.syshook.d/start/50-crowdsec
new file mode 100755
index 000000000..58359695f
--- /dev/null
+++ b/security/crowdsec/src/etc/rc.syshook.d/start/50-crowdsec
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+# https://docs.opnsense.org/development/backend/autorun.html
+
+/usr/local/opnsense/scripts/OPNsense/CrowdSec/hub-upgrade.sh
+
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
new file mode 100644
index 000000000..6aff27e81
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/AlertsController.php
@@ -0,0 +1,33 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\CrowdSec\CrowdSec;
+use OPNsense\Core\Backend;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class AlertsController extends ApiControllerBase
+{
+ /**
+ * retrieve list of alerts
+ * @return array of alerts
+ * @throws \OPNsense\Base\ModelException
+ * @throws \ReflectionException
+ */
+ public function getAction()
+ {
+ $backend = new Backend();
+ $bckresult = json_decode(trim($backend->configdRun("crowdsec alerts-list")), true);
+ if ($bckresult !== null) {
+ // only return valid json type responses
+ return $bckresult;
+ }
+ return array("message" => "unable to list alerts");
+ }
+}
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
new file mode 100644
index 000000000..94de1a877
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/BouncersController.php
@@ -0,0 +1,33 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\CrowdSec\CrowdSec;
+use OPNsense\Core\Backend;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class BouncersController extends ApiControllerBase
+{
+ /**
+ * retrieve list of bouncers
+ * @return array of bouncers
+ * @throws \OPNsense\Base\ModelException
+ * @throws \ReflectionException
+ */
+ public function getAction()
+ {
+ $backend = new Backend();
+ $bckresult = json_decode(trim($backend->configdRun("crowdsec bouncers-list")), true);
+ if ($bckresult !== null) {
+ // only return valid json type responses
+ return $bckresult;
+ }
+ return array("message" => "unable to list bouncers");
+ }
+}
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..62c63afa6
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/CollectionsController.php
@@ -0,0 +1,33 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\CrowdSec\CrowdSec;
+use OPNsense\Core\Backend;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class CollectionsController extends ApiControllerBase
+{
+ /**
+ * retrieve list of collections
+ * @return array of collections
+ * @throws \OPNsense\Base\ModelException
+ * @throws \ReflectionException
+ */
+ public function getAction()
+ {
+ $backend = new Backend();
+ $bckresult = json_decode(trim($backend->configdRun("crowdsec collections-list")), true);
+ if ($bckresult !== null) {
+ // only return valid json type responses
+ return $bckresult;
+ }
+ return array("message" => "unable to list collections");
+ }
+}
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
new file mode 100644
index 000000000..7421e74e4
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/DecisionsController.php
@@ -0,0 +1,53 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\CrowdSec\CrowdSec;
+use OPNsense\Core\Backend;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class DecisionsController extends ApiControllerBase
+{
+ /**
+ * retrieve list of decisions
+ * @return array of decisions
+ * @throws \OPNsense\Base\ModelException
+ * @throws \ReflectionException
+ */
+ public function getAction()
+ {
+ $backend = new Backend();
+ $bckresult = json_decode(trim($backend->configdRun("crowdsec decisions-list")), true);
+ if ($bckresult !== null) {
+ // only return valid json type responses
+ return $bckresult;
+ }
+ return array("message" => "unable to list decisions");
+ }
+
+ public function deleteAction($decision_id)
+ {
+ if ($this->request->isDelete()) {
+ $backend = new Backend();
+ $bckresult = $backend->configdRun("crowdsec decisions-delete ${decision_id}");
+ if ($bckresult !== null) {
+ // why does the action return \n\n for empty output?
+ if (trim($bckresult) === '') {
+ return array("message" => "OK");
+ }
+ // TODO handle error
+ return array("message" => $bckresult);
+ }
+ return array("message" => "OK");
+ } 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/GeneralController.php b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/GeneralController.php
new file mode 100644
index 000000000..038e49181
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/GeneralController.php
@@ -0,0 +1,17 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiMutableModelControllerBase;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class GeneralController extends ApiMutableModelControllerBase
+{
+ protected static $internalModelName = 'general';
+ protected static $internalModelClass = '\OPNsense\CrowdSec\General';
+}
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
new file mode 100644
index 000000000..617e43bf4
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/MachinesController.php
@@ -0,0 +1,33 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\CrowdSec\CrowdSec;
+use OPNsense\Core\Backend;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class MachinesController extends ApiControllerBase
+{
+ /**
+ * retrieve list of registered machines
+ * @return array of machines
+ * @throws \OPNsense\Base\ModelException
+ * @throws \ReflectionException
+ */
+ public function getAction()
+ {
+ $backend = new Backend();
+ $bckresult = json_decode(trim($backend->configdRun("crowdsec machines-list")), true);
+ if ($bckresult !== null) {
+ // only return valid json type responses
+ return $bckresult;
+ }
+ return array("message" => "unable to list machines");
+ }
+}
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..6dfcfcdb8
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ParsersController.php
@@ -0,0 +1,33 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\CrowdSec\CrowdSec;
+use OPNsense\Core\Backend;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class ParsersController extends ApiControllerBase
+{
+ /**
+ * retrieve list of registered parsers
+ * @return array of parsers
+ * @throws \OPNsense\Base\ModelException
+ * @throws \ReflectionException
+ */
+ public function getAction()
+ {
+ $backend = new Backend();
+ $bckresult = json_decode(trim($backend->configdRun("crowdsec parsers-list")), true);
+ if ($bckresult !== null) {
+ // only return valid json type responses
+ return $bckresult;
+ }
+ return array("message" => "unable to list parsers");
+ }
+}
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..a52fc928c
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/PostoverflowsController.php
@@ -0,0 +1,33 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\CrowdSec\CrowdSec;
+use OPNsense\Core\Backend;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class PostoverflowsController extends ApiControllerBase
+{
+ /**
+ * retrieve list of registered postoverflows
+ * @return array of postoverflows
+ * @throws \OPNsense\Base\ModelException
+ * @throws \ReflectionException
+ */
+ public function getAction()
+ {
+ $backend = new Backend();
+ $bckresult = json_decode(trim($backend->configdRun("crowdsec postoverflows-list")), true);
+ if ($bckresult !== null) {
+ // only return valid json type responses
+ return $bckresult;
+ }
+ return array("message" => "unable to list postoverflows");
+ }
+}
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..5daa6b82b
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ScenariosController.php
@@ -0,0 +1,33 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\CrowdSec\CrowdSec;
+use OPNsense\Core\Backend;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class ScenariosController extends ApiControllerBase
+{
+ /**
+ * retrieve list of registered scenarios
+ * @return array of scenarios
+ * @throws \OPNsense\Base\ModelException
+ * @throws \ReflectionException
+ */
+ public function getAction()
+ {
+ $backend = new Backend();
+ $bckresult = json_decode(trim($backend->configdRun("crowdsec scenarios-list")), true);
+ if ($bckresult !== null) {
+ // only return valid json type responses
+ return $bckresult;
+ }
+ return array("message" => "unable to list scenarios");
+ }
+}
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
new file mode 100644
index 000000000..dae2711c7
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/ServiceController.php
@@ -0,0 +1,78 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\Core\Backend;
+
+/**
+ * Class ServiceController
+ * @package OPNsense\CrowdSec
+ */
+class ServiceController extends ApiControllerBase
+{
+ /**
+ * reconfigure CrowdSec
+ */
+ public function reloadAction()
+ {
+ $status = "failed";
+ if ($this->request->isPost()) {
+ $backend = new Backend();
+ $bckresult = trim($backend->configdRun('template reload OPNsense/CrowdSec'));
+ if ($bckresult == "OK") {
+ $bckresult = trim($backend->configdRun('crowdsec reconfigure'));
+ if ($bckresult == "OK") {
+ $status = "ok";
+ }
+ }
+ }
+ return array("status" => $status);
+ }
+
+ /**
+ * retrieve status of crowdsec
+ * @return array
+ * @throws \Exception
+ */
+ public function statusAction()
+ {
+ $backend = new Backend();
+ $response = $backend->configdRun("crowdsec crowdsec-status");
+
+ $status = "unkown";
+ if (strpos($response, "not running") > 0) {
+ $status = "stopped";
+ } elseif (strpos($response, "is running") > 0) {
+ $status = "running";
+ }
+
+ $response = $backend->configdRun("crowdsec crowdsec-firewall-status");
+
+ $firewall_status = "unknown";
+ if (strpos($response, "not running") > 0) {
+ $firewall_status = "stopped";
+ } elseif (strpos($response, "is running") > 0) {
+ $firewall_status = "running";
+ }
+
+ return array(
+ "crowdsec-status" => $status,
+ "crowdsec-firewall-status" => $firewall_status,
+ );
+ }
+
+ /**
+ * return debug information
+ * @return array
+ */
+ public function debugAction()
+ {
+ $backend = new Backend();
+ $response = $backend->configdRun("crowdsec debug");
+ return array("message" => $response);
+ }
+}
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
new file mode 100644
index 000000000..d236c26f8
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/Api/VersionController.php
@@ -0,0 +1,28 @@
+
+
+namespace OPNsense\CrowdSec\Api;
+
+use OPNsense\Base\ApiControllerBase;
+use OPNsense\CrowdSec\CrowdSec;
+use OPNsense\Core\Backend;
+
+/**
+ * @package OPNsense\CrowdSec
+ */
+class VersionController extends ApiControllerBase
+{
+ /**
+ * retrieve version description
+ * @return version description
+ * @throws \OPNsense\Base\ModelException
+ * @throws \ReflectionException
+ */
+ public function getAction()
+ {
+ $backend = new Backend();
+ return $backend->configdRun("crowdsec version");
+ }
+}
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
new file mode 100644
index 000000000..115ca686c
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/GeneralController.php
@@ -0,0 +1,19 @@
+
+
+namespace OPNsense\CrowdSec;
+
+/**
+ * Class GeneralController
+ * @package OPNsense\CrowdSec
+ */
+class GeneralController extends \OPNsense\Base\IndexController
+{
+ public function indexAction()
+ {
+ $this->view->pick('OPNsense/CrowdSec/general');
+ $this->view->generalForm = $this->getForm("general");
+ }
+}
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
new file mode 100644
index 000000000..6dbd461d5
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/OverviewController.php
@@ -0,0 +1,18 @@
+
+
+namespace OPNsense\CrowdSec;
+
+/**
+ * Class OverviewController
+ * @package OPNsense\CrowdSec
+ */
+class OverviewController extends \OPNsense\Base\IndexController
+{
+ public function indexAction()
+ {
+ $this->view->pick('OPNsense/CrowdSec/overview');
+ }
+}
diff --git a/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/forms/general.xml b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/forms/general.xml
new file mode 100644
index 000000000..596c3b3e9
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/controllers/OPNsense/CrowdSec/forms/general.xml
@@ -0,0 +1,95 @@
+
diff --git a/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/ACL/ACL.xml b/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/ACL/ACL.xml
new file mode 100644
index 000000000..54840d23f
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/ACL/ACL.xml
@@ -0,0 +1,9 @@
+
+
+ CrowdSec
+
+ ui/crowdsec/*
+ api/crowdsec/*
+
+
+
diff --git a/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/General.php b/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/General.php
new file mode 100644
index 000000000..3307e8ba6
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/General.php
@@ -0,0 +1,12 @@
+
+
+namespace OPNsense\CrowdSec;
+
+use OPNsense\Base\BaseModel;
+
+class General extends BaseModel
+{
+}
diff --git a/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/General.xml b/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/General.xml
new file mode 100644
index 000000000..16d3b3632
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/General.xml
@@ -0,0 +1,56 @@
+
+ //OPNsense/crowdsec/general
+ CrowdSec general configuration
+ 0.2
+
+
+
+ 1
+ Y
+
+
+
+ 1
+ Y
+
+
+
+ 1
+ Y
+
+
+
+ 0
+ Y
+
+
+
+ 127.0.0.1
+ Y
+ ((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))
+
+
+
+ 8080
+ Y
+ N
+ N
+
+
+
+ 0
+ Y
+
+
+
+
+ N
+
+
+
+ 0
+ Y
+
+
+
+
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
new file mode 100644
index 000000000..c50b8cd43
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/models/OPNsense/CrowdSec/Menu/Menu.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/general.volt b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/general.volt
new file mode 100644
index 000000000..e6753a4bf
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/general.volt
@@ -0,0 +1,142 @@
+{# SPDX-License-Identifier: MIT #}
+{# SPDX-FileCopyrightText: © 2021 CrowdSec #}
+
+
+
+
+
+
+
+
+
+
+
This plugin installs a CrowdSec agent/LAPI
+ node, and a Firewall Bouncer .
+
+
Out of the box, by enabling them in the "Settings" tab, they can protect the OPNsense server
+ by receiving thousands of IP addresses of active attackers, which are immediately banned at the
+ firewall level. In addition, the logs of the ssh service and OPNsense administration interface are
+ analyzed for possible brute-force attacks; any such scenario triggers a ban and is reported to the
+ CrowdSec Central API
+ (meaning timestamp, scenario, attacking IP ).
+
+
Other attack behaviors can be recognized on the OPNsense server and its plugins, or
+ any other agent
+ connected to the same LAPI node. Other types of remediation are possible (ex. captcha test for scraping attempts).
+
+
Please refer to the tutorials to explore
+ the possibilities.
+
+
A few remarks:
+
+
+
+ If your OPNsense is <22.1, you must check "Disable circular logs" in the Settings menu for the
+ ssh and web-auth parsers to work. If you upgrade to 22.1, it will be done automatically.
+ See acquis.d/opnsense.yaml
+
+
+ At the moment, the CrowdSec package for OPNsense is fully functional on the
+ command line but its web interface is limited; you can only list the installed objects and revoke
+ decisions . For anything else
+ you need the shell.
+
+
+ Do not enable/start the agent and bouncer services with sysrc or /etc/rc.conf
+ like you would on vanilla freebsd, the plugin takes care of that.
+
+
+ The parsers, scenarios and all objects from the CrowdSec Hub
+ are periodically upgraded. The
+ crowdsecurity/freebsd and
+ crowdsecurity/opnsense
+ collections are installed by default.
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ partial("layout_partials/base_form",['fields':generalForm,'id':'frm_GeneralSettings'])}}
+
+
+
+ {{ lang._('Apply') }}
+
+
+
+
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
new file mode 100644
index 000000000..87987c30a
--- /dev/null
+++ b/security/crowdsec/src/opnsense/mvc/app/views/OPNsense/CrowdSec/overview.volt
@@ -0,0 +1,249 @@
+{# SPDX-License-Identifier: MIT #}
+{# SPDX-FileCopyrightText: © 2021 CrowdSec #}
+
+
+
+
+
+
+
+
+
+ Service status: crowdsec ... - firewall bouncer ...
+
+
+
+
+
+
+
+
+
+
+ Name
+ IP Address
+ Last Update
+ Validated?
+ Version
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ IP Address
+ Valid
+ Last API Pull
+ Type
+ Version
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ Status
+ Version
+ Local Path
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ Status
+ Version
+ Path
+ Description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ Status
+ Version
+ Local Path
+ Description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ Status
+ Version
+ Local Path
+ Description
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ Value
+ Reason
+ Country
+ AS
+ Decisions
+ Created 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.
+
+
+
+
+ ID
+ Source
+ Scope:Value
+ Reason
+ Action
+ Country
+ AS
+ Events
+ Expiration
+ Alert ID
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modal content...
+
+
+
+
+
+
+
diff --git a/security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/hub-upgrade.sh b/security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/hub-upgrade.sh
new file mode 100755
index 000000000..1c00e7a1b
--- /dev/null
+++ b/security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/hub-upgrade.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if [ ! -e "/usr/local/etc/crowdsec/collections/opnsense.yaml" ]; then
+ /usr/local/bin/cscli --error collections install crowdsecurity/opnsense
+fi
+
+/usr/local/bin/cscli --error hub update \
+ && /usr/local/bin/cscli --error hub upgrade
+
+if service crowdsec enabled; then
+ # have to check status explicitly because "restart" can set $? = 0 even when failing
+ if ! service crowdsec status >/dev/null 2>&1; then
+ service crowdsec start >/dev/null 2>&1 || :
+ else
+ service crowdsec restart >/dev/null 2>&1 || :
+ fi
+fi
+
diff --git a/security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/reconfigure.py b/security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/reconfigure.py
new file mode 100755
index 000000000..6f012249b
--- /dev/null
+++ b/security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/reconfigure.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+
+import logging
+import json
+import urllib.parse
+import yaml
+
+logging.basicConfig(level=logging.INFO)
+
+
+def load_config(filename):
+ with open(filename) as fin:
+ return yaml.safe_load(fin)
+
+
+# only save if some value has changed
+def save_config(filename, new_config):
+ old_config = load_config(filename)
+ if old_config != new_config:
+ with open(filename, 'w') as fout:
+ yaml.dump(new_config, fout)
+
+
+def get_netloc(settings):
+ # defaults if config has not been saved yet
+ listen_address = settings.get('lapi_listen_address', '127.0.0.1')
+ listen_port = settings.get('lapi_listen_port', '8080')
+ return '{}:{}'.format(listen_address, listen_port)
+
+
+def get_new_url(old_url, settings):
+ old_tuple = urllib.parse.urlsplit(old_url)
+ new_tuple = old_tuple._replace(netloc=get_netloc(settings))
+ new_url = urllib.parse.urlunsplit(new_tuple)
+ # client lapi requires a trailing slash for the path part
+ # and no, query and fragment don't make much sense
+ if not new_tuple.query and not new_tuple.fragment and not new_url.endswith('/'):
+ new_url += '/'
+ return new_url
+
+
+def configure_agent(settings):
+ config_path = '/usr/local/etc/crowdsec/config.yaml'
+ config = load_config(config_path)
+
+ config['common']['log_dir'] = '/var/log/crowdsec'
+ config['crowdsec_service']['acquisition_dir'] = '/usr/local/etc/crowdsec/acquis.d/'
+
+ if not int(settings.get('lapi_manual_configuration', '0')):
+ config['api']['server']['listen_uri'] = get_netloc(settings)
+
+ save_config(config_path, config)
+
+
+def configure_lapi_credentials(settings):
+ config_path = '/usr/local/etc/crowdsec/local_api_credentials.yaml'
+ config = load_config(config_path)
+
+ if not int(settings.get('lapi_manual_configuration', '0')):
+ config['url'] = get_new_url(config['url'], settings)
+
+ save_config(config_path, config)
+
+
+def configure_bouncer(settings):
+ config_path = '/usr/local/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml'
+ config = load_config(config_path)
+
+ config['log_dir'] = '/var/log/crowdsec'
+ config['blacklists_ipv4'] = 'crowdsec_blacklists'
+ config['blacklists_ipv6'] = 'crowdsec6_blacklists'
+ config['pf'] = {'anchor_name': ''}
+
+ if not int(settings.get('lapi_manual_configuration', '0')):
+ config['api_url'] = get_new_url(config['api_url'], settings)
+
+ save_config(config_path, config)
+
+
+def main():
+ try:
+ with open('/usr/local/etc/crowdsec/opnsense/settings.json') as f:
+ settings = json.load(f)
+ except FileNotFoundError:
+ logging.info("settings.json not found, won't change crowdsec config")
+ return
+
+ configure_agent(settings)
+ configure_lapi_credentials(settings)
+ configure_bouncer(settings)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/reconfigure.sh b/security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/reconfigure.sh
new file mode 100755
index 000000000..c6d0c2c2c
--- /dev/null
+++ b/security/crowdsec/src/opnsense/scripts/OPNsense/CrowdSec/reconfigure.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+# This script is run
+# - when the plugin is installed (by +POST_INSTALL.post)
+# - when saving the "settings" form (which calls /api/crowdsec/service/reload)
+# - by hand, running "configctl crowdsec reconfigure"
+
+set -e
+
+# apply configuration options specific to opnsense
+/usr/local/opnsense/scripts/OPNsense/CrowdSec/reconfigure.py
+
+# enable pf anchor here - the tables and rules will be created by the bouncer
+/usr/local/sbin/configctl filter reload >/dev/null
+
+# the hub is upgraded by cron too
+/usr/local/opnsense/scripts/OPNsense/CrowdSec/hub-upgrade.sh
+
+# crowdsec was already restarted by hub-upgrade.sh
+if service crowdsec_firewall enabled; then
+ # have to check status explicitly because "restart" can set $? = 0 even when failing
+ if ! service crowdsec_firewall status >/dev/null 2>&1; then
+ service crowdsec_firewall start >/dev/null 2>&1 || :
+ else
+ service crowdsec_firewall restart >/dev/null 2>&1 || :
+ fi
+fi
+
+# left from v0.0.8
+rm -f /usr/local/etc/crowdsec/opnsense-settings.json
+
+echo "OK"
+
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
new file mode 100644
index 000000000..e0e0f6293
--- /dev/null
+++ b/security/crowdsec/src/opnsense/service/conf/actions.d/actions_crowdsec.conf
@@ -0,0 +1,94 @@
+
+# https://docs.opnsense.org/development/backend/configd.html
+
+[start]
+command:/usr/local/etc/rc.d/oscrowdsec start
+type: script
+message: starting crowdsec services
+
+[stop]
+command:/usr/local/etc/rc.d/oscrowdsec stop
+type: script
+message: stopping crowdsec services
+
+[status]
+command:/usr/local/etc/rc.d/oscrowdsec status; exit 0
+type: script_output
+message: oscrowdsec status
+
+[restart]
+command:/usr/local/etc/rc.d/oscrowdsec restart
+type: script
+message: stopping crowdsec services
+
+[reload]
+command:/usr/local/etc/rc.d/oscrowdsec reload
+type: script
+message: reload crowdsec configuration
+
+[crowdsec-status]
+command:/usr/local/etc/rc.d/crowdsec status;exit 0
+type:script_output
+message: request crowdsec status
+
+[crowdsec-firewall-status]
+command:/usr/local/etc/rc.d/crowdsec_firewall status;exit 0
+type:script_output
+message: request crowdsec_firewall status
+
+[alerts-list]
+command:/usr/local/bin/cscli alerts list -l 0 -o json | sed 's/^null$/\[\]/'
+type:script_output
+message:crowdsec alerts list
+
+[bouncers-list]
+command:/usr/local/bin/cscli bouncers list -o json | sed 's/^null$/\[\]/'
+type:script_output
+message:crowdsec bouncers list
+
+[collections-list]
+command:/usr/local/bin/cscli collections list -o json
+type:script_output
+message:crowdsec collections list
+
+[decisions-list]
+command:/usr/local/bin/cscli decisions list -l 0 -o json | sed 's/^null$/\[\]/'
+type:script_output
+message:crowdsec decisions list
+
+[decisions-delete]
+command:/usr/local/bin/cscli --error decisions delete 2>&1
+parameters:--id %s
+type:script_output
+message:crowdsec decisions delete
+
+[machines-list]
+command:/usr/local/bin/cscli machines list -o json | sed 's/^null$/\[\]/'
+type:script_output
+message:crowdsec machines 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
+
+[scenarios-list]
+command:/usr/local/bin/cscli scenarios list -o json
+type:script_output
+message:crowdsec scenarios list
+
+[version]
+command:/usr/local/bin/cscli version 2>&1
+type:script_output
+message:crowdsec version
+
+[reconfigure]
+command:/usr/local/opnsense/scripts/OPNsense/CrowdSec/reconfigure.sh
+type:script_output
+message:crowdsec reconfigure
+
diff --git a/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/+TARGETS b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/+TARGETS
new file mode 100644
index 000000000..c2ebc9224
--- /dev/null
+++ b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/+TARGETS
@@ -0,0 +1,4 @@
+oscrowdsec.rc.conf.d:/etc/rc.conf.d/oscrowdsec
+crowdsec.rc.conf.d:/etc/rc.conf.d/crowdsec
+crowdsec_firewall.rc.conf.d:/etc/rc.conf.d/crowdsec_firewall
+settings.json:/usr/local/etc/crowdsec/opnsense/settings.json
diff --git a/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/crowdsec.rc.conf.d b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/crowdsec.rc.conf.d
new file mode 100644
index 000000000..77eebf92f
--- /dev/null
+++ b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/crowdsec.rc.conf.d
@@ -0,0 +1,11 @@
+# DO NOT EDIT THIS FILE -- OPNsense auto-generated file
+{% if helpers.exists('OPNsense.crowdsec.general.agent_enabled') and OPNsense.crowdsec.general.agent_enabled|default("1") == "1" %}
+crowdsec_enable="YES"
+{% else %}
+crowdsec_enable="NO"
+{% endif %}
+{% if helpers.exists('OPNsense.crowdsec.general.lapi_enabled') and OPNsense.crowdsec.general.lapi_enabled|default("1") == "1" %}
+crowdsec_flags=""
+{% else %}
+crowdsec_flags="-no-api"
+{% endif %}
diff --git a/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/crowdsec_firewall.rc.conf.d b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/crowdsec_firewall.rc.conf.d
new file mode 100644
index 000000000..61bae583b
--- /dev/null
+++ b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/crowdsec_firewall.rc.conf.d
@@ -0,0 +1,11 @@
+# DO NOT EDIT THIS FILE -- OPNsense auto-generated file
+{% if helpers.exists('OPNsense.crowdsec.general.firewall_bouncer_enabled') and OPNsense.crowdsec.general.firewall_bouncer_enabled|default("1") == "1" %}
+crowdsec_firewall_enable="YES"
+{% else %}
+crowdsec_firewall_enable="NO"
+{% endif %}
+{% if helpers.exists('OPNsense.crowdsec.general.crowdsec_firewall_verbose') and OPNsense.crowdsec.general.crowdsec_firewall_verbose|default("0") == "1" %}
+crowdsec_firewall_flags="-v"
+{% else %}
+crowdsec_firewall_flags=""
+{% endif %}
diff --git a/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/oscrowdsec.rc.conf.d b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/oscrowdsec.rc.conf.d
new file mode 100644
index 000000000..dd3cec08e
--- /dev/null
+++ b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/oscrowdsec.rc.conf.d
@@ -0,0 +1 @@
+oscrowdsec_enable="YES"
diff --git a/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/settings.json b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/settings.json
new file mode 100644
index 000000000..0c3ed00b0
--- /dev/null
+++ b/security/crowdsec/src/opnsense/service/templates/OPNsense/CrowdSec/settings.json
@@ -0,0 +1,5 @@
+{% if helpers.exists('OPNsense.crowdsec.general') -%}
+ {{ OPNsense.crowdsec.general | tojson }}
+{%- endif %}
+
+
diff --git a/security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec.js b/security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec.js
new file mode 100644
index 000000000..39eaacd43
--- /dev/null
+++ b/security/crowdsec/src/opnsense/www/js/CrowdSec/crowdsec.js
@@ -0,0 +1,426 @@
+/*global moment, $ */
+/*exported CrowdSec */
+/*eslint no-undef: "error"*/
+/*eslint semi: "error"*/
+
+var CrowdSec = (function() {
+ 'use strict';
+
+ var _refresh_template = ' ';
+
+ var _dataFormatters = {
+ yesno: function(column, row) {
+ return _yesno2html(row[column.id]);
+ },
+
+ delete: function(column, row) {
+ var val = row.id;
+ if (isNaN(val)) {
+ return '';
+ }
+ return ' ';
+ },
+
+ duration: function(column, row) {
+ var 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) {
+ var dt = row[column.id];
+ var 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 _parseDuration(duration) {
+ var re = /(-?)(?:(?:(\d+)h)?(\d+)m)?(\d+).\d+(m?)s/m;
+ var matches = duration.match(re);
+ var 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 ("m" === parseInt(matches[5], 10)) {
+ // units in milliseconds
+ seconds *= 0.001;
+ }
+ if ("-" === parseInt(matches[1], 10)) {
+ // negative
+ seconds = -seconds;
+ }
+ return seconds;
+ }
+
+ function _updateFreshness(selector, timestamp) {
+ var $freshness = $(selector).find('.actionBar .freshness');
+ if (timestamp) {
+ $freshness.data('refresh_timestamp', timestamp);
+ } else {
+ timestamp = $freshness.data('refresh_timestamp');
+ }
+ var howlong_human = '???';
+ if (timestamp) {
+ var howlong_ms = moment() - moment(timestamp);
+ howlong_human = moment.duration(howlong_ms).humanize();
+ }
+ $freshness.text(howlong_human + ' ago');
+ }
+
+ function _addFreshness(selector) {
+ // this creates one timer per tab
+ var freshness_template = 'Last refresh: ';
+ $(selector).find('.actionBar').prepend(freshness_template);
+ setInterval(function() {
+ _updateFreshness(selector);
+ }, 5000);
+ }
+
+ 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 _decisionsByType(decisions) {
+ var dectypes = {};
+ if (!decisions) {
+ return '';
+ }
+ decisions.map(function(decision) {
+ // TODO ignore negative expiration?
+ dectypes[decision.type] = dectypes[decision.type] ? (dectypes[decision.type]+1) : 1;
+ });
+ var ret = '';
+ for (var type in dectypes) {
+ if (ret !== '') {
+ ret += ' ';
+ }
+ ret += (type + ':' + dectypes[type]);
+ }
+ return ret;
+ }
+
+ function _initService() {
+ $.ajax({
+ url: '/api/crowdsec/service/status',
+ cache: false
+ }).done(function(data) {
+ // TODO handle errors
+ var crowdsec_status = data['crowdsec-status'];
+ if (crowdsec_status === 'unknown') {
+ crowdsec_status = 'Unknown ';
+ } else {
+ crowdsec_status = _yesno2html(crowdsec_status === 'running');
+ }
+ $('#crowdsec-status').html(crowdsec_status);
+
+ var crowdsec_firewall_status = data['crowdsec-firewall-status'];
+ if (crowdsec_firewall_status === 'unknown') {
+ crowdsec_firewall_status = 'Unknown ';
+ } else {
+ crowdsec_firewall_status = _yesno2html(crowdsec_firewall_status === 'running');
+ }
+ $('#crowdsec-firewall-status').html(crowdsec_firewall_status);
+ });
+ }
+
+ function _initDebug() {
+ $.ajax({
+ url: '/api/crowdsec/service/debug',
+ cache: false
+ }).done(function(data) {
+ $('#debug pre').text(data.message);
+ });
+ }
+
+ function _initTab(selector, url, dataCallback) {
+ var $tab = $(selector);
+ if ($tab.find('table.bootgrid-table').length) {
+ return;
+ }
+ $tab.find('table').
+ on("initialized.rs.jquery.bootgrid", function() {
+ $(_refresh_template).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 _refreshTab(selector, url, dataCallback) {
+ $.ajax({
+ url: url,
+ cache: false
+ }).done(dataCallback);
+ _updateFreshness(selector, moment());
+ }
+
+ function _initMachines() {
+ var url = '/api/crowdsec/machines/get';
+ var dataCallback = function(data) {
+ var 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 || ' '
+ });
+ });
+ $('#machines table').bootgrid('clear').bootgrid('append', rows);
+ };
+ _initTab('#machines', url, dataCallback);
+ }
+
+ function _initCollections() {
+ var url = '/api/crowdsec/collections/get';
+ var dataCallback = function(data) {
+ var rows = [];
+ data.collections.map(function(row) {
+ rows.push({
+ name: row.name,
+ status: row.status,
+ local_version: row.local_version || ' ',
+ local_path: row.local_path || ' '
+ });
+ });
+ $('#collections table').bootgrid('clear').bootgrid('append', rows);
+ };
+ _initTab('#collections', url, dataCallback);
+ }
+
+ function _initScenarios() {
+ var url = '/api/crowdsec/scenarios/get';
+ var dataCallback = function(data) {
+ var rows = [];
+ data.scenarios.map(function(row) {
+ rows.push({
+ name: row.name,
+ status: row.status,
+ local_version: row.local_version || ' ',
+ local_path: row.local_path || ' ',
+ description: row.description || ' '
+ });
+ });
+ $('#scenarios table').bootgrid('clear').bootgrid('append', rows);
+ };
+ _initTab('#scenarios', url, dataCallback);
+ }
+
+ function _initParsers() {
+ var url = '/api/crowdsec/parsers/get';
+ var dataCallback = function(data) {
+ var rows = [];
+ data.parsers.map(function(row) {
+ rows.push({
+ name: row.name,
+ status: row.status,
+ local_version: row.local_version || ' ',
+ local_path: row.local_path || ' ',
+ description: row.description || ' '
+ });
+ });
+ $('#parsers table').bootgrid('clear').bootgrid('append', rows);
+ };
+ _initTab('#parsers ', url, dataCallback);
+ }
+
+ function _initPostoverflows() {
+ var url = '/api/crowdsec/postoverflows/get';
+ var dataCallback = function(data) {
+ var rows = [];
+ data.postoverflows.map(function(row) {
+ rows.push({
+ name: row.name,
+ status: row.status,
+ local_version: row.local_version || ' ',
+ local_path: row.local_path || ' ',
+ description: row.description || ' '
+ });
+ });
+ $('#postoverflows table').bootgrid('clear').bootgrid('append', rows);
+ };
+ _initTab('#postoverflows ', url, dataCallback);
+ }
+
+ function _initBouncers() {
+ var url = '/api/crowdsec/bouncers/get';
+ var dataCallback = function(data) {
+ var 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 || ' '
+ });
+ });
+ $('#bouncers table').bootgrid('clear').bootgrid('append', rows);
+ };
+ _initTab('#bouncers ', url, dataCallback);
+ }
+
+ function _initAlerts() {
+ var url = '/api/crowdsec/alerts/get';
+ var dataCallback = function(data) {
+ var 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
+ });
+ });
+ $('#alerts table').bootgrid('clear').bootgrid('append', rows);
+ };
+ _initTab('#alerts ', url, dataCallback);
+ }
+
+ function _initDecisions() {
+ var url = '/api/crowdsec/decisions/get';
+ var dataCallback = function(data) {
+ var 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 || ' '
+ });
+ });
+ });
+ $('#decisions table').bootgrid('clear').bootgrid('append', rows);
+ };
+ _initTab('#decisions ', url, dataCallback);
+ }
+
+ function deleteDecision(decisionId) {
+ var $modal = $('#delete-decision-modal');
+ $modal.find('.modal-title').text('Delete decision #' + decisionId);
+ $modal.find('.modal-body').text('Are you sure?');
+ $modal.find('#delete-decision-confirm').on('click', function() {
+ $.ajax({
+ // XXX handle errors
+ url: '/api/crowdsec/decisions/delete/' + decisionId,
+ type: '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', _initMachines);
+ $('#collections_tab').on('click', _initCollections);
+ $('#scenarios_tab').on('click', _initScenarios);
+ $('#parsers_tab').on('click', _initParsers);
+ $('#postoverflows_tab').on('click', _initPostoverflows);
+ $('#bouncers_tab').on('click', _initBouncers);
+ $('#alerts_tab').on('click', _initAlerts);
+ $('#decisions_tab').on('click', _initDecisions);
+
+ $('[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();
+ });
+
+ if (new URLSearchParams(window.location.search).has('debug')) {
+ $('#debug_tab').show().on('click', _initDebug);
+ }
+
+ // 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
+ };
+
+}());