Merge branch '2276' into 'v2.0'

2276

See merge request lemonldap-ng/lemonldap-ng!159
This commit is contained in:
Christophe Maudoux 2020-08-28 15:06:01 +02:00
commit 5d056699c4
21 changed files with 141 additions and 85 deletions

View File

@ -20,6 +20,15 @@ Go in Manager, ``General Parameters`` » ``Advanced Parameters`` »
``Security`` » ``Brute-force attack protection`` » ``Activation``\ and
set to ``On``.
- **Parameters**:
- **Activation**: Enable/disable brute force attack protection
- **Lock time**: Waiting time before another login attempt
- **Allowed failed login**: Number of failed login attempts allowed before account is locked
- **Incremental lock**: Enable/disable incremental lock times
- **Incremental lock times**: List of comma separated lock time values in seconds
Incremental lock time enabled
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -35,33 +44,29 @@ in ``lemonldap-ng.ini`` [portal] section:
[portal]
bruteForceProtectionIncrementalTempo = 1
Lock time increases between each failed login attempt. To modify lock
time values ('5 15 60 300 600' seconds by default) or max lock time
value (900 seconds by default) edit ``lemonldap-ng.ini`` in [portal]
section:
Lock time increases between each failed login attempt after allowed failed logins.
.. code-block:: ini
[portal]
bruteForceProtectionLockTimes = '5 15 60 300 600'
bruteForceProtectionLockTimes = 5, 15, 60, 300, 600
bruteForceProtectionMaxLockTime = 900
.. note::
Max lock time value is used by this plugin if a lock time is
missing (number of failed logins higher than listed lock time values).
Max lock time value is used if a lock time is missing
(number of failed logins higher than listed lock time values).
Lock time values can not be higher than max lock time.
Incremental lock time disabled
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After ``bruteForceProtectionMaxFailed`` failed login attempts, user must
wait ``bruteForceProtectionTempo`` seconds before trying to log in
again. To modify waiting time (30 seconds by default), MaxAge between
current and last stored failed login (300 seconds by default) or number
of allowed failed login attempts (3 by default) edit
``lemonldap-ng.ini`` in [portal] section:
After allowed failed login attempts, user must
wait the lock time before trying to log in again.
To modify delta (MaxAge) between current and last stored
failed login (300 seconds by default) edit ``lemonldap-ng.ini`` in [portal] section:
.. code-block:: ini
@ -72,7 +77,12 @@ of allowed failed login attempts (3 by default) edit
.. attention::
Number of failed login attempts history might be also higher than
number of incremental lock time value plus allowed failed login attempts.
Incremental lock time values list will be truncated if not.
.. danger::
Number of failed login attempts stored in history MUST
be higher than allowed failed logins for this plugin takes effect.
See :doc:`History plugin<loginhistory>`

View File

