CrowdSec plugin (#2451)
parent
057cfb29e8
commit
b5c0ca94c4
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,98 @@ |
||||
package Lemonldap::NG::Portal::Plugins::CrowdSec; |
||||
|
||||
use strict; |
||||
use Mouse; |
||||
use JSON qw(from_json); |
||||
use Lemonldap::NG::Common::UserAgent; |
||||
use Lemonldap::NG::Portal::Main::Constants qw( |
||||
PE_ERROR |
||||
PE_OK |
||||
PE_SESSIONNOTGRANTED |
||||
); |
||||
|
||||
our $VERSION = '2.0.10'; |
||||
|
||||
extends 'Lemonldap::NG::Portal::Main::Plugin'; |
||||
|
||||
# Entrypoint |
||||
use constant beforeAuth => 'check'; |
||||
|
||||
has ua => ( |
||||
is => 'rw', |
||||
lazy => 1, |
||||
builder => sub { |
||||
|
||||
# TODO : LWP options to use a proxy for example |
||||
my $ua = Lemonldap::NG::Common::UserAgent->new( $_[0]->{conf} ); |
||||
$ua->env_proxy(); |
||||
return $ua; |
||||
} |
||||
); |
||||
|
||||
sub init { |
||||
my ($self) = @_; |
||||
if ( $self->conf->{crowdsecUrl} ) { |
||||
$self->conf->{crowdsecUrl} =~ s#/+$##; |
||||
} |
||||
else { |
||||
$self->logger->warn( |
||||
"crowdsecUrl isn't set, fallback to http://localhost:8080"); |
||||
$self->conf->{crowdsecUrl} = 'http://localhost:8080'; |
||||
} |
||||
$self->logger->notice( "CrowdSec policy is: " |
||||
. ( $self->conf->{crowdsecAction} ? 'reject' : 'warn' ) ); |
||||
return 1; |
||||
} |
||||
|
||||
sub check { |
||||
my ( $self, $req ) = @_; |
||||
my $ip = $req->address; |
||||
my $resp = $self->ua->get( |
||||
$self->conf->{crowdsecUrl} . "/v1/decisions?ip=$ip", |
||||
'Accept' => 'application/json', |
||||
'X-Api-Key' => $self->conf->{crowdsecKey}, |
||||
); |
||||
if ( $resp->is_error ) { |
||||
$self->logger->error( "Bad CrowdSec response: " . $resp->message ); |
||||
$self->logger->debug( $resp->content ); |
||||
return PE_ERROR; |
||||
} |
||||
my $content = $resp->decoded_content; |
||||
unless ($content) { |
||||
$self->userLogger->info("$ip isn't known by CrowsSec"); |
||||
return PE_OK; |
||||
} |
||||
my $json_hash; |
||||
eval { $json_hash = from_json( $content, { allow_nonref => 1 } ); }; |
||||
if ($@) { |
||||
$self->logger->error("Unable to decode CrowdSec response: $content"); |
||||
$self->logger->debug($@); |
||||
return PE_ERROR; |
||||
} |
||||
$self->logger->debug("CrowdSec response: $content"); |
||||
|
||||
# Response is "null" when IP is unknown |
||||
if ($json_hash) { |
||||
|
||||
# CrowdSec may return more than one decision |
||||
foreach my $decision (@$json_hash) { |
||||
if ( $decision->{type} and $decision->{type} eq 'ban' ) { |
||||
$self->userLogger->warn( "$ip banned by CrowdSec ('" |
||||
. $decision->{scenario} |
||||
. "' for $decision->{duration})" ); |
||||
if ( $self->conf->{crowdsecAction} eq 'reject' ) { |
||||
$self->userLogger->error("$ip rejected by CrowdSec"); |
||||
return PE_SESSIONNOTGRANTED; |
||||
} |
||||
else { |
||||
$req->env->{CROWDSEC_REJECT} = 1; |
||||
return PE_OK; |
||||
} |
||||
} |
||||
} |
||||
$self->userLogger->info("$ip not banned by CrowdSec"); |
||||
return PE_OK; |
||||
} |
||||
} |
||||
|
||||
1; |
@ -0,0 +1,69 @@ |
||||
use Test::More; |
||||
use strict; |
||||
use lib 'inc'; |
||||
use IO::String; |
||||
use Data::Dumper; |
||||
use LWP::UserAgent; |
||||
use LWP::Protocol::PSGI; |
||||
|
||||
BEGIN { |
||||
require 't/test-lib.pm'; |
||||
} |
||||
|
||||
my $reject = 1; |
||||
|
||||
LWP::Protocol::PSGI->register( |
||||
sub { |
||||
my $req = Plack::Request->new(@_); |
||||
my $res = $reject |
||||
? '[{"duration":"1h2m10.462947715s", |
||||
"id":1121, |
||||
"origin":"CAPI", |
||||
"scenario":"crowdsecurity/iptables-scan-multi_ports", |
||||
"scope":"ip", |
||||
"type":"ban", |
||||
"value":"127.0.0.1/32"}]' |
||||
: ''; |
||||
return [ 200, [], [$res] ]; |
||||
} |
||||
); |
||||
|
||||
my $res; |
||||
|
||||
my $client = LLNG::Manager::Test->new( { |
||||
ini => { |
||||
logLevel => 'debug', |
||||
authentication => 'Demo', |
||||
userDB => 'Same', |
||||
crowdsec => 1, |
||||
crowdsecAction => 'warn', |
||||
} |
||||
} |
||||
); |
||||
|
||||
ok( |
||||
$res = $client->_post( |
||||
'/', |
||||
IO::String->new('user=dwho&password=dwho'), |
||||
length => 23, |
||||
), |
||||
'Auth query' |
||||
); |
||||
expectOK($res); |
||||
count(1); |
||||
|
||||
$reject = 0; |
||||
ok( |
||||
$res = $client->_post( |
||||
'/', |
||||
IO::String->new('user=dwho&password=dwho'), |
||||
length => 23, |
||||
), |
||||
'Auth query' |
||||
); |
||||
expectOK($res); |
||||
count(1); |
||||
|
||||
clean_sessions(); |
||||
|
||||
done_testing( count() ); |
@ -0,0 +1,69 @@ |
||||
use Test::More; |
||||
use strict; |
||||
use lib 'inc'; |
||||
use IO::String; |
||||
use Data::Dumper; |
||||
use LWP::UserAgent; |
||||
use LWP::Protocol::PSGI; |
||||
|
||||
BEGIN { |
||||
require 't/test-lib.pm'; |
||||
} |
||||
|
||||
my $reject = 1; |
||||
|
||||
LWP::Protocol::PSGI->register( |
||||
sub { |
||||
my $req = Plack::Request->new(@_); |
||||
my $res = $reject |
||||
? '[{"duration":"1h2m10.462947715s", |
||||
"id":1121, |
||||
"origin":"CAPI", |
||||
"scenario":"crowdsecurity/iptables-scan-multi_ports", |
||||
"scope":"ip", |
||||
"type":"ban", |
||||
"value":"127.0.0.1/32"}]' |
||||
: ''; |
||||
return [ 200, [], [$res] ]; |
||||
} |
||||
); |
||||
|
||||
my $res; |
||||
|
||||
my $client = LLNG::Manager::Test->new( { |
||||
ini => { |
||||
logLevel => 'debug', |
||||
authentication => 'Demo', |
||||
userDB => 'Same', |
||||
crowdsec => 1, |
||||
crowdsecAction => 'reject', |
||||
} |
||||
} |
||||
); |
||||
|
||||
ok( |
||||
$res = $client->_post( |
||||
'/', |
||||
IO::String->new('user=dwho&password=dwho'), |
||||
length => 23, |
||||
), |
||||
'Auth query' |
||||
); |
||||
expectReject($res); |
||||
count(1); |
||||
|
||||
$reject = 0; |
||||
ok( |
||||
$res = $client->_post( |
||||
'/', |
||||
IO::String->new('user=dwho&password=dwho'), |
||||
length => 23, |
||||
), |
||||
'Auth query' |
||||
); |
||||
expectOK($res); |
||||
count(1); |
||||
|
||||
clean_sessions(); |
||||
|
||||
done_testing( count() ); |
Loading…
Reference in new issue