Skip to content

Commit 932c02e

Browse files
authored
[management] Approve all pending peers when peer approval is disabled (#4806)
1 parent abcbde2 commit 932c02e

File tree

9 files changed

+148
-18
lines changed

9 files changed

+148
-18
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ require (
6464
github.com/mdlayher/socket v0.5.1
6565
github.com/miekg/dns v1.1.59
6666
github.com/mitchellh/hashstructure/v2 v2.0.2
67-
github.com/netbirdio/management-integrations/integrations v0.0.0-20251202114414-534cf891e0ba
67+
github.com/netbirdio/management-integrations/integrations v0.0.0-20251203183432-d5400f030847
6868
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45
6969
github.com/okta/okta-sdk-golang/v2 v2.18.0
7070
github.com/oschwald/maxminddb-golang v1.12.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,8 @@ github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6S
368368
github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ=
369369
github.com/netbirdio/ice/v4 v4.0.0-20250908184934-6202be846b51 h1:Ov4qdafATOgGMB1wbSuh+0aAHcwz9hdvB6VZjh1mVMI=
370370
github.com/netbirdio/ice/v4 v4.0.0-20250908184934-6202be846b51/go.mod h1:ZSIbPdBn5hePO8CpF1PekH2SfpTxg1PDhEwtbqZS7R8=
371-
github.com/netbirdio/management-integrations/integrations v0.0.0-20251202114414-534cf891e0ba h1:pD6eygRJ5EYAlgzeNskPU3WqszMz6/HhPuc6/Bc/580=
372-
github.com/netbirdio/management-integrations/integrations v0.0.0-20251202114414-534cf891e0ba/go.mod h1:qzLCKeR253jtsWhfZTt4fyegI5zei32jKZykV+oSQOo=
371+
github.com/netbirdio/management-integrations/integrations v0.0.0-20251203183432-d5400f030847 h1:V0zsYYMU5d2UN1m9zOLPEZCGWpnhtkYcxQVi9Rrx3bY=
372+
github.com/netbirdio/management-integrations/integrations v0.0.0-20251203183432-d5400f030847/go.mod h1:qzLCKeR253jtsWhfZTt4fyegI5zei32jKZykV+oSQOo=
373373
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9axERMVN63dqyFqnvuD+EMJHzM7mNGON8=
374374
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
375375
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=

management/server/account.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -295,10 +295,23 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
295295
return err
296296
}
297297

298-
if err = am.validateSettingsUpdate(ctx, transaction, newSettings, oldSettings, userID, accountID); err != nil {
298+
if err = am.validateSettingsUpdate(ctx, newSettings, oldSettings, userID, accountID); err != nil {
299299
return err
300300
}
301301

302+
if oldSettings.Extra != nil && newSettings.Extra != nil &&
303+
oldSettings.Extra.PeerApprovalEnabled && !newSettings.Extra.PeerApprovalEnabled {
304+
approvedCount, err := transaction.ApproveAccountPeers(ctx, accountID)
305+
if err != nil {
306+
return fmt.Errorf("failed to approve pending peers: %w", err)
307+
}
308+
309+
if approvedCount > 0 {
310+
log.WithContext(ctx).Debugf("approved %d pending peers in account %s", approvedCount, accountID)
311+
updateAccountPeers = true
312+
}
313+
}
314+
302315
if oldSettings.NetworkRange != newSettings.NetworkRange {
303316
if err = am.reallocateAccountPeerIPs(ctx, transaction, accountID, newSettings.NetworkRange); err != nil {
304317
return err
@@ -372,7 +385,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
372385
return newSettings, nil
373386
}
374387

375-
func (am *DefaultAccountManager) validateSettingsUpdate(ctx context.Context, transaction store.Store, newSettings, oldSettings *types.Settings, userID, accountID string) error {
388+
func (am *DefaultAccountManager) validateSettingsUpdate(ctx context.Context, newSettings, oldSettings *types.Settings, userID, accountID string) error {
376389
halfYearLimit := 180 * 24 * time.Hour
377390
if newSettings.PeerLoginExpiration > halfYearLimit {
378391
return status.Errorf(status.InvalidArgument, "peer login expiration can't be larger than 180 days")
@@ -386,17 +399,7 @@ func (am *DefaultAccountManager) validateSettingsUpdate(ctx context.Context, tra
386399
return status.Errorf(status.InvalidArgument, "invalid domain \"%s\" provided for DNS domain", newSettings.DNSDomain)
387400
}
388401

389-
peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "")
390-
if err != nil {
391-
return err
392-
}
393-
394-
peersMap := make(map[string]*nbpeer.Peer, len(peers))
395-
for _, peer := range peers {
396-
peersMap[peer.ID] = peer
397-
}
398-
399-
return am.integratedPeerValidator.ValidateExtraSettings(ctx, newSettings.Extra, oldSettings.Extra, peersMap, userID, accountID)
402+
return am.integratedPeerValidator.ValidateExtraSettings(ctx, newSettings.Extra, oldSettings.Extra, userID, accountID)
400403
}
401404

402405
func (am *DefaultAccountManager) handleRoutingPeerDNSResolutionSettings(ctx context.Context, oldSettings, newSettings *types.Settings, userID, accountID string) {

management/server/account_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,43 @@ func TestDefaultAccountManager_UpdateAccountSettings(t *testing.T) {
20582058
require.Error(t, err, "expecting to fail when providing PeerLoginExpiration more than 180 days")
20592059
}
20602060

2061+
func TestDefaultAccountManager_UpdateAccountSettings_PeerApproval(t *testing.T) {
2062+
manager, _, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
2063+
2064+
accountID := account.Id
2065+
userID := account.Users[account.CreatedBy].Id
2066+
ctx := context.Background()
2067+
2068+
newSettings := account.Settings.Copy()
2069+
newSettings.Extra = &types.ExtraSettings{
2070+
PeerApprovalEnabled: true,
2071+
}
2072+
_, err := manager.UpdateAccountSettings(ctx, accountID, userID, newSettings)
2073+
require.NoError(t, err)
2074+
2075+
peer1.Status.RequiresApproval = true
2076+
peer2.Status.RequiresApproval = true
2077+
peer3.Status.RequiresApproval = false
2078+
2079+
require.NoError(t, manager.Store.SavePeer(ctx, accountID, peer1))
2080+
require.NoError(t, manager.Store.SavePeer(ctx, accountID, peer2))
2081+
require.NoError(t, manager.Store.SavePeer(ctx, accountID, peer3))
2082+
2083+
newSettings = account.Settings.Copy()
2084+
newSettings.Extra = &types.ExtraSettings{
2085+
PeerApprovalEnabled: false,
2086+
}
2087+
_, err = manager.UpdateAccountSettings(ctx, accountID, userID, newSettings)
2088+
require.NoError(t, err)
2089+
2090+
accountPeers, err := manager.Store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "")
2091+
require.NoError(t, err)
2092+
2093+
for _, peer := range accountPeers {
2094+
assert.False(t, peer.Status.RequiresApproval, "peer %s should not require approval after disabling peer approval", peer.ID)
2095+
}
2096+
}
2097+
20612098
func TestAccount_GetExpiredPeers(t *testing.T) {
20622099
type test struct {
20632100
name string

management/server/integrated_validator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ type MockIntegratedValidator struct {
127127
ValidatePeerFunc func(_ context.Context, update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *types.ExtraSettings) (*nbpeer.Peer, bool, error)
128128
}
129129

130-
func (a MockIntegratedValidator) ValidateExtraSettings(_ context.Context, newExtraSettings *types.ExtraSettings, oldExtraSettings *types.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error {
130+
func (a MockIntegratedValidator) ValidateExtraSettings(_ context.Context, newExtraSettings *types.ExtraSettings, oldExtraSettings *types.ExtraSettings, userID string, accountID string) error {
131131
return nil
132132
}
133133

management/server/integrations/integrated_validator/interface.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
// IntegratedValidator interface exists to avoid the circle dependencies
1212
type IntegratedValidator interface {
13-
ValidateExtraSettings(ctx context.Context, newExtraSettings *types.ExtraSettings, oldExtraSettings *types.ExtraSettings, peers map[string]*nbpeer.Peer, userID string, accountID string) error
13+
ValidateExtraSettings(ctx context.Context, newExtraSettings *types.ExtraSettings, oldExtraSettings *types.ExtraSettings, userID string, accountID string) error
1414
ValidatePeer(ctx context.Context, update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *types.ExtraSettings) (*nbpeer.Peer, bool, error)
1515
PreparePeer(ctx context.Context, accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *types.ExtraSettings, temporary bool) *nbpeer.Peer
1616
IsNotValidPeer(ctx context.Context, accountID string, peer *nbpeer.Peer, peersGroup []string, extraSettings *types.ExtraSettings) (bool, bool, error)

management/server/store/sql_store.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,18 @@ func (s *SqlStore) SavePeerLocation(ctx context.Context, accountID string, peerW
412412
return nil
413413
}
414414

415+
// ApproveAccountPeers marks all peers that currently require approval in the given account as approved.
416+
func (s *SqlStore) ApproveAccountPeers(ctx context.Context, accountID string) (int, error) {
417+
result := s.db.Model(&nbpeer.Peer{}).
418+
Where("account_id = ? AND peer_status_requires_approval = ?", accountID, true).
419+
Update("peer_status_requires_approval", false)
420+
if result.Error != nil {
421+
return 0, status.Errorf(status.Internal, "failed to approve pending account peers: %v", result.Error)
422+
}
423+
424+
return int(result.RowsAffected), nil
425+
}
426+
415427
// SaveUsers saves the given list of users to the database.
416428
func (s *SqlStore) SaveUsers(ctx context.Context, users []*types.User) error {
417429
if len(users) == 0 {

management/server/store/sql_store_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3717,3 +3717,80 @@ func TestSqlStore_GetPeersByGroupIDs(t *testing.T) {
37173717
})
37183718
}
37193719
}
3720+
3721+
func TestSqlStore_ApproveAccountPeers(t *testing.T) {
3722+
runTestForAllEngines(t, "", func(t *testing.T, store Store) {
3723+
accountID := "test-account"
3724+
ctx := context.Background()
3725+
3726+
account := newAccountWithId(ctx, accountID, "testuser", "example.com")
3727+
err := store.SaveAccount(ctx, account)
3728+
require.NoError(t, err)
3729+
3730+
peers := []*nbpeer.Peer{
3731+
{
3732+
ID: "peer1",
3733+
AccountID: accountID,
3734+
DNSLabel: "peer1.netbird.cloud",
3735+
Key: "peer1-key",
3736+
IP: net.ParseIP("100.64.0.1"),
3737+
Status: &nbpeer.PeerStatus{
3738+
RequiresApproval: true,
3739+
LastSeen: time.Now().UTC(),
3740+
},
3741+
},
3742+
{
3743+
ID: "peer2",
3744+
AccountID: accountID,
3745+
DNSLabel: "peer2.netbird.cloud",
3746+
Key: "peer2-key",
3747+
IP: net.ParseIP("100.64.0.2"),
3748+
Status: &nbpeer.PeerStatus{
3749+
RequiresApproval: true,
3750+
LastSeen: time.Now().UTC(),
3751+
},
3752+
},
3753+
{
3754+
ID: "peer3",
3755+
AccountID: accountID,
3756+
DNSLabel: "peer3.netbird.cloud",
3757+
Key: "peer3-key",
3758+
IP: net.ParseIP("100.64.0.3"),
3759+
Status: &nbpeer.PeerStatus{
3760+
RequiresApproval: false,
3761+
LastSeen: time.Now().UTC(),
3762+
},
3763+
},
3764+
}
3765+
3766+
for _, peer := range peers {
3767+
err = store.AddPeerToAccount(ctx, peer)
3768+
require.NoError(t, err)
3769+
}
3770+
3771+
t.Run("approve all pending peers", func(t *testing.T) {
3772+
count, err := store.ApproveAccountPeers(ctx, accountID)
3773+
require.NoError(t, err)
3774+
assert.Equal(t, 2, count)
3775+
3776+
allPeers, err := store.GetAccountPeers(ctx, LockingStrengthNone, accountID, "", "")
3777+
require.NoError(t, err)
3778+
3779+
for _, peer := range allPeers {
3780+
assert.False(t, peer.Status.RequiresApproval, "peer %s should not require approval", peer.ID)
3781+
}
3782+
})
3783+
3784+
t.Run("no peers to approve", func(t *testing.T) {
3785+
count, err := store.ApproveAccountPeers(ctx, accountID)
3786+
require.NoError(t, err)
3787+
assert.Equal(t, 0, count)
3788+
})
3789+
3790+
t.Run("non-existent account", func(t *testing.T) {
3791+
count, err := store.ApproveAccountPeers(ctx, "non-existent")
3792+
require.NoError(t, err)
3793+
assert.Equal(t, 0, count)
3794+
})
3795+
})
3796+
}

management/server/store/store.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ type Store interface {
143143
SavePeer(ctx context.Context, accountID string, peer *nbpeer.Peer) error
144144
SavePeerStatus(ctx context.Context, accountID, peerID string, status nbpeer.PeerStatus) error
145145
SavePeerLocation(ctx context.Context, accountID string, peer *nbpeer.Peer) error
146+
ApproveAccountPeers(ctx context.Context, accountID string) (int, error)
146147
DeletePeer(ctx context.Context, accountID string, peerID string) error
147148

148149
GetSetupKeyBySecret(ctx context.Context, lockStrength LockingStrength, key string) (*types.SetupKey, error)

0 commit comments

Comments
 (0)