@ -19,7 +19,7 @@ sub defaultValues {
'authentication' => 'Demo',
'available2F' => 'UTOTP,TOTP,U2F,REST,Mail2F,Ext2F,Yubikey,Radius',
'available2FSelfRegistration' => 'TOTP,U2F,Yubikey',
'bruteForceProtectionLockTimes' => '5 15 60 300 600',
'bruteForceProtectionLockTimes' => '5, 15, 60, 300, 600',
'bruteForceProtectionMaxAge' => 300,
'bruteForceProtectionMaxFailed' => 3,
'bruteForceProtectionMaxLockTime' => 900,

View File

@ -636,7 +636,7 @@ sub attributes {
'type' => 'bool'
},
'bruteForceProtectionLockTimes' => {
'default' => '5 15 60 300 600',
'default' => '5, 15, 60, 300, 600',
'type' => 'text'
},
'bruteForceProtectionMaxAge' => {

View File

@ -833,7 +833,7 @@ sub attributes {
},
bruteForceProtectionLockTimes => {
type => 'text',
default => '5 15 60 300 600',
default => '5, 15, 60, 300, 600',
documentation =>
'Incremental lock time values for brute force attack protection',
},

View File

@ -630,7 +630,8 @@ sub tree {
'notificationStorageOptions',
{
title => 'serverNotification',
help => 'notifications.html#notification-server',
help =>
'notifications.html#notification-server',
nodes => [
'notificationServer',
'notificationDefaultCond',
@ -959,7 +960,10 @@ sub tree {
form => 'simpleInputContainer',
nodes => [
'bruteForceProtection',
'bruteForceProtectionTempo',
'bruteForceProtectionMaxFailed',
'bruteForceProtectionIncrementalTempo',
'bruteForceProtectionLockTimes',
]
},
'lwpOpts',

View File

@ -254,8 +254,6 @@ sub tests {
return ( 1, "Cookie TTL should be higher or equal than one hour" )
unless ( $conf->{cookieExpiration} >= 3600
|| $conf->{cookieExpiration} == 0 );
# Return
return 1;
},
@ -265,8 +263,6 @@ sub tests {
return ( -1, "Session timeout should be higher than ten minutes" )
unless ( $conf->{timeout} > 600
|| $conf->{timeout} == 0 );
# Return
return 1;
},
@ -278,8 +274,6 @@ sub tests {
)
unless ( $conf->{timeoutActivity} > 59
|| $conf->{timeoutActivity} == 0 );
# Return
return 1;
},
@ -292,8 +286,6 @@ sub tests {
if ( $conf->{timeoutActivity}
and $conf->{timeoutActivity} <=
$conf->{timeoutActivityInterval} );
# Return
return 1;
},
@ -338,8 +330,6 @@ sub tests {
return ( 1, "SMTP authentication failed" )
unless $smtp->auth( $conf->{SMTPAuthUser},
$conf->{SMTPAuthPass} );
# Return
return 1;
},
@ -441,8 +431,6 @@ sub tests {
unless ( $conf->{combination} );
return ( 0, 'userDB must be set to "Same" to enable Combination' )
unless ( $conf->{userDB} eq "Same" );
# Return
return 1;
},
@ -482,8 +470,6 @@ sub tests {
"Auth::Yubikey_WebClient module is required to enable Yubikey"
) if ($@);
}
# Return
return 1;
},
@ -521,8 +507,6 @@ sub tests {
unless ( $conf->{totp2fRange} );
return ( 1, "TOTP interval should be higher than 10s" )
unless ( $conf->{totp2fInterval} > 10 );
# Return
return 1;
},
@ -570,7 +554,6 @@ sub tests {
|| $conf->{'totp2fSelfRegistration'} );
$msg = "A self registrable module should be enabled to require 2FA"
unless ($ok);
return ( 1, $msg );
},
@ -583,8 +566,6 @@ sub tests {
return ( 0, "External 2F Validate command must be set" )
unless ( defined $conf->{ext2FValidateCommand} );
}
# Return
return 1;
},
@ -595,8 +576,6 @@ sub tests {
unless ( $conf->{formTimeout} > 30 );
return ( 1, "XSRF form token TTL should not be higher than 2mn" )
if ( $conf->{formTimeout} > 120 );
# Return
return 1;
},
@ -607,8 +586,6 @@ sub tests {
unless ( $conf->{issuersTimeout} > 30 );
return ( 1, "Issuers token TTL should not be higher than 2mn" )
if ( $conf->{issuersTimeout} > 120 );
# Return
return 1;
},
@ -617,8 +594,6 @@ sub tests {
return 1 unless ( $conf->{portalDisplayResetPassword} );
return ( 1, "Number of reset password retries should not be null" )
unless ( $conf->{passwordResetAllowedRetries} );
# Return
return 1;
},
@ -641,8 +616,18 @@ sub tests {
return ( 1,
'Number of failed logins must be higher than 2 to enable "BruteForceProtection" plugin'
) unless ( $conf->{failedLoginNumber} > 2 );
# Return
return ( 1,
'Number of failed logins history must be higher than allowed failed logins plus lock time values'
)
if ( $conf->{bruteForceProtectionIncrementalTempo}
&& $conf->{failedLoginNumber} <=
$conf->{bruteForceProtectionMaxFailed} +
$conf->{bruteForceProtectionLockTimes} );
return ( 1,
'Number of failed logins history must be higher than allowed failed logins'
)
unless ( $conf->{failedLoginNumber} >
$conf->{bruteForceProtectionMaxFailed} );
return 1;
},
@ -654,8 +639,6 @@ sub tests {
)
unless ( $conf->{requireToken}
or $conf->{captcha_mail_enabled} );
# Return
return 1;
},
@ -666,8 +649,6 @@ sub tests {
)
if ( $conf->{impersonationRule}
&& $conf->{contextSwitchingRule} );
# Return
return 1;
},
@ -691,8 +672,6 @@ sub tests {
return ( 1,
"BruteForceProtection plugin enabled WITHOUT persistent session storage"
) if ( $conf->{bruteForceProtection} );
# Return
return 1;
},
@ -707,8 +686,6 @@ sub tests {
return ( 1,
"XML::LibXSLT module is required to enable old format notifications"
) if ($@);
# Return
return 1;
},
@ -722,8 +699,6 @@ sub tests {
return ( 1,
"DateTime::Format::RFC3339 module is required to enable CertificateResetByMail plugin"
) if ($@);
# Return
return 1;
},
@ -867,7 +842,6 @@ sub tests {
and $conf->{portal} !~ /^https:/ );
return 1;
},
};
}

