diff --git a/go.mod b/go.mod index c6c8221e18e..12b7d9a95fb 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,8 @@ 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-20231027143200-a966bce7db88 + github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48 + github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 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 84b8816e9ba..27f48b31ee5 100644 --- a/go.sum +++ b/go.sum @@ -495,8 +495,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nadoo/ipset v0.5.0 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc= github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88 h1:zhe8qseauBuYOS910jpl5sv8Tb+36zxQPXrwYXqll0g= -github.com/netbirdio/management-integrations/integrations v0.0.0-20231027143200-a966bce7db88/go.mod h1:KSqjzHcqlodTWiuap5lRXxt5KT3vtYRoksL0KIrTK40= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48 h1:TEaiKhkxwIFA2AmIj/7x9X/6zlXjV68Vs6Wclew1oTs= +github.com/netbirdio/management-integrations/additions v0.0.0-20231204152258-2328ed53de48/go.mod h1:31FhBNvQ+riHEIu6LSTmqr8IeuSIsGfQffqV4LFmbwA= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97 h1:2VmrReIXt4W45l7O93kzoTq7Bpn3CDGKhBwnqih0eao= +github.com/netbirdio/management-integrations/integrations v0.0.0-20231128140355-566178608e97/go.mod h1:eRv50kd3bXd2y59HK3OY4RI8YUL0JEN290D5dqW4llY= 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 3de68412e0b..baaea2005d1 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -17,15 +17,18 @@ import ( "github.com/eko/gocache/v3/cache" cacheStore "github.com/eko/gocache/v3/store" + "github.com/netbirdio/management-integrations/additions" gocache "github.com/patrickmn/go-cache" "github.com/rs/xid" log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/base62" nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/server/account" "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 +71,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 +109,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 } @@ -159,17 +162,24 @@ type Settings struct { // JWTGroupsClaimName from which we extract groups name to add it to account groups JWTGroupsClaimName string + + // Extra is a dictionary of Account settings + Extra *account.ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"` } // 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 } // Account represents a unique account of the system @@ -185,8 +195,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:"-"` @@ -221,7 +231,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...) { @@ -345,10 +355,22 @@ func (a *Account) GetGroup(groupID string) *Group { // GetPeerNetworkMap returns a group by ID if exists, nil otherwise func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { + peer := a.Peers[peerID] + if peer == nil { + return &NetworkMap{ + Network: a.Network.Copy(), + } + } + validatedPeers := additions.ValidatePeers([]*nbpeer.Peer{peer}) + if len(validatedPeers) == 0 { + return &NetworkMap{ + Network: a.Network.Copy(), + } + } aclPeers, firewallRules := a.getPeerConnectionResources(peerID) // 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 { @@ -386,8 +408,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 { @@ -426,8 +448,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) @@ -437,8 +459,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) } @@ -452,7 +474,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 } @@ -481,7 +503,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 @@ -492,8 +514,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) @@ -585,7 +607,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() } @@ -662,7 +684,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] } @@ -872,6 +894,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string, return nil, err } + err = additions.ValidateExtraSettings(newSettings.Extra, account.Settings.Extra, account.Peers, userID, accountID, am.eventStore) + if err != nil { + return nil, err + } + user, err := account.FindUser(userID) if err != nil { return nil, err @@ -1698,7 +1725,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/account.go b/management/server/account/account.go new file mode 100644 index 00000000000..b8b71a6de9e --- /dev/null +++ b/management/server/account/account.go @@ -0,0 +1,13 @@ +package account + +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, + } +} diff --git a/management/server/account_test.go b/management/server/account_test.go index cfd2d0ca95a..5e204984e6b 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 { @@ -805,9 +961,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) @@ -873,9 +1029,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) @@ -940,7 +1096,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) @@ -948,9 +1104,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) @@ -1122,9 +1278,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) @@ -1263,8 +1419,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{ @@ -1307,7 +1463,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) @@ -1317,7 +1473,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) } @@ -1338,10 +1494,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, @@ -1468,9 +1624,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") @@ -1517,9 +1673,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") @@ -1558,9 +1714,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") @@ -1639,13 +1795,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, }, @@ -1657,11 +1813,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, @@ -1672,7 +1828,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, @@ -1684,7 +1840,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, @@ -1724,19 +1880,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, @@ -1750,7 +1906,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, @@ -1793,7 +1949,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 @@ -1804,7 +1960,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, @@ -1812,16 +1968,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, @@ -1835,16 +1991,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, @@ -1858,9 +2014,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, }, @@ -1868,7 +2024,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: true, }, @@ -1883,9 +2039,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, }, @@ -1894,7 +2050,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { UserID: userID, }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: true, }, @@ -1909,9 +2065,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, }, @@ -1919,7 +2075,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) { SetupKey: "key", }, "peer-2": { - Status: &PeerStatus{ + Status: &nbpeer.PeerStatus{ Connected: true, LoginExpired: false, }, @@ -1954,7 +2110,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"}, @@ -2002,7 +2158,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"}, @@ -2038,7 +2194,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/activity/codes.go b/management/server/activity/codes.go index 8114810488d..54a27e4cc17 100644 --- a/management/server/activity/codes.go +++ b/management/server/activity/codes.go @@ -120,6 +120,14 @@ const ( IntegrationUpdated // IntegrationDeleted indicates that the user deleted an integration IntegrationDeleted + // AccountPeerApprovalEnabled indicates that the user enabled peer approval for the account + AccountPeerApprovalEnabled + // AccountPeerApprovalDisabled indicates that the user disabled peer approval for the account + AccountPeerApprovalDisabled + // PeerApproved indicates that the peer has been approved + PeerApproved + // PeerApprovalRevoked indicates that the peer approval has been revoked + PeerApprovalRevoked // TransferredOwnerRole indicates that the user transferred the owner role of the account TransferredOwnerRole ) @@ -180,6 +188,10 @@ var activityMap = map[Activity]Code{ IntegrationCreated: {"Integration created", "integration.create"}, IntegrationUpdated: {"Integration updated", "integration.update"}, IntegrationDeleted: {"Integration deleted", "integration.delete"}, + AccountPeerApprovalEnabled: {"Account peer approval enabled", "account.setting.peer.approval.enable"}, + AccountPeerApprovalDisabled: {"Account peer approval disabled", "account.setting.peer.approval.disable"}, + PeerApproved: {"Peer approved", "peer.approve"}, + PeerApprovalRevoked: {"Peer approval revoked", "peer.approval.revoke"}, TransferredOwnerRole: {"Transferred owner role", "transferred.owner.role"}, } diff --git a/management/server/dns.go b/management/server/dns.go index 3a9aef1922f..820a5431f4b 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 62408e4b37d..bff0c987845 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 0e76e58acc9..9d70a05d148 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 d271e5fcafe..3e36335e3a5 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 97fdc9a92e0..818d9a4db0c 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() { @@ -606,7 +607,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 f5baf985852..ef97993786e 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 @@ -179,13 +180,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", @@ -600,19 +601,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 f32f6347a00..8d3d82661d0 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/accounts_handler.go b/management/server/http/accounts_handler.go index 362c4b2a8f6..c2751abd478 100644 --- a/management/server/http/accounts_handler.go +++ b/management/server/http/accounts_handler.go @@ -8,6 +8,7 @@ import ( "github.com/gorilla/mux" "github.com/netbirdio/netbird/management/server" + "github.com/netbirdio/netbird/management/server/account" "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/jwtclaims" @@ -77,6 +78,10 @@ func (h *AccountsHandler) UpdateAccount(w http.ResponseWriter, r *http.Request) PeerLoginExpiration: time.Duration(float64(time.Second.Nanoseconds()) * float64(req.Settings.PeerLoginExpiration)), } + if req.Settings.Extra != nil { + settings.Extra = &account.ExtraSettings{PeerApprovalEnabled: *req.Settings.Extra.PeerApprovalEnabled} + } + if req.Settings.JwtGroupsEnabled != nil { settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled } @@ -123,14 +128,20 @@ func (h *AccountsHandler) DeleteAccount(w http.ResponseWriter, r *http.Request) } func toAccountResponse(account *server.Account) *api.Account { + settings := api.AccountSettings{ + PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()), + PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled, + GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled, + JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled, + JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName, + } + + if account.Settings.Extra != nil { + settings.Extra = &api.AccountExtraSettings{PeerApprovalEnabled: &account.Settings.Extra.PeerApprovalEnabled} + } + return &api.Account{ - Id: account.Id, - Settings: api.AccountSettings{ - PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()), - PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled, - GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled, - JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled, - JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName, - }, + Id: account.Id, + Settings: settings, } } diff --git a/management/server/http/api/openapi.yml b/management/server/http/api/openapi.yml index 0ed8261480f..2b8d614f639 100644 --- a/management/server/http/api/openapi.yml +++ b/management/server/http/api/openapi.yml @@ -66,9 +66,18 @@ components: description: Name of the claim from which we extract groups names to add it to account groups. type: string example: "roles" + extra: + $ref: '#/components/schemas/AccountExtraSettings' required: - peer_login_expiration_enabled - peer_login_expiration + AccountExtraSettings: + type: object + properties: + peer_approval_enabled: + description: (Cloud only) Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + type: boolean + example: true AccountRequest: type: object properties: @@ -213,6 +222,10 @@ components: login_expiration_enabled: type: boolean example: false + approval_required: + description: (Cloud only) Indicates whether peer needs approval + type: boolean + example: true required: - name - ssh_enabled @@ -281,6 +294,10 @@ components: type: string format: date-time example: 2023-05-05T09:00:35.477782Z + approval_required: + description: (Cloud only) Indicates whether peer needs approval + type: boolean + example: true required: - ip - connected diff --git a/management/server/http/api/types.gen.go b/management/server/http/api/types.gen.go index ea70b7f3a84..8e41f567249 100644 --- a/management/server/http/api/types.gen.go +++ b/management/server/http/api/types.gen.go @@ -142,6 +142,12 @@ type Account struct { Settings AccountSettings `json:"settings"` } +// AccountExtraSettings defines model for AccountExtraSettings. +type AccountExtraSettings struct { + // PeerApprovalEnabled (Cloud only) Enables or disables peer approval globally. If enabled, all peers added will be in pending state until approved by an admin. + PeerApprovalEnabled *bool `json:"peer_approval_enabled,omitempty"` +} + // AccountRequest defines model for AccountRequest. type AccountRequest struct { Settings AccountSettings `json:"settings"` @@ -149,6 +155,8 @@ type AccountRequest struct { // AccountSettings defines model for AccountSettings. type AccountSettings struct { + Extra *AccountExtraSettings `json:"extra,omitempty"` + // GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"` @@ -323,6 +331,9 @@ type Peer struct { // AccessiblePeers List of accessible peers AccessiblePeers []AccessiblePeer `json:"accessible_peers"` + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + // Connected Peer to Management connection status Connected bool `json:"connected"` @@ -374,6 +385,9 @@ type Peer struct { // PeerBase defines model for PeerBase. type PeerBase struct { + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + // Connected Peer to Management connection status Connected bool `json:"connected"` @@ -428,6 +442,9 @@ type PeerBatch struct { // AccessiblePeersCount Number of accessible peers AccessiblePeersCount int `json:"accessible_peers_count"` + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` + // Connected Peer to Management connection status Connected bool `json:"connected"` @@ -488,6 +505,8 @@ type PeerMinimum struct { // PeerRequest defines model for PeerRequest. type PeerRequest struct { + // ApprovalRequired (Cloud only) Indicates whether peer needs approval + ApprovalRequired *bool `json:"approval_required,omitempty"` LoginExpirationEnabled bool `json:"login_expiration_enabled"` Name string `json:"name"` SshEnabled bool `json:"ssh_enabled"` diff --git a/management/server/http/groups_handler_test.go b/management/server/http/groups_handler_test.go index 5f40be7a904..5b47b120861 100644 --- a/management/server/http/groups_handler_test.go +++ b/management/server/http/groups_handler_test.go @@ -19,10 +19,11 @@ import ( "github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/mock_server" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) -var TestPeers = map[string]*server.Peer{ +var TestPeers = map[string]*nbpeer.Peer{ "A": {Key: "A", ID: "peer-A-ID", IP: net.ParseIP("100.100.100.100")}, "B": {Key: "B", ID: "peer-B-ID", IP: net.ParseIP("200.200.200.200")}, } diff --git a/management/server/http/peers_handler.go b/management/server/http/peers_handler.go index 527e18f9d93..c586bd6c8f2 100644 --- a/management/server/http/peers_handler.go +++ b/management/server/http/peers_handler.go @@ -11,6 +11,7 @@ import ( "github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" ) @@ -31,7 +32,7 @@ func NewPeersHandler(accountManager server.AccountManager, authCfg AuthCfg) *Pee } } -func (h *PeersHandler) checkPeerStatus(peer *server.Peer) (*server.Peer, error) { +func (h *PeersHandler) checkPeerStatus(peer *nbpeer.Peer) (*nbpeer.Peer, error) { peerToReturn := peer.Copy() if peer.Status.Connected { statuses, err := h.accountManager.GetAllConnectedPeers() @@ -79,8 +80,13 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe return } - update := &server.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name, + update := &nbpeer.Peer{ID: peerID, SSHEnabled: req.SshEnabled, Name: req.Name, LoginExpirationEnabled: req.LoginExpirationEnabled} + + if req.ApprovalRequired != nil { + update.Status = &nbpeer.PeerStatus{RequiresApproval: *req.ApprovalRequired} + } + peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update) if err != nil { util.WriteError(err, w) @@ -229,7 +235,7 @@ func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMin return groupsInfo } -func toSinglePeerResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer { +func toSinglePeerResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeer []api.AccessiblePeer) *api.Peer { return &api.Peer{ Id: peer.ID, Name: peer.Name, @@ -248,10 +254,11 @@ func toSinglePeerResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsD LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, AccessiblePeers: accessiblePeer, + ApprovalRequired: &peer.Status.RequiresApproval, } } -func toPeerListItemResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch { +func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dnsDomain string, accessiblePeersCount int) *api.PeerBatch { return &api.PeerBatch{ Id: peer.ID, Name: peer.Name, @@ -270,10 +277,11 @@ func toPeerListItemResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dn LastLogin: peer.LastLogin, LoginExpired: peer.Status.LoginExpired, AccessiblePeersCount: accessiblePeersCount, + ApprovalRequired: &peer.Status.RequiresApproval, } } -func fqdn(peer *server.Peer, dnsDomain string) string { +func fqdn(peer *nbpeer.Peer, dnsDomain string) string { fqdn := peer.FQDN(dnsDomain) if fqdn == "" { return peer.DNSLabel diff --git a/management/server/http/peers_handler_test.go b/management/server/http/peers_handler_test.go index 673cd90c578..d13db447b6b 100644 --- a/management/server/http/peers_handler_test.go +++ b/management/server/http/peers_handler_test.go @@ -13,6 +13,7 @@ import ( "github.com/gorilla/mux" "github.com/netbirdio/netbird/management/server/http/api" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/jwtclaims" @@ -25,11 +26,11 @@ import ( const testPeerID = "test_peer" const noUpdateChannelTestPeerID = "no-update-channel" -func initTestMetaData(peers ...*server.Peer) *PeersHandler { +func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler { return &PeersHandler{ accountManager: &mock_server.MockAccountManager{ - UpdatePeerFunc: func(accountID, userID string, update *server.Peer) (*server.Peer, error) { - var p *server.Peer + UpdatePeerFunc: func(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) { + var p *nbpeer.Peer for _, peer := range peers { if update.ID == peer.ID { p = peer.Copy() @@ -41,8 +42,8 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { p.Name = update.Name return p, nil }, - GetPeerFunc: func(accountID, peerID, userID string) (*server.Peer, error) { - var p *server.Peer + GetPeerFunc: func(accountID, peerID, userID string) (*nbpeer.Peer, error) { + var p *nbpeer.Peer for _, peer := range peers { if peerID == peer.ID { p = peer.Copy() @@ -51,7 +52,7 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { } return p, nil }, - GetPeersFunc: func(accountID, userID string) ([]*server.Peer, error) { + GetPeersFunc: func(accountID, userID string) ([]*nbpeer.Peer, error) { return peers, nil }, GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) { @@ -59,8 +60,9 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { return &server.Account{ Id: claims.AccountId, Domain: "hotmail.com", - Peers: map[string]*server.Peer{ + Peers: map[string]*nbpeer.Peer{ peers[0].ID: peers[0], + peers[1].ID: peers[1], }, Users: map[string]*server.User{ "test_user": user, @@ -107,15 +109,15 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler { // Use the metadata generated by initTestMetaData() to check for values func TestGetPeers(t *testing.T) { - peer := &server.Peer{ + peer := &nbpeer.Peer{ ID: testPeerID, Key: "key", SetupKey: "setupkey", IP: net.ParseIP("100.64.0.1"), - Status: &server.PeerStatus{Connected: true}, + Status: &nbpeer.PeerStatus{Connected: true}, Name: "PeerName", LoginExpirationEnabled: false, - Meta: server.PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ Hostname: "hostname", GoOS: "GoOS", Kernel: "kernel", @@ -144,7 +146,7 @@ func TestGetPeers(t *testing.T) { requestPath string requestBody io.Reader expectedArray bool - expectedPeer *server.Peer + expectedPeer *nbpeer.Peer }{ { name: "GetPeersMetaData", diff --git a/management/server/http/routes_handler_test.go b/management/server/http/routes_handler_test.go index 0bb4587e4f4..c02292f2a94 100644 --- a/management/server/http/routes_handler_test.go +++ b/management/server/http/routes_handler_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/netbirdio/netbird/management/server/http/api" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" @@ -55,12 +56,12 @@ var baseExistingRoute = &route.Route{ var testingAccount = &server.Account{ Id: testAccountID, Domain: "hotmail.com", - Peers: map[string]*server.Peer{ + Peers: map[string]*nbpeer.Peer{ existingPeerID: { Key: existingPeerKey, IP: netip.MustParseAddr(existingPeerIP1).AsSlice(), ID: existingPeerID, - Meta: server.PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ GoOS: "linux", }, }, @@ -68,7 +69,7 @@ var testingAccount = &server.Account{ Key: nonLinuxExistingPeerID, IP: netip.MustParseAddr(existingPeerIP2).AsSlice(), ID: nonLinuxExistingPeerID, - Meta: server.PeerSystemMeta{ + Meta: nbpeer.PeerSystemMeta{ GoOS: "darwin", }, }, diff --git a/management/server/metrics/selfhosted_test.go b/management/server/metrics/selfhosted_test.go index 7717ff4094a..a577f8309d7 100644 --- a/management/server/metrics/selfhosted_test.go +++ b/management/server/metrics/selfhosted_test.go @@ -5,6 +5,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/route" ) @@ -37,12 +38,12 @@ func (mockDatasource) GetAllAccounts() []*server.Account { NameServerGroups: map[string]*nbdns.NameServerGroup{ "1": {}, }, - Peers: map[string]*server.Peer{ + Peers: map[string]*nbpeer.Peer{ "1": { ID: "1", UserID: "test", SSHEnabled: true, - Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, + Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, }, }, Policies: []*server.Policy{ @@ -101,12 +102,12 @@ func (mockDatasource) GetAllAccounts() []*server.Account { NameServerGroups: map[string]*nbdns.NameServerGroup{ "1": {}, }, - Peers: map[string]*server.Peer{ + Peers: map[string]*nbpeer.Peer{ "1": { ID: "1", UserID: "test", SSHEnabled: true, - Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, + Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, }, }, Policies: []*server.Policy{ diff --git a/management/server/mock_server/account_mock.go b/management/server/mock_server/account_mock.go index 84b23a4f257..b200af0c3a7 100644 --- a/management/server/mock_server/account_mock.go +++ b/management/server/mock_server/account_mock.go @@ -10,6 +10,7 @@ import ( "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/jwtclaims" + nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/route" ) @@ -21,12 +22,12 @@ type MockAccountManager struct { GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error) GetUserFunc func(claims jwtclaims.AuthorizationClaims) (*server.User, error) ListUsersFunc func(accountID string) ([]*server.User, error) - GetPeersFunc func(accountID, userID string) ([]*server.Peer, error) + GetPeersFunc func(accountID, userID string) ([]*nbpeer.Peer, error) MarkPeerConnectedFunc func(peerKey string, connected bool) error DeletePeerFunc func(accountID, peerKey, userID string) error GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error) GetPeerNetworkFunc func(peerKey string) (*server.Network, error) - AddPeerFunc func(setupKey string, userId string, peer *server.Peer) (*server.Peer, *server.NetworkMap, error) + AddPeerFunc func(setupKey string, userId string, peer *nbpeer.Peer) (*nbpeer.Peer, *server.NetworkMap, error) GetGroupFunc func(accountID, groupID string) (*server.Group, error) SaveGroupFunc func(accountID, userID string, group *server.Group) error DeleteGroupFunc func(accountID, userId, groupID string) error @@ -44,9 +45,9 @@ type MockAccountManager struct { GetUsersFromAccountFunc func(accountID, userID string) ([]*server.UserInfo, error) GetAccountFromPATFunc func(pat string) (*server.Account, *server.User, *server.PersonalAccessToken, error) MarkPATUsedFunc func(pat string) error - UpdatePeerMetaFunc func(peerID string, meta server.PeerSystemMeta) error + UpdatePeerMetaFunc func(peerID string, meta nbpeer.PeerSystemMeta) error UpdatePeerSSHKeyFunc func(peerID string, sshKey string) error - UpdatePeerFunc func(accountID, userID string, peer *server.Peer) (*server.Peer, error) + UpdatePeerFunc func(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) CreateRouteFunc func(accountID, prefix, peer string, peerGroups []string, description, netID string, masquerade bool, metric int, groups []string, enabled bool, userID string) (*route.Route, error) GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error) SaveRouteFunc func(accountID, userID string, route *route.Route) error @@ -74,10 +75,10 @@ type MockAccountManager struct { GetEventsFunc func(accountID, userID string) ([]*activity.Event, error) GetDNSSettingsFunc func(accountID, userID string) (*server.DNSSettings, error) SaveDNSSettingsFunc func(accountID, userID string, dnsSettingsToSave *server.DNSSettings) error - GetPeerFunc func(accountID, peerID, userID string) (*server.Peer, error) + GetPeerFunc func(accountID, peerID, userID string) (*nbpeer.Peer, error) UpdateAccountSettingsFunc func(accountID, userID string, newSettings *server.Settings) (*server.Account, error) - LoginPeerFunc func(login server.PeerLogin) (*server.Peer, *server.NetworkMap, error) - SyncPeerFunc func(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error) + LoginPeerFunc func(login server.PeerLogin) (*nbpeer.Peer, *server.NetworkMap, error) + SyncPeerFunc func(sync server.PeerSync) (*nbpeer.Peer, *server.NetworkMap, error) InviteUserFunc func(accountID string, initiatorUserID string, targetUserEmail string) error GetAllConnectedPeersFunc func() (map[string]struct{}, error) GetExternalCacheManagerFunc func() server.ExternalCacheManager @@ -226,8 +227,8 @@ func (am *MockAccountManager) GetPeerNetwork(peerKey string) (*server.Network, e func (am *MockAccountManager) AddPeer( setupKey string, userId string, - peer *server.Peer, -) (*server.Peer, *server.NetworkMap, error) { + peer *nbpeer.Peer, +) (*nbpeer.Peer, *server.NetworkMap, error) { if am.AddPeerFunc != nil { return am.AddPeerFunc(setupKey, userId, peer) } @@ -347,7 +348,7 @@ func (am *MockAccountManager) ListPolicies(accountID, userID string) ([]*server. } // UpdatePeerMeta mock implementation of UpdatePeerMeta from server.AccountManager interface -func (am *MockAccountManager) UpdatePeerMeta(peerID string, meta server.PeerSystemMeta) error { +func (am *MockAccountManager) UpdatePeerMeta(peerID string, meta nbpeer.PeerSystemMeta) error { if am.UpdatePeerMetaFunc != nil { return am.UpdatePeerMetaFunc(peerID, meta) } @@ -378,7 +379,7 @@ func (am *MockAccountManager) UpdatePeerSSHKey(peerID string, sshKey string) err } // UpdatePeer mocks UpdatePeerFunc function of the account manager -func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *server.Peer) (*server.Peer, error) { +func (am *MockAccountManager) UpdatePeer(accountID, userID string, peer *nbpeer.Peer) (*nbpeer.Peer, error) { if am.UpdatePeerFunc != nil { return am.UpdatePeerFunc(accountID, userID, peer) } @@ -542,7 +543,7 @@ func (am *MockAccountManager) GetAccountFromToken(claims jwtclaims.Authorization } // GetPeers mocks GetPeers of the AccountManager interface -func (am *MockAccountManager) GetPeers(accountID, userID string) ([]*server.Peer, error) { +func (am *MockAccountManager) GetPeers(accountID, userID string) ([]*nbpeer.Peer, error) { if am.GetAccountFromTokenFunc != nil { return am.GetPeersFunc(accountID, userID) } @@ -582,7 +583,7 @@ func (am *MockAccountManager) SaveDNSSettings(accountID string, userID string, d } // GetPeer mocks GetPeer of the AccountManager interface -func (am *MockAccountManager) GetPeer(accountID, peerID, userID string) (*server.Peer, error) { +func (am *MockAccountManager) GetPeer(accountID, peerID, userID string) (*nbpeer.Peer, error) { if am.GetPeerFunc != nil { return am.GetPeerFunc(accountID, peerID, userID) } @@ -598,7 +599,7 @@ func (am *MockAccountManager) UpdateAccountSettings(accountID, userID string, ne } // LoginPeer mocks LoginPeer of the AccountManager interface -func (am *MockAccountManager) LoginPeer(login server.PeerLogin) (*server.Peer, *server.NetworkMap, error) { +func (am *MockAccountManager) LoginPeer(login server.PeerLogin) (*nbpeer.Peer, *server.NetworkMap, error) { if am.LoginPeerFunc != nil { return am.LoginPeerFunc(login) } @@ -606,7 +607,7 @@ func (am *MockAccountManager) LoginPeer(login server.PeerLogin) (*server.Peer, * } // SyncPeer mocks SyncPeer of the AccountManager interface -func (am *MockAccountManager) SyncPeer(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error) { +func (am *MockAccountManager) SyncPeer(sync server.PeerSync) (*nbpeer.Peer, *server.NetworkMap, error) { if am.SyncPeerFunc != nil { return am.SyncPeerFunc(sync) } diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index d1f4cd01543..791dc567762 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 daa67f2dcc7..ffe098c964c 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 dcfe2641002..18d3f0e0183 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -2,13 +2,14 @@ package server import ( "fmt" - "net" "strings" "time" + "github.com/netbirdio/management-integrations/additions" "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" @@ -16,38 +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 -} - // 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 @@ -61,146 +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, - } -} - // 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 @@ -211,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.HasAdminPower() && user.Id != peer.UserID { // only display peers that belong to the current user if the current user is not an admin @@ -231,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) } @@ -290,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() @@ -304,6 +143,11 @@ func (am *DefaultAccountManager) UpdatePeer(accountID, userID string, update *Pe return nil, status.Errorf(status.NotFound, "peer %s not found", update.ID) } + update, err = additions.ValidatePeersUpdateRequest(update, peer, userID, accountID, am.eventStore, am.GetDNSDomain()) + if err != nil { + return nil, err + } + if peer.SSHEnabled != update.SSHEnabled { peer.SSHEnabled = update.SSHEnabled event := activity.PeerSSHEnabled @@ -364,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) @@ -456,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") @@ -547,7 +391,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, @@ -556,7 +400,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(), @@ -564,6 +408,10 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (* Ephemeral: ephemeral, } + if account.Settings.Extra != nil { + newPeer = additions.PreparePeer(newPeer, account.Settings.Extra) + } + // add peer to 'All' group group, err := account.GetGroupAll() if err != nil { @@ -614,7 +462,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 { @@ -651,14 +499,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, @@ -728,7 +576,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 { @@ -741,7 +589,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") @@ -753,7 +601,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 { @@ -763,21 +611,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 @@ -848,7 +687,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() @@ -891,7 +730,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 00000000000..a4e4cc3aa36 --- /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 9d5a8bfb99d..ee84ea47dab 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 428d28d9c38..0eb2fb5385c 100644 --- a/management/server/policy.go +++ b/management/server/policy.go @@ -5,10 +5,12 @@ import ( "strconv" "strings" + "github.com/netbirdio/management-integrations/additions" log "github.com/sirupsen/logrus" "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 +207,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 { @@ -219,6 +221,8 @@ func (a *Account) getPeerConnectionResources(peerID string) ([]*Peer, []*Firewal sourcePeers, peerInSources := getAllPeersFromGroups(a, rule.Sources, peerID) destinationPeers, peerInDestinations := getAllPeersFromGroups(a, rule.Destinations, peerID) + sourcePeers = additions.ValidatePeers(sourcePeers) + destinationPeers = additions.ValidatePeers(destinationPeers) if rule.Bidirectional { if peerInSources { @@ -247,11 +251,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 +263,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 +303,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 +482,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 971bd27d957..3ed08f4e6d7 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 5112a578042..94f169a9bf0 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 0cd0abe4a47..1bc2db3f17f 100644 --- a/management/server/sqlite_store.go +++ b/management/server/sqlite_store.go @@ -14,6 +14,8 @@ import ( "gorm.io/gorm/logger" nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/server/account" + 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" @@ -59,9 +61,9 @@ 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{}, + &installation{}, &account.ExtraSettings{}, ) if err != nil { return nil, err @@ -251,8 +253,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 { @@ -379,7 +381,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() } @@ -437,7 +439,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") @@ -451,7 +453,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 eef469f40d7..e493368fafe 100644 --- a/management/server/sqlite_store_test.go +++ b/management/server/sqlite_store_test.go @@ -12,6 +12,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" ) @@ -37,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) @@ -52,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) @@ -116,13 +117,13 @@ func TestSqlite_DeleteAccount(t *testing.T) { account := newAccountWithId("account_id", testUserID, "") 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()}, } account.Users[testUserID] = user @@ -184,19 +185,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) @@ -291,13 +292,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 25511539a28..a482ca9470c 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" ) @@ -31,7 +32,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 4bd46f617ad..a84765cb176 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" ) @@ -1034,7 +1035,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 {