mirror of
https://github.com/opnsense/plugins.git
synced 2026-06-04 06:15:39 -04:00
crowdsec: 1.0.10 (#4706)
* Update crowdsec rule reference ($ -> <>); bump release * php cleanup * javascript: reformat * backport js changes from pfsense: var -> const, let, function order * backport name change * backport changes: const -> var; id = * prettier * tabs * backport callback style * cron: avoid spamming stdout when the hub index is updated * icon * add outgoing rules * blacklists -> blocklists * some python typing * enroll to console from the settings * v1.0.10 with option to disable rule generation
This commit is contained in:
parent
90fedaaee3
commit
248fa0c978
18 changed files with 637 additions and 517 deletions
|
|
@ -41,8 +41,8 @@ function removeAlias($name)
|
|||
}
|
||||
}
|
||||
|
||||
removeAlias('crowdsec_blacklists');
|
||||
removeAlias('crowdsec6_blacklists');
|
||||
removeAlias('crowdsec_blocklists');
|
||||
removeAlias('crowdsec6_blocklists');
|
||||
EOT
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
PLUGIN_NAME= crowdsec
|
||||
PLUGIN_VERSION= 1.0.9
|
||||
PLUGIN_VERSION= 1.0.10
|
||||
PLUGIN_DEPENDS= crowdsec
|
||||
PLUGIN_COMMENT= Lightweight and collaborative security engine
|
||||
PLUGIN_MAINTAINER= marco@crowdsec.net
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ WWW: https://crowdsec.net/
|
|||
Plugin Changelog
|
||||
================
|
||||
|
||||
1.0.10
|
||||
* changed alias names crowdsec*blacklists -> crowdsec*blocklists
|
||||
* added rules for outgoing connections too
|
||||
* added enroll_key to settings for automatic enrollment
|
||||
* option to disable rule generation (bring your own rules!)
|
||||
* code cleanup, reformat, typing
|
||||
|
||||
1.0.9
|
||||
|
||||
* Update rule reference ($ -> <>) for opnsense 25.1
|
||||
|
|
|
|||
|
|
@ -43,39 +43,69 @@ function crowdsec_firewall(Plugin $fw)
|
|||
$rules_tag = $general['rules_tag'];
|
||||
}
|
||||
|
||||
add_alias_if_not_exist('crowdsec_blacklists', 'CrowdSec (IPv4)', 'IPv4');
|
||||
add_alias_if_not_exist('crowdsec_blocklists', 'CrowdSec (IPv4)', 'IPv4');
|
||||
add_alias_if_not_exist('crowdsec6_blocklists', 'CrowdSec (IPv6)', 'IPv6');
|
||||
|
||||
// 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>',
|
||||
'direction' => 'in',
|
||||
'type' => 'block',
|
||||
'log' => $rules_log_enabled,
|
||||
'tag' => $rules_tag,
|
||||
'quick' => true
|
||||
)
|
||||
);
|
||||
// if missing, default to true
|
||||
if (!isset($general['rules_enabled']) || $general['rules_enabled'] != 0) {
|
||||
$fw->registerFilterRule(
|
||||
1, /* priority */
|
||||
array(
|
||||
'ipprotocol' => 'inet',
|
||||
'descr' => 'CrowdSec (IPv4) in',
|
||||
'from' => '<crowdsec_blocklists>',
|
||||
'direction' => 'in',
|
||||
'type' => 'block',
|
||||
'log' => $rules_log_enabled,
|
||||
'tag' => $rules_tag,
|
||||
'quick' => true
|
||||
)
|
||||
);
|
||||
|
||||
add_alias_if_not_exist('crowdsec6_blacklists', 'CrowdSec (IPv6)', 'IPv6');
|
||||
$fw->registerFilterRule(
|
||||
1, /* priority */
|
||||
array(
|
||||
'ipprotocol' => 'inet',
|
||||
'descr' => 'CrowdSec (IPv4) out',
|
||||
'to' => '<crowdsec_blocklists>',
|
||||
'direction' => 'out',
|
||||
'type' => 'block',
|
||||
'log' => $rules_log_enabled,
|
||||
'tag' => $rules_tag,
|
||||
'quick' => true
|
||||
)
|
||||
);
|
||||
|
||||
$fw->registerFilterRule(
|
||||
1, /* priority */
|
||||
array(
|
||||
'ipprotocol' => 'inet6',
|
||||
'descr' => 'CrowdSec (IPv6)',
|
||||
'from' => '<crowdsec6_blacklists>',
|
||||
'direction' => 'in',
|
||||
'type' => 'block',
|
||||
'log' => $rules_log_enabled,
|
||||
'tag' => $rules_tag,
|
||||
'quick' => true
|
||||
)
|
||||
);
|
||||
$fw->registerFilterRule(
|
||||
1, /* priority */
|
||||
array(
|
||||
'ipprotocol' => 'inet6',
|
||||
'descr' => 'CrowdSec (IPv6) in',
|
||||
'from' => '<crowdsec6_blocklists>',
|
||||
'direction' => 'in',
|
||||
'type' => 'block',
|
||||
'log' => $rules_log_enabled,
|
||||
'tag' => $rules_tag,
|
||||
'quick' => true
|
||||
)
|
||||
);
|
||||
|
||||
$fw->registerFilterRule(
|
||||
1, /* priority */
|
||||
array(
|
||||
'ipprotocol' => 'inet6',
|
||||
'descr' => 'CrowdSec (IPv6) out',
|
||||
'to' => '<crowdsec6_blocklists>',
|
||||
'direction' => 'out',
|
||||
'type' => 'block',
|
||||
'log' => $rules_log_enabled,
|
||||
'tag' => $rules_tag,
|
||||
'quick' => true
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function crowdsec_services()
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@ use OPNsense\Core\Backend;
|
|||
class AlertsController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* retrieve list of alerts
|
||||
* 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) {
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec alerts-list")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $bckresult;
|
||||
return $result;
|
||||
}
|
||||
return array("message" => "unable to list alerts");
|
||||
return ["message" => "unable to list alerts"];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@ use OPNsense\Core\Backend;
|
|||
class BouncersController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* retrieve list of bouncers
|
||||
* 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) {
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec bouncers-list")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $bckresult;
|
||||
return $result;
|
||||
}
|
||||
return array("message" => "unable to list bouncers");
|
||||
return ["message" => "unable to list bouncers"];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,36 +15,35 @@ use OPNsense\Core\Backend;
|
|||
class DecisionsController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* retrieve list of decisions
|
||||
* 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) {
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec decisions-list")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $bckresult;
|
||||
return $result;
|
||||
}
|
||||
return array("message" => "unable to list decisions");
|
||||
return ["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) {
|
||||
$result = (new Backend())->configdRun("crowdsec decisions-delete ${decision_id}");
|
||||
if ($result !== null) {
|
||||
// why does the action return \n\n for empty output?
|
||||
if (trim($bckresult) === '') {
|
||||
return array("message" => "OK");
|
||||
if (trim($result) === '') {
|
||||
return ["message" => "OK"];
|
||||
}
|
||||
// TODO handle error
|
||||
return array("message" => $bckresult);
|
||||
return ["message" => result];
|
||||
}
|
||||
return array("message" => "OK");
|
||||
return ["message" => "OK"];
|
||||
} else {
|
||||
$this->response->setStatusCode(405, "Method Not Allowed");
|
||||
$this->response->setHeader("Allow", "DELETE");
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@ use OPNsense\Core\Backend;
|
|||
class HubController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* retrieve the registered hub items
|
||||
* Retrieve the registered hub items
|
||||
*
|
||||
* @return dictionary of items, by type
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
$backend = new Backend();
|
||||
$bckresult = json_decode(trim($backend->configdRun("crowdsec hub-items")), true);
|
||||
if ($bckresult !== null) {
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec hub-items")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $bckresult;
|
||||
return $result;
|
||||
}
|
||||
return array("message" => "unable to list hub items");
|
||||
return ["message" => "unable to list hub items"];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@ use OPNsense\Core\Backend;
|
|||
class MachinesController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* retrieve list of registered machines
|
||||
* 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) {
|
||||
$result = json_decode(trim((new Backend())->configdRun("crowdsec machines-list")), true);
|
||||
if ($result !== null) {
|
||||
// only return valid json type responses
|
||||
return $bckresult;
|
||||
return $result;
|
||||
}
|
||||
return array("message" => "unable to list machines");
|
||||
return ["message" => "unable to list machines"];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,11 +30,12 @@ class ServiceController extends ApiControllerBase
|
|||
}
|
||||
}
|
||||
}
|
||||
return array("status" => $status);
|
||||
return ["status" => $status];
|
||||
}
|
||||
|
||||
/**
|
||||
* retrieve status of crowdsec
|
||||
* Retrieve status of crowdsec
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
|
@ -59,20 +60,9 @@ class ServiceController extends ApiControllerBase
|
|||
$firewall_status = "running";
|
||||
}
|
||||
|
||||
return array(
|
||||
return [
|
||||
"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);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,14 +15,14 @@ use OPNsense\Core\Backend;
|
|||
class VersionController extends ApiControllerBase
|
||||
{
|
||||
/**
|
||||
* retrieve version description
|
||||
* Retrieve version description
|
||||
*
|
||||
* @return version description
|
||||
* @throws \OPNsense\Base\ModelException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function getAction()
|
||||
{
|
||||
$backend = new Backend();
|
||||
return $backend->configdRun("crowdsec version");
|
||||
return (new Backend())->configdRun("crowdsec version");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@
|
|||
packets from the attacking IP addresses.</help>
|
||||
</field>
|
||||
|
||||
<!-- enroll_key -->
|
||||
<field>
|
||||
<id>general.enroll_key</id>
|
||||
<label>Enrollment key from https://app.crowdsec.net</label>
|
||||
<type>text</type>
|
||||
<help>Click "Enroll command" on the the website and copy the key here.</help>
|
||||
</field>
|
||||
|
||||
<!-- lapi_manual_configuration -->
|
||||
<field>
|
||||
<id>general.lapi_manual_configuration</id>
|
||||
|
|
@ -66,6 +74,16 @@
|
|||
services.</help>
|
||||
</field>
|
||||
|
||||
<!-- rules_enabled -->
|
||||
<field>
|
||||
<id>general.rules_enabled</id>
|
||||
<label>Create blocklist rules</label>
|
||||
<type>checkbox</type>
|
||||
<help>Generate block rules from the Crowdsec blocklists.
|
||||
They are applied t all interfaces, ipv4/v6, ingress and egress.
|
||||
If you disable this, you'll have to write your own rules to block anything.</help>
|
||||
</field>
|
||||
|
||||
<!-- rules_log -->
|
||||
<field>
|
||||
<id>general.rules_log</id>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<model>
|
||||
<mount>//OPNsense/crowdsec/general</mount>
|
||||
<description>CrowdSec general configuration</description>
|
||||
<version>1.0.9</version>
|
||||
<version>1.0.10</version>
|
||||
<items>
|
||||
|
||||
<agent_enabled type="BooleanField">
|
||||
|
|
@ -37,6 +37,11 @@
|
|||
<EnableRanges>N</EnableRanges>
|
||||
</lapi_listen_port>
|
||||
|
||||
<rules_enabled type="BooleanField">
|
||||
<default>1</default>
|
||||
<Required>Y</Required>
|
||||
</rules_enabled>
|
||||
|
||||
<rules_log type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
|
|
@ -47,6 +52,11 @@
|
|||
<ValidationMessage>A tag must only contain numbers and letters and must be between 1 and 63 characters.</ValidationMessage>
|
||||
</rules_tag>
|
||||
|
||||
<enroll_key type="TextField">
|
||||
<Mask>/^([0-9a-zA-Z]{1,63})$/u</Mask>
|
||||
<ValidationMessage>The enrollment key can only contain numbers and letters and must be between 1 and 63 characters. Did you take it from app.crowdsec.net?</ValidationMessage>
|
||||
</enroll_key>
|
||||
|
||||
<crowdsec_firewall_verbose type="BooleanField">
|
||||
<default>0</default>
|
||||
<Required>Y</Required>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<script>
|
||||
$( document ).ready(function() {
|
||||
var data_get_map = {'frm_GeneralSettings':"/api/crowdsec/general/get"};
|
||||
const data_get_map = {'frm_GeneralSettings':"/api/crowdsec/general/get"};
|
||||
mapDataToFormUI(data_get_map).done(function(data){
|
||||
// place actions to run after load, for example update form styles.
|
||||
});
|
||||
|
|
@ -64,8 +64,8 @@
|
|||
<a href="https://doc.crowdsec.net/docs/next/user_guides/multiserver_setup">any other agent</a>
|
||||
connected to the same LAPI node. Other types of remediation are possible (ex. captcha test for scraping attempts).</p>
|
||||
|
||||
We recommend you to <a href="https://app.crowdsec.net/">register to the Console</a>. This helps you manage your instances,
|
||||
and us to have better overall metrics.
|
||||
We recommend you to <a href="https://app.crowdsec.net/">register to the Console</a>. This helps you manage your instances,
|
||||
and us to have better overall metrics.
|
||||
|
||||
<p>Please refer to the <a href="https://crowdsec.net/blog/category/tutorial/">tutorials</a> to explore
|
||||
the possibilities.</p>
|
||||
|
|
@ -148,16 +148,16 @@
|
|||
<p>
|
||||
It might be a good idea to have a secondary IP from which you can
|
||||
connect, should anything go wrong.
|
||||
</p>
|
||||
</p>
|
||||
|
||||
<pre><code>[root@OPNsense ~]# cscli decisions add -t ban -d 2m -i <your_ip_address></code></pre>
|
||||
<pre><code>[root@OPNsense ~]# cscli decisions add -t ban -d 2m -i <your_ip_address></code></pre>
|
||||
|
||||
<p>
|
||||
This is a more secure way to test than attempting to brute-force
|
||||
yourself: the default ban period is 4 hours, and Crowdsec reads the
|
||||
logs from the beginning, so it could ban you even if you failed ssh
|
||||
login 10 times in 30 seconds two hours before installing it.
|
||||
</p>
|
||||
<p>
|
||||
This is a more secure way to test than attempting to brute-force
|
||||
yourself: the default ban period is 4 hours, and Crowdsec reads the
|
||||
logs from the beginning, so it could ban you even if you failed ssh
|
||||
login 10 times in 30 seconds two hours before installing it.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<a class="btn btn-default btn-info" href="https://github.com/crowdsecurity/crowdsec">
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ ul.nav>li>a {
|
|||
<li><a data-toggle="tab" id="postoverflows_tab" href="#postoverflows">Postoverflows</a></li>
|
||||
<li class="spaced"><a data-toggle="tab" id="alerts_tab" href="#alerts">Alerts</a></li>
|
||||
<li><a data-toggle="tab" id="decisions_tab" href="#decisions">Decisions</a></li>
|
||||
<li class="pull-right"><a data-toggle="tab" id="debug_tab" href="#debug" style="display:none">Debug</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content content-box">
|
||||
|
|
@ -223,13 +222,8 @@ ul.nav>li>a {
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<div id="debug" class="tab-pane fade in">
|
||||
<pre>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<!-- Modal popup to confirm decision deletion -->
|
||||
<div class="modal fade" id="delete-decision-modal" tabindex="-1" role="dialog" aria-labelledby="modalLabel" aria-hidden="true">
|
||||
<div class="modal fade" id="remove-decision-modal" tabindex="-1" role="dialog" aria-labelledby="modalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
|
|
@ -243,7 +237,7 @@ ul.nav>li>a {
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">No, cancel</button>
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal" id="delete-decision-confirm">Yes, delete</button>
|
||||
<button type="button" class="btn btn-danger" data-dismiss="modal" id="remove-decision-confirm">Yes, delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
test -x /usr/local/bin/cscli || exit 0
|
||||
|
||||
/usr/local/bin/cscli --error hub update
|
||||
/usr/local/bin/cscli --error -o human hub update >/dev/null
|
||||
|
||||
upgraded=$(/usr/local/bin/cscli --error hub upgrade)
|
||||
upgraded=$(/usr/local/bin/cscli --error -o human hub upgrade)
|
||||
|
||||
if [ ! -e "/usr/local/etc/crowdsec/collections/opnsense.yaml" ]; then
|
||||
/usr/local/bin/cscli --error collections install crowdsecurity/opnsense
|
||||
|
|
|
|||
|
|
@ -2,33 +2,35 @@
|
|||
|
||||
import logging
|
||||
import json
|
||||
import subprocess
|
||||
import urllib.parse
|
||||
from typing import cast, Any
|
||||
import yaml
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def load_config(filename):
|
||||
def load_config(filename: str) -> dict[str, Any]:
|
||||
with open(filename) as fin:
|
||||
return yaml.safe_load(fin)
|
||||
|
||||
|
||||
# only save if some value has changed
|
||||
def save_config(filename, new_config):
|
||||
def save_config(filename: str, new_config: dict[str, Any]):
|
||||
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):
|
||||
def get_netloc(settings: dict[str, str]):
|
||||
# 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):
|
||||
def get_new_url(old_url: str, settings: dict[str, str]):
|
||||
old_tuple = urllib.parse.urlsplit(old_url)
|
||||
new_tuple = old_tuple._replace(netloc=get_netloc(settings))
|
||||
new_url = urllib.parse.urlunsplit(new_tuple)
|
||||
|
|
@ -39,7 +41,7 @@ def get_new_url(old_url, settings):
|
|||
return new_url
|
||||
|
||||
|
||||
def configure_agent(settings):
|
||||
def configure_agent(settings: dict[str, str]):
|
||||
config_path = '/usr/local/etc/crowdsec/config.yaml'
|
||||
config = load_config(config_path)
|
||||
|
||||
|
|
@ -53,7 +55,7 @@ def configure_agent(settings):
|
|||
save_config(config_path, config)
|
||||
|
||||
|
||||
def configure_lapi(settings):
|
||||
def configure_lapi(settings: dict[str, str]):
|
||||
config_path = '/usr/local/etc/crowdsec/config.yaml'
|
||||
config = load_config(config_path)
|
||||
|
||||
|
|
@ -63,7 +65,7 @@ def configure_lapi(settings):
|
|||
save_config(config_path, config)
|
||||
|
||||
|
||||
def configure_lapi_credentials(settings):
|
||||
def configure_lapi_credentials(settings: dict[str, str]):
|
||||
config_path = '/usr/local/etc/crowdsec/local_api_credentials.yaml'
|
||||
config = load_config(config_path)
|
||||
|
||||
|
|
@ -73,13 +75,13 @@ def configure_lapi_credentials(settings):
|
|||
save_config(config_path, config)
|
||||
|
||||
|
||||
def configure_bouncer(settings):
|
||||
def configure_bouncer(settings: dict[str, str]):
|
||||
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['blacklists_ipv4'] = 'crowdsec_blocklists'
|
||||
config['blacklists_ipv6'] = 'crowdsec6_blocklists'
|
||||
config['retry_initial_connect'] = True
|
||||
config['pf'] = {'anchor_name': ''}
|
||||
|
||||
|
|
@ -89,10 +91,35 @@ def configure_bouncer(settings):
|
|||
save_config(config_path, config)
|
||||
|
||||
|
||||
def enroll(settings: dict[str, str]):
|
||||
enroll_key = settings.get('enroll_key')
|
||||
if enroll_key:
|
||||
try:
|
||||
p = subprocess.run(['cscli', 'capi', 'status'], check=True, text=True, stdout=subprocess.PIPE)
|
||||
if "instance is enrolled" in p.stdout:
|
||||
logging.info("crowdsec instance is already enrolled")
|
||||
return
|
||||
except subprocess.CalledProcessError:
|
||||
return
|
||||
except Exception as e:
|
||||
logging.error("could not run command 'cscli' to perform enrollment: %s", e)
|
||||
|
||||
try:
|
||||
logging.info("enrolling crowdsec instance, please accept the enrollment on https://app.crowdsec.net")
|
||||
_ = subprocess.run(
|
||||
['cscli', 'console', 'enroll', '-e', 'context', enroll_key],
|
||||
check=True, text=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error("enrollment failed: %s", e)
|
||||
return
|
||||
except Exception as e:
|
||||
logging.error("could not run command 'cscli' to perform enrollment: %s", e)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
with open('/usr/local/etc/crowdsec/opnsense/settings.json') as f:
|
||||
settings = json.load(f)
|
||||
settings = cast(dict[str, str], json.load(f))
|
||||
except FileNotFoundError:
|
||||
logging.info("settings.json not found, won't change crowdsec config")
|
||||
return
|
||||
|
|
@ -100,6 +127,7 @@ def main():
|
|||
configure_agent(settings)
|
||||
configure_lapi(settings)
|
||||
configure_lapi_credentials(settings)
|
||||
enroll(settings)
|
||||
configure_bouncer(settings)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,427 +3,471 @@
|
|||
/* eslint no-undef: "error" */
|
||||
/* eslint semi: "error" */
|
||||
|
||||
var CrowdSec = (function () {
|
||||
'use strict';
|
||||
const CrowdSec = (function () {
|
||||
'use strict';
|
||||
|
||||
var crowdsec_path = '/usr/local/etc/crowdsec/';
|
||||
var _refreshTemplate = '<button class="btn btn-default" type="button" title="Refresh"><span class="icon glyphicon glyphicon-refresh"></span></button>';
|
||||
const crowdsec_path = '/usr/local/etc/crowdsec/';
|
||||
const _refreshTemplate =
|
||||
'<button class="btn btn-default" type="button" title="Refresh"><span class="icon fa fa-refresh"></span></button>';
|
||||
|
||||
var _dataFormatters = {
|
||||
yesno: function (column, row) {
|
||||
return _yesno2html(row[column.id]);
|
||||
},
|
||||
const _dataFormatters = {
|
||||
yesno: function (column, row) {
|
||||
return _yesno2html(row[column.id]);
|
||||
},
|
||||
|
||||
delete: function (column, row) {
|
||||
var val = row.id;
|
||||
if (isNaN(val)) {
|
||||
return '';
|
||||
}
|
||||
return '<button type="button" class="btn btn-secondary btn-sm" value="' + val + '" onclick="CrowdSec.deleteDecision(' + val + ')"><i class="fa fa-trash" /></button>';
|
||||
},
|
||||
delete: function (column, row) {
|
||||
const val = row.id;
|
||||
if (isNaN(val)) {
|
||||
return '';
|
||||
}
|
||||
return (
|
||||
'<button type="button" class="btn btn-secondary btn-sm" value="' +
|
||||
val +
|
||||
'" onclick="CrowdSec.deleteDecision(' +
|
||||
val +
|
||||
')"><i class="fa fa-trash" /></button>'
|
||||
);
|
||||
},
|
||||
|
||||
duration: function (column, row) {
|
||||
var duration = row[column.id];
|
||||
if (!duration) {
|
||||
return 'n/a';
|
||||
}
|
||||
return $('<div>').attr({
|
||||
'data-toggle': 'tooltip',
|
||||
'data-placement': 'left',
|
||||
title: duration
|
||||
}).text(_humanizeDuration(duration)).prop('outerHTML');
|
||||
},
|
||||
duration: function (column, row) {
|
||||
const duration = row[column.id];
|
||||
if (!duration) {
|
||||
return 'n/a';
|
||||
}
|
||||
return $('<div>')
|
||||
.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 $('<div>').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 (parseInt(matches[5], 10) === 'm') {
|
||||
// units in milliseconds
|
||||
seconds *= 0.001;
|
||||
}
|
||||
if (parseInt(matches[1], 10) === '-') {
|
||||
// negative
|
||||
seconds = -seconds;
|
||||
}
|
||||
return seconds;
|
||||
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 $('<div>')
|
||||
.attr({
|
||||
'data-toggle': 'tooltip',
|
||||
'data-placement': 'left',
|
||||
title: parsed.format(),
|
||||
})
|
||||
.text(_humanizeDate(dt))
|
||||
.prop('outerHTML');
|
||||
},
|
||||
};
|
||||
function _decisionsByType(decisions) {
|
||||
const dectypes = {};
|
||||
if (!decisions) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function _updateFreshness (selector, timestamp) {
|
||||
var $freshness = $(selector).find('.actionBar .freshness');
|
||||
if (timestamp) {
|
||||
$freshness.data('refresh_timestamp', timestamp);
|
||||
} else {
|
||||
timestamp = $freshness.data('refresh_timestamp');
|
||||
}
|
||||
var howlongHuman = '???';
|
||||
var howlongms;
|
||||
if (timestamp) {
|
||||
howlongms = moment() - moment(timestamp);
|
||||
howlongHuman = moment.duration(howlongms).humanize();
|
||||
}
|
||||
$freshness.text(howlongHuman + ' ago');
|
||||
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 _addFreshness (selector) {
|
||||
// this creates one timer per tab
|
||||
var freshnessTemplate = '<span style="float:left">Last refresh: <span class="freshness"></span></span>';
|
||||
$(selector).find('.actionBar').prepend(freshnessTemplate);
|
||||
setInterval(function () {
|
||||
_updateFreshness(selector);
|
||||
}, 5000);
|
||||
function _updateFreshness(selector, timestamp) {
|
||||
const $freshness = $(selector).find('.actionBar .freshness');
|
||||
if (timestamp) {
|
||||
$freshness.data('refresh_timestamp', timestamp);
|
||||
} else {
|
||||
timestamp = $freshness.data('refresh_timestamp');
|
||||
}
|
||||
|
||||
function _humanizeDate (text) {
|
||||
return moment(text).fromNow();
|
||||
const howlongHuman = '???';
|
||||
if (timestamp) {
|
||||
const howlongms = moment() - moment(timestamp);
|
||||
howlongHuman = moment.duration(howlongms).humanize();
|
||||
}
|
||||
$freshness.text(howlongHuman + ' ago');
|
||||
}
|
||||
|
||||
function _humanizeDuration (text) {
|
||||
return moment.duration(_parseDuration(text), 'seconds').humanize();
|
||||
}
|
||||
function _addFreshness(selector) {
|
||||
// this creates one timer per tab
|
||||
const freshnessTemplate =
|
||||
'<span style="float:left">Last refresh: <span class="freshness"></span></span>';
|
||||
$(selector).find('.actionBar').prepend(freshnessTemplate);
|
||||
setInterval(function () {
|
||||
_updateFreshness(selector);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function _yesno2html (val) {
|
||||
if (val) {
|
||||
return '<i class="fa fa-check text-success"></i>';
|
||||
} else {
|
||||
return '<i class="fa fa-times text-danger"></i>';
|
||||
}
|
||||
}
|
||||
|
||||
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 = '';
|
||||
var type;
|
||||
for (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 crowdsecStatus = data['crowdsec-status'];
|
||||
if (crowdsecStatus === 'unknown') {
|
||||
crowdsecStatus = '<span class="text-danger">Unknown</span>';
|
||||
} else {
|
||||
crowdsecStatus = _yesno2html(crowdsecStatus === 'running');
|
||||
}
|
||||
$('#crowdsec-status').html(crowdsecStatus);
|
||||
|
||||
var crowdsecFirewallStatus = data['crowdsec-firewall-status'];
|
||||
if (crowdsecFirewallStatus === 'unknown') {
|
||||
crowdsecFirewallStatus = '<span class="text-danger">Unknown</span>';
|
||||
} else {
|
||||
crowdsecFirewallStatus = _yesno2html(crowdsecFirewallStatus === 'running');
|
||||
}
|
||||
$('#crowdsec-firewall-status').html(crowdsecFirewallStatus);
|
||||
});
|
||||
}
|
||||
|
||||
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 () {
|
||||
$(_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 _refreshTab (selector, url, dataCallback) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
cache: false
|
||||
}).done(dataCallback);
|
||||
function _refreshTab(selector, url, dataCallback) {
|
||||
$.ajax({
|
||||
url: url,
|
||||
cache: false,
|
||||
success: dataCallback,
|
||||
complete: function () {
|
||||
_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 _parseDuration(duration) {
|
||||
const re = /(-?)(?:(?:(\d+)h)?(\d+)m)?(\d+).\d+(m?)s/m;
|
||||
const matches = duration.match(re);
|
||||
let seconds = 0;
|
||||
|
||||
function _initCollections () {
|
||||
var url = '/api/crowdsec/hub/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 ? row.local_path.replace(crowdsec_path, '') : ' ',
|
||||
description: row.description || ' '
|
||||
});
|
||||
});
|
||||
$('#collections table').bootgrid('clear').bootgrid('append', rows);
|
||||
};
|
||||
_initTab('#collections', url, dataCallback);
|
||||
if (!matches.length) {
|
||||
throw new Error(
|
||||
'Unable to parse the following duration: ' + duration + '.',
|
||||
);
|
||||
}
|
||||
|
||||
function _initScenarios () {
|
||||
var url = '/api/crowdsec/hub/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 ? row.local_path.replace(crowdsec_path, '') : ' ',
|
||||
description: row.description || ' '
|
||||
});
|
||||
});
|
||||
$('#scenarios table').bootgrid('clear').bootgrid('append', rows);
|
||||
};
|
||||
_initTab('#scenarios', url, dataCallback);
|
||||
if (typeof matches[2] !== 'undefined') {
|
||||
seconds += parseInt(matches[2], 10) * 3600; // hours
|
||||
}
|
||||
|
||||
function _initParsers () {
|
||||
var url = '/api/crowdsec/hub/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 ? row.local_path.replace(crowdsec_path, '') : ' ',
|
||||
description: row.description || ' '
|
||||
});
|
||||
});
|
||||
$('#parsers table').bootgrid('clear').bootgrid('append', rows);
|
||||
};
|
||||
_initTab('#parsers ', url, dataCallback);
|
||||
if (typeof matches[3] !== 'undefined') {
|
||||
seconds += parseInt(matches[3], 10) * 60; // minutes
|
||||
}
|
||||
|
||||
function _initPostoverflows () {
|
||||
var url = '/api/crowdsec/hub/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 ? row.local_path.replace(crowdsec_path, '') : ' ',
|
||||
description: row.description || ' '
|
||||
});
|
||||
});
|
||||
$('#postoverflows table').bootgrid('clear').bootgrid('append', rows);
|
||||
};
|
||||
_initTab('#postoverflows ', url, dataCallback);
|
||||
if (typeof matches[4] !== 'undefined') {
|
||||
seconds += parseInt(matches[4], 10); // seconds
|
||||
}
|
||||
|
||||
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);
|
||||
if (parseInt(matches[5], 10) === 'm') {
|
||||
// units in milliseconds
|
||||
seconds *= 0.001;
|
||||
}
|
||||
|
||||
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);
|
||||
if (parseInt(matches[1], 10) === '-') {
|
||||
// negative
|
||||
seconds = -seconds;
|
||||
}
|
||||
return seconds;
|
||||
}
|
||||
|
||||
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 _humanizeDate(text) {
|
||||
return moment(text).fromNow();
|
||||
}
|
||||
|
||||
function _humanizeDuration(text) {
|
||||
return moment.duration(_parseDuration(text), 'seconds').humanize();
|
||||
}
|
||||
|
||||
function _yesno2html(val) {
|
||||
if (val) {
|
||||
return '<i class="fa fa-check text-success"></i>';
|
||||
} else {
|
||||
return '<i class="fa fa-times text-danger"></i>';
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
||||
});
|
||||
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 || ' ',
|
||||
});
|
||||
$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
|
||||
});
|
||||
$(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 = '<span class="text-danger">Unknown</span>';
|
||||
} else {
|
||||
crowdsecStatus = _yesno2html(crowdsecStatus === 'running');
|
||||
}
|
||||
$('#crowdsec-status').html(crowdsecStatus);
|
||||
|
||||
let crowdsecFirewallStatus = data['crowdsec-firewall-status'];
|
||||
if (crowdsecFirewallStatus === 'unknown') {
|
||||
crowdsecFirewallStatus = '<span class="text-danger">Unknown</span>';
|
||||
} 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,
|
||||
};
|
||||
})();
|
||||
|
|
|
|||
Loading…
Reference in a new issue