View File

@ -106,7 +106,10 @@
"browseTree":"تصفح الهيكل",
"bruteForceProtection":"تفعيل",
"bruteForceAttackProtection":"Brute-force attack protection",
"bruteForceProtectionIncrementalTempo":"Incremental lock times",
"bruteForceProtectionIncrementalTempo":"Incremental lock",
"bruteForceProtectionLockTimes":"Incremental lock times",
"bruteForceProtectionMaxFailed":"Allowed failed logins",
"bruteForceProtectionTempo":"Lock time",
"cancel":"إلغاء",
"captcha_login_enabled":"التفعيل في استمارة تسجيل الدخول",
"captcha_mail_enabled":"التفعيل في إعادة تعيين كلمة المرور بواسطة استمارة البريد",

View File

@ -106,7 +106,10 @@
"browseTree":"Browse tree",
"bruteForceProtection":"Activation",
"bruteForceAttackProtection":"Brute-force attack protection",
"bruteForceProtectionIncrementalTempo":"Incremental lock times",
"bruteForceProtectionIncrementalTempo":"Incremental lock",
"bruteForceProtectionLockTimes":"Incremental lock times",
"bruteForceProtectionMaxFailed":"Allowed failed logins",
"bruteForceProtectionTempo":"Lock time",
"cancel":"Abbrechen",
"captcha_login_enabled":"Activation in login form",
"captcha_mail_enabled":"Activation in password reset by mail form",

View File

@ -106,7 +106,10 @@
"browseTree":"Browse tree",
"bruteForceProtection":"Activation",
"bruteForceAttackProtection":"Brute-force attack protection",
"bruteForceProtectionIncrementalTempo":"Incremental lock times",
"bruteForceProtectionIncrementalTempo":"Incremental lock",
"bruteForceProtectionLockTimes":"Incremental lock times",
"bruteForceProtectionMaxFailed":"Allowed failed logins",
"bruteForceProtectionTempo":"Lock time",
"cancel":"Cancel",
"captcha_login_enabled":"Activation in login form",
"captcha_mail_enabled":"Activation in password reset by mail form",

View File

@ -106,7 +106,10 @@
"browseTree":"Parcourir l'arbre",
"bruteForceProtection":"Activation",
"bruteForceAttackProtection":"Protection contre les attaques par force brute",
"bruteForceProtectionIncrementalTempo":"Temps de verrouillage incrémentiels",
"bruteForceProtectionIncrementalTempo":"Verrouillage incrémentiel",
"bruteForceProtectionLockTimes":"Temps de verrouillage incrémentiel",
"bruteForceProtectionMaxFailed":"Nombre d'échecs de connexion autorisés",
"bruteForceProtectionTempo":"Temps de verrouillage",
"cancel":"Annuler",
"captcha_login_enabled":"Activation dans le formulaire d'authentification",
"captcha_mail_enabled":"Activation dans le formulaire de réinitialisation par mail",

View File

@ -106,7 +106,10 @@
"browseTree":"Naviga albero",
"bruteForceProtection":"Attivazione",
"bruteForceAttackProtection":"Brute-force attack protection",
"bruteForceProtectionIncrementalTempo":"Incremental lock times",
"bruteForceProtectionIncrementalTempo":"Incremental lock",
"bruteForceProtectionLockTimes":"Incremental lock times",
"bruteForceProtectionMaxFailed":"Allowed failed logins",
"bruteForceProtectionTempo":"Lock time",
"cancel":"Cancella",
"captcha_login_enabled":"Attivazione nel modulo di login",
"captcha_mail_enabled":"Attivazione della reimpostazione della password tramite modulo di posta",

