Merge pull request #1341 from netbirdio/feature/peer-approval

Add peer and settings validation
This commit is contained in:
pascal-fischer 2023-12-05 12:11:14 +01:00 committed by GitHub
commit 3f8b500f0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 739 additions and 423 deletions

3
go.mod
View File

@ -51,7 +51,8 @@ require (
github.com/miekg/dns v1.1.43 github.com/miekg/dns v1.1.43
github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/nadoo/ipset v0.5.0 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/okta/okta-sdk-golang/v2 v2.18.0
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pion/logging v0.2.2 github.com/pion/logging v0.2.2

6
go.sum
View File

@ -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/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 h1:5GJUAuZ7ITQQQGne5J96AmFjRtI8Avlbk6CabzYWVUc=
github.com/nadoo/ipset v0.5.0/go.mod h1:rYF5DQLRGGoQ8ZSWeK+6eX5amAuPqwFkWjhQlEITGJQ= 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/additions v0.0.0-20231204152258-2328ed53de48 h1:TEaiKhkxwIFA2AmIj/7x9X/6zlXjV68Vs6Wclew1oTs=
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/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 h1:hirFRfx3grVA/9eEyjME5/z3nxdJlN9kfQpvWWPk32g=
github.com/netbirdio/service v0.0.0-20230215170314-b923b89432b0/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= 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= github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949 h1:xbWM9BU6mwZZLHxEjxIX/V8Hv3HurQt4mReIE4mY4DM=

View File

@ -17,15 +17,18 @@ import (
"github.com/eko/gocache/v3/cache" "github.com/eko/gocache/v3/cache"
cacheStore "github.com/eko/gocache/v3/store" cacheStore "github.com/eko/gocache/v3/store"
"github.com/netbirdio/management-integrations/additions"
gocache "github.com/patrickmn/go-cache" gocache "github.com/patrickmn/go-cache"
"github.com/rs/xid" "github.com/rs/xid"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/base62" "github.com/netbirdio/netbird/base62"
nbdns "github.com/netbirdio/netbird/dns" 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/activity"
"github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/jwtclaims" "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/management/server/status"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@ -68,13 +71,13 @@ type AccountManager interface {
MarkPATUsed(tokenID string) error MarkPATUsed(tokenID string) error
GetUser(claims jwtclaims.AuthorizationClaims) (*User, error) GetUser(claims jwtclaims.AuthorizationClaims) (*User, error)
ListUsers(accountID string) ([]*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 MarkPeerConnected(peerKey string, connected bool) error
DeletePeer(accountID, peerID, userID string) 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) GetNetworkMap(peerID string) (*NetworkMap, error)
GetPeerNetwork(peerID string) (*Network, 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) CreatePAT(accountID string, initiatorUserID string, targetUserID string, tokenName string, expiresIn int) (*PersonalAccessTokenGenerated, error)
DeletePAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) error DeletePAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) error
GetPAT(accountID string, initiatorUserID string, targetUserID string, tokenID string) (*PersonalAccessToken, 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) GetEvents(accountID, userID string) ([]*activity.Event, error)
GetDNSSettings(accountID string, userID string) (*DNSSettings, error) GetDNSSettings(accountID string, userID string) (*DNSSettings, error)
SaveDNSSettings(accountID string, userID string, dnsSettingsToSave *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) UpdateAccountSettings(accountID, userID string, newSettings *Settings) (*Account, error)
LoginPeer(login PeerLogin) (*Peer, *NetworkMap, error) // used by peer gRPC API LoginPeer(login PeerLogin) (*nbpeer.Peer, *NetworkMap, error) // used by peer gRPC API
SyncPeer(sync PeerSync) (*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) GetAllConnectedPeers() (map[string]struct{}, error)
GetExternalCacheManager() ExternalCacheManager GetExternalCacheManager() ExternalCacheManager
} }
@ -159,17 +162,24 @@ type Settings struct {
// JWTGroupsClaimName from which we extract groups name to add it to account groups // JWTGroupsClaimName from which we extract groups name to add it to account groups
JWTGroupsClaimName string JWTGroupsClaimName string
// Extra is a dictionary of Account settings
Extra *account.ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"`
} }
// Copy copies the Settings struct // Copy copies the Settings struct
func (s *Settings) Copy() *Settings { func (s *Settings) Copy() *Settings {
return &Settings{ settings := &Settings{
PeerLoginExpirationEnabled: s.PeerLoginExpirationEnabled, PeerLoginExpirationEnabled: s.PeerLoginExpirationEnabled,
PeerLoginExpiration: s.PeerLoginExpiration, PeerLoginExpiration: s.PeerLoginExpiration,
JWTGroupsEnabled: s.JWTGroupsEnabled, JWTGroupsEnabled: s.JWTGroupsEnabled,
JWTGroupsClaimName: s.JWTGroupsClaimName, JWTGroupsClaimName: s.JWTGroupsClaimName,
GroupsPropagationEnabled: s.GroupsPropagationEnabled, GroupsPropagationEnabled: s.GroupsPropagationEnabled,
} }
if s.Extra != nil {
settings.Extra = s.Extra.Copy()
}
return settings
} }
// Account represents a unique account of the system // Account represents a unique account of the system
@ -185,8 +195,8 @@ type Account struct {
SetupKeys map[string]*SetupKey `gorm:"-"` SetupKeys map[string]*SetupKey `gorm:"-"`
SetupKeysG []SetupKey `json:"-" gorm:"foreignKey:AccountID;references:id"` SetupKeysG []SetupKey `json:"-" gorm:"foreignKey:AccountID;references:id"`
Network *Network `gorm:"embedded;embeddedPrefix:network_"` Network *Network `gorm:"embedded;embeddedPrefix:network_"`
Peers map[string]*Peer `gorm:"-"` Peers map[string]*nbpeer.Peer `gorm:"-"`
PeersG []Peer `json:"-" gorm:"foreignKey:AccountID;references:id"` PeersG []nbpeer.Peer `json:"-" gorm:"foreignKey:AccountID;references:id"`
Users map[string]*User `gorm:"-"` Users map[string]*User `gorm:"-"`
UsersG []User `json:"-" gorm:"foreignKey:AccountID;references:id"` UsersG []User `json:"-" gorm:"foreignKey:AccountID;references:id"`
Groups map[string]*Group `gorm:"-"` Groups map[string]*Group `gorm:"-"`
@ -221,7 +231,7 @@ type UserInfo struct {
// getRoutesToSync returns the enabled routes for the peer ID and the routes // 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. // 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. // 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) routes, peerDisabledRoutes := a.getRoutingPeerRoutes(peerID)
peerRoutesMembership := make(lookupMap) peerRoutesMembership := make(lookupMap)
for _, r := range append(routes, peerDisabledRoutes...) { 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 // GetPeerNetworkMap returns a group by ID if exists, nil otherwise
func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap { 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) aclPeers, firewallRules := a.getPeerConnectionResources(peerID)
// exclude expired peers // exclude expired peers
var peersToConnect []*Peer var peersToConnect []*nbpeer.Peer
var expiredPeers []*Peer var expiredPeers []*nbpeer.Peer
for _, p := range aclPeers { for _, p := range aclPeers {
expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration) expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration)
if a.Settings.PeerLoginExpirationEnabled && expired { if a.Settings.PeerLoginExpirationEnabled && expired {
@ -386,8 +408,8 @@ func (a *Account) GetPeerNetworkMap(peerID, dnsDomain string) *NetworkMap {
} }
// GetExpiredPeers returns peers that have been expired // GetExpiredPeers returns peers that have been expired
func (a *Account) GetExpiredPeers() []*Peer { func (a *Account) GetExpiredPeers() []*nbpeer.Peer {
var peers []*Peer var peers []*nbpeer.Peer
for _, peer := range a.GetPeersWithExpiration() { for _, peer := range a.GetPeersWithExpiration() {
expired, _ := peer.LoginExpired(a.Settings.PeerLoginExpiration) expired, _ := peer.LoginExpired(a.Settings.PeerLoginExpiration)
if expired { 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 // 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 { func (a *Account) GetPeersWithExpiration() []*nbpeer.Peer {
peers := make([]*Peer, 0) peers := make([]*nbpeer.Peer, 0)
for _, peer := range a.Peers { for _, peer := range a.Peers {
if peer.LoginExpirationEnabled && peer.AddedWithSSOLogin() { if peer.LoginExpirationEnabled && peer.AddedWithSSOLogin() {
peers = append(peers, peer) peers = append(peers, peer)
@ -437,8 +459,8 @@ func (a *Account) GetPeersWithExpiration() []*Peer {
} }
// GetPeers returns a list of all Account peers // GetPeers returns a list of all Account peers
func (a *Account) GetPeers() []*Peer { func (a *Account) GetPeers() []*nbpeer.Peer {
var peers []*Peer var peers []*nbpeer.Peer
for _, peer := range a.Peers { for _, peer := range a.Peers {
peers = append(peers, peer) peers = append(peers, peer)
} }
@ -452,7 +474,7 @@ func (a *Account) UpdateSettings(update *Settings) *Account {
} }
// UpdatePeer saves new or replaces existing peer // 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 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. // 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. // 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 { for _, peer := range a.Peers {
if peer.Key == peerPubKey { if peer.Key == peerPubKey {
return peer.Copy(), nil 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) // FindUserPeers returns a list of peers that user owns (created)
func (a *Account) FindUserPeers(userID string) ([]*Peer, error) { func (a *Account) FindUserPeers(userID string) ([]*nbpeer.Peer, error) {
peers := make([]*Peer, 0) peers := make([]*nbpeer.Peer, 0)
for _, peer := range a.Peers { for _, peer := range a.Peers {
if peer.UserID == userID { if peer.UserID == userID {
peers = append(peers, peer) peers = append(peers, peer)
@ -585,7 +607,7 @@ func (a *Account) getPeerDNSLabels() lookupMap {
} }
func (a *Account) Copy() *Account { func (a *Account) Copy() *Account {
peers := map[string]*Peer{} peers := map[string]*nbpeer.Peer{}
for id, peer := range a.Peers { for id, peer := range a.Peers {
peers[id] = peer.Copy() peers[id] = peer.Copy()
} }
@ -662,7 +684,7 @@ func (a *Account) GetGroupAll() (*Group, error) {
} }
// GetPeer looks up a Peer by ID // 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] return a.Peers[peerID]
} }
@ -872,6 +894,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(accountID, userID string,
return nil, err 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) user, err := account.FindUser(userID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1698,7 +1725,7 @@ func newAccountWithId(accountID, userID, domain string) *Account {
log.Debugf("creating new account") log.Debugf("creating new account")
network := NewNetwork() network := NewNetwork()
peers := make(map[string]*Peer) peers := make(map[string]*nbpeer.Peer)
users := make(map[string]*User) users := make(map[string]*User)
routes := make(map[string]*route.Route) routes := make(map[string]*route.Route)
setupKeys := map[string]*SetupKey{} setupKeys := map[string]*SetupKey{}

View File

@ -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,
}
}

View File

@ -15,6 +15,7 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -26,10 +27,10 @@ import (
func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) { func verifyCanAddPeerToAccount(t *testing.T, manager AccountManager, account *Account, userID string) {
t.Helper() t.Helper()
peer := &Peer{ peer := &nbpeer.Peer{
Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=", Key: "BhRPtynAAYRDy08+q4HTMsos8fs4plTP4NOSh7C1ry8=",
Name: "test-host@netbird.io", Name: "test-host@netbird.io",
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host@netbird.io", Hostname: "test-host@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",
@ -110,13 +111,14 @@ func verifyNewAccountHasDefaultFields(t *testing.T, account *Account, createdBy
func TestAccount_GetPeerNetworkMap(t *testing.T) { func TestAccount_GetPeerNetworkMap(t *testing.T) {
peerID1 := "peer-1" peerID1 := "peer-1"
peerID2 := "peer-2" peerID2 := "peer-2"
// peerID3 := "peer-3"
tt := []struct { tt := []struct {
name string name string
accountSettings Settings accountSettings Settings
peerID string peerID string
expectedPeers []string expectedPeers []string
expectedOfflinePeers []string expectedOfflinePeers []string
peers map[string]*Peer peers map[string]*nbpeer.Peer
}{ }{
{ {
name: "Should return ALL peers when global peer login expiration disabled", name: "Should return ALL peers when global peer login expiration disabled",
@ -124,14 +126,14 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
peerID: peerID1, peerID: peerID1,
expectedPeers: []string{peerID2}, expectedPeers: []string{peerID2},
expectedOfflinePeers: []string{}, expectedOfflinePeers: []string{},
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
ID: peerID1, ID: peerID1,
Key: "peer-1-key", Key: "peer-1-key",
IP: net.IP{100, 64, 0, 1}, IP: net.IP{100, 64, 0, 1},
Name: peerID1, Name: peerID1,
DNSLabel: peerID1, DNSLabel: peerID1,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: false, Connected: false,
LoginExpired: true, LoginExpired: true,
@ -145,7 +147,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
IP: net.IP{100, 64, 0, 1}, IP: net.IP{100, 64, 0, 1},
Name: peerID2, Name: peerID2,
DNSLabel: peerID2, DNSLabel: peerID2,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: false, Connected: false,
LoginExpired: false, LoginExpired: false,
@ -162,14 +164,14 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
peerID: peerID1, peerID: peerID1,
expectedPeers: []string{}, expectedPeers: []string{},
expectedOfflinePeers: []string{peerID2}, expectedOfflinePeers: []string{peerID2},
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
ID: peerID1, ID: peerID1,
Key: "peer-1-key", Key: "peer-1-key",
IP: net.IP{100, 64, 0, 1}, IP: net.IP{100, 64, 0, 1},
Name: peerID1, Name: peerID1,
DNSLabel: peerID1, DNSLabel: peerID1,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: false, Connected: false,
LoginExpired: true, LoginExpired: true,
@ -184,7 +186,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
IP: net.IP{100, 64, 0, 1}, IP: net.IP{100, 64, 0, 1},
Name: peerID2, Name: peerID2,
DNSLabel: peerID2, DNSLabel: peerID2,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: false, Connected: false,
LoginExpired: true, 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} netIP := net.IP{100, 64, 0, 0}
@ -209,6 +364,7 @@ func TestAccount_GetPeerNetworkMap(t *testing.T) {
for _, testCase := range tt { for _, testCase := range tt {
account := newAccountWithId("account-1", userID, "netbird.io") account := newAccountWithId("account-1", userID, "netbird.io")
account.UpdateSettings(&testCase.accountSettings)
account.Network = network account.Network = network
account.Peers = testCase.peers account.Peers = testCase.peers
for _, peer := range account.Peers { for _, peer := range account.Peers {
@ -805,9 +961,9 @@ func TestAccountManager_AddPeer(t *testing.T) {
expectedPeerKey := key.PublicKey().String() expectedPeerKey := key.PublicKey().String()
expectedSetupKey := setupKey.Key expectedSetupKey := setupKey.Key
peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: expectedPeerKey, Key: expectedPeerKey,
Meta: PeerSystemMeta{Hostname: expectedPeerKey}, Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err) 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() expectedPeerKey := key.PublicKey().String()
expectedUserID := userID expectedUserID := userID
peer, _, err := manager.AddPeer("", userID, &Peer{ peer, _, err := manager.AddPeer("", userID, &nbpeer.Peer{
Key: expectedPeerKey, Key: expectedPeerKey,
Meta: PeerSystemMeta{Hostname: expectedPeerKey}, Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v, account users: %v", err, account.CreatedBy) 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 return
} }
getPeer := func() *Peer { getPeer := func() *nbpeer.Peer {
key, err := wgtypes.GeneratePrivateKey() key, err := wgtypes.GeneratePrivateKey()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -948,9 +1104,9 @@ func TestAccountManager_NetworkUpdates(t *testing.T) {
} }
expectedPeerKey := key.PublicKey().String() expectedPeerKey := key.PublicKey().String()
peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: expectedPeerKey, Key: expectedPeerKey,
Meta: PeerSystemMeta{Hostname: expectedPeerKey}, Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}) })
if err != nil { if err != nil {
t.Fatalf("expecting peer1 to be added, got failure %v", err) 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() peerKey := key.PublicKey().String()
peer, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: peerKey, Key: peerKey,
Meta: PeerSystemMeta{Hostname: peerKey}, Meta: nbpeer.PeerSystemMeta{Hostname: peerKey},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err) t.Errorf("expecting peer to be added, got failure %v", err)
@ -1263,8 +1419,8 @@ func TestAccount_GetRoutesToSync(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.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"}}, "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"}}}, Groups: map[string]*Group{"group1": {ID: "group1", Peers: []string{"peer-1", "peer-2"}}},
Routes: map[string]*route.Route{ 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) assert.Len(t, routes, 2)
routeIDs := make(map[string]struct{}, 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-2")
assert.Contains(t, routeIDs, "route-3") 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) assert.Len(t, emptyRoutes, 0)
} }
@ -1338,10 +1494,10 @@ func TestAccount_Copy(t *testing.T) {
Network: &Network{ Network: &Network{
Identifier: "net1", Identifier: "net1",
}, },
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peer1": { "peer1": {
Key: "key1", Key: "key1",
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now(), LastSeen: time.Now(),
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
@ -1468,9 +1624,9 @@ func TestDefaultAccountManager_UpdatePeer_PeerLoginExpiration(t *testing.T) {
key, err := wgtypes.GenerateKey() key, err := wgtypes.GenerateKey()
require.NoError(t, err, "unable to generate WireGuard key") 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(), Key: key.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
}) })
require.NoError(t, err, "unable to add peer") require.NoError(t, err, "unable to add peer")
@ -1517,9 +1673,9 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.
key, err := wgtypes.GenerateKey() key, err := wgtypes.GenerateKey()
require.NoError(t, err, "unable to generate WireGuard key") require.NoError(t, err, "unable to generate WireGuard key")
_, _, err = manager.AddPeer("", userID, &Peer{ _, _, err = manager.AddPeer("", userID, &nbpeer.Peer{
Key: key.PublicKey().String(), Key: key.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
}) })
require.NoError(t, err, "unable to add peer") require.NoError(t, err, "unable to add peer")
@ -1558,9 +1714,9 @@ func TestDefaultAccountManager_UpdateAccountSettings_PeerLoginExpiration(t *test
key, err := wgtypes.GenerateKey() key, err := wgtypes.GenerateKey()
require.NoError(t, err, "unable to generate WireGuard key") require.NoError(t, err, "unable to generate WireGuard key")
_, _, err = manager.AddPeer("", userID, &Peer{ _, _, err = manager.AddPeer("", userID, &nbpeer.Peer{
Key: key.PublicKey().String(), Key: key.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
}) })
require.NoError(t, err, "unable to add peer") require.NoError(t, err, "unable to add peer")
@ -1639,13 +1795,13 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) {
func TestAccount_GetExpiredPeers(t *testing.T) { func TestAccount_GetExpiredPeers(t *testing.T) {
type test struct { type test struct {
name string name string
peers map[string]*Peer peers map[string]*nbpeer.Peer
expectedPeers map[string]struct{} expectedPeers map[string]struct{}
} }
testCases := []test{ testCases := []test{
{ {
name: "Peers with login expiration disabled, no expired peers", name: "Peers with login expiration disabled, no expired peers",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
}, },
@ -1657,11 +1813,11 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
}, },
{ {
name: "Two peers expired", name: "Two peers expired",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
ID: "peer-1", ID: "peer-1",
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
@ -1672,7 +1828,7 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
"peer-2": { "peer-2": {
ID: "peer-2", ID: "peer-2",
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
@ -1684,7 +1840,7 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
"peer-3": { "peer-3": {
ID: "peer-3", ID: "peer-3",
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
LastSeen: time.Now().UTC(), LastSeen: time.Now().UTC(),
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
@ -1724,19 +1880,19 @@ func TestAccount_GetExpiredPeers(t *testing.T) {
func TestAccount_GetPeersWithExpiration(t *testing.T) { func TestAccount_GetPeersWithExpiration(t *testing.T) {
type test struct { type test struct {
name string name string
peers map[string]*Peer peers map[string]*nbpeer.Peer
expectedPeers map[string]struct{} expectedPeers map[string]struct{}
} }
testCases := []test{ testCases := []test{
{ {
name: "No account peers, no peers with expiration", name: "No account peers, no peers with expiration",
peers: map[string]*Peer{}, peers: map[string]*nbpeer.Peer{},
expectedPeers: map[string]struct{}{}, expectedPeers: map[string]struct{}{},
}, },
{ {
name: "Peers with login expiration disabled, no peers with expiration", name: "Peers with login expiration disabled, no peers with expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
UserID: userID, UserID: userID,
@ -1750,7 +1906,7 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) {
}, },
{ {
name: "Peers with login expiration enabled, return peers with expiration", name: "Peers with login expiration enabled, return peers with expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
ID: "peer-1", ID: "peer-1",
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
@ -1793,7 +1949,7 @@ func TestAccount_GetPeersWithExpiration(t *testing.T) {
func TestAccount_GetNextPeerExpiration(t *testing.T) { func TestAccount_GetNextPeerExpiration(t *testing.T) {
type test struct { type test struct {
name string name string
peers map[string]*Peer peers map[string]*nbpeer.Peer
expiration time.Duration expiration time.Duration
expirationEnabled bool expirationEnabled bool
expectedNextRun bool expectedNextRun bool
@ -1804,7 +1960,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
testCases := []test{ testCases := []test{
{ {
name: "No peers, no expiration", name: "No peers, no expiration",
peers: map[string]*Peer{}, peers: map[string]*nbpeer.Peer{},
expiration: time.Second, expiration: time.Second,
expirationEnabled: false, expirationEnabled: false,
expectedNextRun: false, expectedNextRun: false,
@ -1812,16 +1968,16 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "No connected peers, no expiration", name: "No connected peers, no expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: false, Connected: false,
}, },
LoginExpirationEnabled: true, LoginExpirationEnabled: true,
UserID: userID, UserID: userID,
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
}, },
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
@ -1835,16 +1991,16 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "Connected peers with disabled expiration, no expiration", name: "Connected peers with disabled expiration, no expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
}, },
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
UserID: userID, UserID: userID,
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
}, },
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
@ -1858,9 +2014,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "Expired peers, no expiration", name: "Expired peers, no expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: true, LoginExpired: true,
}, },
@ -1868,7 +2024,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
UserID: userID, UserID: userID,
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: true, LoginExpired: true,
}, },
@ -1883,9 +2039,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "To be expired peer, return expiration", name: "To be expired peer, return expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
}, },
@ -1894,7 +2050,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
UserID: userID, UserID: userID,
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: true, LoginExpired: true,
}, },
@ -1909,9 +2065,9 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
}, },
{ {
name: "Peers added with setup keys, no expiration", name: "Peers added with setup keys, no expiration",
peers: map[string]*Peer{ peers: map[string]*nbpeer.Peer{
"peer-1": { "peer-1": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
}, },
@ -1919,7 +2075,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
SetupKey: "key", SetupKey: "key",
}, },
"peer-2": { "peer-2": {
Status: &PeerStatus{ Status: &nbpeer.PeerStatus{
Connected: true, Connected: true,
LoginExpired: false, LoginExpired: false,
}, },
@ -1954,7 +2110,7 @@ func TestAccount_GetNextPeerExpiration(t *testing.T) {
func TestAccount_SetJWTGroups(t *testing.T) { func TestAccount_SetJWTGroups(t *testing.T) {
// create a new account // create a new account
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", 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) { func TestAccount_UserGroupsAddToPeers(t *testing.T) {
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", 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) { func TestAccount_UserGroupsRemoveFromPeers(t *testing.T) {
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peer1": {ID: "peer1", Key: "key1", UserID: "user1"}, "peer1": {ID: "peer1", Key: "key1", UserID: "user1"},
"peer2": {ID: "peer2", Key: "key2", UserID: "user1"}, "peer2": {ID: "peer2", Key: "key2", UserID: "user1"},
"peer3": {ID: "peer3", Key: "key3", UserID: "user1"}, "peer3": {ID: "peer3", Key: "key3", UserID: "user1"},

View File

@ -120,6 +120,14 @@ const (
IntegrationUpdated IntegrationUpdated
// IntegrationDeleted indicates that the user deleted an integration // IntegrationDeleted indicates that the user deleted an integration
IntegrationDeleted 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 indicates that the user transferred the owner role of the account
TransferredOwnerRole TransferredOwnerRole
) )
@ -180,6 +188,10 @@ var activityMap = map[Activity]Code{
IntegrationCreated: {"Integration created", "integration.create"}, IntegrationCreated: {"Integration created", "integration.create"},
IntegrationUpdated: {"Integration updated", "integration.update"}, IntegrationUpdated: {"Integration updated", "integration.update"},
IntegrationDeleted: {"Integration deleted", "integration.delete"}, 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"}, TransferredOwnerRole: {"Transferred owner role", "transferred.owner.role"},
} }

View File

@ -10,6 +10,7 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "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 // 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 { for _, ns := range nsGroup.NameServers {
if peer.IP.Equal(ns.IP.AsSlice()) { if peer.IP.Equal(ns.IP.AsSlice()) {
return true return true

View File

@ -8,6 +8,7 @@ import (
"github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "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) { func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) {
t.Helper() t.Helper()
peer1 := &Peer{ peer1 := &nbpeer.Peer{
Key: dnsPeer1Key, Key: dnsPeer1Key,
Name: "test-host1@netbird.io", Name: "test-host1@netbird.io",
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host1@netbird.io", Hostname: "test-host1@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",
@ -223,10 +224,10 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, erro
}, },
DNSLabel: dnsPeer1Key, DNSLabel: dnsPeer1Key,
} }
peer2 := &Peer{ peer2 := &nbpeer.Peer{
Key: dnsPeer2Key, Key: dnsPeer2Key,
Name: "test-host2@netbird.io", Name: "test-host2@netbird.io",
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host2@netbird.io", Hostname: "test-host2@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",

View File

@ -7,6 +7,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
) )
const ( 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 // 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. // 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 { if !peer.Ephemeral {
return 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 // 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. // 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 { if !peer.Ephemeral {
return return
} }

View File

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"testing" "testing"
"time" "time"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
) )
type MockStore struct { type MockStore struct {
@ -124,7 +126,7 @@ func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int)
for i := 0; i < numberOfPeers; i++ { for i := 0; i < numberOfPeers; i++ {
peerId := fmt.Sprintf("peer_%d", i) peerId := fmt.Sprintf("peer_%d", i)
p := &Peer{ p := &nbpeer.Peer{
ID: peerId, ID: peerId,
Ephemeral: false, Ephemeral: false,
} }
@ -133,7 +135,7 @@ func seedPeers(store *MockStore, numberOfPeers int, numberOfEphemeralPeers int)
for i := 0; i < numberOfEphemeralPeers; i++ { for i := 0; i < numberOfEphemeralPeers; i++ {
peerId := fmt.Sprintf("ephemeral_peer_%d", i) peerId := fmt.Sprintf("ephemeral_peer_%d", i)
p := &Peer{ p := &nbpeer.Peer{
ID: peerId, ID: peerId,
Ephemeral: true, Ephemeral: true,
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/rs/xid" "github.com/rs/xid"
log "github.com/sirupsen/logrus" 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/status"
"github.com/netbirdio/netbird/management/server/telemetry" "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. // Set the Peer.ID to the newly generated value.
// Replace all the mentions of Peer.Key as ID (groups and routes). // Replace all the mentions of Peer.Key as ID (groups and routes).
// Swap Peer.Key with Peer.ID in the Account.Peers map. // 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 { for key, peer := range account.Peers {
// set LastLogin for the peers that were onboarded before the peer login expiration feature // set LastLogin for the peers that were onboarded before the peer login expiration feature
if peer.LastLogin.IsZero() { 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. // 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. // 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() s.mux.Lock()
defer s.mux.Unlock() defer s.mux.Unlock()

View File

@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/util" "github.com/netbirdio/netbird/util"
) )
@ -35,7 +36,7 @@ func TestStalePeerIndices(t *testing.T) {
peerID := "some_peer" peerID := "some_peer"
peerKey := "some_peer_key" peerKey := "some_peer_key"
account.Peers[peerID] = &Peer{ account.Peers[peerID] = &nbpeer.Peer{
ID: peerID, ID: peerID,
Key: peerKey, Key: peerKey,
} }
@ -89,13 +90,13 @@ func TestSaveAccount(t *testing.T) {
account := newAccountWithId("account_id", "testuser", "") account := newAccountWithId("account_id", "testuser", "")
setupKey := GenerateDefaultSetupKey() setupKey := GenerateDefaultSetupKey()
account.SetupKeys[setupKey.Key] = setupKey account.SetupKeys[setupKey.Key] = setupKey
account.Peers["testpeer"] = &Peer{ account.Peers["testpeer"] = &nbpeer.Peer{
Key: "peerkey", Key: "peerkey",
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
} }
// SaveAccount should trigger persist // SaveAccount should trigger persist
@ -179,13 +180,13 @@ func TestStore(t *testing.T) {
store := newStore(t) store := newStore(t)
account := newAccountWithId("account_id", "testuser", "") account := newAccountWithId("account_id", "testuser", "")
account.Peers["testpeer"] = &Peer{ account.Peers["testpeer"] = &nbpeer.Peer{
Key: "peerkey", Key: "peerkey",
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
} }
account.Groups["all"] = &Group{ account.Groups["all"] = &Group{
ID: "all", ID: "all",
@ -600,19 +601,19 @@ func TestFileStore_SavePeerStatus(t *testing.T) {
} }
// save status of non-existing peer // 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) err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus)
assert.Error(t, err) assert.Error(t, err)
// save new status of existing peer // save new status of existing peer
account.Peers["testpeer"] = &Peer{ account.Peers["testpeer"] = &nbpeer.Peer{
Key: "peerkey", Key: "peerkey",
ID: "testpeer", ID: "testpeer",
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()},
} }
err = store.SaveAccount(account) err = store.SaveAccount(account)

View File

@ -17,6 +17,7 @@ import (
"github.com/netbirdio/netbird/encryption" "github.com/netbirdio/netbird/encryption"
"github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
internalStatus "github.com/netbirdio/netbird/management/server/status" internalStatus "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/management/server/telemetry" "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.peersUpdateManager.CloseChannel(peer.ID)
s.turnCredentialsManager.CancelRefresh(peer.ID) s.turnCredentialsManager.CancelRefresh(peer.ID)
_ = s.accountManager.MarkPeerConnected(peer.Key, false) _ = s.accountManager.MarkPeerConnected(peer.Key, false)
@ -243,8 +244,8 @@ func mapError(err error) error {
return status.Errorf(codes.Internal, "failed handling request") return status.Errorf(codes.Internal, "failed handling request")
} }
func extractPeerMeta(loginReq *proto.LoginRequest) PeerSystemMeta { func extractPeerMeta(loginReq *proto.LoginRequest) nbpeer.PeerSystemMeta {
return PeerSystemMeta{ return nbpeer.PeerSystemMeta{
Hostname: loginReq.GetMeta().GetHostname(), Hostname: loginReq.GetMeta().GetHostname(),
GoOS: loginReq.GetMeta().GetGoOS(), GoOS: loginReq.GetMeta().GetGoOS(),
Kernel: loginReq.GetMeta().GetKernel(), 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() netmask, _ := network.Net.Mask.Size()
fqdn := peer.FQDN(dnsName) fqdn := peer.FQDN(dnsName)
return &proto.PeerConfig{ 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{} remotePeers := []*proto.RemotePeerConfig{}
for _, rPeer := range peers { for _, rPeer := range peers {
fqdn := rPeer.FQDN(dnsName) fqdn := rPeer.FQDN(dnsName)
@ -437,7 +438,7 @@ func toRemotePeerConfig(peers []*Peer, dnsName string) []*proto.RemotePeerConfig
return remotePeers 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) wtConfig := toWiretrusteeConfig(config, turnCredentials)
pConfig := toPeerConfig(peer, networkMap.Network, dnsName) 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 // 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 // make secret time based TURN credentials optional
var turnCredentials *TURNCredentials var turnCredentials *TURNCredentials
if s.config.TURNConfig.TimeBasedCredentials { if s.config.TURNConfig.TimeBasedCredentials {

View File

@ -8,6 +8,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/netbirdio/netbird/management/server" "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/api"
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims" "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)), 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 { if req.Settings.JwtGroupsEnabled != nil {
settings.JWTGroupsEnabled = *req.Settings.JwtGroupsEnabled 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 { 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{ return &api.Account{
Id: account.Id, Id: account.Id,
Settings: api.AccountSettings{ Settings: settings,
PeerLoginExpiration: int(account.Settings.PeerLoginExpiration.Seconds()),
PeerLoginExpirationEnabled: account.Settings.PeerLoginExpirationEnabled,
GroupsPropagationEnabled: &account.Settings.GroupsPropagationEnabled,
JwtGroupsEnabled: &account.Settings.JWTGroupsEnabled,
JwtGroupsClaimName: &account.Settings.JWTGroupsClaimName,
},
} }
} }

View File

@ -66,9 +66,18 @@ components:
description: Name of the claim from which we extract groups names to add it to account groups. description: Name of the claim from which we extract groups names to add it to account groups.
type: string type: string
example: "roles" example: "roles"
extra:
$ref: '#/components/schemas/AccountExtraSettings'
required: required:
- peer_login_expiration_enabled - peer_login_expiration_enabled
- peer_login_expiration - 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: AccountRequest:
type: object type: object
properties: properties:
@ -213,6 +222,10 @@ components:
login_expiration_enabled: login_expiration_enabled:
type: boolean type: boolean
example: false example: false
approval_required:
description: (Cloud only) Indicates whether peer needs approval
type: boolean
example: true
required: required:
- name - name
- ssh_enabled - ssh_enabled
@ -281,6 +294,10 @@ components:
type: string type: string
format: date-time format: date-time
example: 2023-05-05T09:00:35.477782Z example: 2023-05-05T09:00:35.477782Z
approval_required:
description: (Cloud only) Indicates whether peer needs approval
type: boolean
example: true
required: required:
- ip - ip
- connected - connected

View File

@ -142,6 +142,12 @@ type Account struct {
Settings AccountSettings `json:"settings"` 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. // AccountRequest defines model for AccountRequest.
type AccountRequest struct { type AccountRequest struct {
Settings AccountSettings `json:"settings"` Settings AccountSettings `json:"settings"`
@ -149,6 +155,8 @@ type AccountRequest struct {
// AccountSettings defines model for AccountSettings. // AccountSettings defines model for AccountSettings.
type AccountSettings struct { type AccountSettings struct {
Extra *AccountExtraSettings `json:"extra,omitempty"`
// GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user // GroupsPropagationEnabled Allows propagate the new user auto groups to peers that belongs to the user
GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"` GroupsPropagationEnabled *bool `json:"groups_propagation_enabled,omitempty"`
@ -323,6 +331,9 @@ type Peer struct {
// AccessiblePeers List of accessible peers // AccessiblePeers List of accessible peers
AccessiblePeers []AccessiblePeer `json:"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 Peer to Management connection status
Connected bool `json:"connected"` Connected bool `json:"connected"`
@ -374,6 +385,9 @@ type Peer struct {
// PeerBase defines model for PeerBase. // PeerBase defines model for PeerBase.
type PeerBase struct { type PeerBase struct {
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
ApprovalRequired *bool `json:"approval_required,omitempty"`
// Connected Peer to Management connection status // Connected Peer to Management connection status
Connected bool `json:"connected"` Connected bool `json:"connected"`
@ -428,6 +442,9 @@ type PeerBatch struct {
// AccessiblePeersCount Number of accessible peers // AccessiblePeersCount Number of accessible peers
AccessiblePeersCount int `json:"accessible_peers_count"` 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 Peer to Management connection status
Connected bool `json:"connected"` Connected bool `json:"connected"`
@ -488,6 +505,8 @@ type PeerMinimum struct {
// PeerRequest defines model for PeerRequest. // PeerRequest defines model for PeerRequest.
type PeerRequest struct { type PeerRequest struct {
// ApprovalRequired (Cloud only) Indicates whether peer needs approval
ApprovalRequired *bool `json:"approval_required,omitempty"`
LoginExpirationEnabled bool `json:"login_expiration_enabled"` LoginExpirationEnabled bool `json:"login_expiration_enabled"`
Name string `json:"name"` Name string `json:"name"`
SshEnabled bool `json:"ssh_enabled"` SshEnabled bool `json:"ssh_enabled"`

View File

@ -19,10 +19,11 @@ import (
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
"github.com/netbirdio/netbird/management/server/mock_server" "github.com/netbirdio/netbird/management/server/mock_server"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "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")}, "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")}, "B": {Key: "B", ID: "peer-B-ID", IP: net.ParseIP("200.200.200.200")},
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
"github.com/netbirdio/netbird/management/server/http/util" "github.com/netbirdio/netbird/management/server/http/util"
"github.com/netbirdio/netbird/management/server/jwtclaims" "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/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() peerToReturn := peer.Copy()
if peer.Status.Connected { if peer.Status.Connected {
statuses, err := h.accountManager.GetAllConnectedPeers() statuses, err := h.accountManager.GetAllConnectedPeers()
@ -79,8 +80,13 @@ func (h *PeersHandler) updatePeer(account *server.Account, user *server.User, pe
return 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} LoginExpirationEnabled: req.LoginExpirationEnabled}
if req.ApprovalRequired != nil {
update.Status = &nbpeer.PeerStatus{RequiresApproval: *req.ApprovalRequired}
}
peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update) peer, err := h.accountManager.UpdatePeer(account.Id, user.Id, update)
if err != nil { if err != nil {
util.WriteError(err, w) util.WriteError(err, w)
@ -229,7 +235,7 @@ func toGroupsInfo(groups map[string]*server.Group, peerID string) []api.GroupMin
return groupsInfo 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{ return &api.Peer{
Id: peer.ID, Id: peer.ID,
Name: peer.Name, Name: peer.Name,
@ -248,10 +254,11 @@ func toSinglePeerResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dnsD
LastLogin: peer.LastLogin, LastLogin: peer.LastLogin,
LoginExpired: peer.Status.LoginExpired, LoginExpired: peer.Status.LoginExpired,
AccessiblePeers: accessiblePeer, 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{ return &api.PeerBatch{
Id: peer.ID, Id: peer.ID,
Name: peer.Name, Name: peer.Name,
@ -270,10 +277,11 @@ func toPeerListItemResponse(peer *server.Peer, groupsInfo []api.GroupMinimum, dn
LastLogin: peer.LastLogin, LastLogin: peer.LastLogin,
LoginExpired: peer.Status.LoginExpired, LoginExpired: peer.Status.LoginExpired,
AccessiblePeersCount: accessiblePeersCount, 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) fqdn := peer.FQDN(dnsDomain)
if fqdn == "" { if fqdn == "" {
return peer.DNSLabel return peer.DNSLabel

View File

@ -13,6 +13,7 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/netbirdio/netbird/management/server/http/api" "github.com/netbirdio/netbird/management/server/http/api"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
@ -25,11 +26,11 @@ import (
const testPeerID = "test_peer" const testPeerID = "test_peer"
const noUpdateChannelTestPeerID = "no-update-channel" const noUpdateChannelTestPeerID = "no-update-channel"
func initTestMetaData(peers ...*server.Peer) *PeersHandler { func initTestMetaData(peers ...*nbpeer.Peer) *PeersHandler {
return &PeersHandler{ return &PeersHandler{
accountManager: &mock_server.MockAccountManager{ accountManager: &mock_server.MockAccountManager{
UpdatePeerFunc: func(accountID, userID string, update *server.Peer) (*server.Peer, error) { UpdatePeerFunc: func(accountID, userID string, update *nbpeer.Peer) (*nbpeer.Peer, error) {
var p *server.Peer var p *nbpeer.Peer
for _, peer := range peers { for _, peer := range peers {
if update.ID == peer.ID { if update.ID == peer.ID {
p = peer.Copy() p = peer.Copy()
@ -41,8 +42,8 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
p.Name = update.Name p.Name = update.Name
return p, nil return p, nil
}, },
GetPeerFunc: func(accountID, peerID, userID string) (*server.Peer, error) { GetPeerFunc: func(accountID, peerID, userID string) (*nbpeer.Peer, error) {
var p *server.Peer var p *nbpeer.Peer
for _, peer := range peers { for _, peer := range peers {
if peerID == peer.ID { if peerID == peer.ID {
p = peer.Copy() p = peer.Copy()
@ -51,7 +52,7 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
} }
return p, nil return p, nil
}, },
GetPeersFunc: func(accountID, userID string) ([]*server.Peer, error) { GetPeersFunc: func(accountID, userID string) ([]*nbpeer.Peer, error) {
return peers, nil return peers, nil
}, },
GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) { GetAccountFromTokenFunc: func(claims jwtclaims.AuthorizationClaims) (*server.Account, *server.User, error) {
@ -59,8 +60,9 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
return &server.Account{ return &server.Account{
Id: claims.AccountId, Id: claims.AccountId,
Domain: "hotmail.com", Domain: "hotmail.com",
Peers: map[string]*server.Peer{ Peers: map[string]*nbpeer.Peer{
peers[0].ID: peers[0], peers[0].ID: peers[0],
peers[1].ID: peers[1],
}, },
Users: map[string]*server.User{ Users: map[string]*server.User{
"test_user": user, "test_user": user,
@ -107,15 +109,15 @@ func initTestMetaData(peers ...*server.Peer) *PeersHandler {
// Use the metadata generated by initTestMetaData() to check for values // Use the metadata generated by initTestMetaData() to check for values
func TestGetPeers(t *testing.T) { func TestGetPeers(t *testing.T) {
peer := &server.Peer{ peer := &nbpeer.Peer{
ID: testPeerID, ID: testPeerID,
Key: "key", Key: "key",
SetupKey: "setupkey", SetupKey: "setupkey",
IP: net.ParseIP("100.64.0.1"), IP: net.ParseIP("100.64.0.1"),
Status: &server.PeerStatus{Connected: true}, Status: &nbpeer.PeerStatus{Connected: true},
Name: "PeerName", Name: "PeerName",
LoginExpirationEnabled: false, LoginExpirationEnabled: false,
Meta: server.PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "hostname", Hostname: "hostname",
GoOS: "GoOS", GoOS: "GoOS",
Kernel: "kernel", Kernel: "kernel",
@ -144,7 +146,7 @@ func TestGetPeers(t *testing.T) {
requestPath string requestPath string
requestBody io.Reader requestBody io.Reader
expectedArray bool expectedArray bool
expectedPeer *server.Peer expectedPeer *nbpeer.Peer
}{ }{
{ {
name: "GetPeersMetaData", name: "GetPeersMetaData",

View File

@ -11,6 +11,7 @@ import (
"testing" "testing"
"github.com/netbirdio/netbird/management/server/http/api" "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/management/server/status"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
@ -55,12 +56,12 @@ var baseExistingRoute = &route.Route{
var testingAccount = &server.Account{ var testingAccount = &server.Account{
Id: testAccountID, Id: testAccountID,
Domain: "hotmail.com", Domain: "hotmail.com",
Peers: map[string]*server.Peer{ Peers: map[string]*nbpeer.Peer{
existingPeerID: { existingPeerID: {
Key: existingPeerKey, Key: existingPeerKey,
IP: netip.MustParseAddr(existingPeerIP1).AsSlice(), IP: netip.MustParseAddr(existingPeerIP1).AsSlice(),
ID: existingPeerID, ID: existingPeerID,
Meta: server.PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
GoOS: "linux", GoOS: "linux",
}, },
}, },
@ -68,7 +69,7 @@ var testingAccount = &server.Account{
Key: nonLinuxExistingPeerID, Key: nonLinuxExistingPeerID,
IP: netip.MustParseAddr(existingPeerIP2).AsSlice(), IP: netip.MustParseAddr(existingPeerIP2).AsSlice(),
ID: nonLinuxExistingPeerID, ID: nonLinuxExistingPeerID,
Meta: server.PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
GoOS: "darwin", GoOS: "darwin",
}, },
}, },

View File

@ -5,6 +5,7 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@ -37,12 +38,12 @@ func (mockDatasource) GetAllAccounts() []*server.Account {
NameServerGroups: map[string]*nbdns.NameServerGroup{ NameServerGroups: map[string]*nbdns.NameServerGroup{
"1": {}, "1": {},
}, },
Peers: map[string]*server.Peer{ Peers: map[string]*nbpeer.Peer{
"1": { "1": {
ID: "1", ID: "1",
UserID: "test", UserID: "test",
SSHEnabled: true, SSHEnabled: true,
Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
}, },
}, },
Policies: []*server.Policy{ Policies: []*server.Policy{
@ -101,12 +102,12 @@ func (mockDatasource) GetAllAccounts() []*server.Account {
NameServerGroups: map[string]*nbdns.NameServerGroup{ NameServerGroups: map[string]*nbdns.NameServerGroup{
"1": {}, "1": {},
}, },
Peers: map[string]*server.Peer{ Peers: map[string]*nbpeer.Peer{
"1": { "1": {
ID: "1", ID: "1",
UserID: "test", UserID: "test",
SSHEnabled: true, SSHEnabled: true,
Meta: server.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"}, Meta: nbpeer.PeerSystemMeta{GoOS: "linux", WtVersion: "0.0.1"},
}, },
}, },
Policies: []*server.Policy{ Policies: []*server.Policy{

View File

@ -10,6 +10,7 @@ import (
"github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/jwtclaims" "github.com/netbirdio/netbird/management/server/jwtclaims"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@ -21,12 +22,12 @@ type MockAccountManager struct {
GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error) GetAccountByUserOrAccountIdFunc func(userId, accountId, domain string) (*server.Account, error)
GetUserFunc func(claims jwtclaims.AuthorizationClaims) (*server.User, error) GetUserFunc func(claims jwtclaims.AuthorizationClaims) (*server.User, error)
ListUsersFunc func(accountID string) ([]*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 MarkPeerConnectedFunc func(peerKey string, connected bool) error
DeletePeerFunc func(accountID, peerKey, userID string) error DeletePeerFunc func(accountID, peerKey, userID string) error
GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error) GetNetworkMapFunc func(peerKey string) (*server.NetworkMap, error)
GetPeerNetworkFunc func(peerKey string) (*server.Network, 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) GetGroupFunc func(accountID, groupID string) (*server.Group, error)
SaveGroupFunc func(accountID, userID string, group *server.Group) error SaveGroupFunc func(accountID, userID string, group *server.Group) error
DeleteGroupFunc func(accountID, userId, groupID string) error DeleteGroupFunc func(accountID, userId, groupID string) error
@ -44,9 +45,9 @@ type MockAccountManager struct {
GetUsersFromAccountFunc func(accountID, userID string) ([]*server.UserInfo, error) GetUsersFromAccountFunc func(accountID, userID string) ([]*server.UserInfo, error)
GetAccountFromPATFunc func(pat string) (*server.Account, *server.User, *server.PersonalAccessToken, error) GetAccountFromPATFunc func(pat string) (*server.Account, *server.User, *server.PersonalAccessToken, error)
MarkPATUsedFunc func(pat string) 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 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) 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) GetRouteFunc func(accountID, routeID, userID string) (*route.Route, error)
SaveRouteFunc func(accountID, userID string, route *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) GetEventsFunc func(accountID, userID string) ([]*activity.Event, error)
GetDNSSettingsFunc func(accountID, userID string) (*server.DNSSettings, error) GetDNSSettingsFunc func(accountID, userID string) (*server.DNSSettings, error)
SaveDNSSettingsFunc func(accountID, userID string, dnsSettingsToSave *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) UpdateAccountSettingsFunc func(accountID, userID string, newSettings *server.Settings) (*server.Account, error)
LoginPeerFunc func(login server.PeerLogin) (*server.Peer, *server.NetworkMap, error) LoginPeerFunc func(login server.PeerLogin) (*nbpeer.Peer, *server.NetworkMap, error)
SyncPeerFunc func(sync server.PeerSync) (*server.Peer, *server.NetworkMap, error) SyncPeerFunc func(sync server.PeerSync) (*nbpeer.Peer, *server.NetworkMap, error)
InviteUserFunc func(accountID string, initiatorUserID string, targetUserEmail string) error InviteUserFunc func(accountID string, initiatorUserID string, targetUserEmail string) error
GetAllConnectedPeersFunc func() (map[string]struct{}, error) GetAllConnectedPeersFunc func() (map[string]struct{}, error)
GetExternalCacheManagerFunc func() server.ExternalCacheManager GetExternalCacheManagerFunc func() server.ExternalCacheManager
@ -226,8 +227,8 @@ func (am *MockAccountManager) GetPeerNetwork(peerKey string) (*server.Network, e
func (am *MockAccountManager) AddPeer( func (am *MockAccountManager) AddPeer(
setupKey string, setupKey string,
userId string, userId string,
peer *server.Peer, peer *nbpeer.Peer,
) (*server.Peer, *server.NetworkMap, error) { ) (*nbpeer.Peer, *server.NetworkMap, error) {
if am.AddPeerFunc != nil { if am.AddPeerFunc != nil {
return am.AddPeerFunc(setupKey, userId, peer) 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 // 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 { if am.UpdatePeerMetaFunc != nil {
return am.UpdatePeerMetaFunc(peerID, meta) 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 // 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 { if am.UpdatePeerFunc != nil {
return am.UpdatePeerFunc(accountID, userID, peer) return am.UpdatePeerFunc(accountID, userID, peer)
} }
@ -542,7 +543,7 @@ func (am *MockAccountManager) GetAccountFromToken(claims jwtclaims.Authorization
} }
// GetPeers mocks GetPeers of the AccountManager interface // 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 { if am.GetAccountFromTokenFunc != nil {
return am.GetPeersFunc(accountID, userID) 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 // 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 { if am.GetPeerFunc != nil {
return am.GetPeerFunc(accountID, peerID, userID) 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 // 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 { if am.LoginPeerFunc != nil {
return am.LoginPeerFunc(login) return am.LoginPeerFunc(login)
} }
@ -606,7 +607,7 @@ func (am *MockAccountManager) LoginPeer(login server.PeerLogin) (*server.Peer, *
} }
// SyncPeer mocks SyncPeer of the AccountManager interface // 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 { if am.SyncPeerFunc != nil {
return am.SyncPeerFunc(sync) return am.SyncPeerFunc(sync)
} }

View File

@ -8,6 +8,7 @@ import (
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
) )
const ( const (
@ -763,10 +764,10 @@ func createNSStore(t *testing.T) (Store, error) {
func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) { func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error) {
t.Helper() t.Helper()
peer1 := &Peer{ peer1 := &nbpeer.Peer{
Key: nsGroupPeer1Key, Key: nsGroupPeer1Key,
Name: "test-host1@netbird.io", Name: "test-host1@netbird.io",
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host1@netbird.io", Hostname: "test-host1@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",
@ -777,10 +778,10 @@ func initTestNSAccount(t *testing.T, am *DefaultAccountManager) (*Account, error
UIVersion: "development", UIVersion: "development",
}, },
} }
peer2 := &Peer{ peer2 := &nbpeer.Peer{
Key: nsGroupPeer2Key, Key: nsGroupPeer2Key,
Name: "test-host2@netbird.io", Name: "test-host2@netbird.io",
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host2@netbird.io", Hostname: "test-host2@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",

View File

@ -10,6 +10,7 @@ import (
"github.com/rs/xid" "github.com/rs/xid"
nbdns "github.com/netbirdio/netbird/dns" nbdns "github.com/netbirdio/netbird/dns"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@ -25,11 +26,11 @@ const (
) )
type NetworkMap struct { type NetworkMap struct {
Peers []*Peer Peers []*nbpeer.Peer
Network *Network Network *Network
Routes []*route.Route Routes []*route.Route
DNSConfig nbdns.Config DNSConfig nbdns.Config
OfflinePeers []*Peer OfflinePeers []*nbpeer.Peer
FirewallRules []*FirewallRule FirewallRules []*FirewallRule
} }

View File

@ -2,13 +2,14 @@ package server
import ( import (
"fmt" "fmt"
"net"
"strings" "strings"
"time" "time"
"github.com/netbirdio/management-integrations/additions"
"github.com/rs/xid" "github.com/rs/xid"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -16,38 +17,6 @@ import (
"github.com/netbirdio/netbird/management/proto" "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. // PeerSync used as a data object between the gRPC API and AccountManager on Sync request.
type PeerSync struct { type PeerSync struct {
// WireGuardPubKey is a peers WireGuard public key // 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 is a peer's ssh key. Can be empty (e.g., old version do not provide it, or this feature is disabled)
SSHKey string SSHKey string
// Meta is the system information passed by peer, must be always present. // 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 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 UserID string
// SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required. // SetupKey references to a server.SetupKey to log in. Can be empty when UserID is used or auth is not required.
SetupKey string 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 // 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. // 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) account, err := am.Store.GetAccount(accountID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -211,8 +50,8 @@ func (am *DefaultAccountManager) GetPeers(accountID, userID string) ([]*Peer, er
return nil, err return nil, err
} }
peers := make([]*Peer, 0) peers := make([]*nbpeer.Peer, 0)
peersMap := make(map[string]*Peer) peersMap := make(map[string]*nbpeer.Peer)
for _, peer := range account.Peers { for _, peer := range account.Peers {
if !user.HasAdminPower() && user.Id != peer.UserID { if !user.HasAdminPower() && user.Id != peer.UserID {
// only display peers that belong to the current user if the current user is not an admin // 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 { for _, peer := range peersMap {
peers = append(peers, peer) 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. // 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) unlock := am.Store.AcquireAccountLock(accountID)
defer unlock() 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) 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 { if peer.SSHEnabled != update.SSHEnabled {
peer.SSHEnabled = update.SSHEnabled peer.SSHEnabled = update.SSHEnabled
event := activity.PeerSSHEnabled 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 // the first loop is needed to ensure all peers present under the account before modifying, otherwise
// we might have some inconsistencies // we might have some inconsistencies
peers := make([]*Peer, 0, len(peerIDs)) peers := make([]*nbpeer.Peer, 0, len(peerIDs))
for _, peerID := range peerIDs { for _, peerID := range peerIDs {
peer := account.GetPeer(peerID) 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 // 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). // 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 // 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 == "" { if setupKey == "" && userID == "" {
// no auth method provided => reject access // 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") 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 return nil, nil, err
} }
newPeer := &Peer{ newPeer := &nbpeer.Peer{
ID: xid.New().String(), ID: xid.New().String(),
Key: peer.Key, Key: peer.Key,
SetupKey: upperKey, SetupKey: upperKey,
@ -556,7 +400,7 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
Name: peer.Meta.Hostname, Name: peer.Meta.Hostname,
DNSLabel: newLabel, DNSLabel: newLabel,
UserID: userID, UserID: userID,
Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()},
SSHEnabled: false, SSHEnabled: false,
SSHKey: peer.SSHKey, SSHKey: peer.SSHKey,
LastLogin: time.Now().UTC(), LastLogin: time.Now().UTC(),
@ -564,6 +408,10 @@ func (am *DefaultAccountManager) AddPeer(setupKey, userID string, peer *Peer) (*
Ephemeral: ephemeral, Ephemeral: ephemeral,
} }
if account.Settings.Extra != nil {
newPeer = additions.PreparePeer(newPeer, account.Settings.Extra)
}
// add peer to 'All' group // add peer to 'All' group
group, err := account.GetGroupAll() group, err := account.GetGroupAll()
if err != nil { 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 // 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) account, err := am.Store.GetAccountByPeerPubKey(sync.WireGuardPubKey)
if err != nil { if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { 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. // 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. // 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) account, err := am.Store.GetAccountByPeerPubKey(login.WireGuardPubKey)
if err != nil { if err != nil {
if errStatus, ok := status.FromError(err); ok && errStatus.Type() == status.NotFound { 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. // we couldn't find this peer by its public key which can mean that peer hasn't been registered yet.
// Try registering it. // Try registering it.
return am.AddPeer(login.SetupKey, login.UserID, &Peer{ return am.AddPeer(login.SetupKey, login.UserID, &nbpeer.Peer{
Key: login.WireGuardPubKey, Key: login.WireGuardPubKey,
Meta: login.Meta, Meta: login.Meta,
SSHKey: login.SSHKey, SSHKey: login.SSHKey,
@ -728,7 +576,7 @@ func (am *DefaultAccountManager) LoginPeer(login PeerLogin) (*Peer, *NetworkMap,
return peer, account.GetPeerNetworkMap(peer.ID, am.dnsDomain), nil 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() { if peer.AddedWithSSOLogin() {
user, err := account.FindUser(peer.UserID) user, err := account.FindUser(peer.UserID)
if err != nil { if err != nil {
@ -741,7 +589,7 @@ func checkIfPeerOwnerIsBlocked(peer *Peer, account *Account) error {
return nil return nil
} }
func checkAuth(loginUserID string, peer *Peer) error { func checkAuth(loginUserID string, peer *nbpeer.Peer) error {
if loginUserID == "" { if loginUserID == "" {
// absence of a user ID indicates that JWT wasn't provided. // 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") 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 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, expiresIn := peer.LoginExpired(account.Settings.PeerLoginExpiration)
expired = account.Settings.PeerLoginExpirationEnabled && expired expired = account.Settings.PeerLoginExpirationEnabled && expired
if expired || peer.Status.LoginExpired { if expired || peer.Status.LoginExpired {
@ -763,21 +611,12 @@ func peerLoginExpired(peer *Peer, account *Account) bool {
return false return false
} }
func updatePeerLastLogin(peer *Peer, account *Account) { func updatePeerLastLogin(peer *nbpeer.Peer, account *Account) {
peer.UpdateLastLogin() peer.UpdateLastLogin()
account.UpdatePeer(peer) account.UpdatePeer(peer)
} }
// UpdateLastLogin and set login expired false func (am *DefaultAccountManager) checkAndUpdatePeerSSHKey(peer *nbpeer.Peer, account *Account, newSSHKey string) (*nbpeer.Peer, error) {
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) {
if len(newSSHKey) == 0 { if len(newSSHKey) == 0 {
log.Debugf("no new SSH key provided for peer %s, skipping update", peer.ID) log.Debugf("no new SSH key provided for peer %s, skipping update", peer.ID)
return peer, nil 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. // 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) unlock := am.Store.AcquireAccountLock(accountID)
defer unlock() 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) 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) { if peer.UpdateMetaIfNew(meta) {
account.UpdatePeer(peer) account.UpdatePeer(peer)
return peer, true return peer, true

View File

@ -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
}

View File

@ -8,6 +8,8 @@ import (
"github.com/rs/xid" "github.com/rs/xid"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes" "golang.zx2c4.com/wireguard/wgctrl/wgtypes"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
) )
func TestPeer_LoginExpired(t *testing.T) { func TestPeer_LoginExpired(t *testing.T) {
@ -52,7 +54,7 @@ func TestPeer_LoginExpired(t *testing.T) {
for _, c := range tt { for _, c := range tt {
t.Run(c.name, func(t *testing.T) { t.Run(c.name, func(t *testing.T) {
peer := &Peer{ peer := &nbpeer.Peer{
LoginExpirationEnabled: c.expirationEnabled, LoginExpirationEnabled: c.expirationEnabled,
LastLogin: c.lastLogin, LastLogin: c.lastLogin,
UserID: userID, UserID: userID,
@ -90,9 +92,9 @@ func TestAccountManager_GetNetworkMap(t *testing.T) {
return return
} }
peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: peerKey1.PublicKey().String(), Key: peerKey1.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-1"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err) t.Errorf("expecting peer to be added, got failure %v", err)
@ -104,9 +106,9 @@ func TestAccountManager_GetNetworkMap(t *testing.T) {
t.Fatal(err) t.Fatal(err)
return return
} }
_, _, err = manager.AddPeer(setupKey.Key, "", &Peer{ _, _, err = manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: peerKey2.PublicKey().String(), Key: peerKey2.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-2"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"},
}) })
if err != nil { if err != nil {
@ -163,9 +165,9 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
return return
} }
peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: peerKey1.PublicKey().String(), Key: peerKey1.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-1"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err) t.Errorf("expecting peer to be added, got failure %v", err)
@ -177,9 +179,9 @@ func TestAccountManager_GetNetworkMapWithPolicy(t *testing.T) {
t.Fatal(err) t.Fatal(err)
return return
} }
peer2, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer2, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: peerKey2.PublicKey().String(), Key: peerKey2.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-2"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err) t.Errorf("expecting peer to be added, got failure %v", err)
@ -339,9 +341,9 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) {
return return
} }
peer1, _, err := manager.AddPeer(setupKey.Key, "", &Peer{ peer1, _, err := manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: peerKey1.PublicKey().String(), Key: peerKey1.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-1"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-1"},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err) t.Errorf("expecting peer to be added, got failure %v", err)
@ -353,9 +355,9 @@ func TestAccountManager_GetPeerNetwork(t *testing.T) {
t.Fatal(err) t.Fatal(err)
return return
} }
_, _, err = manager.AddPeer(setupKey.Key, "", &Peer{ _, _, err = manager.AddPeer(setupKey.Key, "", &nbpeer.Peer{
Key: peerKey2.PublicKey().String(), Key: peerKey2.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-2"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"},
}) })
if err != nil { if err != nil {
@ -409,9 +411,9 @@ func TestDefaultAccountManager_GetPeer(t *testing.T) {
return return
} }
peer1, _, err := manager.AddPeer("", someUser, &Peer{ peer1, _, err := manager.AddPeer("", someUser, &nbpeer.Peer{
Key: peerKey1.PublicKey().String(), Key: peerKey1.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-2"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"},
}) })
if err != nil { if err != nil {
t.Errorf("expecting peer to be added, got failure %v", err) 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 // 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(), Key: peerKey2.PublicKey().String(),
Meta: PeerSystemMeta{Hostname: "test-peer-2"}, Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer-2"},
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -5,10 +5,12 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/netbirdio/management-integrations/additions"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/proto"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/management/server/status"
) )
@ -205,7 +207,7 @@ type FirewallRule struct {
// getPeerConnectionResources for a given peer // getPeerConnectionResources for a given peer
// //
// This function returns the list of peers and firewall rules that are applicable to 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() generateResources, getAccumulatedResources := a.connResourcesGenerator()
for _, policy := range a.Policies { for _, policy := range a.Policies {
if !policy.Enabled { if !policy.Enabled {
@ -219,6 +221,8 @@ func (a *Account) getPeerConnectionResources(peerID string) ([]*Peer, []*Firewal
sourcePeers, peerInSources := getAllPeersFromGroups(a, rule.Sources, peerID) sourcePeers, peerInSources := getAllPeersFromGroups(a, rule.Sources, peerID)
destinationPeers, peerInDestinations := getAllPeersFromGroups(a, rule.Destinations, peerID) destinationPeers, peerInDestinations := getAllPeersFromGroups(a, rule.Destinations, peerID)
sourcePeers = additions.ValidatePeers(sourcePeers)
destinationPeers = additions.ValidatePeers(destinationPeers)
if rule.Bidirectional { if rule.Bidirectional {
if peerInSources { 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. // 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 // 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. // 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{}) rulesExists := make(map[string]struct{})
peersExists := make(map[string]struct{}) peersExists := make(map[string]struct{})
rules := make([]*FirewallRule, 0) rules := make([]*FirewallRule, 0)
peers := make([]*Peer, 0) peers := make([]*nbpeer.Peer, 0)
all, err := a.GetGroupAll() all, err := a.GetGroupAll()
if err != nil { if err != nil {
@ -259,7 +263,7 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), fun
all = &Group{} 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) isAll := (len(all.Peers) - 1) == len(groupPeers)
for _, peer := range groupPeers { for _, peer := range groupPeers {
if peer == nil { if peer == nil {
@ -299,7 +303,7 @@ func (a *Account) connResourcesGenerator() (func(*PolicyRule, []*Peer, int), fun
rules = append(rules, &pr) rules = append(rules, &pr)
} }
} }
}, func() ([]*Peer, []*FirewallRule) { }, func() ([]*nbpeer.Peer, []*FirewallRule) {
return peers, rules return peers, rules
} }
} }
@ -478,9 +482,9 @@ func toProtocolFirewallRules(update []*FirewallRule) []*proto.FirewallRule {
// getAllPeersFromGroups for given peer ID and list of groups // getAllPeersFromGroups for given peer ID and list of groups
// //
// Returns list of peers and boolean indicating if peer is in any of the 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 peerInGroups := false
filteredPeers := make([]*Peer, 0, len(groups)) filteredPeers := make([]*nbpeer.Peer, 0, len(groups))
for _, g := range groups { for _, g := range groups {
group, ok := account.Groups[g] group, ok := account.Groups[g]
if !ok { if !ok {

View File

@ -7,11 +7,13 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
) )
func TestAccount_getPeersByPolicy(t *testing.T) { func TestAccount_getPeersByPolicy(t *testing.T) {
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peerA": { "peerA": {
ID: "peerA", ID: "peerA",
IP: net.ParseIP("100.65.14.88"), IP: net.ParseIP("100.65.14.88"),
@ -255,7 +257,7 @@ func TestAccount_getPeersByPolicy(t *testing.T) {
func TestAccount_getPeersByPolicyDirect(t *testing.T) { func TestAccount_getPeersByPolicyDirect(t *testing.T) {
account := &Account{ account := &Account{
Peers: map[string]*Peer{ Peers: map[string]*nbpeer.Peer{
"peerA": { "peerA": {
ID: "peerA", ID: "peerA",
IP: net.ParseIP("100.65.14.88"), IP: net.ParseIP("100.65.14.88"),

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
) )
@ -1045,13 +1046,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
return nil, err return nil, err
} }
peer1 := &Peer{ peer1 := &nbpeer.Peer{
IP: peer1IP, IP: peer1IP,
ID: peer1ID, ID: peer1ID,
Key: peer1Key, Key: peer1Key,
Name: "test-host1@netbird.io", Name: "test-host1@netbird.io",
UserID: userID, UserID: userID,
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host1@netbird.io", Hostname: "test-host1@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",
@ -1070,13 +1071,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
return nil, err return nil, err
} }
peer2 := &Peer{ peer2 := &nbpeer.Peer{
IP: peer2IP, IP: peer2IP,
ID: peer2ID, ID: peer2ID,
Key: peer2Key, Key: peer2Key,
Name: "test-host2@netbird.io", Name: "test-host2@netbird.io",
UserID: userID, UserID: userID,
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host2@netbird.io", Hostname: "test-host2@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",
@ -1095,13 +1096,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
return nil, err return nil, err
} }
peer3 := &Peer{ peer3 := &nbpeer.Peer{
IP: peer3IP, IP: peer3IP,
ID: peer3ID, ID: peer3ID,
Key: peer3Key, Key: peer3Key,
Name: "test-host3@netbird.io", Name: "test-host3@netbird.io",
UserID: userID, UserID: userID,
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host3@netbird.io", Hostname: "test-host3@netbird.io",
GoOS: "darwin", GoOS: "darwin",
Kernel: "Darwin", Kernel: "Darwin",
@ -1120,13 +1121,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
return nil, err return nil, err
} }
peer4 := &Peer{ peer4 := &nbpeer.Peer{
IP: peer4IP, IP: peer4IP,
ID: peer4ID, ID: peer4ID,
Key: peer4Key, Key: peer4Key,
Name: "test-host4@netbird.io", Name: "test-host4@netbird.io",
UserID: userID, UserID: userID,
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host4@netbird.io", Hostname: "test-host4@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",
@ -1145,13 +1146,13 @@ func initTestRouteAccount(t *testing.T, am *DefaultAccountManager) (*Account, er
return nil, err return nil, err
} }
peer5 := &Peer{ peer5 := &nbpeer.Peer{
IP: peer5IP, IP: peer5IP,
ID: peer5ID, ID: peer5ID,
Key: peer5Key, Key: peer5Key,
Name: "test-host4@netbird.io", Name: "test-host4@netbird.io",
UserID: userID, UserID: userID,
Meta: PeerSystemMeta{ Meta: nbpeer.PeerSystemMeta{
Hostname: "test-host4@netbird.io", Hostname: "test-host4@netbird.io",
GoOS: "linux", GoOS: "linux",
Kernel: "Linux", Kernel: "Linux",

View File

@ -14,6 +14,8 @@ import (
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
nbdns "github.com/netbirdio/netbird/dns" 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/status"
"github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/telemetry"
"github.com/netbirdio/netbird/route" "github.com/netbirdio/netbird/route"
@ -59,9 +61,9 @@ func NewSqliteStore(dataDir string, metrics telemetry.AppMetrics) (*SqliteStore,
sql.SetMaxOpenConns(conns) // TODO: make it configurable sql.SetMaxOpenConns(conns) // TODO: make it configurable
err = db.AutoMigrate( err = db.AutoMigrate(
&SetupKey{}, &Peer{}, &User{}, &PersonalAccessToken{}, &Group{}, &Rule{}, &SetupKey{}, &nbpeer.Peer{}, &User{}, &PersonalAccessToken{}, &Group{}, &Rule{},
&Account{}, &Policy{}, &PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{}, &Account{}, &Policy{}, &PolicyRule{}, &route.Route{}, &nbdns.NameServerGroup{},
&installation{}, &installation{}, &account.ExtraSettings{},
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -251,8 +253,8 @@ func (s *SqliteStore) GetInstallationID() string {
return installation.InstallationIDValue return installation.InstallationIDValue
} }
func (s *SqliteStore) SavePeerStatus(accountID, peerID string, peerStatus PeerStatus) error { func (s *SqliteStore) SavePeerStatus(accountID, peerID string, peerStatus nbpeer.PeerStatus) error {
var peer Peer var peer nbpeer.Peer
result := s.db.First(&peer, "account_id = ? and id = ?", accountID, peerID) result := s.db.First(&peer, "account_id = ? and id = ?", accountID, peerID)
if result.Error != nil { if result.Error != nil {
@ -379,7 +381,7 @@ func (s *SqliteStore) GetAccount(accountID string) (*Account, error) {
} }
account.SetupKeysG = nil 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 { for _, peer := range account.PeersG {
account.Peers[peer.ID] = peer.Copy() 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) { 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) result := s.db.Select("account_id").First(&peer, "id = ?", peerID)
if result.Error != nil { if result.Error != nil {
return nil, status.Errorf(status.NotFound, "account not found: index lookup failed") 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) { 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) result := s.db.Select("account_id").First(&peer, "key = ?", peerKey)
if result.Error != nil { if result.Error != nil {

View File

@ -12,6 +12,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/util" "github.com/netbirdio/netbird/util"
) )
@ -37,13 +38,13 @@ func TestSqlite_SaveAccount(t *testing.T) {
account := newAccountWithId("account_id", "testuser", "") account := newAccountWithId("account_id", "testuser", "")
setupKey := GenerateDefaultSetupKey() setupKey := GenerateDefaultSetupKey()
account.SetupKeys[setupKey.Key] = setupKey account.SetupKeys[setupKey.Key] = setupKey
account.Peers["testpeer"] = &Peer{ account.Peers["testpeer"] = &nbpeer.Peer{
Key: "peerkey", Key: "peerkey",
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
} }
err := store.SaveAccount(account) err := store.SaveAccount(account)
@ -52,13 +53,13 @@ func TestSqlite_SaveAccount(t *testing.T) {
account2 := newAccountWithId("account_id2", "testuser2", "") account2 := newAccountWithId("account_id2", "testuser2", "")
setupKey = GenerateDefaultSetupKey() setupKey = GenerateDefaultSetupKey()
account2.SetupKeys[setupKey.Key] = setupKey account2.SetupKeys[setupKey.Key] = setupKey
account2.Peers["testpeer2"] = &Peer{ account2.Peers["testpeer2"] = &nbpeer.Peer{
Key: "peerkey2", Key: "peerkey2",
SetupKey: "peerkeysetupkey2", SetupKey: "peerkeysetupkey2",
IP: net.IP{127, 0, 0, 2}, IP: net.IP{127, 0, 0, 2},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name 2", 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) err = store.SaveAccount(account2)
@ -116,13 +117,13 @@ func TestSqlite_DeleteAccount(t *testing.T) {
account := newAccountWithId("account_id", testUserID, "") account := newAccountWithId("account_id", testUserID, "")
setupKey := GenerateDefaultSetupKey() setupKey := GenerateDefaultSetupKey()
account.SetupKeys[setupKey.Key] = setupKey account.SetupKeys[setupKey.Key] = setupKey
account.Peers["testpeer"] = &Peer{ account.Peers["testpeer"] = &nbpeer.Peer{
Key: "peerkey", Key: "peerkey",
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
} }
account.Users[testUserID] = user account.Users[testUserID] = user
@ -184,19 +185,19 @@ func TestSqlite_SavePeerStatus(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// save status of non-existing peer // 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) err = store.SavePeerStatus(account.Id, "non-existing-peer", newStatus)
assert.Error(t, err) assert.Error(t, err)
// save new status of existing peer // save new status of existing peer
account.Peers["testpeer"] = &Peer{ account.Peers["testpeer"] = &nbpeer.Peer{
Key: "peerkey", Key: "peerkey",
ID: "testpeer", ID: "testpeer",
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: false, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: false, LastSeen: time.Now().UTC()},
} }
err = store.SaveAccount(account) err = store.SaveAccount(account)
@ -291,13 +292,13 @@ func newAccount(store Store, id int) error {
account := newAccountWithId(str, str+"-testuser", "example.com") account := newAccountWithId(str, str+"-testuser", "example.com")
setupKey := GenerateDefaultSetupKey() setupKey := GenerateDefaultSetupKey()
account.SetupKeys[setupKey.Key] = setupKey account.SetupKeys[setupKey.Key] = setupKey
account.Peers["p"+str] = &Peer{ account.Peers["p"+str] = &nbpeer.Peer{
Key: "peerkey" + str, Key: "peerkey" + str,
SetupKey: "peerkeysetupkey", SetupKey: "peerkeysetupkey",
IP: net.IP{127, 0, 0, 1}, IP: net.IP{127, 0, 0, 1},
Meta: PeerSystemMeta{}, Meta: nbpeer.PeerSystemMeta{},
Name: "peer name", Name: "peer name",
Status: &PeerStatus{Connected: true, LastSeen: time.Now().UTC()}, Status: &nbpeer.PeerStatus{Connected: true, LastSeen: time.Now().UTC()},
} }
return store.SaveAccount(account) return store.SaveAccount(account)

View File

@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/telemetry"
) )
@ -31,7 +32,7 @@ type Store interface {
AcquireAccountLock(accountID string) func() AcquireAccountLock(accountID string) func()
// AcquireGlobalLock should attempt to acquire a global lock and return a function that releases the lock // AcquireGlobalLock should attempt to acquire a global lock and return a function that releases the lock
AcquireGlobalLock() func() 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 SaveUserLastLogin(accountID, userID string, lastLogin time.Time) error
// Close should close the store persisting all unsaved data. // Close should close the store persisting all unsaved data.
Close() error Close() error

View File

@ -11,6 +11,7 @@ import (
"github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/idp" "github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/jwtclaims" "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/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 // 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 var peerIDs []string
for _, peer := range peers { for _, peer := range peers {
if peer.Status.LoginExpired { if peer.Status.LoginExpired {