From a729c83b0659827159246a5755da6f16c076d12c Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Tue, 28 Nov 2023 13:45:26 +0100 Subject: [PATCH] extract peer into seperate package --- go.mod | 2 +- go.sum | 2 + management/server/account.go | 88 +++--- management/server/account_test.go | 276 +++++++++++++++---- management/server/dns.go | 3 +- management/server/dns_test.go | 9 +- management/server/ephemeral.go | 5 +- management/server/ephemeral_test.go | 6 +- management/server/file_store.go | 5 +- management/server/file_store_test.go | 23 +- management/server/grpcserver.go | 15 +- management/server/http/peers_handler_test.go | 3 +- management/server/nameserver_test.go | 9 +- management/server/network.go | 5 +- management/server/peer.go | 218 ++------------- management/server/peer/peer.go | 181 ++++++++++++ management/server/peer_test.go | 36 +-- management/server/policy.go | 15 +- management/server/policy_test.go | 6 +- management/server/route_test.go | 21 +- management/server/sqlite_store.go | 22 +- management/server/sqlite_store_test.go | 30 +- management/server/store.go | 3 +- management/server/user.go | 3 +- 24 files changed, 587 insertions(+), 399 deletions(-) create mode 100644 management/server/peer/peer.go diff --git a/go.mod b/go.mod index d4b76653e..44d7cd6ec 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/miekg/dns v1.1.43 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818 + github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262 github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pion/logging v0.2.2 diff --git a/go.sum b/go.sum index f316fa058..4038f9b5f 100644 --- a/go.sum +++ b/go.sum @@ -507,6 +507,8 @@ github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411- github.com/netbirdio/management-integrations/integrations v0.0.0-20231127171411-ffb4ff80f85a/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818 h1:PTQ2SSijkoN8Qkctq9oLzEdzCLLv7WoD2dqScmpb15o= github.com/netbirdio/management-integrations/integrations v0.0.0-20231128103220-a3b41e63c818/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262 h1:JVImKBfZC2tC88GcJS0Hi6sdFL+FkhCcmbpU8lRAWss= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128110844-6dc8ff1bf262/go.mod h1:aRyvEvLzMX9+eDgW+cMRh0CkxR8sYIszmEITaWFZ5Vc= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g= github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM= diff --git a/management/server/account.go b/management/server/account.go index a08416e2c..1563570fa 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -27,6 +27,7 @@ import ( "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" ) @@ -68,13 +69,13 @@ type AccountManager interface { MarkPATUsed(tokenID string) error GetUser(claims jwtclaims.AuthorizationClaims) (*User, error) ListUsers(accountID string) ([]*User, error) - GetPeers(accountID, userID string) ([]*Peer, error) + GetPeers(accountID, userID string) ([]*nbpeer.Peer, error) MarkPeerConnected(peerKey string, connected bool) error DeletePeer(accountID, peerID, userID string) error - UpdatePeer(accountID, userID string, peer *Peer) (*Peer, error) + UpdatePeer(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) GetNetworkMap(peerID string) (*NetworkMap, error) GetPeerNetwork(peerID string) (*Network, error) - AddPeer(setupKey, userID string, peer *Peer) (*Peer, *NetworkMap, error) + AddPeer(setupKey, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, *NetworkMap, error) CreatePAT(accountID string, initiatorUserID string, targetUserID string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error) DeletePAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) error GetPAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) (*PersonalAccessToken, error) @@ -106,10 +107,10 @@ type AccountManager interface { GetEvents(accountID, userID string) ([]*activity.Event, error) GetDNSSettings(accountID string, userID string) (*DNSSettings, error) SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *DNSSettings) error - GetPeer(accountID, peerID, userID string) (*Peer, error) + GetPeer(accountID, peerID, userID string) (*nbpeer.Peer, error) UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error) - LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) // used by peer gRPC API - SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) // used by peer gRPC API + LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API + SyncPeer(sync PeerSync) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API GetAllConnectedPeers() (map[string]struct{}, error) GetExternalCacheManager() ExternalCacheManager } @@ -164,20 +165,31 @@ type Settings struct { Extra *ExtraSettings } -type ExtraSettings struct { - // PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator - PeerApprovalEnabled bool -} - // Copy copies the Settings struct func (s *Settings) Copy() *Settings { - return &Settings{ + settings := &Settings{ PeerLoginExpirationEnabled: s.PeerLoginExpirationEnabled, PeerLoginExpiration: s.PeerLoginExpiration, JWTGroupsEnabled: s.JWTGroupsEnabled, JWTGroupsClaimName: s.JWTGroupsClaimName, GroupsPropagationEnabled: s.GroupsPropagationEnabled, } + if s.Extra != nil { + settings.Extra = s.Extra.Copy() + } + return settings +} + +type ExtraSettings struct { + // PeerApprovalEnabled enables or disables the need for peers bo be approved by an administrator + PeerApprovalEnabled bool +} + +// Copy copies the ExtraSettings struct +func (e *ExtraSettings) Copy() *ExtraSettings { + return &ExtraSettings{ + PeerApprovalEnabled: e.PeerApprovalEnabled, + } } // Account represents a unique account of the system @@ -193,8 +205,8 @@ type Account struct { SetupKeys map[string]*SetupKey `gorm:"-"` SetupKeysG []SetupKey `json:"-" gorm:"foreignKey:AccountID;references:id"` Network *Network `gorm:"embedded;embeddedPrefix:network_"` - Peers map[string]*Peer `gorm:"-"` - PeersG []Peer `json:"-" gorm:"foreignKey:AccountID;references:id"` + Peers map[string]*nbpeer.Peer `gorm:"-"` + PeersG []nbpeer.Peer `json:"-" gorm:"foreignKey:AccountID;references:id"` Users map[string]*User `gorm:"-"` UsersG []User `json:"-" gorm:"foreignKey:AccountID;references:id"` Groups map[string]*Group `gorm:"-"` @@ -229,7 +241,7 @@ type UserInfo struct { // getRoutesToSync returns the enabled routes for the peer ID and the routes // from the ACL peers that have distribution groups associated with the peer ID. // Please mind, that the returned route.Route objects will contain Peer.Key instead of Peer.ID. -func (a *Account) getRoutesToSync(peerID string, aclPeers []*Peer) []*route.Route { +func (a *Account) getRoutesToSync(peerID string, aclPeers []*nbpeer.Peer) []*route.Route { routes, peerDisabledRoutes := a.getRoutingPeerRoutes(peerID) peerRoutesMembership := make(lookupMap) for _, r := range append(routes, peerDisabledRoutes...) { @@ -359,7 +371,7 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { Network: a.Network.Copy(), } } - validatedPeers := integrations.ValidatePeers([]*Peer{peer}, a) + validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}, a) if len(validatedPeers) == 0 { return &NetworkMap{ Network: a.Network.Copy(), @@ -368,8 +380,8 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { aclPeers, firewallRules := a.getPeerConnectionResources(peerID) aclPeers = integrations.ValidatePeers(aclPeers, a) // exclude expired peers - var peersToConnect []*Peer - var expiredPeers []*Peer + var peersToConnect []*nbpeer.Peer + var expiredPeers []*nbpeer.Peer for _, p := range aclPeers { expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration) if a.Settings.PeerLoginExpirationEnabled && expired { @@ -407,8 +419,8 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { } // GetExpiredPeers returns peers that have been expired -func (a *Account) GetExpiredPeers() []*Peer { - var peers []*Peer +func (a *Account) GetExpiredPeers() []*nbpeer.Peer { + var peers []*nbpeer.Peer for _, peer := range a.GetPeersWithExpiration() { expired, _ := peer.LoginExpired(a.Settings.PeerLoginExpiration) if expired { @@ -447,8 +459,8 @@ func (a *Account) GetNextPeerExpiration() (time.Duration, bool) { } // GetPeersWithExpiration returns a list of peers that have Peer.LoginExpirationEnabled set to true and that were added by a user -func (a *Account) GetPeersWithExpiration() []*Peer { - peers := make([]*Peer, 0) +func (a *Account) GetPeersWithExpiration() []*nbpeer.Peer { + peers := make([]*nbpeer.Peer, 0) for _, peer := range a.Peers { if peer.LoginExpirationEnabled && peer.AddedWithSSOLogin() { peers = append(peers, peer) @@ -458,8 +470,8 @@ func (a *Account) GetPeersWithExpiration() []*Peer { } // GetPeers returns a list of all Account peers -func (a *Account) GetPeers() []*Peer { - var peers []*Peer +func (a *Account) GetPeers() []*nbpeer.Peer { + var peers []*nbpeer.Peer for _, peer := range a.Peers { peers = append(peers, peer) } @@ -473,7 +485,7 @@ func (a *Account) UpdateSettings(update *Settings) *Account { } // UpdatePeer saves new or replaces existing peer -func (a *Account) UpdatePeer(update *Peer) { +func (a *Account) UpdatePeer(update *nbpeer.Peer) { a.Peers[update.ID] = update } @@ -502,7 +514,7 @@ func (a *Account) DeletePeer(peerID string) { // FindPeerByPubKey looks for a Peer by provided WireGuard public key in the Account or returns error if it wasn't found. // It will return an object copy of the peer. -func (a *Account) FindPeerByPubKey(peerPubKey string) (*Peer, error) { +func (a *Account) FindPeerByPubKey(peerPubKey string) (*nbpeer.Peer, error) { for _, peer := range a.Peers { if peer.Key == peerPubKey { return peer.Copy(), nil @@ -513,8 +525,8 @@ func (a *Account) FindPeerByPubKey(peerPubKey string) (*Peer, error) { } // FindUserPeers returns a list of peers that user owns (created) -func (a *Account) FindUserPeers(userID string) ([]*Peer, error) { - peers := make([]*Peer, 0) +func (a *Account) FindUserPeers(userID string) ([]*nbpeer.Peer, error) { + peers := make([]*nbpeer.Peer, 0) for _, peer := range a.Peers { if peer.UserID == userID { peers = append(peers, peer) @@ -606,7 +618,7 @@ func (a *Account) getPeerDNSLabels() lookupMap { } func (a *Account) Copy() *Account { - peers := map[string]*Peer{} + peers := map[string]*nbpeer.Peer{} for id, peer := range a.Peers { peers[id] = peer.Copy() } @@ -683,7 +695,7 @@ func (a *Account) GetGroupAll() (*Group, error) { } // GetPeer looks up a Peer by ID -func (a *Account) GetPeer(peerID string) *Peer { +func (a *Account) GetPeer(peerID string) *nbpeer.Peer { return a.Peers[peerID] } @@ -893,7 +905,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, return nil, err } - err = integrations.ValidateExtraSettings(newSettings.Extra, account, am) + err = integrations.ValidateExtraSettings(newSettings.Extra, account, userID, am) if err != nil { return nil, err } @@ -924,18 +936,6 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, am.checkAndSchedulePeerLoginExpiration(account) } - // if oldSettings.PeerApprovalEnabled != newSettings.PeerApprovalEnabled { - // event := activity.AccountPeerApprovalEnabled - // if !newSettings.PeerApprovalEnabled { - // event = activity.AccountPeerApprovalDisabled - // } - // am.StoreEvent(userID, accountID, accountID, event, nil) - // - // for _, peer := range account.Peers { - // peer.Status.RequiresApproval = false - // } - // } - updatedAccount := account.UpdateSettings(newSettings) err = am.Store.SaveAccount(account) @@ -1683,7 +1683,7 @@ func newAccountWithId(accountID, userID, domain string) *Account { log.Debugf("creating new account") network := NewNetwork() - peers := make(map[string]*Peer) + peers := make(map[string]*nbpeer.Peer) users := make(map[string]*User) routes := make(map[string]*route.Route) setupKeys := map[string]*SetupKey{} diff --git a/management/server/account_test.go b/management/server/account_test.go index ad0ccfbce..9be9d8257 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -15,6 +15,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/route" "github.com/stretchr/testify/assert" @@ -26,10 +27,10 @@ import ( func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) { t.Helper() - peer := &Peer{ + peer := &nbpeer.Peer{ Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=", Name: "test-host@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -110,13 +111,14 @@ func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy func TestAccount_GetPeerNetworkMap(t *testing.T) { peerID1 := "peer-1" peerID2 := "peer-2" + // peerID3 := "peer-3" tt := []struct { name string accountSettings Settings peerID string expectedPeers []string expectedOfflinePeers []string - peers map[string]*Peer + peers map[string]*nbpeer.Peer }{ { name: "Should return ALL peers when global peer login expiration disabled", @@ -124,14 +126,14 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { peerID: peerID1, expectedPeers: []string{peerID2}, expectedOfflinePeers: []string{}, - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { ID: peerID1, Key: "peer-1-key", IP: net.IP{100, 64, 0, 1}, Name: peerID1, DNSLabel: peerID1, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: false, LoginExpired: true, @@ -145,7 +147,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { IP: net.IP{100, 64, 0, 1}, Name: peerID2, DNSLabel: peerID2, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: false, LoginExpired: false, @@ -162,14 +164,14 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { peerID: peerID1, expectedPeers: []string{}, expectedOfflinePeers: []string{peerID2}, - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { ID: peerID1, Key: "peer-1-key", IP: net.IP{100, 64, 0, 1}, Name: peerID1, DNSLabel: peerID1, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: false, LoginExpired: true, @@ -184,7 +186,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { IP: net.IP{100, 64, 0, 1}, Name: peerID2, DNSLabel: peerID2, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: false, LoginExpired: true, @@ -195,6 +197,159 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { }, }, }, + // { + // name: "Should return only peers that are approved when peer approval is enabled", + // accountSettings: Settings{PeerApprovalEnabled: true}, + // peerID: peerID1, + // expectedPeers: []string{peerID3}, + // expectedOfflinePeers: []string{}, + // peers: map[string]*Peer{ + // "peer-1": { + // ID: peerID1, + // Key: "peer-1-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID1, + // DNSLabel: peerID1, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-2": { + // ID: peerID2, + // Key: "peer-2-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID2, + // DNSLabel: peerID2, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: false, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-3": { + // ID: peerID3, + // Key: "peer-3-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID3, + // DNSLabel: peerID3, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // }, + // }, + // { + // name: "Should return all peers when peer approval is disabled", + // accountSettings: Settings{PeerApprovalEnabled: false}, + // peerID: peerID1, + // expectedPeers: []string{peerID2, peerID3}, + // expectedOfflinePeers: []string{}, + // peers: map[string]*Peer{ + // "peer-1": { + // ID: peerID1, + // Key: "peer-1-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID1, + // DNSLabel: peerID1, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-2": { + // ID: peerID2, + // Key: "peer-2-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID2, + // DNSLabel: peerID2, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: false, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-3": { + // ID: peerID3, + // Key: "peer-3-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID3, + // DNSLabel: peerID3, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // }, + // }, + // { + // name: "Should return no peers when peer approval is enabled and the requesting peer is not approved", + // accountSettings: Settings{PeerApprovalEnabled: true}, + // peerID: peerID1, + // expectedPeers: []string{}, + // expectedOfflinePeers: []string{}, + // peers: map[string]*Peer{ + // "peer-1": { + // ID: peerID1, + // Key: "peer-1-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID1, + // DNSLabel: peerID1, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: false, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-2": { + // ID: peerID2, + // Key: "peer-2-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID2, + // DNSLabel: peerID2, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // "peer-3": { + // ID: peerID3, + // Key: "peer-3-key", + // IP: net.IP{100, 64, 0, 1}, + // Name: peerID3, + // DNSLabel: peerID3, + // Status: &PeerStatus{ + // LastSeen: time.Now().UTC(), + // Connected: false, + // Approved: true, + // }, + // UserID: userID, + // LastLogin: time.Now().UTC().Add(-time.Hour * 24 * 30 * 30), + // }, + // }, + // }, } netIP := net.IP{100, 64, 0, 0} @@ -209,6 +364,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) { for _, testCase := range tt { account := newAccountWithId("account-1", userID, "netbird.io") + account.UpdateSettings(&testCase.accountSettings) account.Network = network account.Peers = testCase.peers for _, peer := range account.Peers { @@ -780,9 +936,9 @@ func TestAccountManager_AddPeer(t *testing.T) { expectedPeerKey := key.PublicKey().String() expectedSetupKey := setupKey.Key - peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: expectedPeerKey, - Meta: PeerSystemMeta{Hostname: expectedPeerKey}, + Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -848,9 +1004,9 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) { expectedPeerKey := key.PublicKey().String() expectedUserID := userID - peer, _, err := manager.AddPeer("", userID, &Peer{ + peer, _, err := manager.AddPeer("", userID, &nbpeer.Peer{ Key: expectedPeerKey, - Meta: PeerSystemMeta{Hostname: expectedPeerKey}, + Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v, account users: %v", err, account.CreatedBy) @@ -915,7 +1071,7 @@ func TestAccountManager_NetworkUpdates(t *testing.T) { return } - getPeer := func() *Peer { + getPeer := func() *nbpeer.Peer { key, err := wgtypes.GeneratePrivateKey() if err != nil { t.Fatal(err) @@ -923,9 +1079,9 @@ func TestAccountManager_NetworkUpdates(t *testing.T) { } expectedPeerKey := key.PublicKey().String() - peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: expectedPeerKey, - Meta: PeerSystemMeta{Hostname: expectedPeerKey}, + Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey}, }) if err != nil { t.Fatalf("expecting peer1 to be added, got failure %v", err) @@ -1097,9 +1253,9 @@ func TestAccountManager_DeletePeer(t *testing.T) { peerKey := key.PublicKey().String() - peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey, - Meta: PeerSystemMeta{Hostname: peerKey}, + Meta: nbpeer.PeerSystemMeta{Hostname: peerKey}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -1238,8 +1394,8 @@ func TestAccount_GetRoutesToSync(t *testing.T) { t.Fatal(err) } account := &Account{ - Peers: map[string]*Peer{ - "peer-1": {Key: "peer-1", Meta: PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: PeerSystemMeta{GoOS: "linux"}}, + Peers: map[string]*nbpeer.Peer{ + "peer-1": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-2": {Key: "peer-2", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, "peer-3": {Key: "peer-1", Meta: nbpeer.PeerSystemMeta{GoOS: "linux"}}, }, Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}}, Routes: map[string]*route.Route{ @@ -1282,7 +1438,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) { }, } - routes := account.getRoutesToSync("peer-2", []*Peer{{Key: "peer-1"}, {Key: "peer-3"}}) + routes := account.getRoutesToSync("peer-2", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-3"}}) assert.Len(t, routes, 2) routeIDs := make(map[string]struct{}, 2) @@ -1292,7 +1448,7 @@ func TestAccount_GetRoutesToSync(t *testing.T) { assert.Contains(t, routeIDs, "route-2") assert.Contains(t, routeIDs, "route-3") - emptyRoutes := account.getRoutesToSync("peer-3", []*Peer{{Key: "peer-1"}, {Key: "peer-2"}}) + emptyRoutes := account.getRoutesToSync("peer-3", []*nbpeer.Peer{{Key: "peer-1"}, {Key: "peer-2"}}) assert.Len(t, emptyRoutes, 0) } @@ -1313,10 +1469,10 @@ func TestAccount_Copy(t *testing.T) { Network: &Network{ Identifier: "net1", }, - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peer1": { Key: "key1", - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now(), Connected: true, LoginExpired: false, @@ -1443,9 +1599,9 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) { key, err := wgtypes.GenerateKey() require.NoError(t, err, "unable to generate WireGuard key") - peer, _, err := manager.AddPeer("", userID, &Peer{ + peer, _, err := manager.AddPeer("", userID, &nbpeer.Peer{ Key: key.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"}, LoginExpirationEnabled: true, }) require.NoError(t, err, "unable to add peer") @@ -1492,9 +1648,9 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing. key, err := wgtypes.GenerateKey() require.NoError(t, err, "unable to generate WireGuard key") - _, _, err = manager.AddPeer("", userID, &Peer{ + _, _, err = manager.AddPeer("", userID, &nbpeer.Peer{ Key: key.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"}, LoginExpirationEnabled: true, }) require.NoError(t, err, "unable to add peer") @@ -1533,9 +1689,9 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test key, err := wgtypes.GenerateKey() require.NoError(t, err, "unable to generate WireGuard key") - _, _, err = manager.AddPeer("", userID, &Peer{ + _, _, err = manager.AddPeer("", userID, &nbpeer.Peer{ Key: key.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"}, LoginExpirationEnabled: true, }) require.NoError(t, err, "unable to add peer") @@ -1614,13 +1770,13 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) { func TestAccount_GetExpiredPeers(t *testing.T) { type test struct { name string - peers map[string]*Peer + peers map[string]*nbpeer.Peer expectedPeers map[string]struct{} } testCases := []test{ { name: "Peers with login expiration disabled, no expired peers", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { LoginExpirationEnabled: false, }, @@ -1632,11 +1788,11 @@ func TestAccount_GetExpiredPeers(t *testing.T) { }, { name: "Two peers expired", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { ID: "peer-1", LoginExpirationEnabled: true, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: true, LoginExpired: false, @@ -1647,7 +1803,7 @@ func TestAccount_GetExpiredPeers(t *testing.T) { "peer-2": { ID: "peer-2", LoginExpirationEnabled: true, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: true, LoginExpired: false, @@ -1659,7 +1815,7 @@ func TestAccount_GetExpiredPeers(t *testing.T) { "peer-3": { ID: "peer-3", LoginExpirationEnabled: true, - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ LastSeen: time.Now().UTC(), Connected: true, LoginExpired: false, @@ -1699,19 +1855,19 @@ func TestAccount_GetExpiredPeers(t *testing.T) { func TestAccount_GetPeersWithExpiration(t *testing.T) { type test struct { name string - peers map[string]*Peer + peers map[string]*nbpeer.Peer expectedPeers map[string]struct{} } testCases := []test{ { name: "No account peers, no peers with expiration", - peers: map[string]*Peer{}, + peers: map[string]*nbpeer.Peer{}, expectedPeers: map[string]struct{}{}, }, { name: "Peers with login expiration disabled, no peers with expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { LoginExpirationEnabled: false, UserID: userID, @@ -1725,7 +1881,7 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) { }, { name: "Peers with login expiration enabled, return peers with expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { ID: "peer-1", LoginExpirationEnabled: true, @@ -1768,7 +1924,7 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) { func TestAccount_GetNextPeerExpiration(t *testing.T) { type test struct { name string - peers map[string]*Peer + peers map[string]*nbpeer.Peer expiration time.Duration expirationEnabled bool expectedNextRun bool @@ -1779,7 +1935,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { testCases := []test{ { name: "No peers, no expiration", - peers: map[string]*Peer{}, + peers: map[string]*nbpeer.Peer{}, expiration: time.Second, expirationEnabled: false, expectedNextRun: false, @@ -1787,16 +1943,16 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "No connected peers, no expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: false, }, LoginExpirationEnabled: true, UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, }, LoginExpirationEnabled: false, @@ -1810,16 +1966,16 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "Connected peers with disabled expiration, no expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, }, LoginExpirationEnabled: false, UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, }, LoginExpirationEnabled: false, @@ -1833,9 +1989,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "Expired peers, no expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: true, }, @@ -1843,7 +1999,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: true, }, @@ -1858,9 +2014,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "To be expired peer, return expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: false, }, @@ -1869,7 +2025,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: true, }, @@ -1884,9 +2040,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { }, { name: "Peers added with setup keys, no expiration", - peers: map[string]*Peer{ + peers: map[string]*nbpeer.Peer{ "peer-1": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: false, }, @@ -1894,7 +2050,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { SetupKey: "key", }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: false, }, @@ -1929,7 +2085,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { func TestAccount_SetJWTGroups(t *testing.T) { // create a new account account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, @@ -1977,7 +2133,7 @@ func TestAccount_SetJWTGroups(t *testing.T) { func TestAccount_UserGroupsAddToPeers(t *testing.T) { account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, @@ -2013,7 +2169,7 @@ func TestAccount_UserGroupsAddToPeers(t *testing.T) { func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) { account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, diff --git a/management/server/dns.go b/management/server/dns.go index 4f7f95aaf..236ba1f57 100644 --- a/management/server/dns.go +++ b/management/server/dns.go @@ -10,6 +10,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -200,7 +201,7 @@ func getPeerNSGroups(account *Account, peerID string) []*nbdns.NameServerGroup { } // peerIsNameserver returns true if the peer is a nameserver for a nsGroup -func peerIsNameserver(peer *Peer, nsGroup *nbdns.NameServerGroup) bool { +func peerIsNameserver(peer *nbpeer.Peer, nsGroup *nbdns.NameServerGroup) bool { for _, ns := range nsGroup.NameServers { if peer.IP.Equal(ns.IP.AsSlice()) { return true diff --git a/management/server/dns_test.go b/management/server/dns_test.go index 62408e4b3..bff0c9878 100644 --- a/management/server/dns_test.go +++ b/management/server/dns_test.go @@ -8,6 +8,7 @@ import ( "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -208,10 +209,10 @@ func createDNSStore(t *testing.T) (Store, error) { func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) { t.Helper() - peer1 := &Peer{ + peer1 := &nbpeer.Peer{ Key: dnsPeer1Key, Name: "test-host1@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host1@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -223,10 +224,10 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro }, DNSLabel: dnsPeer1Key, } - peer2 := &Peer{ + peer2 := &nbpeer.Peer{ Key: dnsPeer2Key, Name: "test-host2@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host2@netbird.io", GoOS: "linux", Kernel: "Linux", diff --git a/management/server/ephemeral.go b/management/server/ephemeral.go index 0e76e58ac..9d70a05d1 100644 --- a/management/server/ephemeral.go +++ b/management/server/ephemeral.go @@ -7,6 +7,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) const ( @@ -72,7 +73,7 @@ func (e *EphemeralManager) Stop() { // OnPeerConnected remove the peer from the linked list of ephemeral peers. Because it has been called when the peer // is active the manager will not delete it while it is active. -func (e *EphemeralManager) OnPeerConnected(peer *Peer) { +func (e *EphemeralManager) OnPeerConnected(peer *nbpeer.Peer) { if !peer.Ephemeral { return } @@ -93,7 +94,7 @@ func (e *EphemeralManager) OnPeerConnected(peer *Peer) { // OnPeerDisconnected add the peer to the linked list of ephemeral peers. Because of the peer // is inactive it will be deleted after the ephemeralLifeTime period. -func (e *EphemeralManager) OnPeerDisconnected(peer *Peer) { +func (e *EphemeralManager) OnPeerDisconnected(peer *nbpeer.Peer) { if !peer.Ephemeral { return } diff --git a/management/server/ephemeral_test.go b/management/server/ephemeral_test.go index d271e5fca..3e36335e3 100644 --- a/management/server/ephemeral_test.go +++ b/management/server/ephemeral_test.go @@ -4,6 +4,8 @@ import ( "fmt" "testing" "time" + + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) type MockStore struct { @@ -124,7 +126,7 @@ func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int) for i := 0; i < numberOfPeers; i++ { peerId := fmt.Sprintf("peer_%d", i) - p := &Peer{ + p := &nbpeer.Peer{ ID: peerId, Ephemeral: false, } @@ -133,7 +135,7 @@ func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int) for i := 0; i < numberOfEphemeralPeers; i++ { peerId := fmt.Sprintf("ephemeral_peer_%d", i) - p := &Peer{ + p := &nbpeer.Peer{ ID: peerId, Ephemeral: true, } diff --git a/management/server/file_store.go b/management/server/file_store.go index 73c52927e..ee32d5397 100644 --- a/management/server/file_store.go +++ b/management/server/file_store.go @@ -10,6 +10,7 @@ import ( "github.com/rs/xid" log "github.com/sirupsen/logrus" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/telemetry" @@ -204,7 +205,7 @@ func restore(file string) (*FileStore, error) { // Set the Peer.ID to the newly generated value. // Replace all the mentions of Peer.Key as ID (groups and routes). // Swap Peer.Key with Peer.ID in the Account.Peers map. - migrationPeers := make(map[string]*Peer) // key to Peer + migrationPeers := make(map[string]*nbpeer.Peer) // key to Peer for key, peer := range account.Peers { // set LastLogin for the peers that were onboarded before the peer login expiration feature if peer.LastLogin.IsZero() { @@ -571,7 +572,7 @@ func (s *FileStore) SaveInstallationID(ID string) error { // SavePeerStatus stores the PeerStatus in memory. It doesn't attempt to persist data to speed up things. // PeerStatus will be saved eventually when some other changes occur. -func (s *FileStore) SavePeerStatus(accountID, peerID string, peerStatus PeerStatus) error { +func (s *FileStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error { s.mux.Lock() defer s.mux.Unlock() diff --git a/management/server/file_store_test.go b/management/server/file_store_test.go index 206873d20..eb2e2042b 100644 --- a/management/server/file_store_test.go +++ b/management/server/file_store_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/util" ) @@ -35,7 +36,7 @@ func TestStalePeerIndices(t *testing.T) { peerID := "some_peer" peerKey := "some_peer_key" - account.Peers[peerID] = &Peer{ + account.Peers[peerID] = &nbpeer.Peer{ ID: peerID, Key: peerKey, } @@ -89,13 +90,13 @@ func TestSaveAccount(t *testing.T) { account := newAccountWithId("account_id", "testuser", "") setupKey := GenerateDefaultSetupKey() account.SetupKeys[setupKey.Key] = setupKey - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } // SaveAccount should trigger persist @@ -125,13 +126,13 @@ func TestStore(t *testing.T) { store := newStore(t) account := newAccountWithId("account_id", "testuser", "") - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } account.Groups["all"] = &Group{ ID: "all", @@ -546,19 +547,19 @@ func TestFileStore_SavePeerStatus(t *testing.T) { } // save status of non-existing peer - newStatus := PeerStatus{Connected: true, LastSeen: time.Now().UTC()} + newStatus := nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()} err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus) assert.Error(t, err) // save new status of existing peer - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", ID: "testpeer", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, } err = store.SaveAccount(account) diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index f32f6347a..8d3d82661 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -17,6 +17,7 @@ import ( "github.com/netbirdio/netbird/encryption" "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" internalStatus "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/telemetry" ) @@ -196,7 +197,7 @@ func (s *GRPCServer) Sync(req *proto.EncryptedMessage, srv proto.ManagementServi } } -func (s *GRPCServer) cancelPeerRoutines(peer *Peer) { +func (s *GRPCServer) cancelPeerRoutines(peer *nbpeer.Peer) { s.peersUpdateManager.CloseChannel(peer.ID) s.turnCredentialsManager.CancelRefresh(peer.ID) _ = s.accountManager.MarkPeerConnected(peer.Key, false) @@ -243,8 +244,8 @@ func mapError(err error) error { return status.Errorf(codes.Internal, "failed handling request") } -func extractPeerMeta(loginReq *proto.LoginRequest) PeerSystemMeta { - return PeerSystemMeta{ +func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta { + return nbpeer.PeerSystemMeta{ Hostname: loginReq.GetMeta().GetHostname(), GoOS: loginReq.GetMeta().GetGoOS(), Kernel: loginReq.GetMeta().GetKernel(), @@ -413,7 +414,7 @@ func toWiretrusteeConfig(config *Config, turnCredentials *TURNCredentials) *prot } } -func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfig { +func toPeerConfig(peer *nbpeer.Peer, network *Network, dnsName string) *proto.PeerConfig { netmask, _ := network.Net.Mask.Size() fqdn := peer.FQDN(dnsName) return &proto.PeerConfig{ @@ -423,7 +424,7 @@ func toPeerConfig(peer *Peer, network *Network, dnsName string) *proto.PeerConfi } } -func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig { +func toRemotePeerConfig(peers []*nbpeer.Peer, dnsName string) []*proto.RemotePeerConfig { remotePeers := []*proto.RemotePeerConfig{} for _, rPeer := range peers { fqdn := rPeer.FQDN(dnsName) @@ -437,7 +438,7 @@ func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig return remotePeers } -func toSyncResponse(config *Config, peer *Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string) *proto.SyncResponse { +func toSyncResponse(config *Config, peer *nbpeer.Peer, turnCredentials *TURNCredentials, networkMap *NetworkMap, dnsName string) *proto.SyncResponse { wtConfig := toWiretrusteeConfig(config, turnCredentials) pConfig := toPeerConfig(peer, networkMap.Network, dnsName) @@ -477,7 +478,7 @@ func (s *GRPCServer) IsHealthy(ctx context.Context, req *proto.Empty) (*proto.Em } // sendInitialSync sends initial proto.SyncResponse to the peer requesting synchronization -func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *Peer, networkMap *NetworkMap, srv proto.ManagementService_SyncServer) error { +func (s *GRPCServer) sendInitialSync(peerKey wgtypes.Key, peer *nbpeer.Peer, networkMap *NetworkMap, srv proto.ManagementService_SyncServer) error { // make secret time based TURN credentials optional var turnCredentials *TURNCredentials if s.config.TURNConfig.TimeBasedCredentials { diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index 673cd90c5..1690bb75f 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -61,6 +61,7 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { Domain: "hotmail.com", Peers: map[string]*server.Peer{ peers[0].ID: peers[0], + peers[1].ID: peers[1], }, Users: map[string]*server.User{ "test_user": user, @@ -112,7 +113,7 @@ func TestGetPeers(t *testing.T) { Key: "key", SetupKey: "setupkey", IP: net.ParseIP("100.64.0.1"), - Status: &server.PeerStatus{Connected: true}, + Status: &server.PeerStatus{Connected: true, Approved: true}, Name: "PeerName", LoginExpirationEnabled: false, Meta: server.PeerSystemMeta{ diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index d1f4cd015..791dc5677 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -8,6 +8,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) const ( @@ -763,10 +764,10 @@ func createNSStore(t *testing.T) (Store, error) { func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) { t.Helper() - peer1 := &Peer{ + peer1 := &nbpeer.Peer{ Key: nsGroupPeer1Key, Name: "test-host1@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host1@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -777,10 +778,10 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error UIVersion: "development", }, } - peer2 := &Peer{ + peer2 := &nbpeer.Peer{ Key: nsGroupPeer2Key, Name: "test-host2@netbird.io", - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host2@netbird.io", GoOS: "linux", Kernel: "Linux", diff --git a/management/server/network.go b/management/server/network.go index c5b165cae..23ecb4c59 100644 --- a/management/server/network.go +++ b/management/server/network.go @@ -10,6 +10,7 @@ import ( "github.com/rs/xid" nbdns "github.com/netbirdio/netbird/dns" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" ) @@ -25,11 +26,11 @@ const ( ) type NetworkMap struct { - Peers []*Peer + Peers []*nbpeer.Peer Network *Network Routes []*route.Route DNSConfig nbdns.Config - OfflinePeers []*Peer + OfflinePeers []*nbpeer.Peer FirewallRules []*FirewallRule } diff --git a/management/server/peer.go b/management/server/peer.go index 6268ae794..97cada4f0 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -2,7 +2,6 @@ package server import ( "fmt" - "net" "strings" "time" @@ -10,6 +9,7 @@ import ( "github.com/rs/xid" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" log "github.com/sirupsen/logrus" @@ -17,40 +17,6 @@ import ( "github.com/netbirdio/netbird/management/proto" ) -// PeerSystemMeta is a metadata of a Peer machine system -type PeerSystemMeta struct { - Hostname string - GoOS string - Kernel string - Core string - Platform string - OS string - WtVersion string - UIVersion string -} - -func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { - return p.Hostname == other.Hostname && - p.GoOS == other.GoOS && - p.Kernel == other.Kernel && - p.Core == other.Core && - p.Platform == other.Platform && - p.OS == other.OS && - p.WtVersion == other.WtVersion && - p.UIVersion == other.UIVersion -} - -type PeerStatus struct { - // LastSeen is the last time peer was connected to the management service - LastSeen time.Time - // Connected indicates whether peer is connected to the management service or not - Connected bool - // LoginExpired - LoginExpired bool - // RequiresApproval indicates whether peer requires approval or not - RequiresApproval bool -} - // PeerSync used as a data object between the gRPC API and AccountManager on Sync request. type PeerSync struct { // WireGuardPubKey is a peers WireGuard public key @@ -64,147 +30,16 @@ type PeerLogin struct { // SSHKey is a peer's ssh key. Can be empty (e.g., old version do not provide it, or this feature is disabled) SSHKey string // Meta is the system information passed by peer, must be always present. - Meta PeerSystemMeta + Meta nbpeer.PeerSystemMeta // UserID indicates that JWT was used to log in, and it was valid. Can be empty when SetupKey is used or auth is not required. UserID string // SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required. SetupKey string } -// Peer represents a machine connected to the network. -// The Peer is a WireGuard peer identified by a public key -type Peer struct { - // ID is an internal ID of the peer - ID string `gorm:"primaryKey"` - // AccountID is a reference to Account that this object belongs - AccountID string `json:"-" gorm:"index;uniqueIndex:idx_peers_account_id_ip"` - // WireGuard public key - Key string `gorm:"index"` - // A setup key this peer was registered with - SetupKey string - // IP address of the Peer - IP net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip"` - // Meta is a Peer system meta data - Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"` - // Name is peer's name (machine name) - Name string - // DNSLabel is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's - // domain to the peer label. e.g. peer-dns-label.netbird.cloud - DNSLabel string - // Status peer's management connection status - Status *PeerStatus `gorm:"embedded;embeddedPrefix:peer_status_"` - // The user ID that registered the peer - UserID string - // SSHKey is a public SSH key of the peer - SSHKey string - // SSHEnabled indicates whether SSH server is enabled on the peer - SSHEnabled bool - // LoginExpirationEnabled indicates whether peer's login expiration is enabled and once expired the peer has to re-login. - // Works with LastLogin - LoginExpirationEnabled bool - // LastLogin the time when peer performed last login operation - LastLogin time.Time - // Indicate ephemeral peer attribute - Ephemeral bool -} - -// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user. -func (p *Peer) AddedWithSSOLogin() bool { - return p.UserID != "" -} - -// Copy copies Peer object -func (p *Peer) Copy() *Peer { - peerStatus := p.Status - if peerStatus != nil { - peerStatus = p.Status.Copy() - } - return &Peer{ - ID: p.ID, - AccountID: p.AccountID, - Key: p.Key, - SetupKey: p.SetupKey, - IP: p.IP, - Meta: p.Meta, - Name: p.Name, - DNSLabel: p.DNSLabel, - Status: peerStatus, - UserID: p.UserID, - SSHKey: p.SSHKey, - SSHEnabled: p.SSHEnabled, - LoginExpirationEnabled: p.LoginExpirationEnabled, - LastLogin: p.LastLogin, - Ephemeral: p.Ephemeral, - } -} - -// UpdateMetaIfNew updates peer's system metadata if new information is provided -// returns true if meta was updated, false otherwise -func (p *Peer) UpdateMetaIfNew(meta PeerSystemMeta) bool { - // Avoid overwriting UIVersion if the update was triggered sole by the CLI client - if meta.UIVersion == "" { - meta.UIVersion = p.Meta.UIVersion - } - - if p.Meta.isEqual(meta) { - return false - } - p.Meta = meta - return true -} - -// MarkLoginExpired marks peer's status expired or not -func (p *Peer) MarkLoginExpired(expired bool) { - newStatus := p.Status.Copy() - newStatus.LoginExpired = expired - if expired { - newStatus.Connected = false - } - p.Status = newStatus -} - -// 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. -// Return true if a login has expired, false otherwise, and time left to expiration (negative when expired). -// Login expiration can be disabled/enabled on a Peer level via Peer.LoginExpirationEnabled property. -// Login 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) LoginExpired(expiresIn time.Duration) (bool, time.Duration) { - if !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled { - return false, 0 - } - expiresAt := p.LastLogin.Add(expiresIn) - now := time.Now() - timeLeft := expiresAt.Sub(now) - return timeLeft <= 0, timeLeft -} - -// FQDN returns peers FQDN combined of the peer's DNS label and the system's DNS domain -func (p *Peer) FQDN(dnsDomain string) string { - if dnsDomain == "" { - return "" - } - return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain) -} - -// EventMeta returns activity event meta related to the peer -func (p *Peer) EventMeta(dnsDomain string) map[string]any { - return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP} -} - -// Copy PeerStatus -func (p *PeerStatus) Copy() *PeerStatus { - return &PeerStatus{ - LastSeen: p.LastSeen, - Connected: p.Connected, - LoginExpired: p.LoginExpired, - RequiresApproval: p.RequiresApproval, - } -} - // GetPeers returns a list of peers under the given account filtering out peers that do not belong to a user if // the current user is not an admin. -func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, error) { +func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*nbpeer.Peer, error) { account, err := am.Store.GetAccount(accountID) if err != nil { return nil, err @@ -215,8 +50,8 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er return nil, err } - peers := make([]*Peer, 0) - peersMap := make(map[string]*Peer) + peers := make([]*nbpeer.Peer, 0) + peersMap := make(map[string]*nbpeer.Peer) for _, peer := range account.Peers { if !user.IsAdmin() && user.Id != peer.UserID { // only display peers that belong to the current user if the current user is not an admin @@ -235,7 +70,7 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er } } - peers = make([]*Peer, 0, len(peersMap)) + peers = make([]*nbpeer.Peer, 0, len(peersMap)) for _, peer := range peersMap { peers = append(peers, peer) } @@ -294,7 +129,7 @@ func (am *DefaultAccountManager) MarkPeerConnected(peerPubKey string, connected } // UpdatePeer updates peer. Only Peer.Name, Peer.SSHEnabled, and Peer.LoginExpirationEnabled can be updated. -func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Peer) (*Peer, error) { +func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -373,7 +208,7 @@ func (am *DefaultAccountManager) deletePeers(account *Account, peerIDs []string, // the first loop is needed to ensure all peers present under the account before modifying, otherwise // we might have some inconsistencies - peers := make([]*Peer, 0, len(peerIDs)) + peers := make([]*nbpeer.Peer, 0, len(peerIDs)) for _, peerID := range peerIDs { peer := account.GetPeer(peerID) @@ -465,7 +300,7 @@ func (am *DefaultAccountManager) GetPeerNetwork(peerID string) (*Network, error) // to it. We also add the User ID to the peer metadata to identify registrant. If no userID provided, then fail with status.PermissionDenied // Each new Peer will be assigned a new next net.IP from the Account.Network and Account.Network.LastIP will be updated (IP's are not reused). // The peer property is just a placeholder for the Peer properties to pass further -func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*Peer, *NetworkMap, error) { +func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, *NetworkMap, error) { if setupKey == "" && userID == "" { // no auth method provided => reject access return nil, nil, status.Errorf(status.Unauthenticated, "no peer auth method provided, please use a setup key or interactive SSO login") @@ -554,7 +389,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* return nil, nil, err } - newPeer := &Peer{ + newPeer := &nbpeer.Peer{ ID: xid.New().String(), Key: peer.Key, SetupKey: upperKey, @@ -563,7 +398,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* Name: peer.Meta.Hostname, DNSLabel: newLabel, UserID: userID, - Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, SSHEnabled: false, SSHKey: peer.SSHKey, LastLogin: time.Now().UTC(), @@ -621,7 +456,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* } // SyncPeer checks whether peer is eligible for receiving NetworkMap (authenticated) and returns its NetworkMap if eligible -func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, error) { +func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*nbpeer.Peer, *NetworkMap, error) { account, err := am.Store.GetAccountByPeerPubKey(sync.WireGuardPubKey) if err != nil { if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { @@ -645,7 +480,7 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, er return nil, nil, status.Errorf(status.Unauthenticated, "peer is not registered") } - validatedPeers := integrations.ValidatePeers([]*Peer{peer}, account) + validatedPeers := integrations.ValidatePeers([]*nbpeer.Peer{peer}, account) if len(validatedPeers) == 0 { return nil, nil, status.Errorf(status.PermissionDenied, "peer validation failed") } @@ -663,14 +498,14 @@ func (am *DefaultAccountManager) SyncPeer(sync PeerSync) (*Peer, *NetworkMap, er // LoginPeer logs in or registers a peer. // If peer doesn't exist the function checks whether a setup key or a user is present and registers a new peer if so. -func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) { +func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) { account, err := am.Store.GetAccountByPeerPubKey(login.WireGuardPubKey) if err != nil { if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { // we couldn't find this peer by its public key which can mean that peer hasn't been registered yet. // Try registering it. - return am.AddPeer(login.SetupKey, login.UserID, &Peer{ + return am.AddPeer(login.SetupKey, login.UserID, &nbpeer.Peer{ Key: login.WireGuardPubKey, Meta: login.Meta, SSHKey: login.SSHKey, @@ -740,7 +575,7 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap, return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil } -func checkIfPeerOwnerIsBlocked(peer *Peer, account *Account) error { +func checkIfPeerOwnerIsBlocked(peer *nbpeer.Peer, account *Account) error { if peer.AddedWithSSOLogin() { user, err := account.FindUser(peer.UserID) if err != nil { @@ -753,7 +588,7 @@ func checkIfPeerOwnerIsBlocked(peer *Peer, account *Account) error { return nil } -func checkAuth(loginUserID string, peer *Peer) error { +func checkAuth(loginUserID string, peer *nbpeer.Peer) error { if loginUserID == "" { // absence of a user ID indicates that JWT wasn't provided. return status.Errorf(status.PermissionDenied, "peer login has expired, please log in once more") @@ -765,7 +600,7 @@ func checkAuth(loginUserID string, peer *Peer) error { return nil } -func peerLoginExpired(peer *Peer, account *Account) bool { +func peerLoginExpired(peer *nbpeer.Peer, account *Account) bool { expired, expiresIn := peer.LoginExpired(account.Settings.PeerLoginExpiration) expired = account.Settings.PeerLoginExpirationEnabled && expired if expired || peer.Status.LoginExpired { @@ -775,21 +610,12 @@ func peerLoginExpired(peer *Peer, account *Account) bool { return false } -func updatePeerLastLogin(peer *Peer, account *Account) { +func updatePeerLastLogin(peer *nbpeer.Peer, account *Account) { peer.UpdateLastLogin() account.UpdatePeer(peer) } -// UpdateLastLogin and set login expired false -func (p *Peer) UpdateLastLogin() *Peer { - p.LastLogin = time.Now().UTC() - newStatus := p.Status.Copy() - newStatus.LoginExpired = false - p.Status = newStatus - return p -} - -func (am *DefaultAccountManager) checkAndUpdatePeerSSHKey(peer *Peer, account *Account, newSSHKey string) (*Peer, error) { +func (am *DefaultAccountManager) checkAndUpdatePeerSSHKey(peer *nbpeer.Peer, account *Account, newSSHKey string) (*nbpeer.Peer, error) { if len(newSSHKey) == 0 { log.Debugf("no new SSH key provided for peer %s, skipping update", peer.ID) return peer, nil @@ -860,7 +686,7 @@ func (am *DefaultAccountManager) UpdatePeerSSHKey(peerID string, sshKey string) } // GetPeer for a given accountID, peerID and userID error if not found. -func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*Peer, error) { +func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*nbpeer.Peer, error) { unlock := am.Store.AcquireAccountLock(accountID) defer unlock() @@ -903,7 +729,7 @@ func (am *DefaultAccountManager) GetPeer(accountID, peerID, userID string) (*Pee return nil, status.Errorf(status.Internal, "user %s has no access to peer %s under account %s", userID, peerID, accountID) } -func updatePeerMeta(peer *Peer, meta PeerSystemMeta, account *Account) (*Peer, bool) { +func updatePeerMeta(peer *nbpeer.Peer, meta nbpeer.PeerSystemMeta, account *Account) (*nbpeer.Peer, bool) { if peer.UpdateMetaIfNew(meta) { account.UpdatePeer(peer) return peer, true diff --git a/management/server/peer/peer.go b/management/server/peer/peer.go new file mode 100644 index 000000000..a4e4cc3aa --- /dev/null +++ b/management/server/peer/peer.go @@ -0,0 +1,181 @@ +package peer + +import ( + "fmt" + "net" + "time" +) + +// Peer represents a machine connected to the network. +// The Peer is a WireGuard peer identified by a public key +type Peer struct { + // ID is an internal ID of the peer + ID string `gorm:"primaryKey"` + // AccountID is a reference to Account that this object belongs + AccountID string `json:"-" gorm:"index;uniqueIndex:idx_peers_account_id_ip"` + // WireGuard public key + Key string `gorm:"index"` + // A setup key this peer was registered with + SetupKey string + // IP address of the Peer + IP net.IP `gorm:"uniqueIndex:idx_peers_account_id_ip"` + // Meta is a Peer system meta data + Meta PeerSystemMeta `gorm:"embedded;embeddedPrefix:meta_"` + // Name is peer's name (machine name) + Name string + // DNSLabel is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's + // domain to the peer label. e.g. peer-dns-label.netbird.cloud + DNSLabel string + // Status peer's management connection status + Status *PeerStatus `gorm:"embedded;embeddedPrefix:peer_status_"` + // The user ID that registered the peer + UserID string + // SSHKey is a public SSH key of the peer + SSHKey string + // SSHEnabled indicates whether SSH server is enabled on the peer + SSHEnabled bool + // LoginExpirationEnabled indicates whether peer's login expiration is enabled and once expired the peer has to re-login. + // Works with LastLogin + LoginExpirationEnabled bool + // LastLogin the time when peer performed last login operation + LastLogin time.Time + // Indicate ephemeral peer attribute + Ephemeral bool +} + +type PeerStatus struct { + // LastSeen is the last time peer was connected to the management service + LastSeen time.Time + // Connected indicates whether peer is connected to the management service or not + Connected bool + // LoginExpired + LoginExpired bool + // RequiresApproval indicates whether peer requires approval or not + RequiresApproval bool +} + +// PeerSystemMeta is a metadata of a Peer machine system +type PeerSystemMeta struct { + Hostname string + GoOS string + Kernel string + Core string + Platform string + OS string + WtVersion string + UIVersion string +} + +func (p PeerSystemMeta) isEqual(other PeerSystemMeta) bool { + return p.Hostname == other.Hostname && + p.GoOS == other.GoOS && + p.Kernel == other.Kernel && + p.Core == other.Core && + p.Platform == other.Platform && + p.OS == other.OS && + p.WtVersion == other.WtVersion && + p.UIVersion == other.UIVersion +} + +// AddedWithSSOLogin indicates whether this peer has been added with an SSO login by a user. +func (p *Peer) AddedWithSSOLogin() bool { + return p.UserID != "" +} + +// Copy copies Peer object +func (p *Peer) Copy() *Peer { + peerStatus := p.Status + if peerStatus != nil { + peerStatus = p.Status.Copy() + } + return &Peer{ + ID: p.ID, + AccountID: p.AccountID, + Key: p.Key, + SetupKey: p.SetupKey, + IP: p.IP, + Meta: p.Meta, + Name: p.Name, + DNSLabel: p.DNSLabel, + Status: peerStatus, + UserID: p.UserID, + SSHKey: p.SSHKey, + SSHEnabled: p.SSHEnabled, + LoginExpirationEnabled: p.LoginExpirationEnabled, + LastLogin: p.LastLogin, + Ephemeral: p.Ephemeral, + } +} + +// UpdateMetaIfNew updates peer's system metadata if new information is provided +// returns true if meta was updated, false otherwise +func (p *Peer) UpdateMetaIfNew(meta PeerSystemMeta) bool { + // Avoid overwriting UIVersion if the update was triggered sole by the CLI client + if meta.UIVersion == "" { + meta.UIVersion = p.Meta.UIVersion + } + + if p.Meta.isEqual(meta) { + return false + } + p.Meta = meta + return true +} + +// MarkLoginExpired marks peer's status expired or not +func (p *Peer) MarkLoginExpired(expired bool) { + newStatus := p.Status.Copy() + newStatus.LoginExpired = expired + if expired { + newStatus.Connected = false + } + p.Status = newStatus +} + +// 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. +// Return true if a login has expired, false otherwise, and time left to expiration (negative when expired). +// Login expiration can be disabled/enabled on a Peer level via Peer.LoginExpirationEnabled property. +// Login 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) LoginExpired(expiresIn time.Duration) (bool, time.Duration) { + if !p.AddedWithSSOLogin() || !p.LoginExpirationEnabled { + return false, 0 + } + expiresAt := p.LastLogin.Add(expiresIn) + now := time.Now() + timeLeft := expiresAt.Sub(now) + return timeLeft <= 0, timeLeft +} + +// FQDN returns peers FQDN combined of the peer's DNS label and the system's DNS domain +func (p *Peer) FQDN(dnsDomain string) string { + if dnsDomain == "" { + return "" + } + return fmt.Sprintf("%s.%s", p.DNSLabel, dnsDomain) +} + +// EventMeta returns activity event meta related to the peer +func (p *Peer) EventMeta(dnsDomain string) map[string]any { + return map[string]any{"name": p.Name, "fqdn": p.FQDN(dnsDomain), "ip": p.IP} +} + +// Copy PeerStatus +func (p *PeerStatus) Copy() *PeerStatus { + return &PeerStatus{ + LastSeen: p.LastSeen, + Connected: p.Connected, + LoginExpired: p.LoginExpired, + RequiresApproval: p.RequiresApproval, + } +} + +// UpdateLastLogin and set login expired false +func (p *Peer) UpdateLastLogin() *Peer { + p.LastLogin = time.Now().UTC() + newStatus := p.Status.Copy() + newStatus.LoginExpired = false + p.Status = newStatus + return p +} diff --git a/management/server/peer_test.go b/management/server/peer_test.go index 9d5a8bfb9..ee84ea47d 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -8,6 +8,8 @@ import ( "github.com/rs/xid" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) func TestPeer_LoginExpired(t *testing.T) { @@ -52,7 +54,7 @@ func TestPeer_LoginExpired(t *testing.T) { for _, c := range tt { t.Run(c.name, func(t *testing.T) { - peer := &Peer{ + peer := &nbpeer.Peer{ LoginExpirationEnabled: c.expirationEnabled, LastLogin: c.lastLogin, UserID: userID, @@ -90,9 +92,9 @@ func TestAccountManager_GetNetworkMap(t *testing.T) { return } - peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-1"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -104,9 +106,9 @@ func TestAccountManager_GetNetworkMap(t *testing.T) { t.Fatal(err) return } - _, _, err = manager.AddPeer(setupKey.Key, "", &Peer{ + _, _, err = manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { @@ -163,9 +165,9 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) { return } - peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-1"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -177,9 +179,9 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) { t.Fatal(err) return } - peer2, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer2, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -339,9 +341,9 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) { return } - peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-1"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -353,9 +355,9 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) { t.Fatal(err) return } - _, _, err = manager.AddPeer(setupKey.Key, "", &Peer{ + _, _, err = manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { @@ -409,9 +411,9 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) { return } - peer1, _, err := manager.AddPeer("", someUser, &Peer{ + peer1, _, err := manager.AddPeer("", someUser, &nbpeer.Peer{ Key: peerKey1.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { t.Errorf("expecting peer to be added, got failure %v", err) @@ -425,9 +427,9 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) { } // the second peer added with a setup key - peer2, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ + peer2, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{ Key: peerKey2.PublicKey().String(), - Meta: PeerSystemMeta{Hostname: "test-peer-2"}, + Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"}, }) if err != nil { t.Fatal(err) diff --git a/management/server/policy.go b/management/server/policy.go index 24188f93c..dbfbca3ab 100644 --- a/management/server/policy.go +++ b/management/server/policy.go @@ -9,6 +9,7 @@ import ( "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -205,7 +206,7 @@ type FirewallRule struct { // getPeerConnectionResources for a given peer // // This function returns the list of peers and firewall rules that are applicable to a given peer. -func (a *Account) getPeerConnectionResources(peerID string) ([]*Peer, []*FirewallRule) { +func (a *Account) getPeerConnectionResources(peerID string) ([]*nbpeer.Peer, []*FirewallRule) { generateResources, getAccumulatedResources := a.connResourcesGenerator() for _, policy := range a.Policies { if !policy.Enabled { @@ -247,11 +248,11 @@ func (a *Account) getPeerConnectionResources(peerID string) ([]*Peer, []*Firewal // The generator function is used to generate the list of peers and firewall rules that are applicable to a given peer. // It safe to call the generator function multiple times for same peer and different rules no duplicates will be // generated. The accumulator function returns the result of all the generator calls. -func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), func() ([]*Peer, []*FirewallRule)) { +func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*nbpeer.Peer, int), func() ([]*nbpeer.Peer, []*FirewallRule)) { rulesExists := make(map[string]struct{}) peersExists := make(map[string]struct{}) rules := make([]*FirewallRule, 0) - peers := make([]*Peer, 0) + peers := make([]*nbpeer.Peer, 0) all, err := a.GetGroupAll() if err != nil { @@ -259,7 +260,7 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), fun all = &Group{} } - return func(rule *PolicyRule, groupPeers []*Peer, direction int) { + return func(rule *PolicyRule, groupPeers []*nbpeer.Peer, direction int) { isAll := (len(all.Peers) - 1) == len(groupPeers) for _, peer := range groupPeers { if peer == nil { @@ -299,7 +300,7 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), fun rules = append(rules, &pr) } } - }, func() ([]*Peer, []*FirewallRule) { + }, func() ([]*nbpeer.Peer, []*FirewallRule) { return peers, rules } } @@ -478,9 +479,9 @@ func toProtocolFirewallRules(update []*FirewallRule) []*proto.FirewallRule { // getAllPeersFromGroups for given peer ID and list of groups // // Returns list of peers and boolean indicating if peer is in any of the groups -func getAllPeersFromGroups(account *Account, groups []string, peerID string) ([]*Peer, bool) { +func getAllPeersFromGroups(account *Account, groups []string, peerID string) ([]*nbpeer.Peer, bool) { peerInGroups := false - filteredPeers := make([]*Peer, 0, len(groups)) + filteredPeers := make([]*nbpeer.Peer, 0, len(groups)) for _, g := range groups { group, ok := account.Groups[g] if !ok { diff --git a/management/server/policy_test.go b/management/server/policy_test.go index 971bd27d9..3ed08f4e6 100644 --- a/management/server/policy_test.go +++ b/management/server/policy_test.go @@ -7,11 +7,13 @@ import ( "github.com/stretchr/testify/assert" "golang.org/x/exp/slices" + + nbpeer "github.com/netbirdio/netbird/management/server/peer" ) func TestAccount_getPeersByPolicy(t *testing.T) { account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peerA": { ID: "peerA", IP: net.ParseIP("100.65.14.88"), @@ -255,7 +257,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) { func TestAccount_getPeersByPolicyDirect(t *testing.T) { account := &Account{ - Peers: map[string]*Peer{ + Peers: map[string]*nbpeer.Peer{ "peerA": { ID: "peerA", IP: net.ParseIP("100.65.14.88"), diff --git a/management/server/route_test.go b/management/server/route_test.go index 5112a5780..94f169a9b 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/management/server/activity" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/route" ) @@ -1045,13 +1046,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer1 := &Peer{ + peer1 := &nbpeer.Peer{ IP: peer1IP, ID: peer1ID, Key: peer1Key, Name: "test-host1@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host1@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -1070,13 +1071,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer2 := &Peer{ + peer2 := &nbpeer.Peer{ IP: peer2IP, ID: peer2ID, Key: peer2Key, Name: "test-host2@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host2@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -1095,13 +1096,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer3 := &Peer{ + peer3 := &nbpeer.Peer{ IP: peer3IP, ID: peer3ID, Key: peer3Key, Name: "test-host3@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host3@netbird.io", GoOS: "darwin", Kernel: "Darwin", @@ -1120,13 +1121,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer4 := &Peer{ + peer4 := &nbpeer.Peer{ IP: peer4IP, ID: peer4ID, Key: peer4Key, Name: "test-host4@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host4@netbird.io", GoOS: "linux", Kernel: "Linux", @@ -1145,13 +1146,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er return nil, err } - peer5 := &Peer{ + peer5 := &nbpeer.Peer{ IP: peer5IP, ID: peer5ID, Key: peer5Key, Name: "test-host4@netbird.io", UserID: userID, - Meta: PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "test-host4@netbird.io", GoOS: "linux", Kernel: "Linux", diff --git a/management/server/sqlite_store.go b/management/server/sqlite_store.go index bbb13f8c6..abc3f575a 100644 --- a/management/server/sqlite_store.go +++ b/management/server/sqlite_store.go @@ -7,15 +7,17 @@ import ( "sync" "time" - nbdns "github.com/netbirdio/netbird/dns" - "github.com/netbirdio/netbird/management/server/status" - "github.com/netbirdio/netbird/management/server/telemetry" - "github.com/netbirdio/netbird/route" log "github.com/sirupsen/logrus" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/clause" "gorm.io/gorm/logger" + + nbdns "github.com/netbirdio/netbird/dns" + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/management/server/status" + "github.com/netbirdio/netbird/management/server/telemetry" + "github.com/netbirdio/netbird/route" ) // SqliteStore represents an account storage backed by a Sqlite DB persisted to disk @@ -58,7 +60,7 @@ func NewSqliteStore(dataDir string, metrics telemetry.AppMetrics) (*SqliteStore, sql.SetMaxOpenConns(conns) // TODO: make it configurable err = db.AutoMigrate( - &SetupKey{}, &Peer{}, &User{}, &PersonalAccessToken{}, &Group{}, &Rule{}, + &SetupKey{}, &nbpeer.Peer{}, &User{}, &PersonalAccessToken{}, &Group{}, &Rule{}, &Account{}, &Policy{}, &PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{}, &installation{}, ) @@ -219,8 +221,8 @@ func (s *SqliteStore) GetInstallationID() string { return installation.InstallationIDValue } -func (s *SqliteStore) SavePeerStatus(accountID, peerID string, peerStatus PeerStatus) error { - var peer Peer +func (s *SqliteStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error { + var peer nbpeer.Peer result := s.db.First(&peer, "account_id = ? and id = ?", accountID, peerID) if result.Error != nil { @@ -347,7 +349,7 @@ func (s *SqliteStore) GetAccount(accountID string) (*Account, error) { } account.SetupKeysG = nil - account.Peers = make(map[string]*Peer, len(account.PeersG)) + account.Peers = make(map[string]*nbpeer.Peer, len(account.PeersG)) for _, peer := range account.PeersG { account.Peers[peer.ID] = peer.Copy() } @@ -405,7 +407,7 @@ func (s *SqliteStore) GetAccountByUser(userID string) (*Account, error) { } func (s *SqliteStore) GetAccountByPeerID(peerID string) (*Account, error) { - var peer Peer + var peer nbpeer.Peer result := s.db.Select("account_id").First(&peer, "id = ?", peerID) if result.Error != nil { return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") @@ -419,7 +421,7 @@ func (s *SqliteStore) GetAccountByPeerID(peerID string) (*Account, error) { } func (s *SqliteStore) GetAccountByPeerPubKey(peerKey string) (*Account, error) { - var peer Peer + var peer nbpeer.Peer result := s.db.Select("account_id").First(&peer, "key = ?", peerKey) if result.Error != nil { diff --git a/management/server/sqlite_store_test.go b/management/server/sqlite_store_test.go index 4a16e2525..88d330018 100644 --- a/management/server/sqlite_store_test.go +++ b/management/server/sqlite_store_test.go @@ -9,9 +9,11 @@ import ( "time" "github.com/google/uuid" - "github.com/netbirdio/netbird/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + nbpeer "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/util" ) func TestSqlite_NewStore(t *testing.T) { @@ -36,13 +38,13 @@ func TestSqlite_SaveAccount(t *testing.T) { account := newAccountWithId("account_id", "testuser", "") setupKey := GenerateDefaultSetupKey() account.SetupKeys[setupKey.Key] = setupKey - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } err := store.SaveAccount(account) @@ -51,13 +53,13 @@ func TestSqlite_SaveAccount(t *testing.T) { account2 := newAccountWithId("account_id2", "testuser2", "") setupKey = GenerateDefaultSetupKey() account2.SetupKeys[setupKey.Key] = setupKey - account2.Peers["testpeer2"] = &Peer{ + account2.Peers["testpeer2"] = &nbpeer.Peer{ Key: "peerkey2", SetupKey: "peerkeysetupkey2", IP: net.IP{127, 0, 0, 2}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name 2", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } err = store.SaveAccount(account2) @@ -109,19 +111,19 @@ func TestSqlite_SavePeerStatus(t *testing.T) { require.NoError(t, err) // save status of non-existing peer - newStatus := PeerStatus{Connected: true, LastSeen: time.Now().UTC()} + newStatus := nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()} err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus) assert.Error(t, err) // save new status of existing peer - account.Peers["testpeer"] = &Peer{ + account.Peers["testpeer"] = &nbpeer.Peer{ Key: "peerkey", ID: "testpeer", SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, } err = store.SaveAccount(account) @@ -216,13 +218,13 @@ func newAccount(store Store, id int) error { account := newAccountWithId(str, str+"-testuser", "example.com") setupKey := GenerateDefaultSetupKey() account.SetupKeys[setupKey.Key] = setupKey - account.Peers["p"+str] = &Peer{ + account.Peers["p"+str] = &nbpeer.Peer{ Key: "peerkey" + str, SetupKey: "peerkeysetupkey", IP: net.IP{127, 0, 0, 1}, - Meta: PeerSystemMeta{}, + Meta: nbpeer.PeerSystemMeta{}, Name: "peer name", - Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, + Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, } return store.SaveAccount(account) diff --git a/management/server/store.go b/management/server/store.go index 66b239f96..2733a55f6 100644 --- a/management/server/store.go +++ b/management/server/store.go @@ -8,6 +8,7 @@ import ( log "github.com/sirupsen/logrus" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/telemetry" ) @@ -30,7 +31,7 @@ type Store interface { AcquireAccountLock(accountID string) func() // AcquireGlobalLock should attempt to acquire a global lock and return a function that releases the lock AcquireGlobalLock() func() - SavePeerStatus(accountID, peerID string, status PeerStatus) error + SavePeerStatus(accountID, peerID string, status nbpeer.PeerStatus) error SaveUserLastLogin(accountID, userID string, lastLogin time.Time) error // Close should close the store persisting all unsaved data. Close() error diff --git a/management/server/user.go b/management/server/user.go index b96bf743c..821559f92 100644 --- a/management/server/user.go +++ b/management/server/user.go @@ -11,6 +11,7 @@ import ( "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -980,7 +981,7 @@ func (am *DefaultAccountManager) GetUsersFromAccount(accountID, userID string) ( } // expireAndUpdatePeers expires all peers of the given user and updates them in the account -func (am *DefaultAccountManager) expireAndUpdatePeers(account *Account, peers []*Peer) error { +func (am *DefaultAccountManager) expireAndUpdatePeers(account *Account, peers []*nbpeer.Peer) error { var peerIDs []string for _, peer := range peers { if peer.Status.LoginExpired {