View File

@ -106,7 +106,10 @@
"browseTree":"Przeglądaj drzewo",
"bruteForceProtection":"Aktywacja",
"bruteForceAttackProtection":"Ochrona przed atakiem siłowym",
"bruteForceProtectionIncrementalTempo":"Przyrostowe czasy blokady",
"bruteForceProtectionIncrementalTempo":"Incremental lock",
"bruteForceProtectionLockTimes":"Incremental lock times",
"bruteForceProtectionMaxFailed":"Allowed failed logins",
"bruteForceProtectionTempo":"Lock time",
"cancel":"Anuluj",
"captcha_login_enabled":"Aktywacja w formularzu logowania",
"captcha_mail_enabled":"Aktywacja przy resetowaniu hasła za pomocą formularza pocztowego",

View File

@ -106,7 +106,10 @@
"browseTree":"Ağaca göz at",
"bruteForceProtection":"Aktivasyon",
"bruteForceAttackProtection":"Kaba kuvvet saldırı koruması",
"bruteForceProtectionIncrementalTempo":"Artan gecikme zamanı",
"bruteForceProtectionIncrementalTempo":"Incremental lock",
"bruteForceProtectionLockTimes":"Incremental lock times",
"bruteForceProtectionMaxFailed":"Allowed failed logins",
"bruteForceProtectionTempo":"Lock time",
"cancel":"İptal Et",
"captcha_login_enabled":"Giriş formunda aktivasyon",
"captcha_mail_enabled":"E-posta formu tarafından parola sıfırlamada aktivasyon",

View File

@ -106,7 +106,10 @@
"browseTree":"Duyệt cây",
"bruteForceProtection":"Kích hoạt",
"bruteForceAttackProtection":"Brute-force attack protection",
"bruteForceProtectionIncrementalTempo":"Incremental lock times",
"bruteForceProtectionIncrementalTempo":"Incremental lock",
"bruteForceProtectionLockTimes":"Incremental lock times",
"bruteForceProtectionMaxFailed":"Allowed failed logins",
"bruteForceProtectionTempo":"Lock time",
"cancel":"Hủy",
"captcha_login_enabled":"Kích hoạt ở dạng đăng nhập",
"captcha_mail_enabled":"Kích hoạt đặt lại mật khẩu bằng biểu mẫu thư",

View File

