Skip to content

Commit 49e6510

Browse files
authored
Add session expire functionality based on inactivity (#2326)
Implemented inactivity expiration by checking the status of a peer: after a configurable period of time following netbird down, the peer shows login required.
1 parent d93dd4f commit 49e6510

File tree

11 files changed

+682
-59
lines changed

11 files changed

+682
-59
lines changed

management/server/account.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const (
5151
CacheExpirationMax = 7 * 24 * 3600 * time.Second // 7 days
5252
CacheExpirationMin = 3 * 24 * 3600 * time.Second // 3 days
5353
DefaultPeerLoginExpiration = 24 * time.Hour
54+
DefaultPeerInactivityExpiration = 10 * time.Minute
5455
emptyUserID = "empty user ID in claims"
5556
errorGettingDomainAccIDFmt = "error getting account ID by private domain: %v"
5657
)
@@ -181,6 +182,8 @@ type DefaultAccountManager struct {
181182
dnsDomain string
182183
peerLoginExpiry Scheduler
183184

185+
peerInactivityExpiry Scheduler
186+
184187
// userDeleteFromIDPEnabled allows to delete user from IDP when user is deleted from account
185188
userDeleteFromIDPEnabled bool
186189

@@ -198,6 +201,13 @@ type Settings struct {
198201
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
199202
PeerLoginExpiration time.Duration
200203

204+
// PeerInactivityExpirationEnabled globally enables or disables peer inactivity expiration
205+
PeerInactivityExpirationEnabled bool
206+
207+
// PeerInactivityExpiration is a setting that indicates when peer inactivity expires.
208+
// Applies to all peers that have Peer.PeerInactivityExpirationEnabled set to true.
209+
PeerInactivityExpiration time.Duration
210+
201211
// RegularUsersViewBlocked allows to block regular users from viewing even their own peers and some UI elements
202212
RegularUsersViewBlocked bool
203213

@@ -228,6 +238,9 @@ func (s *Settings) Copy() *Settings {
228238
GroupsPropagationEnabled: s.GroupsPropagationEnabled,
229239
JWTAllowGroups: s.JWTAllowGroups,
230240
RegularUsersViewBlocked: s.RegularUsersViewBlocked,
241+
242+
PeerInactivityExpirationEnabled: s.PeerInactivityExpirationEnabled,
243+
PeerInactivityExpiration: s.PeerInactivityExpiration,
231244
}
232245
if s.Extra != nil {
233246
settings.Extra = s.Extra.Copy()
@@ -609,6 +622,60 @@ func (a *Account) GetPeersWithExpiration() []*nbpeer.Peer {
609622
return peers
610623
}
611624

625+
// GetInactivePeers returns peers that have been expired by inactivity
626+
func (a *Account) GetInactivePeers() []*nbpeer.Peer {
627+
var peers []*nbpeer.Peer
628+
for _, inactivePeer := range a.GetPeersWithInactivity() {
629+
inactive, _ := inactivePeer.SessionExpired(a.Settings.PeerInactivityExpiration)
630+
if inactive {
631+
peers = append(peers, inactivePeer)
632+
}
633+
}
634+
return peers
635+
}
636+
637+
// GetNextInactivePeerExpiration returns the minimum duration in which the next peer of the account will expire if it was found.
638+
// If there is no peer that expires this function returns false and a duration of 0.
639+
// This function only considers peers that haven't been expired yet and that are not connected.
640+
func (a *Account) GetNextInactivePeerExpiration() (time.Duration, bool) {
641+
peersWithExpiry := a.GetPeersWithInactivity()
642+
if len(peersWithExpiry) == 0 {
643+
return 0, false
644+
}
645+
var nextExpiry *time.Duration
646+
for _, peer := range peersWithExpiry {
647+
if peer.Status.LoginExpired || peer.Status.Connected {
648+
continue
649+
}
650+
_, duration := peer.SessionExpired(a.Settings.PeerInactivityExpiration)
651+
if nextExpiry == nil || duration < *nextExpiry {
652+
// if expiration is below 1s return 1s duration
653+
// this avoids issues with ticker that can't be set to < 0
654+
if duration < time.Second {
655+
return time.Second, true
656+
}
657+
nextExpiry = &duration
658+
}
659+
}
660+
661+
if nextExpiry == nil {
662+
return 0, false
663+
}
664+
665+
return *nextExpiry, true
666+
}
667+
668+
// GetPeersWithInactivity eturns a list of peers that have Peer.InactivityExpirationEnabled set to true and that were added by a user
669+
func (a *Account) GetPeersWithInactivity() []*nbpeer.Peer {
670+
peers := make([]*nbpeer.Peer, 0)
671+
for _, peer := range a.Peers {
672+
if peer.InactivityExpirationEnabled && peer.AddedWithSSOLogin() {
673+
peers = append(peers, peer)
674+
}
675+
}
676+
return peers
677+
}
678+
612679
// GetPeers returns a list of all Account peers
613680
func (a *Account) GetPeers() []*nbpeer.Peer {
614681
var peers []*nbpeer.Peer
@@ -975,6 +1042,7 @@ func BuildManager(
9751042
dnsDomain: dnsDomain,
9761043
eventStore: eventStore,
9771044
peerLoginExpiry: NewDefaultScheduler(),
1045+
peerInactivityExpiry: NewDefaultScheduler(),
9781046
userDeleteFromIDPEnabled: userDeleteFromIDPEnabled,
9791047
integratedPeerValidator: integratedPeerValidator,
9801048
metrics: metrics,
@@ -1103,6 +1171,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
11031171
am.checkAndSchedulePeerLoginExpiration(ctx, account)
11041172
}
11051173

1174+
err = am.handleInactivityExpirationSettings(ctx, account, oldSettings, newSettings, userID, accountID)
1175+
if err != nil {
1176+
return nil, err
1177+
}
1178+
11061179
updatedAccount := account.UpdateSettings(newSettings)
11071180

11081181
err = am.Store.SaveAccount(ctx, account)
@@ -1113,6 +1186,26 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
11131186
return updatedAccount, nil
11141187
}
11151188

1189+
func (am *DefaultAccountManager) handleInactivityExpirationSettings(ctx context.Context, account *Account, oldSettings, newSettings *Settings, userID, accountID string) error {
1190+
if oldSettings.PeerInactivityExpirationEnabled != newSettings.PeerInactivityExpirationEnabled {
1191+
event := activity.AccountPeerInactivityExpirationEnabled
1192+
if !newSettings.PeerInactivityExpirationEnabled {
1193+
event = activity.AccountPeerInactivityExpirationDisabled
1194+
am.peerInactivityExpiry.Cancel(ctx, []string{accountID})
1195+
} else {
1196+
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
1197+
}
1198+
am.StoreEvent(ctx, userID, accountID, accountID, event, nil)
1199+
}
1200+
1201+
if oldSettings.PeerInactivityExpiration != newSettings.PeerInactivityExpiration {
1202+
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountPeerInactivityExpirationDurationUpdated, nil)
1203+
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
1204+
}
1205+
1206+
return nil
1207+
}
1208+
11161209
func (am *DefaultAccountManager) peerLoginExpirationJob(ctx context.Context, accountID string) func() (time.Duration, bool) {
11171210
return func() (time.Duration, bool) {
11181211
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
@@ -1148,6 +1241,43 @@ func (am *DefaultAccountManager) checkAndSchedulePeerLoginExpiration(ctx context
11481241
}
11491242
}
11501243

1244+
// peerInactivityExpirationJob marks login expired for all inactive peers and returns the minimum duration in which the next peer of the account will expire by inactivity if found
1245+
func (am *DefaultAccountManager) peerInactivityExpirationJob(ctx context.Context, accountID string) func() (time.Duration, bool) {
1246+
return func() (time.Duration, bool) {
1247+
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
1248+
defer unlock()
1249+
1250+
account, err := am.Store.GetAccount(ctx, accountID)
1251+
if err != nil {
1252+
log.Errorf("failed getting account %s expiring peers", account.Id)
1253+
return account.GetNextInactivePeerExpiration()
1254+
}
1255+
1256+
expiredPeers := account.GetInactivePeers()
1257+
var peerIDs []string
1258+
for _, peer := range expiredPeers {
1259+
peerIDs = append(peerIDs, peer.ID)
1260+
}
1261+
1262+
log.Debugf("discovered %d peers to expire for account %s", len(peerIDs), account.Id)
1263+
1264+
if err := am.expireAndUpdatePeers(ctx, account, expiredPeers); err != nil {
1265+
log.Errorf("failed updating account peers while expiring peers for account %s", account.Id)
1266+
return account.GetNextInactivePeerExpiration()
1267+
}
1268+
1269+
return account.GetNextInactivePeerExpiration()
1270+
}
1271+
}
1272+
1273+
// checkAndSchedulePeerInactivityExpiration periodically checks for inactive peers to end their sessions
1274+
func (am *DefaultAccountManager) checkAndSchedulePeerInactivityExpiration(ctx context.Context, account *Account) {
1275+
am.peerInactivityExpiry.Cancel(ctx, []string{account.Id})
1276+
if nextRun, ok := account.GetNextInactivePeerExpiration(); ok {
1277+
go am.peerInactivityExpiry.Schedule(ctx, nextRun, account.Id, am.peerInactivityExpirationJob(ctx, account.Id))
1278+
}
1279+
}
1280+
11511281
// newAccount creates a new Account with a generated ID and generated default setup keys.
11521282
// If ID is already in use (due to collision) we try one more time before returning error
11531283
func (am *DefaultAccountManager) newAccount(ctx context.Context, userID, domain string) (*Account, error) {
@@ -2412,6 +2542,9 @@ func newAccountWithId(ctx context.Context, accountID, userID, domain string) *Ac
24122542
PeerLoginExpiration: DefaultPeerLoginExpiration,
24132543
GroupsPropagationEnabled: true,
24142544
RegularUsersViewBlocked: true,
2545+
2546+
PeerInactivityExpirationEnabled: false,
2547+
PeerInactivityExpiration: DefaultPeerInactivityExpiration,
24152548
},
24162549
}
24172550

0 commit comments

Comments
 (0)