From 9b205464afbced23d12bd4076be0b7a5b0006b81 Mon Sep 17 00:00:00 2001 From: Xavier Guimard Date: Fri, 8 May 2026 11:58:30 +0200 Subject: [PATCH] Add patch to send crowdsec stats --- Changes.md | 1 + full/Dockerfile | 1 + full/crowdsec-stats.patch | 184 ++++++++++++++++++++++++++++++ manager/Dockerfile | 1 + manager/crowdsec-stats.patch | 184 ++++++++++++++++++++++++++++++ portal/Dockerfile | 1 + portal/crowdsec-stats.patch | 127 +++++++++++++++++++++ uwsgi-portal/Dockerfile | 1 + uwsgi-portal/crowdsec-stats.patch | 127 +++++++++++++++++++++ 9 files changed, 627 insertions(+) create mode 100644 full/crowdsec-stats.patch create mode 100644 manager/crowdsec-stats.patch create mode 100644 portal/crowdsec-stats.patch create mode 100644 uwsgi-portal/crowdsec-stats.patch diff --git a/Changes.md b/Changes.md index 1d21e56..9f17af6 100644 --- a/Changes.md +++ b/Changes.md @@ -9,6 +9,7 @@ * Enable autoLoader plugin * Install all plugins from https://linagora.github.io/lemonldap-ng-plugins * Default to bootstrap5 +* Add Crowdsec statistics patch ## v2.21.3-4 _(2026-03-13)_ * Rebuild with recents Debian updates (imagemagick) diff --git a/full/Dockerfile b/full/Dockerfile index fced530..bb870e6 100644 --- a/full/Dockerfile +++ b/full/Dockerfile @@ -25,6 +25,7 @@ COPY *.patch / RUN set -e && for p in \ better-cfgAuthor.patch \ bootstrap5.patch \ + crowdsec-stats.patch \ ; do echo patch $p && patch -p1 < $p || exit 1; done && \ rm -f *.patch && \ find /usr/share/perl5/Lemonldap -name '*.orig' -delete && \ diff --git a/full/crowdsec-stats.patch b/full/crowdsec-stats.patch new file mode 100644 index 0000000..3dbdb96 --- /dev/null +++ b/full/crowdsec-stats.patch @@ -0,0 +1,184 @@ +--- a/usr/share/perl5/Lemonldap/NG/Manager/Build/Attributes.pm ++++ b/usr/share/perl5/Lemonldap/NG/Manager/Build/Attributes.pm +@@ -1551,6 +1551,16 @@ sub attributes { + type => 'text', + documentation => 'Crowdsec filters directory', + }, ++ crowdsecMetricsEnabled => { ++ default => 0, ++ type => 'bool', ++ documentation => 'Enable CrowdSec usage-metrics push', ++ }, ++ crowdsecMetricsInterval => { ++ default => 60, ++ type => 'int', ++ documentation => 'CrowdSec usage-metrics flush interval (seconds)', ++ }, + crowdSecAgentResponseCode => { + type => 'int', + default => 404, +--- a/usr/share/perl5/Lemonldap/NG/Manager/Build/Tree.pm ++++ b/usr/share/perl5/Lemonldap/NG/Manager/Build/Tree.pm +@@ -1259,6 +1259,8 @@ sub tree { + 'crowdsecAction', + 'crowdsecUrl', + 'crowdsecKey', ++ 'crowdsecMetricsEnabled', ++ 'crowdsecMetricsInterval', + ], + }, + { +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/ar.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/ar.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"مفتاح أل API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"كلمة المرور", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"وجهات أجاكس", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/en.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/en.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"API key", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Password", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Ajax destinations", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/es.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/es.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Clave API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Contraseña", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Destinos Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/fr.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/fr.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Clef de l'API", + "crowdsecMachineId":"ID de l'agent Crowdsec", + "crowdsecMaxFailures":"Nombre maximal d'échecs autorisés dans le délai", ++"crowdsecMetricsEnabled":"Activer les métriques CrowdSec", ++"crowdsecMetricsInterval":"Intervalle d'envoi des métriques (s)", + "crowdsecPassword":"Mot de passe", + "crowdsecUrl":"URL de base de l'API locale", + "cspConnect":"Destinations des requêtes AJAX", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/he.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/he.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"מפתח API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"סיסמה", + "crowdsecUrl":"כתובת בסיס ל־API המקומי", + "cspConnect":"יעדי Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/it.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/it.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Chiave API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Password", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Destinazioni Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/pl.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/pl.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Klucz API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Hasło", + "crowdsecUrl":"Podstawowy adres URL lokalnego API", + "cspConnect":"Miejsca docelowe Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/pt.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/pt.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Chave de API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Senha", + "crowdsecUrl":"URL base da API local", + "cspConnect":"Destinos AJAX", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/pt_BR.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/pt_BR.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Chave de API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Senha", + "crowdsecUrl":"URL base da API local", + "cspConnect":"Destinos AJAX", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/ru.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/ru.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Ключ API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Пароль", + "crowdsecUrl":"Базовый URL локального API", + "cspConnect":"Ajax-адресаты", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/tr.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/tr.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"API anahtarı", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Parola", + "crowdsecUrl":"Yerel API'nin temel URL'si", + "cspConnect":"Ajax hedefleri", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/vi.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/vi.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"khóa API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Mật khẩu", + "crowdsecUrl":"URL cơ sở của API cục bộ", + "cspConnect":"Đích cúa Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/zh.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/zh.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"API 金鑰", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"密码", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Ajax 目的地", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/zh_TW.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/zh_TW.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"API 金鑰", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"密碼", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Ajax 目的地", diff --git a/manager/Dockerfile b/manager/Dockerfile index 5ae8580..b37dca4 100644 --- a/manager/Dockerfile +++ b/manager/Dockerfile @@ -62,6 +62,7 @@ COPY *.patch / RUN set -e && for p in \ better-cfgAuthor.patch \ bootstrap5.patch \ + crowdsec-stats.patch \ ; do echo patch $p && patch -p1 < $p || exit 1; done && \ rm -f *.patch && \ find /usr/share/perl5/Lemonldap -name '*.orig' -delete && \ diff --git a/manager/crowdsec-stats.patch b/manager/crowdsec-stats.patch new file mode 100644 index 0000000..3dbdb96 --- /dev/null +++ b/manager/crowdsec-stats.patch @@ -0,0 +1,184 @@ +--- a/usr/share/perl5/Lemonldap/NG/Manager/Build/Attributes.pm ++++ b/usr/share/perl5/Lemonldap/NG/Manager/Build/Attributes.pm +@@ -1551,6 +1551,16 @@ sub attributes { + type => 'text', + documentation => 'Crowdsec filters directory', + }, ++ crowdsecMetricsEnabled => { ++ default => 0, ++ type => 'bool', ++ documentation => 'Enable CrowdSec usage-metrics push', ++ }, ++ crowdsecMetricsInterval => { ++ default => 60, ++ type => 'int', ++ documentation => 'CrowdSec usage-metrics flush interval (seconds)', ++ }, + crowdSecAgentResponseCode => { + type => 'int', + default => 404, +--- a/usr/share/perl5/Lemonldap/NG/Manager/Build/Tree.pm ++++ b/usr/share/perl5/Lemonldap/NG/Manager/Build/Tree.pm +@@ -1259,6 +1259,8 @@ sub tree { + 'crowdsecAction', + 'crowdsecUrl', + 'crowdsecKey', ++ 'crowdsecMetricsEnabled', ++ 'crowdsecMetricsInterval', + ], + }, + { +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/ar.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/ar.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"مفتاح أل API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"كلمة المرور", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"وجهات أجاكس", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/en.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/en.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"API key", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Password", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Ajax destinations", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/es.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/es.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Clave API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Contraseña", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Destinos Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/fr.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/fr.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Clef de l'API", + "crowdsecMachineId":"ID de l'agent Crowdsec", + "crowdsecMaxFailures":"Nombre maximal d'échecs autorisés dans le délai", ++"crowdsecMetricsEnabled":"Activer les métriques CrowdSec", ++"crowdsecMetricsInterval":"Intervalle d'envoi des métriques (s)", + "crowdsecPassword":"Mot de passe", + "crowdsecUrl":"URL de base de l'API locale", + "cspConnect":"Destinations des requêtes AJAX", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/he.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/he.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"מפתח API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"סיסמה", + "crowdsecUrl":"כתובת בסיס ל־API המקומי", + "cspConnect":"יעדי Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/it.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/it.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Chiave API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Password", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Destinazioni Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/pl.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/pl.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Klucz API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Hasło", + "crowdsecUrl":"Podstawowy adres URL lokalnego API", + "cspConnect":"Miejsca docelowe Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/pt.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/pt.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Chave de API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Senha", + "crowdsecUrl":"URL base da API local", + "cspConnect":"Destinos AJAX", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/pt_BR.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/pt_BR.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Chave de API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Senha", + "crowdsecUrl":"URL base da API local", + "cspConnect":"Destinos AJAX", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/ru.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/ru.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"Ключ API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Пароль", + "crowdsecUrl":"Базовый URL локального API", + "cspConnect":"Ajax-адресаты", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/tr.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/tr.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"API anahtarı", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Parola", + "crowdsecUrl":"Yerel API'nin temel URL'si", + "cspConnect":"Ajax hedefleri", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/vi.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/vi.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"khóa API", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"Mật khẩu", + "crowdsecUrl":"URL cơ sở của API cục bộ", + "cspConnect":"Đích cúa Ajax", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/zh.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/zh.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"API 金鑰", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"密码", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Ajax 目的地", +--- a/usr/share/lemonldap-ng/manager/htdocs/static/languages/zh_TW.json ++++ b/usr/share/lemonldap-ng/manager/htdocs/static/languages/zh_TW.json +@@ -294,6 +294,8 @@ + "crowdsecKey":"API 金鑰", + "crowdsecMachineId":"Crowdsec agent ID", + "crowdsecMaxFailures":"Max failures allowed during delay", ++"crowdsecMetricsEnabled":"Enable CrowdSec usage-metrics", ++"crowdsecMetricsInterval":"Usage-metrics flush interval (s)", + "crowdsecPassword":"密碼", + "crowdsecUrl":"Base URL of local API", + "cspConnect":"Ajax 目的地", diff --git a/portal/Dockerfile b/portal/Dockerfile index d18f2b7..dfc35af 100644 --- a/portal/Dockerfile +++ b/portal/Dockerfile @@ -72,6 +72,7 @@ RUN set -e && for p in \ 874-geoip.patch \ 2.23-oidc-hooks.patch \ crowdsec-2.23.patch \ + crowdsec-stats.patch \ autoloader.patch \ bootstrap5.patch \ ; do echo patch $p && patch -p1 < $p || exit 1; done && \ diff --git a/portal/crowdsec-stats.patch b/portal/crowdsec-stats.patch new file mode 100644 index 0000000..5c58f2f --- /dev/null +++ b/portal/crowdsec-stats.patch @@ -0,0 +1,127 @@ +--- a/usr/share/perl5/Lemonldap/NG/Portal/Lib/CrowdSec.pm ++++ b/usr/share/perl5/Lemonldap/NG/Portal/Lib/CrowdSec.pm +@@ -40,6 +40,13 @@ has ua => ( + has token => ( is => 'rw' ); + has tokenExp => ( is => 'rw', default => 0 ); + ++has _metricsCounters => ( ++ is => 'rw', ++ default => sub { { processed => 0, last_flush => time } }, ++); ++has _droppedByOrigin => ( is => 'rw', default => sub { {} } ); ++has _startupTime => ( is => 'rw' ); ++ + has crowdsecUrl => ( is => 'rw' ); + + sub _init { +@@ -54,6 +61,7 @@ sub _init { + "crowdsecUrl isn't set, fallback to http://localhost:8080"); + $self->crowdsecUrl('http://localhost:8080'); + } ++ $self->_startupTime(time); + return 1; + } + +@@ -92,7 +100,8 @@ sub bouncer { + return ( 0, + "$ip banned by CrowdSec ('" + . $decision->{scenario} +- . "' for $decision->{duration})" ); ++ . "' for $decision->{duration})", ++ $decision ); + } + } + $self->userLogger->info("$ip not banned by CrowdSec"); +@@ -441,4 +450,78 @@ sub getToken { + return $self->token( $json->{token} ); + } + ++sub _recordRequest { ++ my ( $self, $ip, $banDecision ) = @_; ++ return if defined $self->conf->{crowdsecMetricsEnabled} and !$self->conf->{crowdsecMetricsEnabled}; ++ $self->_metricsCounters->{processed}++; ++ if ($banDecision) { ++ my $origin = $banDecision->{origin} || 'unknown'; ++ my $ip_type = ++ ( $ip && index( $ip, ':' ) >= 0 ) ? 'ipv6' : 'ipv4'; ++ $self->_droppedByOrigin->{"$origin|$ip_type"}++; ++ } ++ my $interval = $self->conf->{crowdsecMetricsInterval} || 60; ++ $self->_flushMetrics ++ if time - $self->_metricsCounters->{last_flush} >= $interval; ++ return 1; ++} ++ ++# Push usage-metrics to CrowdSec LAPI ++sub _flushMetrics { ++ my ($self) = @_; ++ my $now = time; ++ my $proc = $self->_metricsCounters->{processed}; ++ my %drops = %{ $self->_droppedByOrigin }; ++ return unless $proc or %drops; ++ ++ my $window = ( $now - $self->_metricsCounters->{last_flush} ) || 1; ++ my @items = ( { ++ name => 'processed', ++ value => $proc, ++ labels => {}, ++ unit => 'request', ++ } ); ++ for my $key ( keys %drops ) { ++ my ( $origin, $ip_type ) = split /\|/, $key, 2; ++ push @items, { ++ name => 'dropped', ++ value => $drops{$key}, ++ labels => { origin => $origin, ip_type => $ip_type }, ++ unit => 'request', ++ }; ++ } ++ ++ my $payload = { ++ remediation_components => [ { ++ name => 'llng', ++ type => 'LLNG-CrowdsecAgent', ++ version => $VERSION, ++ feature_flags => [], ++ utc_startup_timestamp => $self->_startupTime, ++ os => { name => $^O, version => '' }, ++ metrics => [ { ++ meta => { ++ utc_now_timestamp => $now, ++ window_size_seconds => $window, ++ }, ++ items => \@items, ++ } ], ++ } ], ++ }; ++ ++ my $resp = $self->ua->post( ++ $self->crowdsecUrl . '/v1/usage-metrics', ++ 'X-Api-Key' => $self->conf->{crowdsecKey}, ++ 'Content-Type' => 'application/json', ++ Content => to_json($payload), ++ ); ++ if ( !$resp->is_success ) { ++ $self->logger->warn( ++ 'Failed to push usage-metrics: ' . $resp->status_line ); ++ } ++ $self->_metricsCounters( { processed => 0, last_flush => $now } ); ++ $self->_droppedByOrigin( {} ); ++ return; ++} ++ + 1; +--- a/usr/share/perl5/Lemonldap/NG/Portal/Plugins/CrowdSec.pm ++++ b/usr/share/perl5/Lemonldap/NG/Portal/Plugins/CrowdSec.pm +@@ -39,7 +39,8 @@ sub checkCrowdsec { + return $sub->($req); + } + my $ip = $req->address; +- my ( $ok, $err ) = $self->bouncer($ip); ++ my ( $ok, $err, $decision ) = $self->bouncer($ip); ++ $self->_recordRequest( $ip, $ok ? undef : $decision ); + + # bouncer() answer $ok=0 only when Crowdsec response rejects the given IP + return $sub->($req) +new file mode 100644 +--- /dev/null diff --git a/uwsgi-portal/Dockerfile b/uwsgi-portal/Dockerfile index 270234c..48b600c 100644 --- a/uwsgi-portal/Dockerfile +++ b/uwsgi-portal/Dockerfile @@ -71,6 +71,7 @@ RUN set -e && for p in \ 874-geoip.patch \ 2.23-oidc-hooks.patch \ crowdsec-2.23.patch \ + crowdsec-stats.patch \ autoloader.patch \ bootstrap5.patch \ ; do echo patch $p && patch -p1 < $p || exit 1; done && \ diff --git a/uwsgi-portal/crowdsec-stats.patch b/uwsgi-portal/crowdsec-stats.patch new file mode 100644 index 0000000..5c58f2f --- /dev/null +++ b/uwsgi-portal/crowdsec-stats.patch @@ -0,0 +1,127 @@ +--- a/usr/share/perl5/Lemonldap/NG/Portal/Lib/CrowdSec.pm ++++ b/usr/share/perl5/Lemonldap/NG/Portal/Lib/CrowdSec.pm +@@ -40,6 +40,13 @@ has ua => ( + has token => ( is => 'rw' ); + has tokenExp => ( is => 'rw', default => 0 ); + ++has _metricsCounters => ( ++ is => 'rw', ++ default => sub { { processed => 0, last_flush => time } }, ++); ++has _droppedByOrigin => ( is => 'rw', default => sub { {} } ); ++has _startupTime => ( is => 'rw' ); ++ + has crowdsecUrl => ( is => 'rw' ); + + sub _init { +@@ -54,6 +61,7 @@ sub _init { + "crowdsecUrl isn't set, fallback to http://localhost:8080"); + $self->crowdsecUrl('http://localhost:8080'); + } ++ $self->_startupTime(time); + return 1; + } + +@@ -92,7 +100,8 @@ sub bouncer { + return ( 0, + "$ip banned by CrowdSec ('" + . $decision->{scenario} +- . "' for $decision->{duration})" ); ++ . "' for $decision->{duration})", ++ $decision ); + } + } + $self->userLogger->info("$ip not banned by CrowdSec"); +@@ -441,4 +450,78 @@ sub getToken { + return $self->token( $json->{token} ); + } + ++sub _recordRequest { ++ my ( $self, $ip, $banDecision ) = @_; ++ return if defined $self->conf->{crowdsecMetricsEnabled} and !$self->conf->{crowdsecMetricsEnabled}; ++ $self->_metricsCounters->{processed}++; ++ if ($banDecision) { ++ my $origin = $banDecision->{origin} || 'unknown'; ++ my $ip_type = ++ ( $ip && index( $ip, ':' ) >= 0 ) ? 'ipv6' : 'ipv4'; ++ $self->_droppedByOrigin->{"$origin|$ip_type"}++; ++ } ++ my $interval = $self->conf->{crowdsecMetricsInterval} || 60; ++ $self->_flushMetrics ++ if time - $self->_metricsCounters->{last_flush} >= $interval; ++ return 1; ++} ++ ++# Push usage-metrics to CrowdSec LAPI ++sub _flushMetrics { ++ my ($self) = @_; ++ my $now = time; ++ my $proc = $self->_metricsCounters->{processed}; ++ my %drops = %{ $self->_droppedByOrigin }; ++ return unless $proc or %drops; ++ ++ my $window = ( $now - $self->_metricsCounters->{last_flush} ) || 1; ++ my @items = ( { ++ name => 'processed', ++ value => $proc, ++ labels => {}, ++ unit => 'request', ++ } ); ++ for my $key ( keys %drops ) { ++ my ( $origin, $ip_type ) = split /\|/, $key, 2; ++ push @items, { ++ name => 'dropped', ++ value => $drops{$key}, ++ labels => { origin => $origin, ip_type => $ip_type }, ++ unit => 'request', ++ }; ++ } ++ ++ my $payload = { ++ remediation_components => [ { ++ name => 'llng', ++ type => 'LLNG-CrowdsecAgent', ++ version => $VERSION, ++ feature_flags => [], ++ utc_startup_timestamp => $self->_startupTime, ++ os => { name => $^O, version => '' }, ++ metrics => [ { ++ meta => { ++ utc_now_timestamp => $now, ++ window_size_seconds => $window, ++ }, ++ items => \@items, ++ } ], ++ } ], ++ }; ++ ++ my $resp = $self->ua->post( ++ $self->crowdsecUrl . '/v1/usage-metrics', ++ 'X-Api-Key' => $self->conf->{crowdsecKey}, ++ 'Content-Type' => 'application/json', ++ Content => to_json($payload), ++ ); ++ if ( !$resp->is_success ) { ++ $self->logger->warn( ++ 'Failed to push usage-metrics: ' . $resp->status_line ); ++ } ++ $self->_metricsCounters( { processed => 0, last_flush => $now } ); ++ $self->_droppedByOrigin( {} ); ++ return; ++} ++ + 1; +--- a/usr/share/perl5/Lemonldap/NG/Portal/Plugins/CrowdSec.pm ++++ b/usr/share/perl5/Lemonldap/NG/Portal/Plugins/CrowdSec.pm +@@ -39,7 +39,8 @@ sub checkCrowdsec { + return $sub->($req); + } + my $ip = $req->address; +- my ( $ok, $err ) = $self->bouncer($ip); ++ my ( $ok, $err, $decision ) = $self->bouncer($ip); ++ $self->_recordRequest( $ip, $ok ? undef : $decision ); + + # bouncer() answer $ok=0 only when Crowdsec response rejects the given IP + return $sub->($req) +new file mode 100644 +--- /dev/null