@ -106,7 +106,10 @@
"browseTree":"浏览树",
"bruteForceProtection":"激活",
"bruteForceAttackProtection":"Brute-force attack protection",
"bruteForceProtectionIncrementalTempo":"Incremental lock times",
"bruteForceProtectionIncrementalTempo":"Incremental lock",
"bruteForceProtectionLockTimes":"Incremental lock times",
"bruteForceProtectionMaxFailed":"Allowed failed logins",
"bruteForceProtectionTempo":"Lock time",
"cancel":"取消",
"captcha_login_enabled":" 登录激活",
"captcha_mail_enabled":"通过邮件进行密码重置 激活",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -24,10 +24,6 @@ my @notManagedAttributes = (
'sfEngine', 'available2FSelfRegistration', 'available2F', 'max2FDevices',
'max2FDevicesNameLength',
# Brute force attack protection parameters
'bruteForceProtectionMaxAge', 'bruteForceProtectionTempo',
'bruteForceProtectionMaxFailed',
# Handlers
'handlerInternalCache', 'handlerServiceTokenTTL',
@ -42,8 +38,8 @@ my @notManagedAttributes = (
'syslogFacility', 'userLogger', 'logLevel',
# Plugins parameters
'notificationsMaxRetrieve', 'persistentSessionAttributes',
'bruteForceProtectionLockTimes', 'bruteForceProtectionMaxLockTime',
'notificationsMaxRetrieve', 'persistentSessionAttributes',
'bruteForceProtectionMaxAge', 'bruteForceProtectionMaxLockTime',
# PSGI/CGI protection (must be set in lemonldap-ng.ini)
'protection',

View File

@ -4,7 +4,7 @@ use strict;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK PE_WAIT);
our $VERSION = '2.0.8';
our $VERSION = '2.0.9';
extends 'Lemonldap::NG::Portal::Main::Plugin';
@ -38,9 +38,9 @@ sub init {
unless ( $self->conf->{failedLoginNumber} >
$self->conf->{bruteForceProtectionMaxFailed} )
{
$self->logger->error( 'failedLoginNumber('
$self->logger->error( 'Number of failed logins history ('
. $self->conf->{failedLoginNumber}
. ') must be higher than bruteForceProtectionMaxFailed('
. ') must be higher than allowed failed logins attempt ('
. $self->conf->{bruteForceProtectionMaxFailed}
. ')' );
return 0;
@ -48,20 +48,33 @@ sub init {
if ( $self->conf->{bruteForceProtectionIncrementalTempo} ) {
my $lockTimes = @{ $self->lockTimes } =
sort { $a <=> $b }
map { $_ < $self->conf->{bruteForceProtectionMaxLockTime} ? $_ : () }
map {
$_ =~ s/\D//;
$_ < $self->conf->{bruteForceProtectionMaxLockTime} ? $_ : ()
}
grep { /\d+/ }
split /\s+/, $self->conf->{bruteForceProtectionLockTimes};
split /\s*,\s*/, $self->conf->{bruteForceProtectionLockTimes};
unless ($lockTimes) {
@{ $self->lockTimes } = ( 5, 15, 60, 300, 600 );
$lockTimes = 5;
}
if ( $lockTimes > $self->conf->{failedLoginNumber} ) {
$self->logger->warn( 'Number of incremental lock time values ('
. "$lockTimes) is higher than failed logins history ("
for (
my $i = 1 ;
$i <= $self->conf->{bruteForceProtectionMaxFailed} ;
$i++
)
{
unshift @{ $self->lockTimes }, 0;
$lockTimes++;
}
unless ( $lockTimes < $self->conf->{failedLoginNumber} ) {
$self->logger->warn( 'Number failed logins history ('
. $self->conf->{failedLoginNumber}
. ')' );
. ') must be higher than incremental lock time values plus allowed failed logins attempt ('
. "$lockTimes)" );
splice @{ $self->lockTimes }, $self->conf->{failedLoginNumber};
$lockTimes = $self->conf->{failedLoginNumber};
}
@ -96,9 +109,9 @@ sub run {
my $delta = $now - $lastFailedLoginEpoch;
$self->logger->debug(" -> Delta = $delta");
my $waitingTime = $self->lockTimes->[ $countFailed - 1 ]
|| $self->conf->{bruteForceProtectionMaxLockTime};
// $self->conf->{bruteForceProtectionMaxLockTime};
$self->logger->debug(" -> Waiting time = $waitingTime");
unless ( $delta > $waitingTime ) {
if ( $waitingTime && $delta <= $waitingTime ) {
$self->logger->debug("BruteForceProtection enabled");
$req->lockTime($waitingTime);
return PE_WAIT;

View File

@ -26,6 +26,7 @@ SKIP: {
totp2fSelfRegistration => 1,
totp2fActivation => 1,
failedLoginNumber => 4,
bruteForceProtectionMaxFailed => 0,
}
}
);

View File

@ -16,9 +16,10 @@ my $client = LLNG::Manager::Test->new( {
loginHistoryEnabled => 1,
bruteForceProtection => 1,
bruteForceProtectionIncrementalTempo => 1,
failedLoginNumber => 4,
failedLoginNumber => 6,
bruteForceProtectionMaxLockTime => 300,
bruteForceProtectionLockTimes => '5 500 bad 20 10 ',
bruteForceProtectionLockTimes => '5 , 500, bad ,20, 10 ',
bruteForceProtectionMaxFailed => 2,
}
}
);
@ -38,6 +39,36 @@ my $id = expectCookie($res);
expectRedirection( $res, 'http://auth.example.com/' );
$client->logout($id);
## First allowed failed login
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=ohwd'),
length => 23,
accept => 'text/html',
),
'1st allowed Bad Auth query'
);
ok( $res->[2]->[0] =~ /<span trmsg="5"><\/span>/,
'Bad credential' )
or print STDERR Dumper( $res->[2]->[0] );
count(2);
## Second allowed failed login
ok(
$res = $client->_post(
'/',
IO::String->new('user=dwho&password=ohwd'),
length => 23,
accept => 'text/html',
),
'2nd allowed Bad Auth query'
);
ok( $res->[2]->[0] =~ /<span trmsg="5"><\/span>/,
'Bad credential' )
or print STDERR Dumper( $res->[2]->[0] );
count(2);
## First failed connection
ok(
$res = $client->_post(