mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-21 15:43:12 +01:00
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.
This commit is contained in:
parent
d93dd4fc7f
commit
49e65109d2
@ -51,6 +51,7 @@ const (
|
|||||||
CacheExpirationMax = 7 * 24 * 3600 * time.Second // 7 days
|
CacheExpirationMax = 7 * 24 * 3600 * time.Second // 7 days
|
||||||
CacheExpirationMin = 3 * 24 * 3600 * time.Second // 3 days
|
CacheExpirationMin = 3 * 24 * 3600 * time.Second // 3 days
|
||||||
DefaultPeerLoginExpiration = 24 * time.Hour
|
DefaultPeerLoginExpiration = 24 * time.Hour
|
||||||
|
DefaultPeerInactivityExpiration = 10 * time.Minute
|
||||||
emptyUserID = "empty user ID in claims"
|
emptyUserID = "empty user ID in claims"
|
||||||
errorGettingDomainAccIDFmt = "error getting account ID by private domain: %v"
|
errorGettingDomainAccIDFmt = "error getting account ID by private domain: %v"
|
||||||
)
|
)
|
||||||
@ -181,6 +182,8 @@ type DefaultAccountManager struct {
|
|||||||
dnsDomain string
|
dnsDomain string
|
||||||
peerLoginExpiry Scheduler
|
peerLoginExpiry Scheduler
|
||||||
|
|
||||||
|
peerInactivityExpiry Scheduler
|
||||||
|
|
||||||
// userDeleteFromIDPEnabled allows to delete user from IDP when user is deleted from account
|
// userDeleteFromIDPEnabled allows to delete user from IDP when user is deleted from account
|
||||||
userDeleteFromIDPEnabled bool
|
userDeleteFromIDPEnabled bool
|
||||||
|
|
||||||
@ -198,6 +201,13 @@ type Settings struct {
|
|||||||
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
|
// Applies to all peers that have Peer.LoginExpirationEnabled set to true.
|
||||||
PeerLoginExpiration time.Duration
|
PeerLoginExpiration time.Duration
|
||||||
|
|
||||||
|
// PeerInactivityExpirationEnabled globally enables or disables peer inactivity expiration
|
||||||
|
PeerInactivityExpirationEnabled bool
|
||||||
|
|
||||||
|
// PeerInactivityExpiration is a setting that indicates when peer inactivity expires.
|
||||||
|
// Applies to all peers that have Peer.PeerInactivityExpirationEnabled set to true.
|
||||||
|
PeerInactivityExpiration time.Duration
|
||||||
|
|
||||||
// RegularUsersViewBlocked allows to block regular users from viewing even their own peers and some UI elements
|
// RegularUsersViewBlocked allows to block regular users from viewing even their own peers and some UI elements
|
||||||
RegularUsersViewBlocked bool
|
RegularUsersViewBlocked bool
|
||||||
|
|
||||||
@ -228,6 +238,9 @@ func (s *Settings) Copy() *Settings {
|
|||||||
GroupsPropagationEnabled: s.GroupsPropagationEnabled,
|
GroupsPropagationEnabled: s.GroupsPropagationEnabled,
|
||||||
JWTAllowGroups: s.JWTAllowGroups,
|
JWTAllowGroups: s.JWTAllowGroups,
|
||||||
RegularUsersViewBlocked: s.RegularUsersViewBlocked,
|
RegularUsersViewBlocked: s.RegularUsersViewBlocked,
|
||||||
|
|
||||||
|
PeerInactivityExpirationEnabled: s.PeerInactivityExpirationEnabled,
|
||||||
|
PeerInactivityExpiration: s.PeerInactivityExpiration,
|
||||||
}
|
}
|
||||||
if s.Extra != nil {
|
if s.Extra != nil {
|
||||||
settings.Extra = s.Extra.Copy()
|
settings.Extra = s.Extra.Copy()
|
||||||
@ -609,6 +622,60 @@ func (a *Account) GetPeersWithExpiration() []*nbpeer.Peer {
|
|||||||
return peers
|
return peers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetInactivePeers returns peers that have been expired by inactivity
|
||||||
|
func (a *Account) GetInactivePeers() []*nbpeer.Peer {
|
||||||
|
var peers []*nbpeer.Peer
|
||||||
|
for _, inactivePeer := range a.GetPeersWithInactivity() {
|
||||||
|
inactive, _ := inactivePeer.SessionExpired(a.Settings.PeerInactivityExpiration)
|
||||||
|
if inactive {
|
||||||
|
peers = append(peers, inactivePeer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return peers
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNextInactivePeerExpiration returns the minimum duration in which the next peer of the account will expire if it was found.
|
||||||
|
// If there is no peer that expires this function returns false and a duration of 0.
|
||||||
|
// This function only considers peers that haven't been expired yet and that are not connected.
|
||||||
|
func (a *Account) GetNextInactivePeerExpiration() (time.Duration, bool) {
|
||||||
|
peersWithExpiry := a.GetPeersWithInactivity()
|
||||||
|
if len(peersWithExpiry) == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
var nextExpiry *time.Duration
|
||||||
|
for _, peer := range peersWithExpiry {
|
||||||
|
if peer.Status.LoginExpired || peer.Status.Connected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, duration := peer.SessionExpired(a.Settings.PeerInactivityExpiration)
|
||||||
|
if nextExpiry == nil || duration < *nextExpiry {
|
||||||
|
// if expiration is below 1s return 1s duration
|
||||||
|
// this avoids issues with ticker that can't be set to < 0
|
||||||
|
if duration < time.Second {
|
||||||
|
return time.Second, true
|
||||||
|
}
|
||||||
|
nextExpiry = &duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextExpiry == nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return *nextExpiry, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeersWithInactivity eturns a list of peers that have Peer.InactivityExpirationEnabled set to true and that were added by a user
|
||||||
|
func (a *Account) GetPeersWithInactivity() []*nbpeer.Peer {
|
||||||
|
peers := make([]*nbpeer.Peer, 0)
|
||||||
|
for _, peer := range a.Peers {
|
||||||
|
if peer.InactivityExpirationEnabled && peer.AddedWithSSOLogin() {
|
||||||
|
peers = append(peers, peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return peers
|
||||||
|
}
|
||||||
|
|
||||||
// GetPeers returns a list of all Account peers
|
// GetPeers returns a list of all Account peers
|
||||||
func (a *Account) GetPeers() []*nbpeer.Peer {
|
func (a *Account) GetPeers() []*nbpeer.Peer {
|
||||||
var peers []*nbpeer.Peer
|
var peers []*nbpeer.Peer
|
||||||
@ -975,6 +1042,7 @@ func BuildManager(
|
|||||||
dnsDomain: dnsDomain,
|
dnsDomain: dnsDomain,
|
||||||
eventStore: eventStore,
|
eventStore: eventStore,
|
||||||
peerLoginExpiry: NewDefaultScheduler(),
|
peerLoginExpiry: NewDefaultScheduler(),
|
||||||
|
peerInactivityExpiry: NewDefaultScheduler(),
|
||||||
userDeleteFromIDPEnabled: userDeleteFromIDPEnabled,
|
userDeleteFromIDPEnabled: userDeleteFromIDPEnabled,
|
||||||
integratedPeerValidator: integratedPeerValidator,
|
integratedPeerValidator: integratedPeerValidator,
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
@ -1103,6 +1171,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
|||||||
am.checkAndSchedulePeerLoginExpiration(ctx, account)
|
am.checkAndSchedulePeerLoginExpiration(ctx, account)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = am.handleInactivityExpirationSettings(ctx, account, oldSettings, newSettings, userID, accountID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
updatedAccount := account.UpdateSettings(newSettings)
|
updatedAccount := account.UpdateSettings(newSettings)
|
||||||
|
|
||||||
err = am.Store.SaveAccount(ctx, account)
|
err = am.Store.SaveAccount(ctx, account)
|
||||||
@ -1113,6 +1186,26 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
|||||||
return updatedAccount, nil
|
return updatedAccount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) handleInactivityExpirationSettings(ctx context.Context, account *Account, oldSettings, newSettings *Settings, userID, accountID string) error {
|
||||||
|
if oldSettings.PeerInactivityExpirationEnabled != newSettings.PeerInactivityExpirationEnabled {
|
||||||
|
event := activity.AccountPeerInactivityExpirationEnabled
|
||||||
|
if !newSettings.PeerInactivityExpirationEnabled {
|
||||||
|
event = activity.AccountPeerInactivityExpirationDisabled
|
||||||
|
am.peerInactivityExpiry.Cancel(ctx, []string{accountID})
|
||||||
|
} else {
|
||||||
|
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
|
||||||
|
}
|
||||||
|
am.StoreEvent(ctx, userID, accountID, accountID, event, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldSettings.PeerInactivityExpiration != newSettings.PeerInactivityExpiration {
|
||||||
|
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountPeerInactivityExpirationDurationUpdated, nil)
|
||||||
|
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (am *DefaultAccountManager) peerLoginExpirationJob(ctx context.Context, accountID string) func() (time.Duration, bool) {
|
func (am *DefaultAccountManager) peerLoginExpirationJob(ctx context.Context, accountID string) func() (time.Duration, bool) {
|
||||||
return func() (time.Duration, bool) {
|
return func() (time.Duration, bool) {
|
||||||
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
||||||
@ -1148,6 +1241,43 @@ func (am *DefaultAccountManager) checkAndSchedulePeerLoginExpiration(ctx context
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
func (am *DefaultAccountManager) peerInactivityExpirationJob(ctx context.Context, accountID string) func() (time.Duration, bool) {
|
||||||
|
return func() (time.Duration, bool) {
|
||||||
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
|
account, err := am.Store.GetAccount(ctx, accountID)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed getting account %s expiring peers", account.Id)
|
||||||
|
return account.GetNextInactivePeerExpiration()
|
||||||
|
}
|
||||||
|
|
||||||
|
expiredPeers := account.GetInactivePeers()
|
||||||
|
var peerIDs []string
|
||||||
|
for _, peer := range expiredPeers {
|
||||||
|
peerIDs = append(peerIDs, peer.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("discovered %d peers to expire for account %s", len(peerIDs), account.Id)
|
||||||
|
|
||||||
|
if err := am.expireAndUpdatePeers(ctx, account, expiredPeers); err != nil {
|
||||||
|
log.Errorf("failed updating account peers while expiring peers for account %s", account.Id)
|
||||||
|
return account.GetNextInactivePeerExpiration()
|
||||||
|
}
|
||||||
|
|
||||||
|
return account.GetNextInactivePeerExpiration()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkAndSchedulePeerInactivityExpiration periodically checks for inactive peers to end their sessions
|
||||||
|
func (am *DefaultAccountManager) checkAndSchedulePeerInactivityExpiration(ctx context.Context, account *Account) {
|
||||||
|
am.peerInactivityExpiry.Cancel(ctx, []string{account.Id})
|
||||||
|
if nextRun, ok := account.GetNextInactivePeerExpiration(); ok {
|
||||||
|
go am.peerInactivityExpiry.Schedule(ctx, nextRun, account.Id, am.peerInactivityExpirationJob(ctx, account.Id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// newAccount creates a new Account with a generated ID and generated default setup keys.
|
// newAccount creates a new Account with a generated ID and generated default setup keys.
|
||||||
// If ID is already in use (due to collision) we try one more time before returning error
|
// If ID is already in use (due to collision) we try one more time before returning error
|
||||||
func (am *DefaultAccountManager) newAccount(ctx context.Context, userID, domain string) (*Account, error) {
|
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
|
|||||||
PeerLoginExpiration: DefaultPeerLoginExpiration,
|
PeerLoginExpiration: DefaultPeerLoginExpiration,
|
||||||
GroupsPropagationEnabled: true,
|
GroupsPropagationEnabled: true,
|
||||||
RegularUsersViewBlocked: true,
|
RegularUsersViewBlocked: true,
|
||||||
|
|
||||||
|
PeerInactivityExpirationEnabled: false,
|
||||||
|
PeerInactivityExpiration: DefaultPeerInactivityExpiration,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1957,6 +1957,90 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccount_GetInactivePeers(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
name string
|
||||||
|
peers map[string]*nbpeer.Peer
|
||||||
|
expectedPeers map[string]struct{}
|
||||||
|
}
|
||||||
|
testCases := []test{
|
||||||
|
{
|
||||||
|
name: "Peers with inactivity expiration disabled, no expired peers",
|
||||||
|
peers: map[string]*nbpeer.Peer{
|
||||||
|
"peer-1": {
|
||||||
|
InactivityExpirationEnabled: false,
|
||||||
|
},
|
||||||
|
"peer-2": {
|
||||||
|
InactivityExpirationEnabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPeers: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Two peers expired",
|
||||||
|
peers: map[string]*nbpeer.Peer{
|
||||||
|
"peer-1": {
|
||||||
|
ID: "peer-1",
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
LastSeen: time.Now().UTC().Add(-45 * time.Second),
|
||||||
|
Connected: false,
|
||||||
|
LoginExpired: false,
|
||||||
|
},
|
||||||
|
LastLogin: time.Now().UTC().Add(-30 * time.Minute),
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
"peer-2": {
|
||||||
|
ID: "peer-2",
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
LastSeen: time.Now().UTC().Add(-45 * time.Second),
|
||||||
|
Connected: false,
|
||||||
|
LoginExpired: false,
|
||||||
|
},
|
||||||
|
LastLogin: time.Now().UTC().Add(-2 * time.Hour),
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
"peer-3": {
|
||||||
|
ID: "peer-3",
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
LastSeen: time.Now().UTC(),
|
||||||
|
Connected: true,
|
||||||
|
LoginExpired: false,
|
||||||
|
},
|
||||||
|
LastLogin: time.Now().UTC().Add(-1 * time.Hour),
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPeers: map[string]struct{}{
|
||||||
|
"peer-1": {},
|
||||||
|
"peer-2": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
account := &Account{
|
||||||
|
Peers: testCase.peers,
|
||||||
|
Settings: &Settings{
|
||||||
|
PeerInactivityExpirationEnabled: true,
|
||||||
|
PeerInactivityExpiration: time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expiredPeers := account.GetInactivePeers()
|
||||||
|
assert.Len(t, expiredPeers, len(testCase.expectedPeers))
|
||||||
|
for _, peer := range expiredPeers {
|
||||||
|
if _, ok := testCase.expectedPeers[peer.ID]; !ok {
|
||||||
|
t.Fatalf("expected to have peer %s expired", peer.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccount_GetPeersWithExpiration(t *testing.T) {
|
func TestAccount_GetPeersWithExpiration(t *testing.T) {
|
||||||
type test struct {
|
type test struct {
|
||||||
name string
|
name string
|
||||||
@ -2026,6 +2110,75 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccount_GetPeersWithInactivity(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
name string
|
||||||
|
peers map[string]*nbpeer.Peer
|
||||||
|
expectedPeers map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []test{
|
||||||
|
{
|
||||||
|
name: "No account peers, no peers with expiration",
|
||||||
|
peers: map[string]*nbpeer.Peer{},
|
||||||
|
expectedPeers: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peers with login expiration disabled, no peers with expiration",
|
||||||
|
peers: map[string]*nbpeer.Peer{
|
||||||
|
"peer-1": {
|
||||||
|
InactivityExpirationEnabled: false,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
"peer-2": {
|
||||||
|
InactivityExpirationEnabled: false,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPeers: map[string]struct{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peers with login expiration enabled, return peers with expiration",
|
||||||
|
peers: map[string]*nbpeer.Peer{
|
||||||
|
"peer-1": {
|
||||||
|
ID: "peer-1",
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
"peer-2": {
|
||||||
|
InactivityExpirationEnabled: false,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedPeers: map[string]struct{}{
|
||||||
|
"peer-1": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
account := &Account{
|
||||||
|
Peers: testCase.peers,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := account.GetPeersWithInactivity()
|
||||||
|
assert.Len(t, actual, len(testCase.expectedPeers))
|
||||||
|
if len(testCase.expectedPeers) > 0 {
|
||||||
|
for k := range testCase.expectedPeers {
|
||||||
|
contains := false
|
||||||
|
for _, peer := range actual {
|
||||||
|
if k == peer.ID {
|
||||||
|
contains = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.True(t, contains)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccount_GetNextPeerExpiration(t *testing.T) {
|
func TestAccount_GetNextPeerExpiration(t *testing.T) {
|
||||||
type test struct {
|
type test struct {
|
||||||
name string
|
name string
|
||||||
@ -2187,6 +2340,168 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccount_GetNextInactivePeerExpiration(t *testing.T) {
|
||||||
|
type test struct {
|
||||||
|
name string
|
||||||
|
peers map[string]*nbpeer.Peer
|
||||||
|
expiration time.Duration
|
||||||
|
expirationEnabled bool
|
||||||
|
expectedNextRun bool
|
||||||
|
expectedNextExpiration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedNextExpiration := time.Minute
|
||||||
|
testCases := []test{
|
||||||
|
{
|
||||||
|
name: "No peers, no expiration",
|
||||||
|
peers: map[string]*nbpeer.Peer{},
|
||||||
|
expiration: time.Second,
|
||||||
|
expirationEnabled: false,
|
||||||
|
expectedNextRun: false,
|
||||||
|
expectedNextExpiration: time.Duration(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No connected peers, no expiration",
|
||||||
|
peers: map[string]*nbpeer.Peer{
|
||||||
|
"peer-1": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: false,
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: false,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
"peer-2": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: false,
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: false,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expiration: time.Second,
|
||||||
|
expirationEnabled: false,
|
||||||
|
expectedNextRun: false,
|
||||||
|
expectedNextExpiration: time.Duration(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Connected peers with disabled expiration, no expiration",
|
||||||
|
peers: map[string]*nbpeer.Peer{
|
||||||
|
"peer-1": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: true,
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: false,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
"peer-2": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: true,
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: false,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expiration: time.Second,
|
||||||
|
expirationEnabled: false,
|
||||||
|
expectedNextRun: false,
|
||||||
|
expectedNextExpiration: time.Duration(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Expired peers, no expiration",
|
||||||
|
peers: map[string]*nbpeer.Peer{
|
||||||
|
"peer-1": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: true,
|
||||||
|
LoginExpired: true,
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
"peer-2": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: true,
|
||||||
|
LoginExpired: true,
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expiration: time.Second,
|
||||||
|
expirationEnabled: false,
|
||||||
|
expectedNextRun: false,
|
||||||
|
expectedNextExpiration: time.Duration(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "To be expired peer, return expiration",
|
||||||
|
peers: map[string]*nbpeer.Peer{
|
||||||
|
"peer-1": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: false,
|
||||||
|
LoginExpired: false,
|
||||||
|
LastSeen: time.Now().Add(-1 * time.Second),
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
LastLogin: time.Now().UTC(),
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
"peer-2": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: true,
|
||||||
|
LoginExpired: true,
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
UserID: userID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expiration: time.Minute,
|
||||||
|
expirationEnabled: false,
|
||||||
|
expectedNextRun: true,
|
||||||
|
expectedNextExpiration: expectedNextExpiration,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peers added with setup keys, no expiration",
|
||||||
|
peers: map[string]*nbpeer.Peer{
|
||||||
|
"peer-1": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: true,
|
||||||
|
LoginExpired: false,
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
SetupKey: "key",
|
||||||
|
},
|
||||||
|
"peer-2": {
|
||||||
|
Status: &nbpeer.PeerStatus{
|
||||||
|
Connected: true,
|
||||||
|
LoginExpired: false,
|
||||||
|
},
|
||||||
|
InactivityExpirationEnabled: true,
|
||||||
|
SetupKey: "key",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expiration: time.Second,
|
||||||
|
expirationEnabled: false,
|
||||||
|
expectedNextRun: false,
|
||||||
|
expectedNextExpiration: time.Duration(0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
account := &Account{
|
||||||
|
Peers: testCase.peers,
|
||||||
|
Settings: &Settings{PeerInactivityExpiration: testCase.expiration, PeerInactivityExpirationEnabled: testCase.expirationEnabled},
|
||||||
|
}
|
||||||
|
|
||||||
|
expiration, ok := account.GetNextInactivePeerExpiration()
|
||||||
|
assert.Equal(t, testCase.expectedNextRun, ok)
|
||||||
|
if testCase.expectedNextRun {
|
||||||
|
assert.True(t, expiration >= 0 && expiration <= testCase.expectedNextExpiration)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, expiration, testCase.expectedNextExpiration)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccount_SetJWTGroups(t *testing.T) {
|
func TestAccount_SetJWTGroups(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
require.NoError(t, err, "unable to create account manager")
|
require.NoError(t, err, "unable to create account manager")
|
||||||
|
@ -139,6 +139,13 @@ const (
|
|||||||
PostureCheckUpdated Activity = 61
|
PostureCheckUpdated Activity = 61
|
||||||
// PostureCheckDeleted indicates that the user deleted a posture check
|
// PostureCheckDeleted indicates that the user deleted a posture check
|
||||||
PostureCheckDeleted Activity = 62
|
PostureCheckDeleted Activity = 62
|
||||||
|
|
||||||
|
PeerInactivityExpirationEnabled Activity = 63
|
||||||
|
PeerInactivityExpirationDisabled Activity = 64
|
||||||
|
|
||||||
|
AccountPeerInactivityExpirationEnabled Activity = 65
|
||||||
|
AccountPeerInactivityExpirationDisabled Activity = 66
|
||||||
|
AccountPeerInactivityExpirationDurationUpdated Activity = 67
|
||||||
)
|
)
|
||||||
|
|
||||||
var activityMap = map[Activity]Code{
|
var activityMap = map[Activity]Code{
|
||||||
@ -205,6 +212,13 @@ var activityMap = map[Activity]Code{
|
|||||||
PostureCheckCreated: {"Posture check created", "posture.check.created"},
|
PostureCheckCreated: {"Posture check created", "posture.check.created"},
|
||||||
PostureCheckUpdated: {"Posture check updated", "posture.check.updated"},
|
PostureCheckUpdated: {"Posture check updated", "posture.check.updated"},
|
||||||
PostureCheckDeleted: {"Posture check deleted", "posture.check.deleted"},
|
PostureCheckDeleted: {"Posture check deleted", "posture.check.deleted"},
|
||||||
|
|
||||||
|
PeerInactivityExpirationEnabled: {"Peer inactivity expiration enabled", "peer.inactivity.expiration.enable"},
|
||||||
|
PeerInactivityExpirationDisabled: {"Peer inactivity expiration disabled", "peer.inactivity.expiration.disable"},
|
||||||
|
|
||||||
|
AccountPeerInactivityExpirationEnabled: {"Account peer inactivity expiration enabled", "account.peer.inactivity.expiration.enable"},
|
||||||
|
AccountPeerInactivityExpirationDisabled: {"Account peer inactivity expiration disabled", "account.peer.inactivity.expiration.disable"},
|
||||||
|
AccountPeerInactivityExpirationDurationUpdated: {"Account peer inactivity expiration duration updated", "account.peer.inactivity.expiration.update"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringCode returns a string code of the activity
|
// StringCode returns a string code of the activity
|
||||||
|
@ -95,6 +95,9 @@ func restore(ctx context.Context, file string) (*FileStore, error) {
|
|||||||
account.Settings = &Settings{
|
account.Settings = &Settings{
|
||||||
PeerLoginExpirationEnabled: false,
|
PeerLoginExpirationEnabled: false,
|
||||||
PeerLoginExpiration: DefaultPeerLoginExpiration,
|
PeerLoginExpiration: DefaultPeerLoginExpiration,
|
||||||
|
|
||||||
|
PeerInactivityExpirationEnabled: false,
|
||||||
|
PeerInactivityExpiration: DefaultPeerInactivityExpiration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,9 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request)
|
|||||||
PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled,
|
PeerLoginExpirationEnabled: req.Settings.PeerLoginExpirationEnabled,
|
||||||
PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)),
|
PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)),
|
||||||
RegularUsersViewBlocked: req.Settings.RegularUsersViewBlocked,
|
RegularUsersViewBlocked: req.Settings.RegularUsersViewBlocked,
|
||||||
|
|
||||||
|
PeerInactivityExpirationEnabled: req.Settings.PeerInactivityExpirationEnabled,
|
||||||
|
PeerInactivityExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerInactivityExpiration)),
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Settings.Extra != nil {
|
if req.Settings.Extra != nil {
|
||||||
|
@ -54,6 +54,14 @@ components:
|
|||||||
description: Period of time after which peer login expires (seconds).
|
description: Period of time after which peer login expires (seconds).
|
||||||
type: integer
|
type: integer
|
||||||
example: 43200
|
example: 43200
|
||||||
|
peer_inactivity_expiration_enabled:
|
||||||
|
description: Enables or disables peer inactivity expiration globally. After peer's session has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login).
|
||||||
|
type: boolean
|
||||||
|
example: true
|
||||||
|
peer_inactivity_expiration:
|
||||||
|
description: Period of time of inactivity after which peer session expires (seconds).
|
||||||
|
type: integer
|
||||||
|
example: 43200
|
||||||
regular_users_view_blocked:
|
regular_users_view_blocked:
|
||||||
description: Allows blocking regular users from viewing parts of the system.
|
description: Allows blocking regular users from viewing parts of the system.
|
||||||
type: boolean
|
type: boolean
|
||||||
@ -81,6 +89,8 @@ components:
|
|||||||
required:
|
required:
|
||||||
- peer_login_expiration_enabled
|
- peer_login_expiration_enabled
|
||||||
- peer_login_expiration
|
- peer_login_expiration
|
||||||
|
- peer_inactivity_expiration_enabled
|
||||||
|
- peer_inactivity_expiration
|
||||||
- regular_users_view_blocked
|
- regular_users_view_blocked
|
||||||
AccountExtraSettings:
|
AccountExtraSettings:
|
||||||
type: object
|
type: object
|
||||||
@ -243,6 +253,9 @@ components:
|
|||||||
login_expiration_enabled:
|
login_expiration_enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: false
|
example: false
|
||||||
|
inactivity_expiration_enabled:
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
approval_required:
|
approval_required:
|
||||||
description: (Cloud only) Indicates whether peer needs approval
|
description: (Cloud only) Indicates whether peer needs approval
|
||||||
type: boolean
|
type: boolean
|
||||||
@ -251,6 +264,7 @@ components:
|
|||||||
- name
|
- name
|
||||||
- ssh_enabled
|
- ssh_enabled
|
||||||
- login_expiration_enabled
|
- login_expiration_enabled
|
||||||
|
- inactivity_expiration_enabled
|
||||||
Peer:
|
Peer:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/PeerMinimum'
|
- $ref: '#/components/schemas/PeerMinimum'
|
||||||
@ -327,6 +341,10 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
example: "2023-05-05T09:00:35.477782Z"
|
example: "2023-05-05T09:00:35.477782Z"
|
||||||
|
inactivity_expiration_enabled:
|
||||||
|
description: Indicates whether peer inactivity expiration has been enabled or not
|
||||||
|
type: boolean
|
||||||
|
example: false
|
||||||
approval_required:
|
approval_required:
|
||||||
description: (Cloud only) Indicates whether peer needs approval
|
description: (Cloud only) Indicates whether peer needs approval
|
||||||
type: boolean
|
type: boolean
|
||||||
@ -354,6 +372,7 @@ components:
|
|||||||
- last_seen
|
- last_seen
|
||||||
- login_expiration_enabled
|
- login_expiration_enabled
|
||||||
- login_expired
|
- login_expired
|
||||||
|
- inactivity_expiration_enabled
|
||||||
- os
|
- os
|
||||||
- ssh_enabled
|
- ssh_enabled
|
||||||
- user_id
|
- user_id
|
||||||
|
@ -220,6 +220,12 @@ type AccountSettings struct {
|
|||||||
// JwtGroupsEnabled Allows extract groups from JWT claim and add it to account groups.
|
// JwtGroupsEnabled Allows extract groups from JWT claim and add it to account groups.
|
||||||
JwtGroupsEnabled *bool `json:"jwt_groups_enabled,omitempty"`
|
JwtGroupsEnabled *bool `json:"jwt_groups_enabled,omitempty"`
|
||||||
|
|
||||||
|
// PeerInactivityExpiration Period of time of inactivity after which peer session expires (seconds).
|
||||||
|
PeerInactivityExpiration int `json:"peer_inactivity_expiration"`
|
||||||
|
|
||||||
|
// PeerInactivityExpirationEnabled Enables or disables peer inactivity expiration globally. After peer's session has expired the user has to log in (authenticate). Applies only to peers that were added by a user (interactive SSO login).
|
||||||
|
PeerInactivityExpirationEnabled bool `json:"peer_inactivity_expiration_enabled"`
|
||||||
|
|
||||||
// PeerLoginExpiration Period of time after which peer login expires (seconds).
|
// PeerLoginExpiration Period of time after which peer login expires (seconds).
|
||||||
PeerLoginExpiration int `json:"peer_login_expiration"`
|
PeerLoginExpiration int `json:"peer_login_expiration"`
|
||||||
|
|
||||||
@ -538,6 +544,9 @@ type Peer struct {
|
|||||||
// Id Peer ID
|
// Id Peer ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// InactivityExpirationEnabled Indicates whether peer inactivity expiration has been enabled or not
|
||||||
|
InactivityExpirationEnabled bool `json:"inactivity_expiration_enabled"`
|
||||||
|
|
||||||
// Ip Peer's IP address
|
// Ip Peer's IP address
|
||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
@ -613,6 +622,9 @@ type PeerBatch struct {
|
|||||||
// Id Peer ID
|
// Id Peer ID
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
|
|
||||||
|
// InactivityExpirationEnabled Indicates whether peer inactivity expiration has been enabled or not
|
||||||
|
InactivityExpirationEnabled bool `json:"inactivity_expiration_enabled"`
|
||||||
|
|
||||||
// Ip Peer's IP address
|
// Ip Peer's IP address
|
||||||
Ip string `json:"ip"`
|
Ip string `json:"ip"`
|
||||||
|
|
||||||
@ -677,10 +689,11 @@ type PeerNetworkRangeCheckAction string
|
|||||||
// PeerRequest defines model for PeerRequest.
|
// PeerRequest defines model for PeerRequest.
|
||||||
type PeerRequest struct {
|
type PeerRequest struct {
|
||||||
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
|
||||||
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
ApprovalRequired *bool `json:"approval_required,omitempty"`
|
||||||
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
|
InactivityExpirationEnabled bool `json:"inactivity_expiration_enabled"`
|
||||||
Name string `json:"name"`
|
LoginExpirationEnabled bool `json:"login_expiration_enabled"`
|
||||||
SshEnabled bool `json:"ssh_enabled"`
|
Name string `json:"name"`
|
||||||
|
SshEnabled bool `json:"ssh_enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PersonalAccessToken defines model for PersonalAccessToken.
|
// PersonalAccessToken defines model for PersonalAccessToken.
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
nbgroup "github.com/netbirdio/netbird/management/server/group"
|
||||||
"github.com/netbirdio/netbird/management/server/http/api"
|
"github.com/netbirdio/netbird/management/server/http/api"
|
||||||
@ -14,7 +16,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/management/server/status"
|
"github.com/netbirdio/netbird/management/server/status"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PeersHandler is a handler that returns peers of the account
|
// PeersHandler is a handler that returns peers of the account
|
||||||
@ -87,6 +88,8 @@ func (h *PeersHandler) updatePeer(ctx context.Context, account *server.Account,
|
|||||||
SSHEnabled: req.SshEnabled,
|
SSHEnabled: req.SshEnabled,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
LoginExpirationEnabled: req.LoginExpirationEnabled,
|
LoginExpirationEnabled: req.LoginExpirationEnabled,
|
||||||
|
|
||||||
|
InactivityExpirationEnabled: req.InactivityExpirationEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.ApprovalRequired != nil {
|
if req.ApprovalRequired != nil {
|
||||||
@ -331,29 +334,30 @@ func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsD
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &api.Peer{
|
return &api.Peer{
|
||||||
Id: peer.ID,
|
Id: peer.ID,
|
||||||
Name: peer.Name,
|
Name: peer.Name,
|
||||||
Ip: peer.IP.String(),
|
Ip: peer.IP.String(),
|
||||||
ConnectionIp: peer.Location.ConnectionIP.String(),
|
ConnectionIp: peer.Location.ConnectionIP.String(),
|
||||||
Connected: peer.Status.Connected,
|
Connected: peer.Status.Connected,
|
||||||
LastSeen: peer.Status.LastSeen,
|
LastSeen: peer.Status.LastSeen,
|
||||||
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
Os: fmt.Sprintf("%s %s", peer.Meta.OS, osVersion),
|
||||||
KernelVersion: peer.Meta.KernelVersion,
|
KernelVersion: peer.Meta.KernelVersion,
|
||||||
GeonameId: int(peer.Location.GeoNameID),
|
GeonameId: int(peer.Location.GeoNameID),
|
||||||
Version: peer.Meta.WtVersion,
|
Version: peer.Meta.WtVersion,
|
||||||
Groups: groupsInfo,
|
Groups: groupsInfo,
|
||||||
SshEnabled: peer.SSHEnabled,
|
SshEnabled: peer.SSHEnabled,
|
||||||
Hostname: peer.Meta.Hostname,
|
Hostname: peer.Meta.Hostname,
|
||||||
UserId: peer.UserID,
|
UserId: peer.UserID,
|
||||||
UiVersion: peer.Meta.UIVersion,
|
UiVersion: peer.Meta.UIVersion,
|
||||||
DnsLabel: fqdn(peer, dnsDomain),
|
DnsLabel: fqdn(peer, dnsDomain),
|
||||||
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
LoginExpirationEnabled: peer.LoginExpirationEnabled,
|
||||||
LastLogin: peer.LastLogin,
|
LastLogin: peer.LastLogin,
|
||||||
LoginExpired: peer.Status.LoginExpired,
|
LoginExpired: peer.Status.LoginExpired,
|
||||||
ApprovalRequired: !approved,
|
ApprovalRequired: !approved,
|
||||||
CountryCode: peer.Location.CountryCode,
|
CountryCode: peer.Location.CountryCode,
|
||||||
CityName: peer.Location.CityName,
|
CityName: peer.Location.CityName,
|
||||||
SerialNumber: peer.Meta.SystemSerialNumber,
|
SerialNumber: peer.Meta.SystemSerialNumber,
|
||||||
|
InactivityExpirationEnabled: peer.InactivityExpirationEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,6 +391,8 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn
|
|||||||
CountryCode: peer.Location.CountryCode,
|
CountryCode: peer.Location.CountryCode,
|
||||||
CityName: peer.Location.CityName,
|
CityName: peer.Location.CityName,
|
||||||
SerialNumber: peer.Meta.SystemSerialNumber,
|
SerialNumber: peer.Meta.SystemSerialNumber,
|
||||||
|
|
||||||
|
InactivityExpirationEnabled: peer.InactivityExpirationEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +110,31 @@ func (am *DefaultAccountManager) MarkPeerConnected(ctx context.Context, peerPubK
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expired, err := am.updatePeerStatusAndLocation(ctx, peer, connected, realIP, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer.AddedWithSSOLogin() {
|
||||||
|
if peer.LoginExpirationEnabled && account.Settings.PeerLoginExpirationEnabled {
|
||||||
|
am.checkAndSchedulePeerLoginExpiration(ctx, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
if peer.InactivityExpirationEnabled && account.Settings.PeerInactivityExpirationEnabled {
|
||||||
|
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expired {
|
||||||
|
// we need to update other peers because when peer login expires all other peers are notified to disconnect from
|
||||||
|
// the expired one. Here we notify them that connection is now allowed again.
|
||||||
|
am.updateAccountPeers(ctx, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *DefaultAccountManager) updatePeerStatusAndLocation(ctx context.Context, peer *nbpeer.Peer, connected bool, realIP net.IP, account *Account) (bool, error) {
|
||||||
oldStatus := peer.Status.Copy()
|
oldStatus := peer.Status.Copy()
|
||||||
newStatus := oldStatus
|
newStatus := oldStatus
|
||||||
newStatus.LastSeen = time.Now().UTC()
|
newStatus.LastSeen = time.Now().UTC()
|
||||||
@ -138,25 +163,15 @@ func (am *DefaultAccountManager) MarkPeerConnected(ctx context.Context, peerPubK
|
|||||||
|
|
||||||
account.UpdatePeer(peer)
|
account.UpdatePeer(peer)
|
||||||
|
|
||||||
err = am.Store.SavePeerStatus(account.Id, peer.ID, *newStatus)
|
err := am.Store.SavePeerStatus(account.Id, peer.ID, *newStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if peer.AddedWithSSOLogin() && peer.LoginExpirationEnabled && account.Settings.PeerLoginExpirationEnabled {
|
return oldStatus.LoginExpired, nil
|
||||||
am.checkAndSchedulePeerLoginExpiration(ctx, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldStatus.LoginExpired {
|
|
||||||
// we need to update other peers because when peer login expires all other peers are notified to disconnect from
|
|
||||||
// the expired one. Here we notify them that connection is now allowed again.
|
|
||||||
am.updateAccountPeers(ctx, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated.
|
// UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, Peer.LoginExpirationEnabled and Peer.InactivityExpirationEnabled can be updated.
|
||||||
func (am *DefaultAccountManager) UpdatePeer(ctx context.Context, accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) {
|
func (am *DefaultAccountManager) UpdatePeer(ctx context.Context, accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) {
|
||||||
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
unlock := am.Store.AcquireWriteLockByUID(ctx, accountID)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
@ -219,6 +234,25 @@ func (am *DefaultAccountManager) UpdatePeer(ctx context.Context, accountID, user
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if peer.InactivityExpirationEnabled != update.InactivityExpirationEnabled {
|
||||||
|
|
||||||
|
if !peer.AddedWithSSOLogin() {
|
||||||
|
return nil, status.Errorf(status.PreconditionFailed, "this peer hasn't been added with the SSO login, therefore the login expiration can't be updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.InactivityExpirationEnabled = update.InactivityExpirationEnabled
|
||||||
|
|
||||||
|
event := activity.PeerInactivityExpirationEnabled
|
||||||
|
if !update.InactivityExpirationEnabled {
|
||||||
|
event = activity.PeerInactivityExpirationDisabled
|
||||||
|
}
|
||||||
|
am.StoreEvent(ctx, userID, peer.IP.String(), accountID, event, peer.EventMeta(am.GetDNSDomain()))
|
||||||
|
|
||||||
|
if peer.AddedWithSSOLogin() && peer.InactivityExpirationEnabled && account.Settings.PeerInactivityExpirationEnabled {
|
||||||
|
am.checkAndSchedulePeerInactivityExpiration(ctx, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
account.UpdatePeer(peer)
|
account.UpdatePeer(peer)
|
||||||
|
|
||||||
err = am.Store.SaveAccount(ctx, account)
|
err = am.Store.SaveAccount(ctx, account)
|
||||||
@ -442,23 +476,24 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, setupKey, userID s
|
|||||||
|
|
||||||
registrationTime := time.Now().UTC()
|
registrationTime := time.Now().UTC()
|
||||||
newPeer = &nbpeer.Peer{
|
newPeer = &nbpeer.Peer{
|
||||||
ID: xid.New().String(),
|
ID: xid.New().String(),
|
||||||
AccountID: accountID,
|
AccountID: accountID,
|
||||||
Key: peer.Key,
|
Key: peer.Key,
|
||||||
SetupKey: upperKey,
|
SetupKey: upperKey,
|
||||||
IP: freeIP,
|
IP: freeIP,
|
||||||
Meta: peer.Meta,
|
Meta: peer.Meta,
|
||||||
Name: peer.Meta.Hostname,
|
Name: peer.Meta.Hostname,
|
||||||
DNSLabel: freeLabel,
|
DNSLabel: freeLabel,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Status: &nbpeer.PeerStatus{Connected: false, LastSeen: registrationTime},
|
Status: &nbpeer.PeerStatus{Connected: false, LastSeen: registrationTime},
|
||||||
SSHEnabled: false,
|
SSHEnabled: false,
|
||||||
SSHKey: peer.SSHKey,
|
SSHKey: peer.SSHKey,
|
||||||
LastLogin: registrationTime,
|
LastLogin: registrationTime,
|
||||||
CreatedAt: registrationTime,
|
CreatedAt: registrationTime,
|
||||||
LoginExpirationEnabled: addedByUser,
|
LoginExpirationEnabled: addedByUser,
|
||||||
Ephemeral: ephemeral,
|
Ephemeral: ephemeral,
|
||||||
Location: peer.Location,
|
Location: peer.Location,
|
||||||
|
InactivityExpirationEnabled: addedByUser,
|
||||||
}
|
}
|
||||||
opEvent.TargetID = newPeer.ID
|
opEvent.TargetID = newPeer.ID
|
||||||
opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain())
|
opEvent.Meta = newPeer.EventMeta(am.GetDNSDomain())
|
||||||
|
@ -38,6 +38,8 @@ type Peer struct {
|
|||||||
// LoginExpirationEnabled indicates whether peer's login expiration is enabled and once expired the peer has to re-login.
|
// LoginExpirationEnabled indicates whether peer's login expiration is enabled and once expired the peer has to re-login.
|
||||||
// Works with LastLogin
|
// Works with LastLogin
|
||||||
LoginExpirationEnabled bool
|
LoginExpirationEnabled bool
|
||||||
|
|
||||||
|
InactivityExpirationEnabled bool
|
||||||
// LastLogin the time when peer performed last login operation
|
// LastLogin the time when peer performed last login operation
|
||||||
LastLogin time.Time
|
LastLogin time.Time
|
||||||
// CreatedAt records the time the peer was created
|
// CreatedAt records the time the peer was created
|
||||||
@ -187,6 +189,8 @@ func (p *Peer) Copy() *Peer {
|
|||||||
CreatedAt: p.CreatedAt,
|
CreatedAt: p.CreatedAt,
|
||||||
Ephemeral: p.Ephemeral,
|
Ephemeral: p.Ephemeral,
|
||||||
Location: p.Location,
|
Location: p.Location,
|
||||||
|
|
||||||
|
InactivityExpirationEnabled: p.InactivityExpirationEnabled,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,6 +223,22 @@ func (p *Peer) MarkLoginExpired(expired bool) {
|
|||||||
p.Status = newStatus
|
p.Status = newStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SessionExpired indicates whether the peer's session has expired or not.
|
||||||
|
// If Peer.LastLogin plus the expiresIn duration has happened already; then session has expired.
|
||||||
|
// Return true if a session has expired, false otherwise, and time left to expiration (negative when expired).
|
||||||
|
// Session expiration can be disabled/enabled on a Peer level via Peer.LoginExpirationEnabled property.
|
||||||
|
// Session expiration can also be disabled/enabled globally on the Account level via Settings.PeerLoginExpirationEnabled.
|
||||||
|
// Only peers added by interactive SSO login can be expired.
|
||||||
|
func (p *Peer) SessionExpired(expiresIn time.Duration) (bool, time.Duration) {
|
||||||
|
if !p.AddedWithSSOLogin() || !p.InactivityExpirationEnabled || p.Status.Connected {
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
expiresAt := p.Status.LastSeen.Add(expiresIn)
|
||||||
|
now := time.Now()
|
||||||
|
timeLeft := expiresAt.Sub(now)
|
||||||
|
return timeLeft <= 0, timeLeft
|
||||||
|
}
|
||||||
|
|
||||||
// LoginExpired indicates whether the peer's login has expired or not.
|
// LoginExpired indicates whether the peer's login has expired or not.
|
||||||
// If Peer.LastLogin plus the expiresIn duration has happened already; then login has expired.
|
// If Peer.LastLogin plus the expiresIn duration has happened already; then login has expired.
|
||||||
// Return true if a login has expired, false otherwise, and time left to expiration (negative when expired).
|
// Return true if a login has expired, false otherwise, and time left to expiration (negative when expired).
|
||||||
|
@ -82,6 +82,68 @@ func TestPeer_LoginExpired(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPeer_SessionExpired(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
expirationEnabled bool
|
||||||
|
lastLogin time.Time
|
||||||
|
connected bool
|
||||||
|
expected bool
|
||||||
|
accountSettings *Settings
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Peer Inactivity Expiration Disabled. Peer Inactivity Should Not Expire",
|
||||||
|
expirationEnabled: false,
|
||||||
|
connected: false,
|
||||||
|
lastLogin: time.Now().UTC().Add(-1 * time.Second),
|
||||||
|
accountSettings: &Settings{
|
||||||
|
PeerInactivityExpirationEnabled: true,
|
||||||
|
PeerInactivityExpiration: time.Hour,
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer Inactivity Should Expire",
|
||||||
|
expirationEnabled: true,
|
||||||
|
connected: false,
|
||||||
|
lastLogin: time.Now().UTC().Add(-1 * time.Second),
|
||||||
|
accountSettings: &Settings{
|
||||||
|
PeerInactivityExpirationEnabled: true,
|
||||||
|
PeerInactivityExpiration: time.Second,
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Peer Inactivity Should Not Expire",
|
||||||
|
expirationEnabled: true,
|
||||||
|
connected: true,
|
||||||
|
lastLogin: time.Now().UTC(),
|
||||||
|
accountSettings: &Settings{
|
||||||
|
PeerInactivityExpirationEnabled: true,
|
||||||
|
PeerInactivityExpiration: time.Second,
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range tt {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
peerStatus := &nbpeer.PeerStatus{
|
||||||
|
Connected: c.connected,
|
||||||
|
}
|
||||||
|
peer := &nbpeer.Peer{
|
||||||
|
InactivityExpirationEnabled: c.expirationEnabled,
|
||||||
|
LastLogin: c.lastLogin,
|
||||||
|
Status: peerStatus,
|
||||||
|
UserID: userID,
|
||||||
|
}
|
||||||
|
|
||||||
|
expired, _ := peer.SessionExpired(c.accountSettings.PeerInactivityExpiration)
|
||||||
|
assert.Equal(t, expired, c.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestAccountManager_GetNetworkMap(t *testing.T) {
|
func TestAccountManager_GetNetworkMap(t *testing.T) {
|
||||||
manager, err := createManager(t)
|
manager, err := createManager(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user