@@ -2,8 +2,10 @@ package service
22
33import (
44 "context"
5+ "crypto/rand"
56 "errors"
67 "fmt"
8+ "math/big"
79 "net/http"
810 "strings"
911 "sync"
@@ -25,7 +27,6 @@ import (
2527// but for now these are just safety limits to prevent unbounded memory usage
2628const MaxOAuthPendingSessions = 256
2729const OAuthCleanupCount = 16
28- const MaxLoginAttemptRecords = 256
2930
3031var (
3132 ErrUserNotFound = errors .New ("user not found" )
@@ -81,6 +82,8 @@ type AuthService struct {
8182 oauth * CacheStore [OAuthPendingSession ]
8283 ldap * CacheStore [[]string ]
8384 }
85+
86+ maxLoginLimits int
8487}
8588
8689type AuthServiceInput struct {
@@ -111,9 +114,18 @@ func NewAuthService(i AuthServiceInput) *AuthService {
111114 policyEngine : i .PolicyEngine ,
112115 }
113116
117+ // get the max login limits based on the number of users and the configured max retries
118+ service .maxLoginLimits = service .calculateLockdownLimit ()
119+
120+ loginCacheSize := 0
121+
122+ if ! service .config .Auth .LockdownEnabled {
123+ loginCacheSize = service .maxLoginLimits
124+ }
125+
114126 // caches setup
115127 oauthCache := NewCacheStore [OAuthPendingSession ](256 )
116- loginCache := NewCacheStore [LoginAttempt ](1024 )
128+ loginCache := NewCacheStore [LoginAttempt ](loginCacheSize )
117129 ldapCache := NewCacheStore [[]string ](1024 )
118130
119131 service .caches .oauth = oauthCache
@@ -259,7 +271,7 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
259271 return
260272 }
261273
262- if auth .caches .login .Size () >= MaxLoginAttemptRecords {
274+ if ! success && auth .config . Auth . LockdownEnabled && auth . caches .login .Size () >= auth . maxLoginLimits {
263275 if locked , _ := auth .IsInLockdown (); locked {
264276 return
265277 }
@@ -634,16 +646,17 @@ func (auth *AuthService) lockdownMode() {
634646 return
635647 }
636648
637- ctx , cancel := context .WithCancel (context . Background () )
649+ ctx , cancel := context .WithCancel (auth . ctx )
638650
639651 auth .log .App .Warn ().Msg ("Too many failed login attempts, entering lockdown mode" )
640652
641653 auth .lockdown .active = true
642654 auth .lockdown .ctx = ctx
643655 auth .lockdown .cancelFunc = cancel
644- auth .lockdown .until = time .Now ().Add (time .Duration (auth .config .Auth .LoginTimeout ) * time .Second )
645656
646- timer := time .NewTimer (time .Until (auth .lockdown .until ))
657+ d := time .Duration (auth .config .Auth .LoginTimeout ) * time .Second
658+ auth .lockdown .until = time .Now ().Add (d )
659+ timer := time .NewTimer (d )
647660
648661 auth .lockdown .mu .Unlock ()
649662
@@ -655,14 +668,13 @@ func (auth *AuthService) lockdownMode() {
655668 // Timer expired, end lockdown
656669 case <- ctx .Done ():
657670 // Context cancelled, end lockdown
658- case <- auth .ctx .Done ():
659- // Service is shutting down, end lockdown
660671 }
661672
662673 auth .lockdown .mu .Lock ()
663674
664675 auth .log .App .Info ().Msg ("Exiting lockdown mode" )
665676
677+ auth .caches .login .Clear ()
666678 auth .lockdown .active = false
667679 auth .lockdown .until = time.Time {}
668680 auth .lockdown .ctx = nil
@@ -685,3 +697,32 @@ func (auth *AuthService) IsInLockdown() (bool, int) {
685697func (auth * AuthService ) ClearLoginAttempts () {
686698 auth .caches .login .Clear ()
687699}
700+
701+ func (auth * AuthService ) calculateLockdownLimit () int {
702+ userCount := len (auth .runtime .LocalUsers )
703+
704+ if auth .ldap != nil {
705+ ldapUsers , err := auth .ldap .GetUserCount ()
706+ if err != nil {
707+ auth .log .App .Warn ().Err (err ).Msg ("Failed to get LDAP user count" )
708+ } else {
709+ userCount += ldapUsers
710+ }
711+ }
712+
713+ limit := userCount * auth .config .Auth .LoginMaxRetries
714+
715+ jitter , err := rand .Int (rand .Reader , big .NewInt (64 ))
716+
717+ if err != nil {
718+ auth .log .App .Warn ().Err (err ).Msg ("Failed to generate jitter for lockdown limit" )
719+ } else {
720+ limit += int (jitter .Int64 ())
721+ }
722+
723+ if limit < 256 {
724+ limit = 256
725+ }
726+
727+ return limit
728+ }
0 commit comments