Merge remote-tracking branch 'origin/main' into fix/pkce-flow-session-extend

# Conflicts:
#	shared/management/proto/management.pb.go
This commit is contained in:
Zoltán Papp
2026-10-07 13:54:40 +02:00
213 changed files with 9278 additions and 8536 deletions
@@ -24,7 +24,6 @@ import (
"github.com/netbirdio/netbird/management/internals/shared/requestbuffer"
"github.com/netbirdio/netbird/management/server/account"
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/posture"
"github.com/netbirdio/netbird/management/server/settings"
@@ -60,8 +59,6 @@ type Controller struct {
requestBuffer account.RequestBuffer
proxyController port_forwarding.Controller
integratedPeerValidator integrated_validator.IntegratedValidator
serverSupportedSyncMessageVersion sharedgrpc.SyncMessageVersion
@@ -87,7 +84,7 @@ type bufferAffectedUpdate struct {
var _ network_map.Controller = (*Controller)(nil)
func NewController(ctx context.Context, store store.Store, metrics telemetry.AppMetrics, peersUpdateManager network_map.PeersUpdateManager, requestBuffer account.RequestBuffer, integratedPeerValidator integrated_validator.IntegratedValidator, settingsManager settings.Manager, dnsDomain string, proxyController port_forwarding.Controller, ephemeralPeersManager ephemeral.Manager, config *config.Config, nmdataStore *networkmapdb.NetworkMapDBStoreImpl) *Controller {
func NewController(ctx context.Context, store store.Store, metrics telemetry.AppMetrics, peersUpdateManager network_map.PeersUpdateManager, requestBuffer account.RequestBuffer, integratedPeerValidator integrated_validator.IntegratedValidator, settingsManager settings.Manager, dnsDomain string, ephemeralPeersManager ephemeral.Manager, config *config.Config, nmdataStore *networkmapdb.NetworkMapDBStoreImpl) *Controller {
nMetrics, err := newMetrics(metrics.UpdateChannelMetrics())
if err != nil {
log.Fatal(fmt.Errorf("error creating metrics: %w", err))
@@ -104,11 +101,10 @@ func NewController(ctx context.Context, store store.Store, metrics telemetry.App
dnsDomain: dnsDomain,
config: config,
proxyController: proxyController,
EphemeralPeersManager: ephemeralPeersManager,
serverSupportedSyncMessageVersion: sharedgrpc.SyncMessageVersionFromConfig(config.HighestSupportedSyncMessageVersion),
perAccountServerSupportedSyncMessageVersions: sharedgrpc.SyncMessageVersionsFromMap(config.PerAccountHighestSupportedSyncMessageVersion),
nmdataStore: nmdataStore,
nmdataStore: nmdataStore,
}
if nmdataStore != nil {
@@ -226,12 +222,6 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
routers := account.GetResourceRoutersMap()
groupIDToUserIDs := account.GetActiveGroupUsers()
proxyNetworkMaps, err := c.proxyController.GetProxyNetworkMapsAll(ctx, accountID, account.Peers)
if err != nil {
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
return fmt.Errorf("failed to get proxy network maps: %v", err)
}
extraSetting, err := c.settingsManager.GetExtraSettings(ctx, accountID)
if err != nil {
return fmt.Errorf("failed to get flow enabled status: %v", err)
@@ -273,7 +263,6 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
start = time.Now()
peerGroups := account.GetPeerGroups(p.ID)
proxyNetworkMap := proxyNetworkMaps[p.ID]
var update *proto.SyncResponse
commonSyncMessageVersion := sharedgrpc.HighestCommonSyncMessageVersion(
@@ -294,10 +283,7 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
c.metrics.CountCalcPeerNetworkMapDuration(time.Since(start))
start = time.Now()
// proxyNetworkMap rides the envelope as a ProxyPatch sidecar;
// the client merges it into Calculate()'s output the same
// way the legacy server did via NetworkMap.Merge.
update = grpc.ToComponentSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, types.TwinPeer(p), nil, nil, components, proxyNetworkMap, dnsDomain, postureChecks, types.TwinAccountSettings(account.Settings), extraSetting, maps.Keys(peerGroups), dnsFwdPort)
update = grpc.ToComponentSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, types.TwinPeer(p), nil, nil, components, dnsDomain, postureChecks, types.TwinAccountSettings(account.Settings), extraSetting, maps.Keys(peerGroups), dnsFwdPort)
c.metrics.CountToComponentSyncResponseDuration(time.Since(start))
c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{
@@ -313,10 +299,6 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
c.metrics.CountCalcPeerNetworkMapDuration(time.Since(start))
if proxyNetworkMap != nil {
nmap.Merge(proxyNetworkMap)
}
start = time.Now()
update = grpc.ToSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, types.TwinPeer(p), nil, nil, nmap, dnsDomain, postureChecks, dnsCache, types.TwinAccountSettings(account.Settings), extraSetting, maps.Keys(peerGroups), dnsFwdPort)
c.metrics.CountToSyncResponseDuration(time.Since(start))
@@ -451,7 +433,7 @@ func (c *Controller) sendUpdatesFromData(ctx context.Context, accountID string,
c.metrics.CountCalcPeerNetworkMapDuration(time.Since(start))
start = time.Now()
update = grpc.ToComponentSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, p, nil, nil, components, nil, dnsDomain, postureChecks, nmData.AccountSettings, extraSettings, peerGroups, dnsFwdPort)
update = grpc.ToComponentSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, p, nil, nil, components, dnsDomain, postureChecks, nmData.AccountSettings, extraSettings, peerGroups, dnsFwdPort)
c.metrics.CountToComponentSyncResponseDuration(time.Since(start))
c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{
@@ -684,12 +666,6 @@ func (c *Controller) sendUpdateForAffectedPeers(ctx context.Context, accountID s
routers := account.GetResourceRoutersMap()
groupIDToUserIDs := account.GetActiveGroupUsers()
proxyNetworkMaps, err := c.proxyController.GetProxyNetworkMapsAll(ctx, accountID, account.Peers)
if err != nil {
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
return fmt.Errorf("failed to get proxy network maps: %v", err)
}
extraSetting, err := c.settingsManager.GetExtraSettings(ctx, accountID)
if err != nil {
return fmt.Errorf("failed to get flow enabled status: %v", err)
@@ -722,7 +698,6 @@ func (c *Controller) sendUpdateForAffectedPeers(ctx context.Context, accountID s
start = time.Now()
peerGroups := account.GetPeerGroups(p.ID)
proxyNetworkMap := proxyNetworkMaps[p.ID]
var update *proto.SyncResponse
commonSyncMessageVersion := sharedgrpc.HighestCommonSyncMessageVersion(
@@ -743,10 +718,7 @@ func (c *Controller) sendUpdateForAffectedPeers(ctx context.Context, accountID s
c.metrics.CountCalcPeerNetworkMapDuration(time.Since(start))
start = time.Now()
// proxyNetworkMap rides the envelope as a ProxyPatch sidecar;
// the client merges it into Calculate()'s output the same
// way the legacy server did via NetworkMap.Merge.
update = grpc.ToComponentSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, types.TwinPeer(p), nil, nil, components, proxyNetworkMap, dnsDomain, postureChecks, types.TwinAccountSettings(account.Settings), extraSetting, maps.Keys(peerGroups), dnsFwdPort)
update = grpc.ToComponentSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, types.TwinPeer(p), nil, nil, components, dnsDomain, postureChecks, types.TwinAccountSettings(account.Settings), extraSetting, maps.Keys(peerGroups), dnsFwdPort)
c.metrics.CountToComponentSyncResponseDuration(time.Since(start))
c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{
@@ -762,10 +734,6 @@ func (c *Controller) sendUpdateForAffectedPeers(ctx context.Context, accountID s
c.metrics.CountCalcPeerNetworkMapDuration(time.Since(start))
if proxyNetworkMap != nil {
nmap.Merge(proxyNetworkMap)
}
start = time.Now()
update = grpc.ToSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, types.TwinPeer(p), nil, nil, nmap, dnsDomain, postureChecks, dnsCache, types.TwinAccountSettings(account.Settings), extraSetting, maps.Keys(peerGroups), dnsFwdPort)
c.metrics.CountToSyncResponseDuration(time.Since(start))
@@ -843,19 +811,12 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe
return fmt.Errorf("failed to get posture checks for peer %s: %v", peerId, err)
}
proxyNetworkMaps, err := c.proxyController.GetProxyNetworkMaps(ctx, account.Id, peer.ID, account.Peers)
if err != nil {
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
return err
}
accountZones, err := c.repo.GetAccountZones(ctx, account.Id)
if err != nil {
log.WithContext(ctx).Errorf("failed to get account zones: %v", err)
return err
}
proxyNetworkMap := proxyNetworkMaps[peer.ID]
extraSettings, err := c.settingsManager.GetExtraSettings(ctx, peer.AccountID)
if err != nil {
return fmt.Errorf("failed to get extra settings: %v", err)
@@ -881,10 +842,7 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe
components := account.GetPeerNetworkMapComponents(
ctx, peer.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, groupIDToUserIDs)
// proxyNetworkMap rides the envelope as a ProxyPatch sidecar;
// the client merges it into Calculate()'s output the same
// way the legacy server did via NetworkMap.Merge.
update = grpc.ToComponentSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, types.TwinPeer(peer), nil, nil, components, proxyNetworkMap, dnsDomain, postureChecks, types.TwinAccountSettings(account.Settings), extraSettings, maps.Keys(peerGroups), dnsFwdPort)
update = grpc.ToComponentSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, types.TwinPeer(peer), nil, nil, components, dnsDomain, postureChecks, types.TwinAccountSettings(account.Settings), extraSettings, maps.Keys(peerGroups), dnsFwdPort)
c.peersUpdateManager.SendUpdate(ctx, peer.ID, &network_map.UpdateMessage{
Update: update,
@@ -897,10 +855,6 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe
nmap := account.GetPeerNetworkMapFromComponents(
ctx, peer.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, groupIDToUserIDs)
if proxyNetworkMap != nil {
nmap.Merge(proxyNetworkMap)
}
update = grpc.ToSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, types.TwinPeer(peer), nil, nil, nmap, dnsDomain, postureChecks, dnsCache, types.TwinAccountSettings(account.Settings), extraSettings, maps.Keys(peerGroups), dnsFwdPort)
c.peersUpdateManager.SendUpdate(ctx, peer.ID, &network_map.UpdateMessage{
@@ -951,17 +905,16 @@ func (c *Controller) BufferUpdateAccountPeers(ctx context.Context, accountID str
// GetValidatedPeerWithComponents is the components-format counterpart of
// GetValidatedPeerWithMap. It returns raw NetworkMapComponents for capable
// peers along with the proxy NetworkMap fragment (BYOP / port-forwarding
// data the legacy server folds in via NetworkMap.Merge). The gRPC layer
// encodes both into the wire envelope. Callers must gate on capability
// themselves before dispatching here — this method does NOT branch on it.
func (c *Controller) GetValidatedPeerWithComponents(ctx context.Context, isRequiresApproval bool, accountID string, peer *nbpeer.Peer) (*nbpeer.Peer, *types.NetworkMapComponents, *types.NetworkMap, []*nmdata.PostureChecks, int64, error) {
// peers, which the gRPC layer encodes into the wire envelope. Callers must
// gate on capability themselves before dispatching here — this method does
// NOT branch on it.
func (c *Controller) GetValidatedPeerWithComponents(ctx context.Context, isRequiresApproval bool, accountID string, peer *nbpeer.Peer) (*nbpeer.Peer, *types.NetworkMapComponents, []*nmdata.PostureChecks, int64, error) {
if isRequiresApproval {
network, err := c.repo.GetAccountNetwork(ctx, accountID)
if err != nil {
return nil, nil, nil, nil, 0, err
return nil, nil, nil, 0, err
}
return peer, &types.NetworkMapComponents{Network: types.TwinNetwork(network)}, nil, nil, 0, nil
return peer, &types.NetworkMapComponents{Network: types.TwinNetwork(network)}, nil, 0, nil
}
if nmData := c.getNetworkMapData(ctx, accountID); nmData != nil {
@@ -970,39 +923,29 @@ func (c *Controller) GetValidatedPeerWithComponents(ctx context.Context, isRequi
account, err := c.requestBuffer.GetAccountWithBackpressure(ctx, accountID)
if err != nil {
return nil, nil, nil, nil, 0, err
return nil, nil, nil, 0, err
}
// it's possible that the peer gets deleted between the call to "sendInitialSync()" and here, bail out in this case
if _, ok := account.Peers[peer.ID]; !ok {
return nil, nil, nil, nil, 0, fmt.Errorf("peer '%s' no longer exists", peer.ID)
return nil, nil, nil, 0, fmt.Errorf("peer '%s' no longer exists", peer.ID)
}
c.injectAllProxyPolicies(ctx, account)
approvedPeersMap, err := c.integratedPeerValidator.GetValidatedPeers(ctx, account.Id, types.TwinGroups(maps.Values(account.Groups)), types.TwinPeers(maps.Values(account.Peers)), account.Settings.Extra)
if err != nil {
return nil, nil, nil, nil, 0, err
return nil, nil, nil, 0, err
}
postureChecks, err := c.getPeerPostureChecks(account, peer.ID)
if err != nil {
return nil, nil, nil, nil, 0, err
return nil, nil, nil, 0, err
}
accountZones, err := c.repo.GetAccountZones(ctx, account.Id)
if err != nil {
return nil, nil, nil, nil, 0, err
}
// Fetch the proxy network map fragment for this peer alongside the
// components — same single-account-load path the streaming controller
// uses, so initial-sync delivers BYOP/forwarding patches synchronously
// instead of waiting for the next streaming push.
proxyNetworkMaps, err := c.proxyController.GetProxyNetworkMaps(ctx, account.Id, peer.ID, account.Peers)
if err != nil {
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
return nil, nil, nil, nil, 0, err
return nil, nil, nil, 0, err
}
dnsDomain := c.GetDNSDomain(account.Settings)
@@ -1014,13 +957,12 @@ func (c *Controller) GetValidatedPeerWithComponents(ctx context.Context, isRequi
components := account.GetPeerNetworkMapComponents(ctx, peer.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, groupIDToUserIDs)
dnsFwdPort := computeForwarderPort(maps.Values(account.Peers), network_map.DnsForwarderPortMinVersion)
return peer, components, proxyNetworkMaps[peer.ID], postureChecks, dnsFwdPort, nil
return peer, components, postureChecks, dnsFwdPort, nil
}
// getValidatedPeerWithComponentsFromData is the account-free variant of
// GetValidatedPeerWithComponents. The proxy network map fragment is omitted
// like on the other nmdata paths.
func (c *Controller) getValidatedPeerWithComponentsFromData(ctx context.Context, accountID string, peer *nbpeer.Peer, nmData *networkmap.NetworkMapData) (*nbpeer.Peer, *types.NetworkMapComponents, *types.NetworkMap, []*nmdata.PostureChecks, int64, error) {
// GetValidatedPeerWithComponents.
func (c *Controller) getValidatedPeerWithComponentsFromData(ctx context.Context, accountID string, peer *nbpeer.Peer, nmData *networkmap.NetworkMapData) (*nbpeer.Peer, *types.NetworkMapComponents, []*nmdata.PostureChecks, int64, error) {
postureChecks := peerPostureChecksFromData(nmData, peer.ID)
dnsDomain := c.getDNSDomainFromData(nmData.AccountSettings)
@@ -1029,7 +971,7 @@ func (c *Controller) getValidatedPeerWithComponentsFromData(ctx context.Context,
components := nmData.GetPeerNetworkMapComponents(peer.ID, peersCustomZone)
dnsFwdPort := ComputeForwarderPortFromData(nmData.Peers, network_map.DnsForwarderPortMinVersion)
return peer, components, nil, postureChecks, dnsFwdPort, nil
return peer, components, postureChecks, dnsFwdPort, nil
}
// BufferUpdateAffectedPeers accumulates peer IDs and flushes them after the buffer interval.
@@ -1173,22 +1115,11 @@ func (c *Controller) GetValidatedPeerWithMap(ctx context.Context, isRequiresAppr
dnsDomain := c.GetDNSDomain(account.Settings)
peersCustomZone := account.GetPeersCustomZone(ctx, dnsDomain)
proxyNetworkMaps, err := c.proxyController.GetProxyNetworkMaps(ctx, account.Id, peerID, account.Peers)
if err != nil {
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
return nil, nil, 0, err
}
resourcePolicies := account.GetResourcePoliciesMap()
routers := account.GetResourceRoutersMap()
groupIDToUserIDs := account.GetActiveGroupUsers()
networkMap := account.GetPeerNetworkMapFromComponents(ctx, peerID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, groupIDToUserIDs)
proxyNetworkMap, ok := proxyNetworkMaps[peerID]
if ok {
networkMap.Merge(proxyNetworkMap)
}
dnsFwdPort := computeForwarderPort(maps.Values(account.Peers), network_map.DnsForwarderPortMinVersion)
return networkMap, postureChecks, dnsFwdPort, nil
@@ -1457,23 +1388,12 @@ func (c *Controller) GetNetworkMap(ctx context.Context, peerID string) (*types.N
dnsDomain := c.GetDNSDomain(account.Settings)
peersCustomZone := account.GetPeersCustomZone(ctx, dnsDomain)
proxyNetworkMaps, err := c.proxyController.GetProxyNetworkMaps(ctx, account.Id, peerID, account.Peers)
if err != nil {
log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err)
return nil, err
}
c.injectAllProxyPolicies(ctx, account)
resourcePolicies := account.GetResourcePoliciesMap()
routers := account.GetResourceRoutersMap()
groupIDToUserIDs := account.GetActiveGroupUsers()
networkMap := account.GetPeerNetworkMapFromComponents(ctx, peer.ID, peersCustomZone, accountZones, validatedPeers, resourcePolicies, routers, nil, groupIDToUserIDs)
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
if ok {
networkMap.Merge(proxyNetworkMap)
}
return networkMap, nil
}
@@ -122,11 +122,10 @@ func TestGetValidatedPeerWithComponents_DeletedPeer(t *testing.T) {
}
mockrequestBuffer.EXPECT().GetAccountWithBackpressure(gomock.Any(), gomock.Any()).Return(&types.Account{}, nil)
peer, components, netmap, posturechecks, dnsforwardPort, err := c.GetValidatedPeerWithComponents(context.TODO(), false, "test-account-id", &nbpeer.Peer{ID: "test-peer-id"})
peer, components, posturechecks, dnsforwardPort, err := c.GetValidatedPeerWithComponents(context.TODO(), false, "test-account-id", &nbpeer.Peer{ID: "test-peer-id"})
assert.Nil(t, peer)
assert.Nil(t, components)
assert.Nil(t, netmap)
assert.Nil(t, posturechecks)
assert.Equal(t, int64(0), dnsforwardPort)
assert.NotNil(t, err)
@@ -89,6 +89,21 @@ func (mr *MockRepositoryMockRecorder) GetAccountPeers(ctx, accountID any) *gomoc
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountPeers", reflect.TypeOf((*MockRepository)(nil).GetAccountPeers), ctx, accountID)
}
// GetAccountServices mocks base method.
func (m *MockRepository) GetAccountServices(ctx context.Context, accountID string) ([]*service.Service, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetAccountServices", ctx, accountID)
ret0, _ := ret[0].([]*service.Service)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetAccountServices indicates an expected call of GetAccountServices.
func (mr *MockRepositoryMockRecorder) GetAccountServices(ctx, accountID any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountServices", reflect.TypeOf((*MockRepository)(nil).GetAccountServices), ctx, accountID)
}
// GetAccountZones mocks base method.
func (m *MockRepository) GetAccountZones(ctx context.Context, accountID string) ([]*zones.Zone, error) {
m.ctrl.T.Helper()
@@ -24,7 +24,7 @@ type Controller interface {
UpdateAccountPeer(ctx context.Context, accountId string, peerId string) error
BufferUpdateAccountPeers(ctx context.Context, accountID string, reason types.UpdateReason) error
GetValidatedPeerWithMap(ctx context.Context, isRequiresApproval bool, accountID string, peerID string) (*types.NetworkMap, []*nmdata.PostureChecks, int64, error)
GetValidatedPeerWithComponents(ctx context.Context, isRequiresApproval bool, accountID string, p *nbpeer.Peer) (*nbpeer.Peer, *types.NetworkMapComponents, *types.NetworkMap, []*nmdata.PostureChecks, int64, error)
GetValidatedPeerWithComponents(ctx context.Context, isRequiresApproval bool, accountID string, p *nbpeer.Peer) (*nbpeer.Peer, *types.NetworkMapComponents, []*nmdata.PostureChecks, int64, error)
GetDNSDomain(settings *types.Settings) string
StartWarmup(context.Context)
GetNetworkMap(ctx context.Context, peerID string) (*types.NetworkMap, error)
@@ -127,16 +127,15 @@ func (mr *MockControllerMockRecorder) GetNetworkMap(ctx, peerID any) *gomock.Cal
}
// GetValidatedPeerWithComponents mocks base method.
func (m *MockController) GetValidatedPeerWithComponents(ctx context.Context, isRequiresApproval bool, accountID string, p *peer.Peer) (*peer.Peer, *types.NetworkMapComponents, *types.NetworkMap, []*nmdata.PostureChecks, int64, error) {
func (m *MockController) GetValidatedPeerWithComponents(ctx context.Context, isRequiresApproval bool, accountID string, p *peer.Peer) (*peer.Peer, *types.NetworkMapComponents, []*nmdata.PostureChecks, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetValidatedPeerWithComponents", ctx, isRequiresApproval, accountID, p)
ret0, _ := ret[0].(*peer.Peer)
ret1, _ := ret[1].(*types.NetworkMapComponents)
ret2, _ := ret[2].(*types.NetworkMap)
ret3, _ := ret[3].([]*nmdata.PostureChecks)
ret4, _ := ret[4].(int64)
ret5, _ := ret[5].(error)
return ret0, ret1, ret2, ret3, ret4, ret5
ret2, _ := ret[2].([]*nmdata.PostureChecks)
ret3, _ := ret[3].(int64)
ret4, _ := ret[4].(error)
return ret0, ret1, ret2, ret3, ret4
}
// GetValidatedPeerWithComponents indicates an expected call of GetValidatedPeerWithComponents.
@@ -123,7 +123,6 @@ func canonicalize(nm *proto.NetworkMap) {
slices.SortFunc(nm.Routes, cmpRoute)
slices.SortFunc(nm.FirewallRules, cmpFirewallRule)
slices.SortFunc(nm.RoutesFirewallRules, cmpRouteFirewallRule)
slices.SortFunc(nm.ForwardingRules, cmpForwardingRule)
for _, r := range nm.FirewallRules {
slices.SortFunc(r.SourcePrefixes, bytes.Compare)
@@ -353,16 +352,6 @@ func cmpRouteFirewallRule(a, b *proto.RouteFirewallRule) int {
return boolCmp(a.IsDynamic, b.IsDynamic)
}
func cmpForwardingRule(a, b *proto.ForwardingRule) int {
if a == nil || b == nil {
return boolCmp(a == nil, b == nil)
}
if c := cmp.Compare(int32(a.Protocol), int32(b.Protocol)); c != 0 {
return c
}
return bytes.Compare(a.TranslatedAddress, b.TranslatedAddress)
}
func portInfoKey(pi *proto.PortInfo) string {
if pi == nil {
return ""
@@ -243,7 +243,7 @@ func computeMode(t *testing.T, ctx context.Context, mode Mode, nmData *networkma
case ModeEnvelope:
components := nmData.GetPeerNetworkMapComponents(peerID, zone)
peerGroups := maps.Keys(nmData.GetPeerGroups(peerID))
resp := mgmtgrpc.ToComponentSyncResponse(ctx, nil, nil, nil, peer, nil, nil, components, nil,
resp := mgmtgrpc.ToComponentSyncResponse(ctx, nil, nil, nil, peer, nil, nil, components,
dnsDomain, nil, nmData.AccountSettings, nil, peerGroups, dnsFwdPort)
res, err := networkmap.EnvelopeToNetworkMap(ctx, resp.NetworkMapEnvelope, peer.Key, dnsDomain, false)
require.NoError(t, err, "expand envelope")
+1 -10
View File
@@ -5,8 +5,6 @@ import (
log "github.com/sirupsen/logrus"
"github.com/netbirdio/management-integrations/integrations"
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy"
proxymanager "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy/manager"
@@ -20,7 +18,6 @@ import (
"github.com/netbirdio/netbird/management/server/auth"
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
nbjwt "github.com/netbirdio/netbird/shared/auth/jwt"
)
@@ -52,12 +49,6 @@ func (s *BaseServer) IntegratedValidator() integrated_validator.IntegratedValida
})
}
func (s *BaseServer) ProxyController() port_forwarding.Controller {
return Create(s, func() port_forwarding.Controller {
return integrations.NewController(s.Store())
})
}
func (s *BaseServer) SecretsManager() grpc.SecretsManager {
return Create(s, func() grpc.SecretsManager {
secretsManager, err := grpc.NewTimeBasedAuthSecretsManager(s.PeersUpdateManager(), s.Config.TURNConfig, s.Config.Relay, s.SettingsManager(), s.GroupsManager())
@@ -123,7 +114,7 @@ func (s *BaseServer) EphemeralManager() ephemeral.Manager {
func (s *BaseServer) NetworkMapController() network_map.Controller {
return Create(s, func() network_map.Controller {
return nmapcontroller.NewController(context.Background(), s.Store(), s.Metrics(), s.PeersUpdateManager(), s.AccountRequestBuffer(), s.IntegratedValidator(), s.SettingsManager(), s.DNSDomain(), s.ProxyController(), s.EphemeralManager(), s.Config, s.NetworkMapStore())
return nmapcontroller.NewController(context.Background(), s.Store(), s.Metrics(), s.PeersUpdateManager(), s.AccountRequestBuffer(), s.IntegratedValidator(), s.SettingsManager(), s.DNSDomain(), s.EphemeralManager(), s.Config, s.NetworkMapStore())
})
}
+2 -2
View File
@@ -8,6 +8,7 @@ import (
"github.com/netbirdio/management-integrations/integrations"
"github.com/netbirdio/netbird/management/internals/modules/agentnetwork"
"github.com/netbirdio/netbird/management/internals/modules/peers"
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/domain/manager"
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxy"
@@ -20,7 +21,6 @@ import (
recordsManager "github.com/netbirdio/netbird/management/internals/modules/zones/records/manager"
"github.com/netbirdio/netbird/management/server"
"github.com/netbirdio/netbird/management/server/account"
"github.com/netbirdio/netbird/management/internals/modules/agentnetwork"
"github.com/netbirdio/netbird/management/server/geolocation"
"github.com/netbirdio/netbird/management/server/groups"
"github.com/netbirdio/netbird/management/server/idp"
@@ -96,7 +96,7 @@ func (s *BaseServer) PeersManager() peers.Manager {
func (s *BaseServer) AccountManager() account.Manager {
return Create(s, func() account.Manager {
accountManager, err := server.BuildManager(context.Background(), s.Config, s.Store(), s.NetworkMapController(), s.JobManager(), s.IdpManager(), s.mgmtSingleAccModeDomain, s.EventStore(), s.GeoLocationManager(), s.userDeleteFromIDPEnabled, s.IntegratedValidator(), s.Metrics(), s.ProxyController(), s.SettingsManager(), s.PermissionsManager(), s.Config.DisableDefaultPolicy, s.CacheStore())
accountManager, err := server.BuildManager(context.Background(), s.Config, s.Store(), s.NetworkMapController(), s.JobManager(), s.IdpManager(), s.mgmtSingleAccModeDomain, s.EventStore(), s.GeoLocationManager(), s.userDeleteFromIDPEnabled, s.IntegratedValidator(), s.Metrics(), s.SettingsManager(), s.PermissionsManager(), s.Config.DisableDefaultPolicy, s.CacheStore())
if err != nil {
log.Fatalf("failed to create account service: %v", err)
}
@@ -28,10 +28,6 @@ type ComponentsEnvelopeInput struct {
// SshAuth.UserIDClaim when reconstructing the NetworkMap. Empty value
// is OK — client treats empty as "no SshAuth to build".
UserIDClaim string
// ProxyPatch carries pre-expanded NetworkMap fragments injected by
// external controllers (BYOP/port-forwarding). Nil when no proxy data
// is present; encoder skips the field in that case.
ProxyPatch *proto.ProxyPatch
}
// EncodeNetworkMapEnvelope converts NetworkMapComponents into the component
@@ -69,7 +65,6 @@ func EncodeNetworkMapEnvelope(in ComponentsEnvelopeInput) *proto.NetworkMapEnvel
DnsForwarderPort: in.DNSForwarderPort,
UserIdClaim: in.UserIDClaim,
AccountSettings: &proto.AccountSettingsCompact{},
ProxyPatch: in.ProxyPatch,
},
},
}
@@ -101,7 +96,6 @@ func EncodeNetworkMapEnvelope(in ComponentsEnvelopeInput) *proto.NetworkMapEnvel
AccountSettings: toAccountSettingsCompact(c.AccountSettings),
DnsForwarderPort: in.DNSForwarderPort,
UserIdClaim: in.UserIDClaim,
ProxyPatch: in.ProxyPatch,
DnsSettings: enc.encodeDNSSettings(c.DNSSettings),
DnsDomain: in.DNSDomain,
CustomZoneDomain: c.CustomZoneDomain,
@@ -713,66 +713,6 @@ func TestEncodeNetworkMapEnvelope_GroupIDToUserIDs(t *testing.T) {
assert.ElementsMatch(t, []string{"user-4"}, full.GroupIdToUserIds["group-users"].UserIds)
}
func TestToProxyPatch_EmptyInputReturnsNil(t *testing.T) {
assert.Nil(t, toProxyPatch(nil, "netbird.cloud", false, false, false))
assert.Nil(t, toProxyPatch(&types.NetworkMap{}, "netbird.cloud", false, false, false),
"empty NetworkMap (no peers, rules, routes etc) → nil patch so proto3 omits the field")
}
func TestToProxyPatch_PopulatesAllFields(t *testing.T) {
nm := &types.NetworkMap{
Peers: []*nmdata.Peer{{
ID: "ext-peer", Key: testWgKeyA, IP: netip.AddrFrom4([4]byte{100, 64, 0, 9}),
DNSLabel: "extpeer", Meta: nmdata.PeerSystemMeta{WtVersion: "0.40.0"},
}},
FirewallRules: []*types.FirewallRule{{
PeerIP: "100.64.0.9", Action: "accept", Direction: 0, Protocol: "tcp",
}},
}
patch := toProxyPatch(nm, "netbird.cloud", false, false, false)
require.NotNil(t, patch)
assert.Len(t, patch.Peers, 1)
assert.Len(t, patch.FirewallRules, 1)
}
// TestEncodeNetworkMapEnvelope_ProxyPatchPropagated covers the ProxyPatch
// pass-through in both encoder branches (normal path + nil-Components
// graceful-degrade). Guards against a regression that drops `ProxyPatch:`
// from one of the envelope struct literals.
func TestEncodeNetworkMapEnvelope_ProxyPatchPropagated(t *testing.T) {
patch := &proto.ProxyPatch{
ForwardingRules: []*proto.ForwardingRule{{
Protocol: proto.RuleProtocol_TCP,
DestinationPort: &proto.PortInfo{PortSelection: &proto.PortInfo_Port{Port: 80}},
TranslatedAddress: net.IPv4(10, 0, 0, 1).To4(),
TranslatedPort: &proto.PortInfo{PortSelection: &proto.PortInfo_Port{Port: 8080}},
}},
}
t.Run("normal_path", func(t *testing.T) {
c := newTestComponents()
full := EncodeNetworkMapEnvelope(ComponentsEnvelopeInput{
Components: c,
ProxyPatch: patch,
}).GetFull()
require.NotNil(t, full.ProxyPatch, "ProxyPatch must propagate through the normal encode path")
assert.Len(t, full.ProxyPatch.ForwardingRules, 1)
})
t.Run("empty_components_graceful_degrade", func(t *testing.T) {
full := EncodeNetworkMapEnvelope(ComponentsEnvelopeInput{
Components: emptyNetworkMapComponents(),
ProxyPatch: patch,
}).GetFull()
require.NotNil(t, full.ProxyPatch, "ProxyPatch must propagate through the nil-Components branch too")
assert.Len(t, full.ProxyPatch.ForwardingRules, 1)
})
}
func TestEncodeNetworkMapEnvelope_NilComponentsGracefulDegrade(t *testing.T) {
// nil Components → minimal envelope, no crash. Matches the legacy
// behaviour for missing/unvalidated peers.
@@ -9,7 +9,6 @@ import (
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
"github.com/netbirdio/netbird/management/server/types"
sharedgrpc "github.com/netbirdio/netbird/shared/management/grpc"
"github.com/netbirdio/netbird/shared/management/networkmap"
nmdata "github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
"github.com/netbirdio/netbird/shared/management/proto"
)
@@ -34,7 +33,6 @@ func ToComponentSyncResponse(
turnCredentials *Token,
relayCredentials *Token,
components *types.NetworkMapComponents,
proxyPatch *types.NetworkMap,
dnsName string,
checks []*nmdata.PostureChecks,
settings *nmdata.AccountSettingsInfo,
@@ -51,9 +49,6 @@ func ToComponentSyncResponse(
enableSSH := computeSSHEnabledForPeer(components, peer)
peerConfig := toPeerConfig(peer, components.Network, dnsName, settings, httpConfig, deviceFlowConfig, enableSSH, components.ForceRoutingPeerDNSResolution)
includeIPv6 := peer.SupportsIPv6() && peer.IPv6.IsValid()
useSourcePrefixes := peer.SupportsSourcePrefixes()
userIDClaim := auth.DefaultUserIDClaim
if httpConfig != nil && httpConfig.AuthUserIDClaim != "" {
userIDClaim = httpConfig.AuthUserIDClaim
@@ -65,7 +60,6 @@ func ToComponentSyncResponse(
DNSDomain: dnsName,
DNSForwarderPort: dnsFwdPort,
UserIDClaim: userIDClaim,
ProxyPatch: toProxyPatch(proxyPatch, dnsName, includeIPv6, useSourcePrefixes, peer.ProxyMeta.Embedded),
})
resp := &proto.SyncResponse{
@@ -91,43 +85,6 @@ func ToComponentSyncResponse(
return resp
}
// toProxyPatch converts a proxy-injected *types.NetworkMap into the wire
// patch the components envelope ships alongside. Returns nil when there are
// no fragments to merge — proto3 omits a nil message field, so the receiver
// sees no patch and skips the merge step entirely.
//
// We reuse the legacy proto-conversion helpers (toProtocolRoutes,
// toProtocolFirewallRules, toProtocolRoutesFirewallRules,
// appendRemotePeerConfig, ForwardingRule.ToProto) because the proxy
// delivers fragments pre-expanded — there's no raw component shape to
// derive them from. Components purity isn't violated: proxy data isn't
// policy-graph-derived, it's externally injected post-Calculate, so the
// client merges it on top of its locally-computed NetworkMap.
func toProxyPatch(nm *types.NetworkMap, dnsName string, includeIPv6, useSourcePrefixes, localIsProxy bool) *proto.ProxyPatch {
if nm == nil {
return nil
}
if len(nm.Peers) == 0 && len(nm.OfflinePeers) == 0 && len(nm.FirewallRules) == 0 &&
len(nm.Routes) == 0 && len(nm.RoutesFirewallRules) == 0 && len(nm.ForwardingRules) == 0 {
return nil
}
patch := &proto.ProxyPatch{
Peers: networkmap.AppendRemotePeerConfig(nil, nm.Peers, dnsName, includeIPv6, localIsProxy),
OfflinePeers: networkmap.AppendRemotePeerConfig(nil, nm.OfflinePeers, dnsName, includeIPv6, localIsProxy),
FirewallRules: networkmap.ToProtocolFirewallRules(nm.FirewallRules, includeIPv6, useSourcePrefixes),
Routes: networkmap.ToProtocolRoutes(nm.Routes),
RouteFirewallRules: networkmap.ToProtocolRoutesFirewallRules(nm.RoutesFirewallRules),
}
if len(nm.ForwardingRules) > 0 {
patch.ForwardingRules = make([]*proto.ForwardingRule, 0, len(nm.ForwardingRules))
for _, r := range nm.ForwardingRules {
patch.ForwardingRules = append(patch.ForwardingRules, r.ToProto())
}
}
return patch
}
// computeSSHEnabledForPeer mirrors the SSH-server-activation bit that
// Calculate() folds into NetworkMap.EnableSSH. Components-format peers
// receive a freshly-computed PeerConfig.SshConfig.SshEnabled at sync time;
@@ -199,14 +199,6 @@ func ToSyncResponse(ctx context.Context, config *nbconfig.Config, httpConfig *nb
response.NetworkMap.RoutesFirewallRules = routesFirewallRules
response.NetworkMap.RoutesFirewallRulesIsEmpty = len(routesFirewallRules) == 0
if networkMap.ForwardingRules != nil {
forwardingRules := make([]*proto.ForwardingRule, 0, len(networkMap.ForwardingRules))
for _, rule := range networkMap.ForwardingRules {
forwardingRules = append(forwardingRules, rule.ToProto())
}
response.NetworkMap.ForwardingRules = forwardingRules
}
if networkMap.AuthorizedUsers != nil {
hashedUsers, machineUsers := networkmap.BuildAuthorizedUsersProto(ctx, networkMap.AuthorizedUsers)
userIDClaim := auth.DefaultUserIDClaim
+2 -2
View File
@@ -955,12 +955,12 @@ func (s *Server) sendInitialSync(ctx context.Context, peerKey wgtypes.Key, peer
// stops doing duplicate work. Deferred until the client-side
// decoder lands and there's a real deployment of capability=3 peers
// worth optimizing for.
freshPeer, components, proxyPatch, freshPostureChecks, freshDnsFwdPort, err := s.networkMapController.GetValidatedPeerWithComponents(ctx, false, peer.AccountID, peer)
freshPeer, components, freshPostureChecks, freshDnsFwdPort, err := s.networkMapController.GetValidatedPeerWithComponents(ctx, false, peer.AccountID, peer)
if err != nil {
log.WithContext(ctx).Errorf("failed to build components for peer %s on initial sync: %v", peer.ID, err)
return status.Errorf(codes.Internal, "failed to build initial sync envelope")
}
plainResp = ToComponentSyncResponse(ctx, s.config, s.config.HttpConfig, s.config.DeviceAuthorizationFlow, types.TwinPeer(freshPeer), turnToken, relayToken, components, proxyPatch, dnsName, freshPostureChecks, types.TwinAccountSettings(settings), settings.Extra, peerGroups, freshDnsFwdPort)
plainResp = ToComponentSyncResponse(ctx, s.config, s.config.HttpConfig, s.config.DeviceAuthorizationFlow, types.TwinPeer(freshPeer), turnToken, relayToken, components, dnsName, freshPostureChecks, types.TwinAccountSettings(settings), settings.Extra, peerGroups, freshDnsFwdPort)
} else {
plainResp = ToSyncResponse(ctx, s.config, s.config.HttpConfig, s.config.DeviceAuthorizationFlow, types.TwinPeer(peer), turnToken, relayToken, networkMap, dnsName, postureChecks, nil, types.TwinAccountSettings(settings), settings.Extra, peerGroups, dnsFwdPort)
}
+87 -32
View File
@@ -35,7 +35,6 @@ import (
"github.com/netbirdio/netbird/management/server/geolocation"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/permissions"
@@ -84,7 +83,6 @@ type DefaultAccountManager struct {
requestBuffer *AccountRequestBuffer
proxyController port_forwarding.Controller
settingsManager settings.Manager
serviceManager service.Manager
@@ -225,7 +223,6 @@ func BuildManager(
userDeleteFromIDPEnabled bool,
integratedPeerValidator integrated_validator.IntegratedValidator,
metrics telemetry.AppMetrics,
proxyController port_forwarding.Controller,
settingsManager settings.Manager,
permissionsManager permissions.Manager,
disableDefaultPolicy bool,
@@ -253,7 +250,6 @@ func BuildManager(
integratedPeerValidator: integratedPeerValidator,
metrics: metrics,
requestBuffer: NewAccountRequestBuffer(ctx, store),
proxyController: proxyController,
settingsManager: settingsManager,
permissionsManager: permissionsManager,
disableDefaultPolicy: disableDefaultPolicy,
@@ -334,6 +330,9 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
var groupChangesAffectPeers bool
var reloadReverseProxy bool
var effectiveOldNetworkRange netip.Prefix
var ipv6Changed bool
var ipv6Snap *affectedpeers.Snapshot
var ipv6Change affectedpeers.Change
err = am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
var groupsUpdated bool
@@ -379,10 +378,10 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
}
if ipv6SettingsChanged(oldSettings, newSettings) {
if err = am.updatePeerIPv6Addresses(ctx, transaction, accountID, newSettings); err != nil {
if ipv6Change, err = am.applyIPv6SettingsChange(ctx, transaction, accountID, oldSettings, newSettings); err != nil {
return err
}
updateAccountPeers = true
ipv6Changed = true
}
if oldSettings.RoutingPeerDNSResolutionEnabled != newSettings.RoutingPeerDNSResolutionEnabled ||
@@ -419,12 +418,20 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
return err
}
if updateAccountPeers || groupsUpdated {
if updateAccountPeers || groupsUpdated || ipv6Changed {
if err = transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
return err
}
}
// A full account refresh already covers the IPv6 change, so the affected-peers
// snapshot is only needed when nothing account-wide changed.
if ipv6Changed && !updateAccountPeers && !groupChangesAffectPeers {
if ipv6Snap, err = affectedpeers.Load(ctx, transaction, accountID, ipv6Change); err != nil {
return fmt.Errorf("load affected peers: %w", err)
}
}
return nil
})
if err != nil {
@@ -486,13 +493,34 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
}
}
if updateAccountPeers || extraSettingsChanged || groupChangesAffectPeers {
go am.UpdateAccountPeers(ctx, accountID, types.UpdateReason{Resource: types.UpdateResourceAccountSettings, Operation: types.UpdateOperationUpdate})
switch {
case updateAccountPeers || extraSettingsChanged || groupChangesAffectPeers:
go am.UpdateAccountPeers(context.WithoutCancel(ctx), accountID, types.UpdateReason{Resource: types.UpdateResourceAccountSettings, Operation: types.UpdateOperationUpdate})
case ipv6Snap != nil:
am.ExpandAndUpdateAffected(ctx, accountID, ipv6Snap, ipv6Change)
}
return newSettings, nil
}
// applyIPv6SettingsChange reconciles peer IPv6 addresses for new IPv6 settings and
// returns the affected-peers change: peers whose address changed refresh together
// with every peer that reaches them. On a range change every peer holding an address
// also refreshes itself, since its interface prefix comes from the account range even
// when its address stays inside the new one.
func (am *DefaultAccountManager) applyIPv6SettingsChange(ctx context.Context, transaction store.Store, accountID string, oldSettings, newSettings *types.Settings) (affectedpeers.Change, error) {
result, err := am.updatePeerIPv6Addresses(ctx, transaction, accountID, newSettings)
if err != nil {
return affectedpeers.Change{}, err
}
change := affectedpeers.Change{ChangedPeerIDs: result.changed}
if oldSettings.NetworkRangeV6 != newSettings.NetworkRangeV6 {
change.OutputPeerIDs = result.withIPv6
}
return change, nil
}
func ipv6SettingsChanged(old, updated *types.Settings) bool {
if old.NetworkRangeV6 != updated.NetworkRangeV6 {
return true
@@ -1742,9 +1770,11 @@ func (am *DefaultAccountManager) SyncUserJWTGroups(ctx context.Context, userAuth
change.LinkGroups = allGroupChanges
if err = am.reconcileIPv6ForGroupChanges(ctx, transaction, userAuth.AccountId, allGroupChanges); err != nil {
ipv6Changed, err := am.reconcileIPv6ForGroupChanges(ctx, transaction, userAuth.AccountId, allGroupChanges)
if err != nil {
return fmt.Errorf("reconcile IPv6 for group changes: %w", err)
}
change.ChangedPeerIDs = append(change.ChangedPeerIDs, ipv6Changed...)
if err = transaction.IncrementNetworkSerial(ctx, userAuth.AccountId); err != nil {
return fmt.Errorf("error incrementing network serial: %w", err)
@@ -2334,7 +2364,8 @@ func (am *DefaultAccountManager) propagateUserGroupMemberships(ctx context.Conte
return false, false, err
}
if err = am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, updatedGroups); err != nil {
ipv6Changed, err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, updatedGroups)
if err != nil {
return false, false, fmt.Errorf("reconcile IPv6 for group changes: %w", err)
}
@@ -2343,7 +2374,7 @@ func (am *DefaultAccountManager) propagateUserGroupMemberships(ctx context.Conte
return false, false, fmt.Errorf("error checking if group changes affect peers: %w", err)
}
return len(updatedGroups) > 0, peersAffected, nil
return len(updatedGroups) > 0, peersAffected || len(ipv6Changed) > 0, nil
}
// propagateAutoGroupsForUsers adds each user's peers to their AutoGroups where not already present.
@@ -2440,56 +2471,78 @@ func (am *DefaultAccountManager) checkIPv6Collision(ctx context.Context, transac
return nil
}
func (am *DefaultAccountManager) updatePeerIPv6Addresses(ctx context.Context, transaction store.Store, accountID string, settings *types.Settings) error {
// ipv6Reassignment reports the outcome of an IPv6 address reconciliation.
type ipv6Reassignment struct {
// changed are the peers whose IPv6 address was assigned, removed or reallocated.
changed []string
// withIPv6 are all peers holding an IPv6 address after the reconciliation.
withIPv6 []string
}
func (am *DefaultAccountManager) updatePeerIPv6Addresses(ctx context.Context, transaction store.Store, accountID string, settings *types.Settings) (ipv6Reassignment, error) {
peers, err := transaction.GetAccountPeers(ctx, store.LockingStrengthUpdate, accountID, "", "", "")
if err != nil {
return fmt.Errorf("get peers: %w", err)
return ipv6Reassignment{}, fmt.Errorf("get peers: %w", err)
}
network, err := transaction.GetAccountNetwork(ctx, store.LockingStrengthUpdate, accountID)
if err != nil {
return fmt.Errorf("get network: %w", err)
return ipv6Reassignment{}, fmt.Errorf("get network: %w", err)
}
if err := am.ensureIPv6Subnet(ctx, transaction, accountID, settings, network); err != nil {
return err
return ipv6Reassignment{}, err
}
allowedPeers, err := am.buildIPv6AllowedPeers(ctx, transaction, accountID, settings)
if err != nil {
return err
return ipv6Reassignment{}, err
}
v6Prefix, err := netip.ParsePrefix(network.NetV6.String())
if err != nil {
return fmt.Errorf("parse IPv6 prefix: %w", err)
return ipv6Reassignment{}, fmt.Errorf("parse IPv6 prefix: %w", err)
}
if err := am.assignPeerIPv6Addresses(ctx, transaction, accountID, peers, network, allowedPeers, v6Prefix); err != nil {
return err
changed, err := am.assignPeerIPv6Addresses(ctx, transaction, accountID, peers, network, allowedPeers, v6Prefix)
if err != nil {
return ipv6Reassignment{}, err
}
log.WithContext(ctx).Infof("updated IPv6 addresses for %d peers in account %s (groups=%d)",
len(peers), accountID, len(settings.IPv6EnabledGroups))
result := ipv6Reassignment{changed: changed}
for _, peer := range peers {
if peer.IPv6.IsValid() {
result.withIPv6 = append(result.withIPv6, peer.ID)
}
}
return nil
log.WithContext(ctx).Infof("updated IPv6 addresses for %d of %d peers in account %s (groups=%d)",
len(changed), len(peers), accountID, len(settings.IPv6EnabledGroups))
return result, nil
}
// reconcileIPv6ForGroupChanges checks whether the given group IDs overlap with
// the account's IPv6EnabledGroups. If they do, it runs a full IPv6 address
// reconciliation so that peers gaining or losing membership in an IPv6-enabled
// group get their addresses assigned or removed.
func (am *DefaultAccountManager) reconcileIPv6ForGroupChanges(ctx context.Context, transaction store.Store, accountID string, groupIDs []string) error {
// group get their addresses assigned or removed. It returns the peers whose IPv6
// address changed, which callers pass as changed peers so every peer that can
// reach them refreshes.
func (am *DefaultAccountManager) reconcileIPv6ForGroupChanges(ctx context.Context, transaction store.Store, accountID string, groupIDs []string) ([]string, error) {
settings, err := transaction.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
if err != nil {
return fmt.Errorf("get account settings: %w", err)
return nil, fmt.Errorf("get account settings: %w", err)
}
if !ipv6ReconcileNeeded(settings, groupIDs) {
return nil
return nil, nil
}
return am.updatePeerIPv6Addresses(ctx, transaction, accountID, settings)
result, err := am.updatePeerIPv6Addresses(ctx, transaction, accountID, settings)
if err != nil {
return nil, err
}
return result.changed, nil
}
// ipv6ReconcileNeeded reports whether changes to the given groups trigger an IPv6
@@ -2528,7 +2581,7 @@ func (am *DefaultAccountManager) assignPeerIPv6Addresses(
ctx context.Context, transaction store.Store, accountID string,
peers []*nbpeer.Peer, network *types.Network,
allowedPeers map[string]struct{}, v6Prefix netip.Prefix,
) error {
) ([]string, error) {
takenV6 := make(map[netip.Addr]struct{})
for _, peer := range peers {
if _, ok := allowedPeers[peer.ID]; ok && peer.IPv6.IsValid() && network.NetV6.Contains(peer.IPv6.AsSlice()) {
@@ -2536,6 +2589,7 @@ func (am *DefaultAccountManager) assignPeerIPv6Addresses(
}
}
var changed []string
for _, peer := range peers {
_, allowed := allowedPeers[peer.ID]
oldIPv6 := peer.IPv6
@@ -2545,7 +2599,7 @@ func (am *DefaultAccountManager) assignPeerIPv6Addresses(
} else if !peer.IPv6.IsValid() || !network.NetV6.Contains(peer.IPv6.AsSlice()) {
newIP, err := allocateIPv6WithRetry(v6Prefix, takenV6, peer.ID)
if err != nil {
return err
return nil, err
}
peer.IPv6 = newIP
}
@@ -2555,10 +2609,11 @@ func (am *DefaultAccountManager) assignPeerIPv6Addresses(
}
if err := transaction.SavePeer(ctx, accountID, peer); err != nil {
return fmt.Errorf("save peer %s: %w", peer.ID, err)
return nil, fmt.Errorf("save peer %s: %w", peer.ID, err)
}
changed = append(changed, peer.ID)
}
return nil
return changed, nil
}
func allocateIPv6WithRetry(prefix netip.Prefix, taken map[netip.Addr]struct{}, peerID string) (netip.Addr, error) {
+76 -16
View File
@@ -13,6 +13,7 @@ import (
"strings"
"sync"
"testing"
"testing/synctest"
"time"
"github.com/prometheus/client_golang/prometheus/push"
@@ -40,6 +41,7 @@ import (
"github.com/netbirdio/netbird/management/internals/modules/zones"
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
networkmapdbfactory "github.com/netbirdio/netbird/management/internals/network_map_db/factory"
networkmap_sqlite "github.com/netbirdio/netbird/management/internals/network_map_db/sqlite"
"github.com/netbirdio/netbird/management/internals/server/config"
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
nbAccount "github.com/netbirdio/netbird/management/server/account"
@@ -47,7 +49,6 @@ import (
"github.com/netbirdio/netbird/management/server/cache"
"github.com/netbirdio/netbird/management/server/http/testing/testing_tools"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
@@ -1297,7 +1298,9 @@ func TestAccountManager_AddPeerWithUserID(t *testing.T) {
}
func TestAccountManager_NetworkUpdates_SaveGroup(t *testing.T) {
testAccountManager_NetworkUpdates_SaveGroup(t)
runPeerUpdateTest(t, func(t *testing.T) {
testAccountManager_NetworkUpdates_SaveGroup(t)
})
}
func testAccountManager_NetworkUpdates_SaveGroup(t *testing.T) {
@@ -1330,6 +1333,8 @@ func testAccountManager_NetworkUpdates_SaveGroup(t *testing.T) {
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
defer updateManager.CloseChannel(context.Background(), peer1.ID)
settleAffectedUpdates(updMsg)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
@@ -1352,7 +1357,9 @@ func testAccountManager_NetworkUpdates_SaveGroup(t *testing.T) {
}
func TestAccountManager_NetworkUpdates_DeletePolicy(t *testing.T) {
testAccountManager_NetworkUpdates_DeletePolicy(t)
runPeerUpdateTest(t, func(t *testing.T) {
testAccountManager_NetworkUpdates_DeletePolicy(t)
})
}
func testAccountManager_NetworkUpdates_DeletePolicy(t *testing.T) {
@@ -1361,13 +1368,7 @@ func testAccountManager_NetworkUpdates_DeletePolicy(t *testing.T) {
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
defer updateManager.CloseChannel(context.Background(), peer1.ID)
// Ensure that we do not receive an update message before the policy is deleted
time.Sleep(time.Second)
select {
case <-updMsg:
t.Logf("received addPeer update message before policy deletion")
default:
}
settleAffectedUpdates(updMsg)
wg := sync.WaitGroup{}
wg.Add(1)
@@ -1390,7 +1391,9 @@ func testAccountManager_NetworkUpdates_DeletePolicy(t *testing.T) {
}
func TestAccountManager_NetworkUpdates_SavePolicy(t *testing.T) {
testAccountManager_NetworkUpdates_SavePolicy(t)
runPeerUpdateTest(t, func(t *testing.T) {
testAccountManager_NetworkUpdates_SavePolicy(t)
})
}
func testAccountManager_NetworkUpdates_SavePolicy(t *testing.T) {
@@ -1410,6 +1413,8 @@ func testAccountManager_NetworkUpdates_SavePolicy(t *testing.T) {
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
defer updateManager.CloseChannel(context.Background(), peer1.ID)
settleAffectedUpdates(updMsg)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
@@ -1443,7 +1448,9 @@ func testAccountManager_NetworkUpdates_SavePolicy(t *testing.T) {
}
func TestAccountManager_NetworkUpdates_DeletePeer(t *testing.T) {
testAccountManager_NetworkUpdates_DeletePeer(t)
runPeerUpdateTest(t, func(t *testing.T) {
testAccountManager_NetworkUpdates_DeletePeer(t)
})
}
func testAccountManager_NetworkUpdates_DeletePeer(t *testing.T) {
@@ -1482,6 +1489,8 @@ func testAccountManager_NetworkUpdates_DeletePeer(t *testing.T) {
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
defer updateManager.CloseChannel(context.Background(), peer1.ID)
settleAffectedUpdates(updMsg)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
@@ -1503,7 +1512,9 @@ func testAccountManager_NetworkUpdates_DeletePeer(t *testing.T) {
}
func TestAccountManager_NetworkUpdates_DeleteGroup(t *testing.T) {
testAccountManager_NetworkUpdates_DeleteGroup(t)
runPeerUpdateTest(t, func(t *testing.T) {
testAccountManager_NetworkUpdates_DeleteGroup(t)
})
}
func testAccountManager_NetworkUpdates_DeleteGroup(t *testing.T) {
@@ -1550,6 +1561,8 @@ func testAccountManager_NetworkUpdates_DeleteGroup(t *testing.T) {
}
}
settleAffectedUpdates(updMsg)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
@@ -3582,6 +3595,9 @@ func createManagerWithNetworkMapStore(t testing.TB) (*DefaultAccountManager, *up
nmdataStore, err := networkmapdbfactory.NewNetworkMapDBStore(context.Background(), types.SqliteStoreEngine, dataDir, MockIntegratedValidator{}, newSettingsMockManager(t))
require.NoError(t, err)
sqliteStore, ok := nmdataStore.Store.(*networkmap_sqlite.SqliteStore)
require.True(t, ok, "network map store is %T, want *networkmap_sqlite.SqliteStore", nmdataStore.Store)
t.Cleanup(func() { assert.NoError(t, sqliteStore.Db.Close()) })
manager, updateManager, err := buildTestManager(t, store, nmdataStore)
require.NoError(t, err)
@@ -3636,20 +3652,24 @@ func buildTestManager(t testing.TB, store store.Store, nmdataStore *networkmapdb
Return(nil).
AnyTimes()
cacheStore, err := cache.NewStore(ctx, 100*time.Millisecond, 300*time.Millisecond, 100)
// The go-cache janitor only stops via a GC finalizer and would outlive synctest bubbles.
cacheStore, err := cache.NewStore(ctx, 100*time.Millisecond, 0, 100)
if err != nil {
return nil, nil, err
}
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := NewAccountRequestBuffer(ctx, store)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)), &config.Config{}, nmdataStore)
manager, err := BuildManager(ctx, &config.Config{}, store, networkMapController, job.NewJobManager(nil, store, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)), &config.Config{}, nmdataStore)
manager, err := BuildManager(ctx, &config.Config{}, store, networkMapController, job.NewJobManager(nil, store, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
if err != nil {
return nil, nil, err
}
cacheManager := manager.cacheManager
t.Cleanup(func() { assert.NoError(t, cacheManager.Close()) })
proxyGrpcServer := nbgrpc.NewProxyServiceServer(nil, nil, nil, nbgrpc.ProxyOIDCConfig{}, peersManager, nil, nil, proxyManager, nil)
t.Cleanup(proxyGrpcServer.Close)
proxyController, err := proxymanager.NewGRPCController(proxyGrpcServer, noop.Meter{})
if err != nil {
return nil, nil, err
@@ -3743,6 +3763,33 @@ func setupNetworkMapTest(t *testing.T) (*DefaultAccountManager, *update_channel.
// when the channel delivers.
const peerUpdateTimeout = 5 * time.Second
// peerUpdateSettleTime bounds how far settleAffectedUpdates advances the fake clock. It must exceed
// the account request and peer update buffer intervals.
const peerUpdateSettleTime = time.Second
// runPeerUpdateTest runs f inside synctest.Test, so the peer update helpers observe every background
// goroutine of the test, and lets the updates still in flight when f returns finish before the bubble
// ends, since the bubble's clock stops with it.
func runPeerUpdateTest(t *testing.T, f func(t *testing.T)) {
synctest.Test(t, func(t *testing.T) {
defer settleAffectedUpdates()
f(t)
})
}
// settleAffectedUpdates runs the synctest bubble's fake clock past every update buffer interval until
// all goroutines are blocked, then discards the updates already delivered to chans, so the next
// assertion only observes updates from the action under test. It must be called inside synctest.Test.
func settleAffectedUpdates(chans ...<-chan *network_map.UpdateMessage) {
time.Sleep(peerUpdateSettleTime)
synctest.Wait()
for _, ch := range chans {
for len(ch) > 0 {
<-ch
}
}
}
func drainPeerUpdates(ch <-chan *network_map.UpdateMessage) {
for {
select {
@@ -3756,6 +3803,19 @@ func drainPeerUpdates(ch <-chan *network_map.UpdateMessage) {
}
}
// step runs f as one named stage of a test inside synctest.Test, which forbids t.Run, and names the
// stage when it fails the test. Stages share t, so a fatal failure ends the remaining stages as well.
func step(t *testing.T, name string, f func(t *testing.T)) {
t.Helper()
failedBefore := t.Failed()
defer func() {
if !failedBefore && t.Failed() {
t.Logf("step %q failed", name)
}
}()
f(t)
}
func peerShouldNotReceiveUpdate(t *testing.T, updateMessage <-chan *network_map.UpdateMessage) {
t.Helper()
select {
@@ -0,0 +1,243 @@
package server
import (
"context"
"net/netip"
"testing"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/store"
"github.com/netbirdio/netbird/management/server/types"
)
const (
ipv6GroupA = "ipv6-grp-a"
ipv6GroupB = "ipv6-grp-b"
ipv6GroupC = "ipv6-grp-c"
ipv6GroupD = "ipv6-grp-d"
)
// ipv6AffectedTest holds three peers: peer1 in group A, peer2 in group B, peer3 in
// group C, with a single A<->B policy. peer3 is unrelated to peer1 and peer2. Group D
// is empty and referenced by nothing.
type ipv6AffectedTest struct {
manager *DefaultAccountManager
accountID string
peer1, peer2, peer3 *nbpeer.Peer
updMsg1, updMsg2, updMsg3 <-chan *network_map.UpdateMessage
}
func setupIPv6AffectedTest(t *testing.T, ipv6Groups []string) *ipv6AffectedTest {
t.Helper()
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
ctx := context.Background()
accountID := account.Id
policies, err := manager.Store.GetAccountPolicies(ctx, store.LockingStrengthNone, accountID)
require.NoError(t, err)
for _, p := range policies {
require.NoError(t, manager.Store.DeletePolicy(ctx, accountID, p.ID))
}
for _, g := range []*types.Group{
{ID: ipv6GroupA, Name: "IPv6-A", Peers: []string{peer1.ID}},
{ID: ipv6GroupB, Name: "IPv6-B", Peers: []string{peer2.ID}},
{ID: ipv6GroupC, Name: "IPv6-C", Peers: []string{peer3.ID}},
{ID: ipv6GroupD, Name: "IPv6-D"},
} {
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, g))
}
_, err = manager.SavePolicy(ctx, accountID, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{{
Enabled: true,
Sources: []string{ipv6GroupA},
Destinations: []string{ipv6GroupB},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
}},
}, true)
require.NoError(t, err)
// New accounts enable IPv6 for the All group; start from the requested groups.
updateIPv6TestSettings(t, manager, accountID, func(s *types.Settings) {
s.IPv6EnabledGroups = ipv6Groups
})
tc := &ipv6AffectedTest{
manager: manager,
accountID: accountID,
peer1: peer1,
peer2: peer2,
peer3: peer3,
}
tc.updMsg1 = updateManager.CreateChannel(ctx, peer1.ID)
tc.updMsg2 = updateManager.CreateChannel(ctx, peer2.ID)
tc.updMsg3 = updateManager.CreateChannel(ctx, peer3.ID)
t.Cleanup(func() {
updateManager.CloseChannel(ctx, peer1.ID)
updateManager.CloseChannel(ctx, peer2.ID)
updateManager.CloseChannel(ctx, peer3.ID)
})
// The setup changes above dispatch asynchronously and can land after the
// channels open, so drop them before the test acts.
drainPeerUpdates(tc.updMsg1)
drainPeerUpdates(tc.updMsg2)
drainPeerUpdates(tc.updMsg3)
return tc
}
// updateIPv6TestSettings applies mutate to a copy of the current settings, so only
// the mutated fields differ from what is stored.
func updateIPv6TestSettings(t *testing.T, manager *DefaultAccountManager, accountID string, mutate func(*types.Settings)) {
t.Helper()
ctx := context.Background()
current, err := manager.Store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
require.NoError(t, err)
updated := current.Copy()
mutate(updated)
_, err = manager.UpdateAccountSettings(ctx, accountID, userID, updated)
require.NoError(t, err)
}
func (tc *ipv6AffectedTest) peerIPv6(t *testing.T, peerID string) netip.Addr {
t.Helper()
peer, err := tc.manager.Store.GetPeerByID(context.Background(), store.LockingStrengthNone, tc.accountID, peerID)
require.NoError(t, err)
return peer.IPv6
}
func TestAffectedPeers_IPv6GroupEnabled_RefreshesOnlyReachablePeers(t *testing.T) {
tc := setupIPv6AffectedTest(t, nil)
updateIPv6TestSettings(t, tc.manager, tc.accountID, func(s *types.Settings) {
s.IPv6EnabledGroups = []string{ipv6GroupA}
})
require.True(t, tc.peerIPv6(t, tc.peer1.ID).IsValid(), "peer1 should get an IPv6 address")
peerShouldReceiveUpdate(t, tc.updMsg1)
peerShouldReceiveUpdate(t, tc.updMsg2)
peerShouldNotReceiveUpdate(t, tc.updMsg3)
}
func TestAffectedPeers_IPv6GroupDisabled_RefreshesOnlyReachablePeers(t *testing.T) {
tc := setupIPv6AffectedTest(t, []string{ipv6GroupA})
require.True(t, tc.peerIPv6(t, tc.peer1.ID).IsValid(), "peer1 should start with an IPv6 address")
updateIPv6TestSettings(t, tc.manager, tc.accountID, func(s *types.Settings) {
s.IPv6EnabledGroups = []string{}
})
require.False(t, tc.peerIPv6(t, tc.peer1.ID).IsValid(), "peer1 should lose its IPv6 address")
peerShouldReceiveUpdate(t, tc.updMsg1)
peerShouldReceiveUpdate(t, tc.updMsg2)
peerShouldNotReceiveUpdate(t, tc.updMsg3)
}
// Widening the IPv6 range keeps peer addresses, but each holder's interface prefix
// comes from the range, so holders refresh while peers that only reach them do not.
func TestAffectedPeers_IPv6RangeWidened_RefreshesAddressHolders(t *testing.T) {
tc := setupIPv6AffectedTest(t, []string{ipv6GroupA})
oldIPv6 := tc.peerIPv6(t, tc.peer1.ID)
require.True(t, oldIPv6.IsValid(), "peer1 should start with an IPv6 address")
// The range is allocated on the account network; settings may leave it empty.
network, err := tc.manager.Store.GetAccountNetwork(context.Background(), store.LockingStrengthNone, tc.accountID)
require.NoError(t, err)
current := prefixFromIPNet(network.NetV6)
require.True(t, current.IsValid(), "account should have an IPv6 range")
widened := netip.PrefixFrom(current.Addr(), current.Bits()-8).Masked()
updateIPv6TestSettings(t, tc.manager, tc.accountID, func(s *types.Settings) {
s.NetworkRangeV6 = widened
})
require.Equal(t, oldIPv6, tc.peerIPv6(t, tc.peer1.ID), "peer1 should keep its address inside the widened range")
peerShouldReceiveUpdate(t, tc.updMsg1)
peerShouldNotReceiveUpdate(t, tc.updMsg2)
peerShouldNotReceiveUpdate(t, tc.updMsg3)
}
func TestAffectedPeers_IPv4RangeChange_RefreshesWholeAccount(t *testing.T) {
tc := setupIPv6AffectedTest(t, nil)
updateIPv6TestSettings(t, tc.manager, tc.accountID, func(s *types.Settings) {
s.NetworkRange = netip.MustParsePrefix("100.70.0.0/16")
})
peerShouldReceiveUpdate(t, tc.updMsg1)
peerShouldReceiveUpdate(t, tc.updMsg2)
peerShouldReceiveUpdate(t, tc.updMsg3)
}
func TestAffectedPeers_IPv6WithAccountWideChange_RefreshesWholeAccount(t *testing.T) {
tc := setupIPv6AffectedTest(t, nil)
updateIPv6TestSettings(t, tc.manager, tc.accountID, func(s *types.Settings) {
s.IPv6EnabledGroups = []string{ipv6GroupA}
s.LazyConnectionEnabled = !s.LazyConnectionEnabled
})
peerShouldReceiveUpdate(t, tc.updMsg1)
peerShouldReceiveUpdate(t, tc.updMsg2)
peerShouldReceiveUpdate(t, tc.updMsg3)
}
// Joining an IPv6-enabled group that no policy references gives peer1 an address.
// peer2 reaches peer1 through group A, not through the joined group, and must still
// learn the new address.
func TestAffectedPeers_GroupAddPeerIPv6_RefreshesPeersReachingThroughOtherGroups(t *testing.T) {
tc := setupIPv6AffectedTest(t, []string{ipv6GroupD})
require.NoError(t, tc.manager.GroupAddPeer(context.Background(), tc.accountID, ipv6GroupD, tc.peer1.ID))
require.True(t, tc.peerIPv6(t, tc.peer1.ID).IsValid(), "peer1 should get an IPv6 address")
peerShouldReceiveUpdate(t, tc.updMsg1)
peerShouldReceiveUpdate(t, tc.updMsg2)
peerShouldNotReceiveUpdate(t, tc.updMsg3)
}
func TestAffectedPeers_UpdateGroupIPv6_RefreshesPeersReachingThroughOtherGroups(t *testing.T) {
tc := setupIPv6AffectedTest(t, []string{ipv6GroupD})
require.NoError(t, tc.manager.UpdateGroup(context.Background(), tc.accountID, userID, &types.Group{
ID: ipv6GroupD,
Name: "IPv6-D",
Peers: []string{tc.peer1.ID},
}))
require.True(t, tc.peerIPv6(t, tc.peer1.ID).IsValid(), "peer1 should get an IPv6 address")
peerShouldReceiveUpdate(t, tc.updMsg1)
peerShouldReceiveUpdate(t, tc.updMsg2)
peerShouldNotReceiveUpdate(t, tc.updMsg3)
}
// Deleting an IPv6-enabled group removes its members' addresses after the
// pre-delete snapshot was taken.
func TestAffectedPeers_DeleteIPv6Group_RefreshesFormerMembersAndReachablePeers(t *testing.T) {
tc := setupIPv6AffectedTest(t, []string{ipv6GroupD})
ctx := context.Background()
require.NoError(t, tc.manager.GroupAddPeer(ctx, tc.accountID, ipv6GroupD, tc.peer1.ID))
require.True(t, tc.peerIPv6(t, tc.peer1.ID).IsValid(), "peer1 should get an IPv6 address")
drainPeerUpdates(tc.updMsg1)
drainPeerUpdates(tc.updMsg2)
drainPeerUpdates(tc.updMsg3)
require.NoError(t, tc.manager.DeleteGroup(ctx, tc.accountID, userID, ipv6GroupD))
require.False(t, tc.peerIPv6(t, tc.peer1.ID).IsValid(), "peer1 should lose its IPv6 address")
peerShouldReceiveUpdate(t, tc.updMsg1)
peerShouldReceiveUpdate(t, tc.updMsg2)
peerShouldNotReceiveUpdate(t, tc.updMsg3)
}
+77 -77
View File
@@ -87,93 +87,93 @@ func TestAffectedPeers_AllowedUsersChange_RefreshesSSHDestinations(t *testing.T)
// auto-group change updates only the user's peers and the peers linked to the changed
// group through policies, instead of fanning out to the whole account.
func TestAffectedPeers_SyncUserJWTGroups_OnlyAffectedPeersUpdated(t *testing.T) {
manager, updateManager, account, _, peer2, peer3 := setupNetworkMapTest(t)
ctx := context.Background()
accountID := account.Id
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, account, _, peer2, peer3 := setupNetworkMapTest(t)
ctx := context.Background()
accountID := account.Id
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
userPeer, _, _, _, err := manager.AddPeer(ctx, accountID, "", userID, &nbpeer.Peer{
Key: key.PublicKey().String(),
Meta: nbpeer.PeerSystemMeta{Hostname: "user-peer"},
}, false)
require.NoError(t, err)
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
userPeer, _, _, _, err := manager.AddPeer(ctx, accountID, "", userID, &nbpeer.Peer{
Key: key.PublicKey().String(),
Meta: nbpeer.PeerSystemMeta{Hostname: "user-peer"},
}, false)
require.NoError(t, err)
policies, err := manager.Store.GetAccountPolicies(ctx, store.LockingStrengthNone, accountID)
require.NoError(t, err)
for _, p := range policies {
require.NoError(t, manager.Store.DeletePolicy(ctx, accountID, p.ID))
}
policies, err := manager.Store.GetAccountPolicies(ctx, store.LockingStrengthNone, accountID)
require.NoError(t, err)
for _, p := range policies {
require.NoError(t, manager.Store.DeletePolicy(ctx, accountID, p.ID))
}
account, err = manager.Store.GetAccount(ctx, accountID)
require.NoError(t, err)
account.Settings.JWTGroupsEnabled = true
account.Settings.JWTGroupsClaimName = "groups"
account.Settings.GroupsPropagationEnabled = true
require.NoError(t, manager.Store.SaveAccount(ctx, account))
account, err = manager.Store.GetAccount(ctx, accountID)
require.NoError(t, err)
account.Settings.JWTGroupsEnabled = true
account.Settings.JWTGroupsClaimName = "groups"
account.Settings.GroupsPropagationEnabled = true
require.NoError(t, manager.Store.SaveAccount(ctx, account))
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "jwt-grp", Name: "jwt-linked", Issued: types.GroupIssuedJWT, Peers: []string{}}))
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "jwt-dest", Name: "jwt-dest", Peers: []string{peer2.ID}}))
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "jwt-grp", Name: "jwt-linked", Issued: types.GroupIssuedJWT, Peers: []string{}}))
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "jwt-dest", Name: "jwt-dest", Peers: []string{peer2.ID}}))
_, err = manager.SavePolicy(ctx, accountID, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"jwt-grp"},
Destinations: []string{"jwt-dest"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
_, err = manager.SavePolicy(ctx, accountID, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"jwt-grp"},
Destinations: []string{"jwt-dest"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
},
}, true)
require.NoError(t, err)
updUser := updateManager.CreateChannel(ctx, userPeer.ID)
upd2 := updateManager.CreateChannel(ctx, peer2.ID)
upd3 := updateManager.CreateChannel(ctx, peer3.ID)
t.Cleanup(func() {
updateManager.CloseChannel(ctx, userPeer.ID)
updateManager.CloseChannel(ctx, peer2.ID)
updateManager.CloseChannel(ctx, peer3.ID)
})
userAuth := auth.UserAuth{
AccountId: accountID,
UserId: userID,
Groups: []string{"jwt-linked"},
}
t.Run("adding JWT group updates only linked peers", func(t *testing.T) {
drainPeerUpdates(updUser)
drainPeerUpdates(upd2)
drainPeerUpdates(upd3)
require.NoError(t, manager.SyncUserJWTGroups(ctx, userAuth))
peerShouldReceiveUpdate(t, updUser)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
user, err := manager.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
}, true)
require.NoError(t, err)
assert.Contains(t, user.AutoGroups, "jwt-grp")
})
t.Run("removing JWT group updates only linked peers", func(t *testing.T) {
drainPeerUpdates(updUser)
drainPeerUpdates(upd2)
drainPeerUpdates(upd3)
updUser := updateManager.CreateChannel(ctx, userPeer.ID)
upd2 := updateManager.CreateChannel(ctx, peer2.ID)
upd3 := updateManager.CreateChannel(ctx, peer3.ID)
t.Cleanup(func() {
updateManager.CloseChannel(ctx, userPeer.ID)
updateManager.CloseChannel(ctx, peer2.ID)
updateManager.CloseChannel(ctx, peer3.ID)
})
userAuth.Groups = nil
require.NoError(t, manager.SyncUserJWTGroups(ctx, userAuth))
userAuth := auth.UserAuth{
AccountId: accountID,
UserId: userID,
Groups: []string{"jwt-linked"},
}
peerShouldReceiveUpdate(t, updUser)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
step(t, "adding JWT group updates only linked peers", func(t *testing.T) {
settleAffectedUpdates(updUser, upd2, upd3)
user, err := manager.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
require.NoError(t, err)
assert.NotContains(t, user.AutoGroups, "jwt-grp")
require.NoError(t, manager.SyncUserJWTGroups(ctx, userAuth))
peerShouldReceiveUpdate(t, updUser)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
user, err := manager.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
require.NoError(t, err)
assert.Contains(t, user.AutoGroups, "jwt-grp")
})
step(t, "removing JWT group updates only linked peers", func(t *testing.T) {
drainPeerUpdates(updUser)
drainPeerUpdates(upd2)
drainPeerUpdates(upd3)
userAuth.Groups = nil
require.NoError(t, manager.SyncUserJWTGroups(ctx, userAuth))
peerShouldReceiveUpdate(t, updUser)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
user, err := manager.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
require.NoError(t, err)
assert.NotContains(t, user.AutoGroups, "jwt-grp")
})
})
}
+101 -97
View File
@@ -24,120 +24,124 @@ import (
// detaching one of them must still refresh that group's policy source peers, even
// though the post-update resource no longer maps to it.
func TestAffectedPeers_E2E_UpdateResource_DetachGroup_RefreshesOldGroupSources(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
// A second resource group + a second source group/peer that reaches the
// resource only through that second group.
const detachGroupID = "rs-detach-grp"
require.NoError(t, s.manager.CreateGroup(ctx, s.accountID, userID, &types.Group{ID: detachGroupID, Name: "rs-detach"}))
// A second resource group + a second source group/peer that reaches the
// resource only through that second group.
const detachGroupID = "rs-detach-grp"
require.NoError(t, s.manager.CreateGroup(ctx, s.accountID, userID, &types.Group{ID: detachGroupID, Name: "rs-detach"}))
const secondSourceGroupID = "rs-source-grp-2"
setupKey, err := s.manager.CreateSetupKey(ctx, s.accountID, "rs-detach-key", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
require.NoError(t, err)
secondSourcePeer := addPeerToAccount(t, s.manager, s.accountID, setupKey.Key)
require.NoError(t, s.manager.CreateGroup(ctx, s.accountID, userID, &types.Group{
ID: secondSourceGroupID, Name: "rs-source-2", Peers: []string{secondSourcePeer.ID},
}))
const secondSourceGroupID = "rs-source-grp-2"
setupKey, err := s.manager.CreateSetupKey(ctx, s.accountID, "rs-detach-key", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
require.NoError(t, err)
secondSourcePeer := addPeerToAccount(t, s.manager, s.accountID, setupKey.Key)
require.NoError(t, s.manager.CreateGroup(ctx, s.accountID, userID, &types.Group{
ID: secondSourceGroupID, Name: "rs-source-2", Peers: []string{secondSourcePeer.ID},
}))
resourcesManager, _, _ := s.managers()
resourcesManager, _, _ := s.managers()
// Attach the resource to the detach group as well: now in [resourceGroup, detachGroup].
_, err = resourcesManager.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/24",
GroupIDs: []string{s.resourceGroupID, detachGroupID},
Enabled: true,
// Attach the resource to the detach group as well: now in [resourceGroup, detachGroup].
_, err = resourcesManager.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/24",
GroupIDs: []string{s.resourceGroupID, detachGroupID},
Enabled: true,
})
require.NoError(t, err)
// Policy granting the second source group access via the detach group.
_, err = s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(secondSourceGroupID, detachGroupID), true)
require.NoError(t, err)
secondSrcCh := s.updateManager.CreateChannel(ctx, secondSourcePeer.ID)
t.Cleanup(func() { s.updateManager.CloseChannel(ctx, secondSourcePeer.ID) })
settleAffectedUpdates(secondSrcCh)
done := make(chan struct{})
go func() {
// Detaching the resource from detachGroup removes the second source's
// access; that source peer must be refreshed even though the post-update
// resource no longer maps to detachGroup.
peerShouldReceiveUpdate(t, secondSrcCh)
close(done)
}()
_, err = resourcesManager.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/24",
GroupIDs: []string{s.resourceGroupID}, // detached detachGroup
Enabled: true,
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: detaching a resource group did not refresh the old group's policy source peer")
}
})
require.NoError(t, err)
// Policy granting the second source group access via the detach group.
_, err = s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(secondSourceGroupID, detachGroupID), true)
require.NoError(t, err)
secondSrcCh := s.updateManager.CreateChannel(ctx, secondSourcePeer.ID)
t.Cleanup(func() { s.updateManager.CloseChannel(ctx, secondSourcePeer.ID) })
settleAffectedUpdates(secondSrcCh)
done := make(chan struct{})
go func() {
// Detaching the resource from detachGroup removes the second source's
// access; that source peer must be refreshed even though the post-update
// resource no longer maps to detachGroup.
peerShouldReceiveUpdate(t, secondSrcCh)
close(done)
}()
_, err = resourcesManager.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/24",
GroupIDs: []string{s.resourceGroupID}, // detached detachGroup
Enabled: true,
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: detaching a resource group did not refresh the old group's policy source peer")
}
}
// TestAffectedPeers_E2E_UpdateRouter_RepointPeer_RefreshesOldRoutingPeer:
// changing router.Peer within the same network must still refresh the OLD routing
// peer, which loses its routing role.
func TestAffectedPeers_E2E_UpdateRouter_RepointPeer_RefreshesOldRoutingPeer(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
_, routersManager, _ := s.managers()
_, routersManager, _ := s.managers()
routers, err := s.manager.Store.GetNetworkRoutersByNetID(ctx, store.LockingStrengthNone, s.accountID, s.networkID)
require.NoError(t, err)
require.Len(t, routers, 1)
router := routers[0]
oldRoutingPeer := router.Peer
require.NotEmpty(t, oldRoutingPeer)
routers, err := s.manager.Store.GetNetworkRoutersByNetID(ctx, store.LockingStrengthNone, s.accountID, s.networkID)
require.NoError(t, err)
require.Len(t, routers, 1)
router := routers[0]
oldRoutingPeer := router.Peer
require.NotEmpty(t, oldRoutingPeer)
// A new peer to become the routing peer in place of the old one.
setupKey, err := s.manager.CreateSetupKey(ctx, s.accountID, "rs-newrouter-key", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
require.NoError(t, err)
newRoutingPeer := addPeerToAccount(t, s.manager, s.accountID, setupKey.Key)
// A new peer to become the routing peer in place of the old one.
setupKey, err := s.manager.CreateSetupKey(ctx, s.accountID, "rs-newrouter-key", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
require.NoError(t, err)
newRoutingPeer := addPeerToAccount(t, s.manager, s.accountID, setupKey.Key)
oldCh := s.updateManager.CreateChannel(ctx, oldRoutingPeer)
t.Cleanup(func() { s.updateManager.CloseChannel(ctx, oldRoutingPeer) })
settleAffectedUpdates(oldCh)
oldCh := s.updateManager.CreateChannel(ctx, oldRoutingPeer)
t.Cleanup(func() { s.updateManager.CloseChannel(ctx, oldRoutingPeer) })
settleAffectedUpdates(oldCh)
done := make(chan struct{})
go func() {
// The old routing peer stops serving the resource and must be refreshed.
peerShouldReceiveUpdate(t, oldCh)
close(done)
}()
done := make(chan struct{})
go func() {
// The old routing peer stops serving the resource and must be refreshed.
peerShouldReceiveUpdate(t, oldCh)
close(done)
}()
_, err = routersManager.UpdateRouter(ctx, userID, &routerTypes.NetworkRouter{
ID: router.ID,
NetworkID: s.networkID,
AccountID: s.accountID,
Peer: newRoutingPeer.ID, // repoint within the same network
Masquerade: true,
Metric: 9999,
Enabled: true,
_, err = routersManager.UpdateRouter(ctx, userID, &routerTypes.NetworkRouter{
ID: router.ID,
NetworkID: s.networkID,
AccountID: s.accountID,
Peer: newRoutingPeer.ID, // repoint within the same network
Masquerade: true,
Metric: 9999,
Enabled: true,
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: re-pointing the router peer did not refresh the old routing peer")
}
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: re-pointing the router peer did not refresh the old routing peer")
}
}
@@ -165,148 +165,154 @@ func (s *routerScenario) createPostureCheckGatedPolicy(t *testing.T, ctx context
}
func TestAffectedPeers_E2E_SavePostureCheck_RefreshesRoutingPeer(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
checkID := s.createPostureCheckGatedPolicy(t, ctx, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID))
checkID := s.createPostureCheckGatedPolicy(t, ctx, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID))
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
unrelatedCh := s.updateManager.CreateChannel(ctx, s.unrelatedPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
s.updateManager.CloseChannel(ctx, s.unrelatedPeerID)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
unrelatedCh := s.updateManager.CreateChannel(ctx, s.unrelatedPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
s.updateManager.CloseChannel(ctx, s.unrelatedPeerID)
})
settleAffectedUpdates(srcCh, routerCh, unrelatedCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
peerShouldNotReceiveUpdate(t, unrelatedCh)
close(done)
}()
_, err := s.manager.SavePostureChecks(ctx, s.accountID, userID, &posture.Checks{
ID: checkID,
Name: "rs-min-version",
Checks: posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{MinVersion: "0.31.0"},
},
}, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: editing a posture check did not refresh source + routing peers")
}
})
settleAffectedUpdates(srcCh, routerCh, unrelatedCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
peerShouldNotReceiveUpdate(t, unrelatedCh)
close(done)
}()
_, err := s.manager.SavePostureChecks(ctx, s.accountID, userID, &posture.Checks{
ID: checkID,
Name: "rs-min-version",
Checks: posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{MinVersion: "0.31.0"},
},
}, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: editing a posture check did not refresh source + routing peers")
}
}
func TestAffectedPeers_E2E_UpdateResource_DestinationResourcePolicy_RefreshesSourcePeer(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByResource(s.sourceGroupID, s.resourceID), true)
require.NoError(t, err)
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByResource(s.sourceGroupID, s.resourceID), true)
require.NoError(t, err)
resourcesManager, _, _ := s.managers()
resourcesManager, _, _ := s.managers()
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
unrelatedCh := s.updateManager.CreateChannel(ctx, s.unrelatedPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
s.updateManager.CloseChannel(ctx, s.unrelatedPeerID)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
unrelatedCh := s.updateManager.CreateChannel(ctx, s.unrelatedPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
s.updateManager.CloseChannel(ctx, s.unrelatedPeerID)
})
settleAffectedUpdates(srcCh, routerCh, unrelatedCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
peerShouldNotReceiveUpdate(t, unrelatedCh)
close(done)
}()
_, err = resourcesManager.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/25",
GroupIDs: []string{s.resourceGroupID},
Enabled: true,
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: updating a DestinationResource-targeted resource did not refresh its policy source peer")
}
})
settleAffectedUpdates(srcCh, routerCh, unrelatedCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
peerShouldNotReceiveUpdate(t, unrelatedCh)
close(done)
}()
_, err = resourcesManager.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/25",
GroupIDs: []string{s.resourceGroupID},
Enabled: true,
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: updating a DestinationResource-targeted resource did not refresh its policy source peer")
}
}
// A disabled sibling router routes to nobody, so updating a resource on its network
// must NOT refresh its peer (the enabled router carries the bridge instead).
func TestAffectedPeers_E2E_UpdateResource_DisabledSiblingRouterNotBridged(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
resourcesManager, routersManager, _ := s.managers()
resourcesManager, routersManager, _ := s.managers()
setupKey, err := s.manager.CreateSetupKey(ctx, s.accountID, "rs-key-disabled", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
require.NoError(t, err)
disabledRouterPeer := addPeerToAccount(t, s.manager, s.accountID, setupKey.Key)
_, err = routersManager.CreateRouter(ctx, userID, &routerTypes.NetworkRouter{
NetworkID: s.networkID,
AccountID: s.accountID,
Peer: disabledRouterPeer.ID,
Masquerade: true,
Metric: 9000,
Enabled: false,
setupKey, err := s.manager.CreateSetupKey(ctx, s.accountID, "rs-key-disabled", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
require.NoError(t, err)
disabledRouterPeer := addPeerToAccount(t, s.manager, s.accountID, setupKey.Key)
_, err = routersManager.CreateRouter(ctx, userID, &routerTypes.NetworkRouter{
NetworkID: s.networkID,
AccountID: s.accountID,
Peer: disabledRouterPeer.ID,
Masquerade: true,
Metric: 9000,
Enabled: false,
})
require.NoError(t, err)
disabledCh := s.updateManager.CreateChannel(ctx, disabledRouterPeer.ID)
enabledCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, disabledRouterPeer.ID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
})
settleAffectedUpdates(disabledCh, enabledCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, enabledCh)
peerShouldNotReceiveUpdate(t, disabledCh)
close(done)
}()
_, err = resourcesManager.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/25",
GroupIDs: []string{s.resourceGroupID},
Enabled: true,
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout")
}
})
require.NoError(t, err)
disabledCh := s.updateManager.CreateChannel(ctx, disabledRouterPeer.ID)
enabledCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, disabledRouterPeer.ID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
})
settleAffectedUpdates(disabledCh, enabledCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, enabledCh)
peerShouldNotReceiveUpdate(t, disabledCh)
close(done)
}()
_, err = resourcesManager.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/25",
GroupIDs: []string{s.resourceGroupID},
Enabled: true,
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout")
}
}
func TestAffectedPeers_GroupChange_RouterInOtherNetworkNotAffected(t *testing.T) {
@@ -346,8 +352,10 @@ func TestAffectedPeers_PeerChange_RouterInOtherNetworkNotAffected(t *testing.T)
// shortcut (the denied peer's map holds no router) and the allow direction
// depends on which meta field moved, leaving the routers with a stale map.
func TestAffectedPeers_E2E_PostureFlip_RefreshesRoutingPeer(t *testing.T) {
runPostureFlipRefreshesRoutingPeer(t, func(s *routerScenario) *types.Policy {
return peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID)
runPeerUpdateTest(t, func(t *testing.T) {
runPostureFlipRefreshesRoutingPeer(t, func(s *routerScenario) *types.Policy {
return peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID)
})
})
}
@@ -355,8 +363,10 @@ func TestAffectedPeers_E2E_PostureFlip_RefreshesRoutingPeer(t *testing.T) {
// scenario with the source peer named directly in the rule: it must receive its posture
// checks and have its flips detected exactly like a group member.
func TestAffectedPeers_E2E_PostureFlip_DirectSourcePeer_RefreshesRoutingPeer(t *testing.T) {
runPostureFlipRefreshesRoutingPeer(t, func(s *routerScenario) *types.Policy {
return peerToResourcePolicyByPeer(s.sourcePeerID, s.resourceGroupID)
runPeerUpdateTest(t, func(t *testing.T) {
runPostureFlipRefreshesRoutingPeer(t, func(s *routerScenario) *types.Policy {
return peerToResourcePolicyByPeer(s.sourcePeerID, s.resourceGroupID)
})
})
}
+256 -252
View File
@@ -8,7 +8,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
"github.com/netbirdio/netbird/management/server/affectedpeers"
"github.com/netbirdio/netbird/management/server/groups"
@@ -320,190 +319,189 @@ func TestAffectedPeers_PolicyToResource_UnrelatedPeerNotAffected(t *testing.T) {
}
func TestAffectedPeers_ResourceSideBridgesToRoutingPeer_DirectRouter(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
// A pre-existing policy grants the source group access to the resource.
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
// A pre-existing policy grants the source group access to the resource.
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
// Drive an update through the resource manager and assert the routing peer
// is among the affected set by observing the channel. This path walks
// policies whose destinations reference the resource's groups, folds in the
// source groups, and loads the network's routers, so it reaches both the
// source peer and the routing peer.
permissionsManager := permissions.NewManager(s.manager.Store)
groupsManager := groups.NewManager(s.manager.Store, permissionsManager, s.manager)
rm := resources.NewManager(s.manager.Store, permissionsManager, groupsManager, s.manager, s.manager.serviceManager)
// Drive an update through the resource manager and assert the routing peer
// is among the affected set by observing the channel. This path walks
// policies whose destinations reference the resource's groups, folds in the
// source groups, and loads the network's routers, so it reaches both the
// source peer and the routing peer.
permissionsManager := permissions.NewManager(s.manager.Store)
groupsManager := groups.NewManager(s.manager.Store, permissionsManager, s.manager)
rm := resources.NewManager(s.manager.Store, permissionsManager, groupsManager, s.manager, s.manager.serviceManager)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
})
settleAffectedUpdates(srcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
_, err = rm.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/24",
GroupIDs: []string{s.resourceGroupID},
Enabled: true,
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: resource update did not refresh source peer + routing peer")
}
})
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
_, err = rm.UpdateResource(ctx, userID, &resourceTypes.NetworkResource{
ID: s.resourceID,
AccountID: s.accountID,
NetworkID: s.networkID,
Name: "rs-resource-host",
Address: "10.20.30.0/24",
GroupIDs: []string{s.resourceGroupID},
Enabled: true,
})
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: resource update did not refresh source peer + routing peer")
}
}
// settleAffectedUpdates waits for in-flight async updates to arrive, then drains
// every given channel so subsequent assertions start from a clean slate.
//
// Setup (CreateNetwork/CreateResource/CreateRouter) fires async UpdateAffectedPeers
// goroutines; draining first means the assertion only observes updates from the
// action under test, not setup stragglers.
func settleAffectedUpdates(chans ...<-chan *network_map.UpdateMessage) {
time.Sleep(300 * time.Millisecond)
for _, ch := range chans {
drainPeerUpdates(ch)
}
}
func TestAffectedPeers_E2E_CreatePolicy_RoutingPeer_DirectRouter(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
unrelatedCh := s.updateManager.CreateChannel(ctx, s.unrelatedPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
s.updateManager.CloseChannel(ctx, s.unrelatedPeerID)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
unrelatedCh := s.updateManager.CreateChannel(ctx, s.unrelatedPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
s.updateManager.CloseChannel(ctx, s.unrelatedPeerID)
})
settleAffectedUpdates(srcCh, routerCh, unrelatedCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
peerShouldNotReceiveUpdate(t, unrelatedCh)
close(done)
}()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: creating peer->resource policy did not refresh the routing peer")
}
})
settleAffectedUpdates(srcCh, routerCh, unrelatedCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
peerShouldNotReceiveUpdate(t, unrelatedCh)
close(done)
}()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: creating peer->resource policy did not refresh the routing peer")
}
}
func TestAffectedPeers_E2E_CreatePolicy_RoutingPeer_RouterPeerGroups(t *testing.T) {
s := setupRouterScenario(t, false)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, false)
ctx := context.Background()
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerGroupPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerGroupPeerID)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerGroupPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerGroupPeerID)
})
settleAffectedUpdates(srcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: routing peer (PeerGroups) not refreshed on policy create")
}
})
settleAffectedUpdates(srcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: routing peer (PeerGroups) not refreshed on policy create")
}
}
func TestAffectedPeers_E2E_DestResource_RoutingPeer(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
})
settleAffectedUpdates(srcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByResource(s.sourceGroupID, s.resourceID), true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: routing peer not refreshed when policy targets DestinationResource")
}
})
settleAffectedUpdates(srcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByResource(s.sourceGroupID, s.resourceID), true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: routing peer not refreshed when policy targets DestinationResource")
}
}
func TestAffectedPeers_E2E_DeletePolicy_RoutingPeer(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
policy, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
policy, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
})
settleAffectedUpdates(srcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
require.NoError(t, s.manager.DeletePolicy(ctx, s.accountID, policy.ID, userID))
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: deleting peer->resource policy did not refresh the routing peer")
}
})
settleAffectedUpdates(srcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
require.NoError(t, s.manager.DeletePolicy(ctx, s.accountID, policy.ID, userID))
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: deleting peer->resource policy did not refresh the routing peer")
}
}
func (s *routerScenario) managers() (resources.Manager, routers.Manager, networks.Manager) {
@@ -572,113 +570,119 @@ func (s *routerScenario) addSecondTopology(t *testing.T, suffix string) secondTo
}
func TestAffectedPeers_E2E_UpdatePolicy_BothRoutingPeers(t *testing.T) {
s := setupRouterScenario(t, true)
second := s.addSecondTopology(t, "b")
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
second := s.addSecondTopology(t, "b")
ctx := context.Background()
policy, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
policy, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerACh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
routerBCh := s.updateManager.CreateChannel(ctx, second.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
s.updateManager.CloseChannel(ctx, second.routerPeerID)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerACh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
routerBCh := s.updateManager.CreateChannel(ctx, second.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
s.updateManager.CloseChannel(ctx, second.routerPeerID)
})
settleAffectedUpdates(srcCh, routerACh, routerBCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerACh)
peerShouldReceiveUpdate(t, routerBCh)
close(done)
}()
policy.Rules[0].Destinations = []string{second.resourceGroupID}
_, err = s.manager.SavePolicy(ctx, s.accountID, userID, policy, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: re-pointing the policy destination did not refresh both routing peers")
}
})
settleAffectedUpdates(srcCh, routerACh, routerBCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerACh)
peerShouldReceiveUpdate(t, routerBCh)
close(done)
}()
policy.Rules[0].Destinations = []string{second.resourceGroupID}
_, err = s.manager.SavePolicy(ctx, s.accountID, userID, policy, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: re-pointing the policy destination did not refresh both routing peers")
}
}
func TestAffectedPeers_E2E_UpdatePolicy_AddSource(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, true)
ctx := context.Background()
const secondSourceGroupID = "rs-source-grp-2"
setupKey, err := s.manager.CreateSetupKey(ctx, s.accountID, "rs-key-2", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
require.NoError(t, err)
secondSourcePeer := addPeerToAccount(t, s.manager, s.accountID, setupKey.Key)
require.NoError(t, s.manager.CreateGroup(ctx, s.accountID, userID, &types.Group{
ID: secondSourceGroupID, Name: "rs-source-2", Peers: []string{secondSourcePeer.ID},
}))
const secondSourceGroupID = "rs-source-grp-2"
setupKey, err := s.manager.CreateSetupKey(ctx, s.accountID, "rs-key-2", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
require.NoError(t, err)
secondSourcePeer := addPeerToAccount(t, s.manager, s.accountID, setupKey.Key)
require.NoError(t, s.manager.CreateGroup(ctx, s.accountID, userID, &types.Group{
ID: secondSourceGroupID, Name: "rs-source-2", Peers: []string{secondSourcePeer.ID},
}))
policy, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
policy, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
require.NoError(t, err)
newSrcCh := s.updateManager.CreateChannel(ctx, secondSourcePeer.ID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, secondSourcePeer.ID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
newSrcCh := s.updateManager.CreateChannel(ctx, secondSourcePeer.ID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, secondSourcePeer.ID)
s.updateManager.CloseChannel(ctx, s.routerPeerID)
})
settleAffectedUpdates(newSrcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, newSrcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
policy.Rules[0].Sources = []string{s.sourceGroupID, secondSourceGroupID}
_, err = s.manager.SavePolicy(ctx, s.accountID, userID, policy, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: adding a source group did not refresh the new source peer + routing peer")
}
})
settleAffectedUpdates(newSrcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, newSrcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
policy.Rules[0].Sources = []string{s.sourceGroupID, secondSourceGroupID}
_, err = s.manager.SavePolicy(ctx, s.accountID, userID, policy, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: adding a source group did not refresh the new source peer + routing peer")
}
}
func TestAffectedPeers_E2E_DestResource_RouterPeerGroups(t *testing.T) {
s := setupRouterScenario(t, false)
ctx := context.Background()
runPeerUpdateTest(t, func(t *testing.T) {
s := setupRouterScenario(t, false)
ctx := context.Background()
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerGroupPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerGroupPeerID)
srcCh := s.updateManager.CreateChannel(ctx, s.sourcePeerID)
routerCh := s.updateManager.CreateChannel(ctx, s.routerGroupPeerID)
t.Cleanup(func() {
s.updateManager.CloseChannel(ctx, s.sourcePeerID)
s.updateManager.CloseChannel(ctx, s.routerGroupPeerID)
})
settleAffectedUpdates(srcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByResource(s.sourceGroupID, s.resourceID), true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: DestinationResource policy with PeerGroups router did not refresh the routing peer")
}
})
settleAffectedUpdates(srcCh, routerCh)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, srcCh)
peerShouldReceiveUpdate(t, routerCh)
close(done)
}()
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByResource(s.sourceGroupID, s.resourceID), true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout: DestinationResource policy with PeerGroups router did not refresh the routing peer")
}
}
func TestAffectedPeers_AllRoutingPeers_Network(t *testing.T) {
File diff suppressed because it is too large Load Diff
+131 -133
View File
@@ -17,154 +17,152 @@ import (
// A user update refreshes only the peers its auto-group change reaches, and a user
// update that changes no group membership refreshes nobody.
func TestAffectedPeers_SaveUser_OnlyAffectedPeersUpdated(t *testing.T) {
manager, updateManager, account, _, peer2, peer3 := setupNetworkMapTest(t)
ctx := context.Background()
accountID := account.Id
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, account, _, peer2, peer3 := setupNetworkMapTest(t)
ctx := context.Background()
accountID := account.Id
const targetUserID = "target-user"
require.NoError(t, manager.Store.SaveUser(ctx, &types.User{
Id: targetUserID, AccountID: accountID, Role: types.UserRoleUser,
}))
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
targetPeer, _, _, _, err := manager.AddPeer(ctx, accountID, "", targetUserID, &nbpeer.Peer{
Key: key.PublicKey().String(),
Meta: nbpeer.PeerSystemMeta{Hostname: "target-peer"},
}, false)
require.NoError(t, err)
policies, err := manager.Store.GetAccountPolicies(ctx, store.LockingStrengthNone, accountID)
require.NoError(t, err)
for _, p := range policies {
require.NoError(t, manager.Store.DeletePolicy(ctx, accountID, p.ID))
}
account, err = manager.Store.GetAccount(ctx, accountID)
require.NoError(t, err)
account.Settings.GroupsPropagationEnabled = true
require.NoError(t, manager.Store.SaveAccount(ctx, account))
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "ug-linked", Name: "ug-linked"}))
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "ug-dest", Name: "ug-dest", Peers: []string{peer2.ID}}))
_, err = manager.SavePolicy(ctx, accountID, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"ug-linked"},
Destinations: []string{"ug-dest"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
}, true)
require.NoError(t, err)
updTarget := updateManager.CreateChannel(ctx, targetPeer.ID)
upd2 := updateManager.CreateChannel(ctx, peer2.ID)
upd3 := updateManager.CreateChannel(ctx, peer3.ID)
t.Cleanup(func() {
updateManager.CloseChannel(ctx, targetPeer.ID)
updateManager.CloseChannel(ctx, peer2.ID)
updateManager.CloseChannel(ctx, peer3.ID)
})
t.Run("auto group change updates only linked peers", func(t *testing.T) {
drainPeerUpdates(updTarget)
drainPeerUpdates(upd2)
drainPeerUpdates(upd3)
_, err := manager.SaveUser(ctx, accountID, activity.SystemInitiator, &types.User{
const targetUserID = "target-user"
require.NoError(t, manager.Store.SaveUser(ctx, &types.User{
Id: targetUserID, AccountID: accountID, Role: types.UserRoleUser,
AutoGroups: []string{"ug-linked"},
})
}))
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
targetPeer, _, _, _, err := manager.AddPeer(ctx, accountID, "", targetUserID, &nbpeer.Peer{
Key: key.PublicKey().String(),
Meta: nbpeer.PeerSystemMeta{Hostname: "target-peer"},
}, false)
require.NoError(t, err)
peerShouldReceiveUpdate(t, updTarget)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
})
t.Run("update without group changes refreshes nobody", func(t *testing.T) {
drainPeerUpdates(updTarget)
drainPeerUpdates(upd2)
drainPeerUpdates(upd3)
_, err := manager.SaveUser(ctx, accountID, activity.SystemInitiator, &types.User{
Id: targetUserID, AccountID: accountID, Role: types.UserRoleUser,
AutoGroups: []string{"ug-linked"}, Name: "renamed",
})
policies, err := manager.Store.GetAccountPolicies(ctx, store.LockingStrengthNone, accountID)
require.NoError(t, err)
for _, p := range policies {
require.NoError(t, manager.Store.DeletePolicy(ctx, accountID, p.ID))
}
peerShouldNotReceiveUpdate(t, updTarget)
peerShouldNotReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
user, err := manager.Store.GetUserByUserID(ctx, store.LockingStrengthNone, targetUserID)
account, err = manager.Store.GetAccount(ctx, accountID)
require.NoError(t, err)
assert.Equal(t, "renamed", user.Name)
})
t.Run("auto group change reassigning IPv6 refreshes the changed peers and their observers", func(t *testing.T) {
account, err := manager.Store.GetAccount(ctx, accountID)
require.NoError(t, err)
account.Settings.IPv6EnabledGroups = []string{"ug-v6"}
account.Settings.GroupsPropagationEnabled = true
require.NoError(t, manager.Store.SaveAccount(ctx, account))
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "ug-v6", Name: "ug-v6"}))
drainPeerUpdates(updTarget)
drainPeerUpdates(upd2)
drainPeerUpdates(upd3)
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "ug-linked", Name: "ug-linked"}))
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "ug-dest", Name: "ug-dest", Peers: []string{peer2.ID}}))
_, err = manager.SaveUser(ctx, accountID, activity.SystemInitiator, &types.User{
Id: targetUserID, AccountID: accountID, Role: types.UserRoleUser,
AutoGroups: []string{"ug-linked", "ug-v6"}, Name: "renamed",
})
require.NoError(t, err)
// The reassigned peer refreshes with everyone it can reach: peer2 via the
// policy, but not peer3, which shares no group or policy with it.
peerShouldReceiveUpdate(t, updTarget)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
})
t.Run("unblocking a user refreshes only the SSH rule destinations", func(t *testing.T) {
// An SSH rule that authorizes no group of its own ships the account's
// allowed-user set to its destinations, so those are the peers an unblock
// reaches — not the whole account.
_, err := manager.SavePolicy(ctx, accountID, userID, &types.Policy{
_, err = manager.SavePolicy(ctx, accountID, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{{
Enabled: true,
Sources: []string{"ug-linked"},
Destinations: []string{"ug-dest"},
Protocol: types.PolicyRuleProtocolNetbirdSSH,
Action: types.PolicyTrafficActionAccept,
}},
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"ug-linked"},
Destinations: []string{"ug-dest"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
}, true)
require.NoError(t, err)
blocked, err := manager.Store.GetUserByUserID(ctx, store.LockingStrengthNone, targetUserID)
require.NoError(t, err)
blocked.Blocked = true
require.NoError(t, manager.Store.SaveUser(ctx, blocked))
drainPeerUpdates(updTarget)
drainPeerUpdates(upd2)
drainPeerUpdates(upd3)
// Same auto-groups as the previous subtest left them, so no group change and
// no IPv6 reconciliation interferes: the unblock alone drives the refresh.
_, err = manager.SaveUser(ctx, accountID, activity.SystemInitiator, &types.User{
Id: targetUserID, AccountID: accountID, Role: types.UserRoleUser,
AutoGroups: []string{"ug-linked", "ug-v6"}, Name: "renamed",
updTarget := updateManager.CreateChannel(ctx, targetPeer.ID)
upd2 := updateManager.CreateChannel(ctx, peer2.ID)
upd3 := updateManager.CreateChannel(ctx, peer3.ID)
t.Cleanup(func() {
updateManager.CloseChannel(ctx, targetPeer.ID)
updateManager.CloseChannel(ctx, peer2.ID)
updateManager.CloseChannel(ctx, peer3.ID)
})
require.NoError(t, err)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
step(t, "auto group change updates only linked peers", func(t *testing.T) {
settleAffectedUpdates(updTarget, upd2, upd3)
_, err := manager.SaveUser(ctx, accountID, activity.SystemInitiator, &types.User{
Id: targetUserID, AccountID: accountID, Role: types.UserRoleUser,
AutoGroups: []string{"ug-linked"},
})
require.NoError(t, err)
peerShouldReceiveUpdate(t, updTarget)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
})
step(t, "update without group changes refreshes nobody", func(t *testing.T) {
drainPeerUpdates(updTarget)
drainPeerUpdates(upd2)
drainPeerUpdates(upd3)
_, err := manager.SaveUser(ctx, accountID, activity.SystemInitiator, &types.User{
Id: targetUserID, AccountID: accountID, Role: types.UserRoleUser,
AutoGroups: []string{"ug-linked"}, Name: "renamed",
})
require.NoError(t, err)
peerShouldNotReceiveUpdate(t, updTarget)
peerShouldNotReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
user, err := manager.Store.GetUserByUserID(ctx, store.LockingStrengthNone, targetUserID)
require.NoError(t, err)
assert.Equal(t, "renamed", user.Name)
})
step(t, "auto group change reassigning IPv6 refreshes the changed peers and their observers", func(t *testing.T) {
require.NoError(t, manager.CreateGroup(ctx, accountID, userID, &types.Group{ID: "ug-v6", Name: "ug-v6"}))// Apply through the settings API so the reconciliation that strips the other
// peers' addresses happens here, leaving the target as the only peer the
// user update reassigns.
updateIPv6TestSettings(t, manager, accountID, func(s *types.Settings) {
s.IPv6EnabledGroups = []string{"ug-v6"}
})
settleAffectedUpdates(updTarget, upd2, upd3)
_, err = manager.SaveUser(ctx, accountID, activity.SystemInitiator, &types.User{
Id: targetUserID, AccountID: accountID, Role: types.UserRoleUser,
AutoGroups: []string{"ug-linked", "ug-v6"}, Name: "renamed",
})
require.NoError(t, err)
// The reassigned peer refreshes with everyone it can reach: peer2 via the
// policy, but not peer3, which shares no group or policy with it.
peerShouldReceiveUpdate(t, updTarget)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
})
step(t, "unblocking a user refreshes only the SSH rule destinations", func(t *testing.T) {
// An SSH rule that authorizes no group of its own ships the account's
// allowed-user set to its destinations, so those are the peers an unblock
// reaches — not the whole account.
_, err := manager.SavePolicy(ctx, accountID, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{{
Enabled: true,
Sources: []string{"ug-linked"},
Destinations: []string{"ug-dest"},
Protocol: types.PolicyRuleProtocolNetbirdSSH,
Action: types.PolicyTrafficActionAccept,
}},
}, true)
require.NoError(t, err)
blocked, err := manager.Store.GetUserByUserID(ctx, store.LockingStrengthNone, targetUserID)
require.NoError(t, err)
blocked.Blocked = true
require.NoError(t, manager.Store.SaveUser(ctx, blocked))
settleAffectedUpdates(updTarget, upd2, upd3)
// Same auto-groups as the previous subtest left them, so no group change and
// no IPv6 reconciliation interferes: the unblock alone drives the refresh.
_, err = manager.SaveUser(ctx, accountID, activity.SystemInitiator, &types.User{
Id: targetUserID, AccountID: accountID, Role: types.UserRoleUser,
AutoGroups: []string{"ug-linked", "ug-v6"}, Name: "renamed",
})
require.NoError(t, err)
peerShouldReceiveUpdate(t, upd2)
peerShouldNotReceiveUpdate(t, upd3)
})
})
}
+9 -3
View File
@@ -88,7 +88,8 @@ func NewUserDataCache(store store.StoreInterface) *UserDataCacheImpl {
// AccountUserDataCache wraps the basic Get, Set and Delete methods for []*idp.UserData objects.
type AccountUserDataCache struct {
cache Marshaler
cache Marshaler
loadable *cache.LoadableCache[any]
}
func (a *AccountUserDataCache) Get(ctx context.Context, key string) ([]*idp.UserData, error) {
@@ -127,13 +128,18 @@ func (a *AccountUserDataCache) Delete(ctx context.Context, key string) error {
return a.cache.Delete(ctx, key)
}
// Close stops the goroutine that stores loaded values. The cache must not be used afterwards.
func (a *AccountUserDataCache) Close() error {
return a.loadable.Close()
}
// NewAccountUserDataCache creates a new AccountUserDataCache object.
func NewAccountUserDataCache(loadableFunc cache.LoadFunction[any], store store.StoreInterface) *AccountUserDataCache {
simpleCache := cache.New[any](store)
loadable := cache.NewLoadable[any](loadableFunc, simpleCache)
if store.GetType() == redis.RedisType {
m := marshaler.New(loadable)
return &AccountUserDataCache{cache: m}
return &AccountUserDataCache{cache: m, loadable: loadable}
}
return &AccountUserDataCache{cache: &marshalerWraper{loadable}}
return &AccountUserDataCache{cache: &marshalerWraper{loadable}, loadable: loadable}
}
+146 -143
View File
@@ -6,8 +6,8 @@ import (
"testing"
"time"
"go.uber.org/mock/gomock"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
@@ -16,7 +16,6 @@ import (
ephemeral_manager "github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral/manager"
"github.com/netbirdio/netbird/management/internals/server/config"
"github.com/netbirdio/netbird/management/server/cache"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
"github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/settings"
@@ -234,9 +233,9 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) {
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := NewAccountRequestBuffer(ctx, store)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.test", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)), &config.Config{}, nil)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.test", ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)), &config.Config{}, nil)
return BuildManager(context.Background(), nil, store, networkMapController, job.NewJobManager(nil, store, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
return BuildManager(context.Background(), nil, store, networkMapController, job.NewJobManager(nil, store, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
}
func createDNSStore(t *testing.T) (store.Store, error) {
@@ -363,163 +362,167 @@ func initTestDNSAccount(t *testing.T, am *DefaultAccountManager) (*types.Account
}
func TestDNSAccountPeersUpdate(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
err := manager.CreateGroups(context.Background(), account.Id, userID, []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
})
assert.NoError(t, err)
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// Saving DNS settings with groups that have no peers should not trigger updates to account peers or send peer updates
t.Run("saving dns setting with unused groups", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{"groupA"},
err := manager.CreateGroups(context.Background(), account.Id, userID, []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Creating DNS settings with groups that have no peers should not update account peers or send peer update
t.Run("creating dns setting with unused groups", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "ns-group", "ns-group", []nbdns.NameServer{{
IP: netip.MustParseAddr(peer1.IP.String()),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupB"},
true, []string{}, true, userID, false,
)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Creating DNS settings with groups that have peers should update account peers and send peer update
t.Run("creating dns setting with used groups", func(t *testing.T) {
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
assert.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Saving DNS settings with groups that have no peers should not trigger updates to account peers or send peer updates
step(t, "saving dns setting with unused groups", func(t *testing.T) {
settleAffectedUpdates(updMsg)
_, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "ns-group-1", "ns-group-1", []nbdns.NameServer{{
IP: netip.MustParseAddr(peer1.IP.String()),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupA"},
true, []string{}, true, userID, false,
)
assert.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{"groupA"},
})
assert.NoError(t, err)
// Saving DNS settings with groups that have peers should update account peers and send peer update
t.Run("saving dns setting with used groups", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{"groupA", "groupB"},
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Creating DNS settings with groups that have no peers should not update account peers or send peer update
step(t, "creating dns setting with unused groups", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
// Removing group with no peers from DNS settings should not trigger updates to account peers or send peer updates
t.Run("removing group with no peers from dns settings", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "ns-group", "ns-group", []nbdns.NameServer{{
IP: netip.MustParseAddr(peer1.IP.String()),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupB"},
true, []string{}, true, userID, false,
)
assert.NoError(t, err)
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{"groupA"},
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Creating DNS settings with groups that have peers should update account peers and send peer update
step(t, "creating dns setting with used groups", func(t *testing.T) {
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
})
assert.NoError(t, err)
// Removing group with peers from DNS settings should trigger updates to account peers and send peer updates
t.Run("removing group with peers from dns settings", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{},
_, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "ns-group-1", "ns-group-1", []nbdns.NameServer{{
IP: netip.MustParseAddr(peer1.IP.String()),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupA"},
true, []string{}, true, userID, false,
)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
// Saving DNS settings with groups that have peers should update account peers and send peer update
step(t, "saving dns setting with used groups", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{"groupA", "groupB"},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Removing group with no peers from DNS settings should not trigger updates to account peers or send peer updates
step(t, "removing group with no peers from dns settings", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{"groupA"},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Removing group with peers from DNS settings should trigger updates to account peers and send peer updates
step(t, "removing group with peers from dns settings", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
})
}
+33 -13
View File
@@ -166,9 +166,11 @@ func (am *DefaultAccountManager) UpdateGroup(ctx context.Context, accountID, use
return err
}
if err = am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, []string{newGroup.ID}); err != nil {
ipv6Changed, err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, []string{newGroup.ID})
if err != nil {
return err
}
change.ChangedPeerIDs = ipv6Changed
// A membership change does not alter which entities reference the group, so
// the dependency walk runs once against the post-change snapshot. The new
@@ -321,7 +323,7 @@ func (am *DefaultAccountManager) UpdateGroups(ctx context.Context, accountID, us
var globalErr error
for _, newGroup := range groups {
change := affectedpeers.Change{ChangedGroupIDs: []string{newGroup.ID}}
events, snap, err := am.updateSingleGroup(ctx, accountID, userID, newGroup, change)
events, snap, change, err := am.updateSingleGroup(ctx, accountID, userID, newGroup, change)
if err != nil {
log.WithContext(ctx).Errorf("failed to update group %s: %v", newGroup.ID, err)
if len(groups) == 1 {
@@ -344,7 +346,7 @@ func (am *DefaultAccountManager) UpdateGroups(ctx context.Context, accountID, us
return globalErr
}
func (am *DefaultAccountManager) updateSingleGroup(ctx context.Context, accountID, userID string, newGroup *types.Group, change affectedpeers.Change) ([]func(), *affectedpeers.Snapshot, error) {
func (am *DefaultAccountManager) updateSingleGroup(ctx context.Context, accountID, userID string, newGroup *types.Group, change affectedpeers.Change) ([]func(), *affectedpeers.Snapshot, affectedpeers.Change, error) {
var events []func()
var snap *affectedpeers.Snapshot
err := am.Store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
@@ -364,9 +366,11 @@ func (am *DefaultAccountManager) updateSingleGroup(ctx context.Context, accountI
return err
}
if err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, []string{newGroup.ID}); err != nil {
ipv6Changed, err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, []string{newGroup.ID})
if err != nil {
return err
}
change.ChangedPeerIDs = ipv6Changed
if err := transaction.IncrementNetworkSerial(ctx, accountID); err != nil {
return err
@@ -377,7 +381,7 @@ func (am *DefaultAccountManager) updateSingleGroup(ctx context.Context, accountI
snap, err = affectedpeers.Load(ctx, transaction, accountID, change)
return err
})
return events, snap, err
return events, snap, change, err
}
// prepareGroupEvents prepares a list of event functions to be stored.
@@ -480,8 +484,8 @@ func (am *DefaultAccountManager) DeleteGroups(ctx context.Context, accountID, us
var allErrors error
var groupIDsToDelete []string
var deletedGroups []*types.Group
var snap *affectedpeers.Snapshot
var change affectedpeers.Change
var snap, ipv6Snap *affectedpeers.Snapshot
var change, ipv6Change affectedpeers.Change
extraSettings, err := am.settingsManager.GetExtraSettings(ctx, accountID)
if err != nil {
@@ -510,10 +514,20 @@ func (am *DefaultAccountManager) DeleteGroups(ctx context.Context, accountID, us
return err
}
if err = am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, groupIDsToDelete); err != nil {
ipv6Changed, err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, groupIDsToDelete)
if err != nil {
return err
}
// Members of a deleted IPv6-enabled group lose their address, which the
// pre-delete snapshot cannot see, so they are resolved post-delete.
if len(ipv6Changed) > 0 {
ipv6Change = affectedpeers.Change{ChangedPeerIDs: ipv6Changed}
if ipv6Snap, err = affectedpeers.Load(ctx, transaction, accountID, ipv6Change); err != nil {
return err
}
}
return transaction.IncrementNetworkSerial(ctx, accountID)
})
if err != nil {
@@ -524,7 +538,7 @@ func (am *DefaultAccountManager) DeleteGroups(ctx context.Context, accountID, us
am.StoreEvent(ctx, userID, group.ID, accountID, activity.GroupDeleted, group.EventMeta())
}
am.ExpandAndUpdateAffected(ctx, accountID, snap, change)
go am.dispatchAffected(ctx, accountID, []*affectedpeers.Snapshot{snap, ipv6Snap}, []affectedpeers.Change{change, ipv6Change})
return allErrors
}
@@ -564,11 +578,14 @@ func (am *DefaultAccountManager) GroupAddPeer(ctx context.Context, accountID, gr
return err
}
if err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, []string{groupID}); err != nil {
ipv6Changed, err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, []string{groupID})
if err != nil {
return err
}
// A peer whose IPv6 address changed is visible to every peer that reaches it
// through any of its groups, not only through this one.
change.ChangedPeerIDs = ipv6Changed
var err error
if snap, err = affectedpeers.Load(ctx, transaction, accountID, change); err != nil {
return err
}
@@ -634,11 +651,14 @@ func (am *DefaultAccountManager) GroupDeletePeer(ctx context.Context, accountID,
return err
}
if err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, []string{groupID}); err != nil {
ipv6Changed, err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, []string{groupID})
if err != nil {
return err
}
// A peer whose IPv6 address changed is visible to every peer that reaches it
// through any of its groups, not only through this one.
change.ChangedPeerIDs = ipv6Changed
var err error
if snap, err = affectedpeers.Load(ctx, transaction, accountID, change); err != nil {
return err
}
+331 -304
View File
@@ -23,6 +23,7 @@ import (
"github.com/netbirdio/netbird/management/server/groups"
"github.com/netbirdio/netbird/management/server/networks"
"github.com/netbirdio/netbird/management/server/networks/resources"
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
"github.com/netbirdio/netbird/management/server/networks/routers"
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
networkTypes "github.com/netbirdio/netbird/management/server/networks/types"
@@ -685,342 +686,368 @@ func initTestGroupAccount(am *DefaultAccountManager) (*DefaultAccountManager, *t
}
func TestGroupAccountPeersUpdate(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
g := []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{peer1.ID, peer3.ID},
},
{
ID: "groupD",
Name: "GroupD",
Peers: []string{},
},
{
ID: "groupE",
Name: "GroupE",
Peers: []string{peer2.ID},
},
}
for _, group := range g {
err := manager.CreateGroup(context.Background(), account.Id, userID, group)
assert.NoError(t, err)
}
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// Saving a group that is not linked to any resource should not update account peers
t.Run("saving unlinked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupB",
Name: "GroupB",
Peers: []string{peer1.ID, peer2.ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Adding a peer to a group that is not linked to any resource should not update account peers
// and not send peer update
t.Run("adding peer to unlinked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.GroupAddPeer(context.Background(), account.Id, "groupB", peer3.ID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Removing a peer from a group that is not linked to any resource should not update account peers
// and not send peer update
t.Run("removing peer from unliked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.GroupDeletePeer(context.Background(), account.Id, "groupB", peer3.ID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Deleting group should not update account peers and not send peer update
t.Run("deleting group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeleteGroup(context.Background(), account.Id, userID, "groupB")
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// adding a group to policy
_, err := manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
g := []*types.Group{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupA"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{peer1.ID, peer3.ID},
},
{
ID: "groupD",
Name: "GroupD",
Peers: []string{},
},
{
ID: "groupE",
Name: "GroupE",
Peers: []string{peer2.ID},
},
},
}, true)
assert.NoError(t, err)
// Saving a group linked to policy should update account peers and send peer update
t.Run("saving linked group to policy", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// adding peer to a used group should update account peers and send peer update
t.Run("adding peer to linked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.GroupAddPeer(context.Background(), account.Id, "groupA", peer3.ID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
for _, group := range g {
err := manager.CreateGroup(context.Background(), account.Id, userID, group)
assert.NoError(t, err)
}
})
// removing peer from a linked group should update account peers and send peer update
t.Run("removing peer from linked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
err := manager.GroupDeletePeer(context.Background(), account.Id, "groupA", peer3.ID)
// Saving a group that is not linked to any resource should not update account peers
step(t, "saving unlinked group", func(t *testing.T) {
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupB",
Name: "GroupB",
Peers: []string{peer1.ID, peer2.ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Adding a peer to a group that is not linked to any resource should not update account peers
// and not send peer update
step(t, "adding peer to unlinked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.GroupAddPeer(context.Background(), account.Id, "groupB", peer3.ID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Removing a peer from a group that is not linked to any resource should not update account peers
// and not send peer update
step(t, "removing peer from unliked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.GroupDeletePeer(context.Background(), account.Id, "groupB", peer3.ID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Deleting group should not update account peers and not send peer update
step(t, "deleting group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeleteGroup(context.Background(), account.Id, userID, "groupB")
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// adding a group to policy
_, err := manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupA"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
}, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Saving a group linked to policy should update account peers and send peer update
step(t, "saving linked group to policy", func(t *testing.T) {
settleAffectedUpdates(updMsg)
// Saving a group linked to name server group should update account peers and send peer update
t.Run("saving group linked to name server group", func(t *testing.T) {
_, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "nsGroup", "nsGroup", []nbdns.NameServer{{
IP: netip.MustParseAddr("1.1.1.1"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupC"},
true, nil, true, userID, false,
)
assert.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID},
})
assert.NoError(t, err)
err := manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupC",
Name: "GroupC",
Peers: []string{peer1.ID, peer3.ID},
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// adding peer to a used group should update account peers and send peer update
step(t, "adding peer to linked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Saving a group linked to route should update account peers and send peer update
t.Run("saving group linked to route", func(t *testing.T) {
newRoute := route.Route{
ID: "route",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{"groupA"},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{"groupC"},
}
_, err := manager.CreateRoute(
context.Background(), account.Id, newRoute.Network, newRoute.NetworkType, newRoute.Domains, newRoute.Peer,
newRoute.PeerGroups, newRoute.Description, newRoute.NetID, newRoute.Masquerade, newRoute.Metric,
newRoute.Groups, []string{}, true, userID, newRoute.KeepRoute, newRoute.SkipAutoApply,
)
require.NoError(t, err)
err := manager.GroupAddPeer(context.Background(), account.Id, "groupA", peer3.ID)
assert.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// removing peer from a linked group should update account peers and send peer update
step(t, "removing peer from linked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Saving a group linked to dns settings should update account peers and send peer update
t.Run("saving group linked to dns settings", func(t *testing.T) {
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{"groupD"},
err := manager.GroupDeletePeer(context.Background(), account.Id, "groupA", peer3.ID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
assert.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Saving a group linked to name server group should update account peers and send peer update
step(t, "saving group linked to name server group", func(t *testing.T) {
_, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "nsGroup", "nsGroup", []nbdns.NameServer{{
IP: netip.MustParseAddr("1.1.1.1"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupC"},
true, nil, true, userID, false,
)
assert.NoError(t, err)
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupD",
Name: "GroupD",
Peers: []string{peer1.ID},
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupC",
Name: "GroupC",
Peers: []string{peer1.ID, peer3.ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Saving a group linked to route should update account peers and send peer update
step(t, "saving group linked to route", func(t *testing.T) {
newRoute := route.Route{
ID: "route",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{"groupA"},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{"groupC"},
}
_, err := manager.CreateRoute(
context.Background(), account.Id, newRoute.Network, newRoute.NetworkType, newRoute.Domains, newRoute.Peer,
newRoute.PeerGroups, newRoute.Description, newRoute.NetID, newRoute.Masquerade, newRoute.Metric,
newRoute.Groups, []string{}, true, userID, newRoute.KeepRoute, newRoute.SkipAutoApply,
)
require.NoError(t, err)
// Saving a group linked to network router should update account peers and send peer update
t.Run("saving group linked to network router", func(t *testing.T) {
permissionsManager := permissions.NewManager(manager.Store)
groupsManager := groups.NewManager(manager.Store, permissionsManager, manager)
resourcesManager := resources.NewManager(manager.Store, permissionsManager, groupsManager, manager, manager.serviceManager)
routersManager := routers.NewManager(manager.Store, permissionsManager, manager)
networksManager := networks.NewManager(manager.Store, permissionsManager, resourcesManager, routersManager, manager)
settleAffectedUpdates(updMsg)
network, err := networksManager.CreateNetwork(context.Background(), userID, &networkTypes.Network{
ID: "network_test",
AccountID: account.Id,
Name: "network_test",
Description: "",
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
require.NoError(t, err)
_, err = routersManager.CreateRouter(context.Background(), userID, &routerTypes.NetworkRouter{
ID: "router_test",
NetworkID: network.ID,
AccountID: account.Id,
PeerGroups: []string{"groupE"},
Masquerade: true,
Metric: 9999,
Enabled: true,
// Saving a group linked to dns settings should update account peers and send peer update
step(t, "saving group linked to dns settings", func(t *testing.T) {
err := manager.SaveDNSSettings(context.Background(), account.Id, userID, &types.DNSSettings{
DisabledManagementGroups: []string{"groupD"},
})
assert.NoError(t, err)
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupD",
Name: "GroupD",
Peers: []string{peer1.ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
require.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Saving a group linked to network router should update account peers and send peer update
step(t, "saving group linked to network router", func(t *testing.T) {
permissionsManager := permissions.NewManager(manager.Store)
groupsManager := groups.NewManager(manager.Store, permissionsManager, manager)
resourcesManager := resources.NewManager(manager.Store, permissionsManager, groupsManager, manager, manager.serviceManager)
routersManager := routers.NewManager(manager.Store, permissionsManager, manager)
networksManager := networks.NewManager(manager.Store, permissionsManager, resourcesManager, routersManager, manager)
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupE",
Name: "GroupE",
Peers: []string{peer2.ID, peer3.ID},
network, err := networksManager.CreateNetwork(context.Background(), userID, &networkTypes.Network{
ID: "network_test",
AccountID: account.Id,
Name: "network_test",
Description: "",
})
require.NoError(t, err)
_, err = routersManager.CreateRouter(context.Background(), userID, &routerTypes.NetworkRouter{
ID: "router_test",
NetworkID: network.ID,
AccountID: account.Id,
PeerGroups: []string{"groupE"},
Masquerade: true,
Metric: 9999,
Enabled: true,
})
require.NoError(t, err)
resource, err := resourcesManager.CreateResource(context.Background(), userID, &resourceTypes.NetworkResource{
AccountID: account.Id,
NetworkID: network.ID,
Name: "resource_test",
Address: "10.20.30.0/24",
Enabled: true,
})
require.NoError(t, err)
_, err = manager.SavePolicy(context.Background(), account.Id, userID, peerToResourcePolicyByResource("groupA", resource.ID), true)
require.NoError(t, err)
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupE",
Name: "GroupE",
Peers: []string{peer2.ID, peer3.ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
}
@@ -29,7 +29,6 @@ import (
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
"github.com/netbirdio/netbird/management/internals/modules/peers"
ephemeral_manager "github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral/manager"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
"github.com/netbirdio/netbird/management/server"
@@ -88,7 +87,6 @@ func BuildApiBlackBoxWithDBState(t testing_tools.TB, sqlFile string, expectedPee
geoMock := &geolocation.Mock{}
validatorMock := server.MockIntegratedValidator{}
proxyController := integrations.NewController(store)
userManager := users.NewManager(store)
permissionsManager := permissions.NewManager(store)
settingsManager := settings.NewManager(store, userManager, integrations.NewManager(&activity.InMemoryEventStore{}), permissionsManager, settings.IdpConfig{})
@@ -102,8 +100,8 @@ func BuildApiBlackBoxWithDBState(t testing_tools.TB, sqlFile string, expectedPee
}
requestBuffer := server.NewAccountRequestBuffer(ctx, store)
networkMapController := controller.NewController(ctx, store, metrics, peersUpdateManager, requestBuffer, server.MockIntegratedValidator{}, settingsManager, "", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(store, peersManager), &config.Config{}, nil)
am, err := server.BuildManager(ctx, nil, store, networkMapController, jobManager, nil, "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController, settingsManager, permissionsManager, false, cacheStore)
networkMapController := controller.NewController(ctx, store, metrics, peersUpdateManager, requestBuffer, server.MockIntegratedValidator{}, settingsManager, "", ephemeral_manager.NewEphemeralManager(store, peersManager), &config.Config{}, nil)
am, err := server.BuildManager(ctx, nil, store, networkMapController, jobManager, nil, "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, settingsManager, permissionsManager, false, cacheStore)
if err != nil {
t.Fatalf("Failed to create manager: %v", err)
}
@@ -228,7 +226,6 @@ func BuildApiBlackBoxWithDBStateAndPeerChannel(t testing_tools.TB, sqlFile strin
geoMock := &geolocation.Mock{}
validatorMock := server.MockIntegratedValidator{}
proxyController := integrations.NewController(store)
userManager := users.NewManager(store)
permissionsManager := permissions.NewManager(store)
settingsManager := settings.NewManager(store, userManager, integrations.NewManager(&activity.InMemoryEventStore{}), permissionsManager, settings.IdpConfig{})
@@ -242,8 +239,8 @@ func BuildApiBlackBoxWithDBStateAndPeerChannel(t testing_tools.TB, sqlFile strin
}
requestBuffer := server.NewAccountRequestBuffer(ctx, store)
networkMapController := controller.NewController(ctx, store, metrics, peersUpdateManager, requestBuffer, server.MockIntegratedValidator{}, settingsManager, "", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(store, peersManager), &config.Config{}, nil)
am, err := server.BuildManager(ctx, nil, store, networkMapController, jobManager, nil, "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController, settingsManager, permissionsManager, false, cacheStore)
networkMapController := controller.NewController(ctx, store, metrics, peersUpdateManager, requestBuffer, server.MockIntegratedValidator{}, settingsManager, "", ephemeral_manager.NewEphemeralManager(store, peersManager), &config.Config{}, nil)
am, err := server.BuildManager(ctx, nil, store, networkMapController, jobManager, nil, "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, settingsManager, permissionsManager, false, cacheStore)
if err != nil {
t.Fatalf("Failed to create manager: %v", err)
}
+2 -3
View File
@@ -23,7 +23,6 @@ import (
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/cache"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
"github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/settings"
@@ -112,8 +111,8 @@ func createManagerWithEmbeddedIdPModeAndSetup(
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := NewAccountRequestBuffer(ctx, testStore)
networkMapController := controller.NewController(ctx, testStore, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(testStore, peersManager), &config.Config{}, nil)
manager, err := BuildManager(ctx, &config.Config{}, testStore, networkMapController, job.NewJobManager(nil, testStore, peersManager), idpManager, singleAccountModeDomain, eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
networkMapController := controller.NewController(ctx, testStore, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", ephemeral_manager.NewEphemeralManager(testStore, peersManager), &config.Config{}, nil)
manager, err := BuildManager(ctx, &config.Config{}, testStore, networkMapController, job.NewJobManager(nil, testStore, peersManager), idpManager, singleAccountModeDomain, eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
if err != nil {
return nil, nil, err
}
@@ -1,38 +0,0 @@
package port_forwarding
import (
"context"
"github.com/netbirdio/netbird/management/server/peer"
nbtypes "github.com/netbirdio/netbird/management/server/types"
)
type Controller interface {
SendUpdate(ctx context.Context, accountID string, affectedProxyID string, affectedPeerIDs []string, accountPeers map[string]*peer.Peer)
GetProxyNetworkMaps(ctx context.Context, accountID, peerID string, accountPeers map[string]*peer.Peer) (map[string]*nbtypes.NetworkMap, error)
GetProxyNetworkMapsAll(ctx context.Context, accountID string, accountPeers map[string]*peer.Peer) (map[string]*nbtypes.NetworkMap, error)
IsPeerInIngressPorts(ctx context.Context, accountID, peerID string) (bool, error)
}
type ControllerMock struct {
}
func NewControllerMock() *ControllerMock {
return &ControllerMock{}
}
func (c *ControllerMock) SendUpdate(ctx context.Context, accountID string, affectedProxyID string, affectedPeerIDs []string, accountPeers map[string]*peer.Peer) {
// noop
}
func (c *ControllerMock) GetProxyNetworkMaps(ctx context.Context, accountID, peerID string, accountPeers map[string]*peer.Peer) (map[string]*nbtypes.NetworkMap, error) {
return make(map[string]*nbtypes.NetworkMap), nil
}
func (c *ControllerMock) GetProxyNetworkMapsAll(ctx context.Context, accountID string, accountPeers map[string]*peer.Peer) (map[string]*nbtypes.NetworkMap, error) {
return make(map[string]*nbtypes.NetworkMap), nil
}
func (c *ControllerMock) IsPeerInIngressPorts(ctx context.Context, accountID, peerID string) (bool, error) {
return false, nil
}
+3 -4
View File
@@ -12,9 +12,9 @@ import (
"testing"
"time"
"go.uber.org/mock/gomock"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
@@ -31,7 +31,6 @@ import (
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/cache"
"github.com/netbirdio/netbird/management/server/groups"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/permissions"
@@ -376,9 +375,9 @@ func startManagementForTest(t *testing.T, testFile string, config *config.Config
return nil, nil, "", cleanup, err
}
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock(), ephemeralMgr, config, nil)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", ephemeralMgr, config, nil)
accountManager, err := BuildManager(ctx, nil, store, networkMapController, jobManager, nil, "",
eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
if err != nil {
cleanup()
+2 -4
View File
@@ -10,10 +10,10 @@ import (
"testing"
"time"
"go.uber.org/mock/gomock"
pb "github.com/golang/protobuf/proto" //nolint
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
@@ -30,7 +30,6 @@ import (
"github.com/netbirdio/netbird/management/server/activity"
nbcache "github.com/netbirdio/netbird/management/server/cache"
"github.com/netbirdio/netbird/management/server/groups"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
"github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/settings"
@@ -216,7 +215,7 @@ func startServer(
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := server.NewAccountRequestBuffer(ctx, str)
networkMapController := controller.NewController(ctx, str, metrics, updateManager, requestBuffer, server.MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(str, peers.NewManager(str, permissionsManager)), config, nil)
networkMapController := controller.NewController(ctx, str, metrics, updateManager, requestBuffer, server.MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", ephemeral_manager.NewEphemeralManager(str, peers.NewManager(str, permissionsManager)), config, nil)
accountManager, err := server.BuildManager(
context.Background(),
@@ -231,7 +230,6 @@ func startServer(
false,
server.MockIntegratedValidator{},
metrics,
port_forwarding.NewControllerMock(),
settingsMockManager,
permissionsManager,
false,
+134 -131
View File
@@ -6,9 +6,9 @@ import (
"testing"
"time"
"go.uber.org/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
nbdns "github.com/netbirdio/netbird/dns"
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
@@ -18,7 +18,6 @@ import (
"github.com/netbirdio/netbird/management/internals/server/config"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/cache"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
nbpeer "github.com/netbirdio/netbird/management/server/peer"
"github.com/netbirdio/netbird/management/server/permissions"
@@ -803,9 +802,9 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) {
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := NewAccountRequestBuffer(ctx, store)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)), &config.Config{}, nil)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)), &config.Config{}, nil)
return BuildManager(context.Background(), nil, store, networkMapController, job.NewJobManager(nil, store, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
return BuildManager(context.Background(), nil, store, networkMapController, job.NewJobManager(nil, store, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
}
func createNSStore(t *testing.T) (store.Store, error) {
@@ -966,147 +965,151 @@ func TestValidateDomain(t *testing.T) {
}
func TestNameServerAccountPeersUpdate(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
var newNameServerGroupA *nbdns.NameServerGroup
var newNameServerGroupB *nbdns.NameServerGroup
var newNameServerGroupA *nbdns.NameServerGroup
var newNameServerGroupB *nbdns.NameServerGroup
err := manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{},
})
assert.NoError(t, err)
err = manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupB",
Name: "GroupB",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
})
assert.NoError(t, err)
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// Creating a nameserver group with a distribution group no peers should not update account peers
// and not send peer update
t.Run("creating nameserver group with distribution group no peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
newNameServerGroupA, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "nsGroupA", "nsGroupA", []nbdns.NameServer{{
IP: netip.MustParseAddr("1.1.1.1"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupA"},
true, []string{}, true, userID, false,
)
err := manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// saving a nameserver group with a distribution group with no peers should not update account peers
// and not send peer update
t.Run("saving nameserver group with distribution group no peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.SaveNameServerGroup(context.Background(), account.Id, userID, newNameServerGroupA)
err = manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupB",
Name: "GroupB",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// Creating a nameserver group with a distribution group no peers should update account peers and send peer update
t.Run("creating nameserver group with distribution group has peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Creating a nameserver group with a distribution group no peers should not update account peers
// and not send peer update
step(t, "creating nameserver group with distribution group no peers", func(t *testing.T) {
settleAffectedUpdates(updMsg)
newNameServerGroupB, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "nsGroupB", "nsGroupB", []nbdns.NameServer{{
IP: netip.MustParseAddr("1.1.1.1"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupB"},
true, []string{}, true, userID, false,
)
assert.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
newNameServerGroupA, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "nsGroupA", "nsGroupA", []nbdns.NameServer{{
IP: netip.MustParseAddr("1.1.1.1"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupA"},
true, []string{}, true, userID, false,
)
assert.NoError(t, err)
// saving a nameserver group with a distribution group with peers should update account peers and send peer update
t.Run("saving nameserver group with distribution group has peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
newNameServerGroupB.NameServers = []nbdns.NameServer{
{
IP: netip.MustParseAddr("1.1.1.2"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
},
{
IP: netip.MustParseAddr("8.8.8.8"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
},
}
err = manager.SaveNameServerGroup(context.Background(), account.Id, userID, newNameServerGroupB)
assert.NoError(t, err)
// saving a nameserver group with a distribution group with no peers should not update account peers
// and not send peer update
step(t, "saving nameserver group with distribution group no peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
err = manager.SaveNameServerGroup(context.Background(), account.Id, userID, newNameServerGroupA)
assert.NoError(t, err)
// Deleting a nameserver group should update account peers and send peer update
t.Run("deleting nameserver group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
err = manager.DeleteNameServerGroup(context.Background(), account.Id, newNameServerGroupB.ID, userID)
assert.NoError(t, err)
// Creating a nameserver group with a distribution group no peers should update account peers and send peer update
step(t, "creating nameserver group with distribution group has peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
newNameServerGroupB, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "nsGroupB", "nsGroupB", []nbdns.NameServer{{
IP: netip.MustParseAddr("1.1.1.1"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupB"},
true, []string{}, true, userID, false,
)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// saving a nameserver group with a distribution group with peers should update account peers and send peer update
step(t, "saving nameserver group with distribution group has peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
newNameServerGroupB.NameServers = []nbdns.NameServer{
{
IP: netip.MustParseAddr("1.1.1.2"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
},
{
IP: netip.MustParseAddr("8.8.8.8"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
},
}
err = manager.SaveNameServerGroup(context.Background(), account.Id, userID, newNameServerGroupB)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting a nameserver group should update account peers and send peer update
step(t, "deleting nameserver group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.DeleteNameServerGroup(context.Background(), account.Id, newNameServerGroupB.ID, userID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
})
}
+4 -13
View File
@@ -710,11 +710,11 @@ func (am *DefaultAccountManager) handleUserAddedPeer(ctx context.Context, accoun
func (am *DefaultAccountManager) handleSetupKeyAddedPeer(ctx context.Context, encodedHashedKey string, peer *nbpeer.Peer, opEvent *activity.Event, config *peerAddAuthConfig) error {
sk, err := am.Store.GetSetupKeyBySecret(ctx, store.LockingStrengthNone, encodedHashedKey)
if err != nil {
return status.Errorf(status.NotFound, "couldn't add peer: setup key is invalid")
return err
}
if !sk.IsValid() {
return status.Errorf(status.NotFound, "couldn't add peer: setup key is invalid")
return status.Errorf(status.PermissionDenied, "couldn't add peer: setup key is invalid")
}
if !sk.AllowExtraDNSLabels && len(peer.ExtraDNSLabels) > 0 {
@@ -915,12 +915,12 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
case addedBySetupKey:
sk, err := transaction.GetSetupKeyBySecret(ctx, store.LockingStrengthUpdate, encodedHashedKey)
if err != nil {
return fmt.Errorf("failed to get setup key: %w", err)
return err
}
// we validate at the end to not block the setup key for too long
if !sk.IsValid() {
return status.Errorf(status.PreconditionFailed, "couldn't add peer: setup key is invalid")
return status.Errorf(status.PermissionDenied, "couldn't add peer: setup key is invalid")
}
err = transaction.IncrementSetupKeyUsage(ctx, peerAddConfig.SetupKeyID)
@@ -1839,15 +1839,6 @@ func deletePeers(ctx context.Context, am *DefaultAccountManager, transaction sto
// validatePeerDelete checks if the peer can be deleted.
func (am *DefaultAccountManager) validatePeerDelete(ctx context.Context, transaction store.Store, accountId, peerId string) error {
linkedInIngressPorts, err := am.proxyController.IsPeerInIngressPorts(ctx, accountId, peerId)
if err != nil {
return err
}
if linkedInIngressPorts {
return status.Errorf(status.PreconditionFailed, "peer is linked to ingress ports: %s", peerId)
}
linked, router := isPeerLinkedToNetworkRouter(ctx, transaction, accountId, peerId)
if linked {
return status.Errorf(status.PreconditionFailed, "peer is linked to a network router: %s", router.ID)
+343 -398
View File
@@ -40,7 +40,6 @@ import (
nbcontext "github.com/netbirdio/netbird/management/server/context"
peershandler "github.com/netbirdio/netbird/management/server/http/handlers/peers"
"github.com/netbirdio/netbird/management/server/http/testing/testing_tools"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
"github.com/netbirdio/netbird/management/server/permissions"
"github.com/netbirdio/netbird/management/server/settings"
@@ -1226,20 +1225,6 @@ func TestToSyncResponse(t *testing.T) {
FirewallRules: []*types.FirewallRule{
{PeerIP: "192.168.1.2", Direction: types.FirewallRuleDirectionIN, Action: string(types.PolicyTrafficActionAccept), Protocol: string(types.PolicyRuleProtocolTCP), Port: "80"},
},
ForwardingRules: []*types.ForwardingRule{
{
RuleProtocol: "tcp",
DestinationPorts: types.RulePortRange{
Start: 1000,
End: 2000,
},
TranslatedAddress: net.IPv4(192, 168, 1, 2),
TranslatedPorts: types.RulePortRange{
Start: 11000,
End: 12000,
},
},
},
}
dnsName := "example.com"
checks := []*nmdata.PostureChecks{
@@ -1334,14 +1319,6 @@ func TestToSyncResponse(t *testing.T) {
// assert posture checks
assert.Equal(t, 1, len(response.Checks))
assert.Equal(t, "/usr/bin/netbird", response.Checks[0].Files[0])
// assert network map ForwardingRules
assert.Equal(t, 1, len(response.NetworkMap.ForwardingRules))
assert.Equal(t, proto.RuleProtocol_TCP, response.NetworkMap.ForwardingRules[0].Protocol)
assert.Equal(t, uint32(1000), response.NetworkMap.ForwardingRules[0].DestinationPort.GetRange().Start)
assert.Equal(t, uint32(2000), response.NetworkMap.ForwardingRules[0].DestinationPort.GetRange().End)
assert.Equal(t, net.IPv4(192, 168, 1, 2).To4(), net.IP(response.NetworkMap.ForwardingRules[0].TranslatedAddress))
assert.Equal(t, uint32(11000), response.NetworkMap.ForwardingRules[0].TranslatedPort.GetRange().Start)
assert.Equal(t, uint32(12000), response.NetworkMap.ForwardingRules[0].TranslatedPort.GetRange().End)
}
func Test_RegisterPeerByUser(t *testing.T) {
@@ -1373,9 +1350,9 @@ func Test_RegisterPeerByUser(t *testing.T) {
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := NewAccountRequestBuffer(ctx, s)
networkMapController := controller.NewController(ctx, s, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(s, peers.NewManager(s, permissionsManager)), &config.Config{}, nil)
networkMapController := controller.NewController(ctx, s, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", ephemeral_manager.NewEphemeralManager(s, peers.NewManager(s, permissionsManager)), &config.Config{}, nil)
am, err := BuildManager(context.Background(), nil, s, networkMapController, job.NewJobManager(nil, s, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
am, err := BuildManager(context.Background(), nil, s, networkMapController, job.NewJobManager(nil, s, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
assert.NoError(t, err)
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1464,9 +1441,9 @@ func Test_RegisterPeerBySetupKey(t *testing.T) {
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := NewAccountRequestBuffer(ctx, s)
networkMapController := controller.NewController(ctx, s, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(s, peers.NewManager(s, permissionsManager)), &config.Config{}, nil)
networkMapController := controller.NewController(ctx, s, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", ephemeral_manager.NewEphemeralManager(s, peers.NewManager(s, permissionsManager)), &config.Config{}, nil)
am, err := BuildManager(context.Background(), nil, s, networkMapController, job.NewJobManager(nil, s, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
am, err := BuildManager(context.Background(), nil, s, networkMapController, job.NewJobManager(nil, s, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
assert.NoError(t, err)
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1518,7 +1495,7 @@ func Test_RegisterPeerBySetupKey(t *testing.T) {
name: "Absent setup key",
existingSetupKeyID: "AAAAAAAA-38F5-4553-B31E-DD66C696CEBB",
expectAddPeerError: true,
errorType: status.NotFound,
errorType: status.PermissionDenied,
expectedErrorMsgSubstring: "couldn't add peer: setup key is invalid",
},
}
@@ -1623,9 +1600,9 @@ func Test_RegisterPeerRollbackOnFailure(t *testing.T) {
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := NewAccountRequestBuffer(ctx, s)
networkMapController := controller.NewController(ctx, s, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(s, peers.NewManager(s, permissionsManager)), &config.Config{}, nil)
networkMapController := controller.NewController(ctx, s, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", ephemeral_manager.NewEphemeralManager(s, peers.NewManager(s, permissionsManager)), &config.Config{}, nil)
am, err := BuildManager(context.Background(), nil, s, networkMapController, job.NewJobManager(nil, s, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
am, err := BuildManager(context.Background(), nil, s, networkMapController, job.NewJobManager(nil, s, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
assert.NoError(t, err)
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1708,9 +1685,9 @@ func Test_LoginPeer(t *testing.T) {
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := NewAccountRequestBuffer(ctx, s)
networkMapController := controller.NewController(ctx, s, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(s, peers.NewManager(s, permissionsManager)), &config.Config{}, nil)
networkMapController := controller.NewController(ctx, s, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.cloud", ephemeral_manager.NewEphemeralManager(s, peers.NewManager(s, permissionsManager)), &config.Config{}, nil)
am, err := BuildManager(context.Background(), nil, s, networkMapController, job.NewJobManager(nil, s, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
am, err := BuildManager(context.Background(), nil, s, networkMapController, job.NewJobManager(nil, s, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
assert.NoError(t, err)
existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
@@ -1844,374 +1821,336 @@ func Test_LoginPeer(t *testing.T) {
}
func TestPeerAccountPeersUpdate(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
err := manager.DeletePolicy(context.Background(), account.Id, account.Policies[0].ID, userID)
require.NoError(t, err)
g := []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{},
},
}
for _, group := range g {
err = manager.CreateGroup(context.Background(), account.Id, userID, group)
require.NoError(t, err)
}
// create a user with auto groups
_, err = manager.SaveOrAddUsers(context.Background(), account.Id, userID, []*types.User{
{
Id: "regularUser1",
AccountID: account.Id,
Role: types.UserRoleAdmin,
Issued: types.UserIssuedAPI,
AutoGroups: []string{"groupA"},
},
{
Id: "regularUser2",
AccountID: account.Id,
Role: types.UserRoleAdmin,
Issued: types.UserIssuedAPI,
AutoGroups: []string{"groupB"},
},
{
Id: "regularUser3",
AccountID: account.Id,
Role: types.UserRoleAdmin,
Issued: types.UserIssuedAPI,
AutoGroups: []string{"groupC"},
},
}, true)
require.NoError(t, err)
var peer4 *nbpeer.Peer
var peer5 *nbpeer.Peer
var peer6 *nbpeer.Peer
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// Updating not expired peer and peer expiration is enabled should not update account peers and not send peer update
t.Run("updating not expired peer and peer expiration is enabled", func(t *testing.T) {
t.Skip("Currently all updates will trigger a network map")
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err := manager.UpdatePeer(context.Background(), account.Id, userID, peer2)
err := manager.DeletePolicy(context.Background(), account.Id, account.Policies[0].ID, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
g := []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{},
},
}
})
// Adding peer to unlinked group should not update account peers and not send peer update
t.Run("adding peer to unlinked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
expectedPeerKey := key.PublicKey().String()
peer4, _, _, _, err = manager.AddPeer(context.Background(), "", "", "regularUser1", &nbpeer.Peer{
Key: expectedPeerKey,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Deleting peer with unlinked group should not update account peers and not send peer update
t.Run("deleting peer with unlinked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.DeletePeer(context.Background(), account.Id, peer4.ID, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Updating peer label should update account peers and send peer update
t.Run("updating peer label", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
peer1.Name = "peer-1"
_, err = manager.UpdatePeer(context.Background(), account.Id, userID, peer1)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
t.Run("validator requires update", func(t *testing.T) {
requireUpdateFunc := func(_ context.Context, update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *types.ExtraSettings) (*nbpeer.Peer, bool, error) {
return update, true, nil
for _, group := range g {
err = manager.CreateGroup(context.Background(), account.Id, userID, group)
require.NoError(t, err)
}
manager.integratedPeerValidator = MockIntegratedValidator{ValidatePeerFunc: requireUpdateFunc}
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.UpdatePeer(context.Background(), account.Id, userID, peer1)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
t.Run("validator requires no update", func(t *testing.T) {
t.Skip("Currently all updates will trigger a network map")
requireNoUpdateFunc := func(_ context.Context, update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *types.ExtraSettings) (*nbpeer.Peer, bool, error) {
return update, false, nil
}
manager.integratedPeerValidator = MockIntegratedValidator{ValidatePeerFunc: requireNoUpdateFunc}
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.UpdatePeer(context.Background(), account.Id, userID, peer1)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Adding peer to group linked with policy should update account peers and send peer update
t.Run("adding peer to group linked with policy", func(t *testing.T) {
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupA"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
// create a user with auto groups
_, err = manager.SaveOrAddUsers(context.Background(), account.Id, userID, []*types.User{
{
Id: "regularUser1",
AccountID: account.Id,
Role: types.UserRoleAdmin,
Issued: types.UserIssuedAPI,
AutoGroups: []string{"groupA"},
},
{
Id: "regularUser2",
AccountID: account.Id,
Role: types.UserRoleAdmin,
Issued: types.UserIssuedAPI,
AutoGroups: []string{"groupB"},
},
{
Id: "regularUser3",
AccountID: account.Id,
Role: types.UserRoleAdmin,
Issued: types.UserIssuedAPI,
AutoGroups: []string{"groupC"},
},
}, true)
require.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
var peer4 *nbpeer.Peer
var peer5 *nbpeer.Peer
var peer6 *nbpeer.Peer
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
expectedPeerKey := key.PublicKey().String()
peer4, _, _, _, err = manager.AddPeer(context.Background(), "", "", "regularUser1", &nbpeer.Peer{
Key: expectedPeerKey,
LoginExpirationEnabled: true,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
// Adding peer to unlinked group should not update account peers and not send peer update
step(t, "adding peer to unlinked group", func(t *testing.T) {
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
// Deleting peer with linked group to policy should update account peers and send peer update
t.Run("deleting peer with linked group to policy", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
expectedPeerKey := key.PublicKey().String()
peer4, _, _, _, err = manager.AddPeer(context.Background(), "", "", "regularUser1", &nbpeer.Peer{
Key: expectedPeerKey,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
err = manager.DeletePeer(context.Background(), account.Id, peer4.ID, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting peer with unlinked group should not update account peers and not send peer update
step(t, "deleting peer with unlinked group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
// drain any buffered updates from previous subtests
drainPeerUpdates(updMsg)
err = manager.DeletePeer(context.Background(), account.Id, peer4.ID, userID)
require.NoError(t, err)
// Adding peer to group linked with route should update peers in that group, not unrelated peers
t.Run("adding peer to group linked with route", func(t *testing.T) {
route := nbroute.Route{
ID: "testingRoute1",
Network: netip.MustParsePrefix("100.65.250.202/32"),
NetID: "superNet",
NetworkType: nbroute.IPv4Network,
PeerGroups: []string{"groupB"},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{"groupB"},
}
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
_, err := manager.CreateRoute(
context.Background(), account.Id, route.Network, route.NetworkType, route.Domains, route.Peer,
route.PeerGroups, route.Description, route.NetID, route.Masquerade, route.Metric,
route.Groups, []string{}, true, userID, route.KeepRoute, route.SkipAutoApply,
)
require.NoError(t, err)
// Updating peer label should update account peers and send peer update
step(t, "updating peer label", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
peer1.Name = "peer-1"
_, err = manager.UpdatePeer(context.Background(), account.Id, userID, peer1)
require.NoError(t, err)
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
expectedPeerKey := key.PublicKey().String()
peer5, _, _, _, err = manager.AddPeer(context.Background(), "", "", "regularUser2", &nbpeer.Peer{
Key: expectedPeerKey,
LoginExpirationEnabled: true,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
step(t, "validator requires update", func(t *testing.T) {
requireUpdateFunc := func(_ context.Context, update *nbpeer.Peer, peer *nbpeer.Peer, userID string, accountID string, dnsDomain string, peersGroup []string, extraSettings *types.ExtraSettings) (*nbpeer.Peer, bool, error) {
return update, true, nil
}
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
manager.integratedPeerValidator = MockIntegratedValidator{ValidatePeerFunc: requireUpdateFunc}
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Deleting peer with linked group to route should update peers in that group, not unrelated peers
t.Run("deleting peer with linked group to route", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.UpdatePeer(context.Background(), account.Id, userID, peer1)
require.NoError(t, err)
err = manager.DeletePeer(context.Background(), account.Id, peer5.ID, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Adding peer to group linked with policy should update account peers and send peer update
step(t, "adding peer to group linked with policy", func(t *testing.T) {
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupA"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
}, true)
require.NoError(t, err)
// Adding peer to group linked with name server group should update peers in that group, not unrelated peers
t.Run("adding peer to group linked with name server group", func(t *testing.T) {
_, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "nsGroup", "nsGroup", []nbdns.NameServer{{
IP: netip.MustParseAddr("1.1.1.1"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupC"},
true, []string{}, true, userID, false,
)
require.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
expectedPeerKey := key.PublicKey().String()
peer4, _, _, _, err = manager.AddPeer(context.Background(), "", "", "regularUser1", &nbpeer.Peer{
Key: expectedPeerKey,
LoginExpirationEnabled: true,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
expectedPeerKey := key.PublicKey().String()
peer6, _, _, _, err = manager.AddPeer(context.Background(), "", "", "regularUser3", &nbpeer.Peer{
Key: expectedPeerKey,
LoginExpirationEnabled: true,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Deleting peer with linked group to policy should update account peers and send peer update
step(t, "deleting peer with linked group to policy", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Deleting peer with linked group to name server group should update peers in that group, not unrelated peers
t.Run("deleting peer with linked group to route", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.DeletePeer(context.Background(), account.Id, peer4.ID, userID)
require.NoError(t, err)
err = manager.DeletePeer(context.Background(), account.Id, peer6.ID, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
// drain any buffered updates from previous subtests
drainPeerUpdates(updMsg)
// Adding peer to group linked with route should update peers in that group, not unrelated peers
step(t, "adding peer to group linked with route", func(t *testing.T) {
route := nbroute.Route{
ID: "testingRoute1",
Network: netip.MustParsePrefix("100.65.250.202/32"),
NetID: "superNet",
NetworkType: nbroute.IPv4Network,
PeerGroups: []string{"groupB"},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{"groupB"},
}
_, err := manager.CreateRoute(
context.Background(), account.Id, route.Network, route.NetworkType, route.Domains, route.Peer,
route.PeerGroups, route.Description, route.NetID, route.Masquerade, route.Metric,
route.Groups, []string{}, true, userID, route.KeepRoute, route.SkipAutoApply,
)
require.NoError(t, err)
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
expectedPeerKey := key.PublicKey().String()
peer5, _, _, _, err = manager.AddPeer(context.Background(), "", "", "regularUser2", &nbpeer.Peer{
Key: expectedPeerKey,
LoginExpirationEnabled: true,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Deleting peer with linked group to route should update peers in that group, not unrelated peers
step(t, "deleting peer with linked group to route", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.DeletePeer(context.Background(), account.Id, peer5.ID, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Adding peer to group linked with name server group should update peers in that group, not unrelated peers
step(t, "adding peer to group linked with name server group", func(t *testing.T) {
_, err = manager.CreateNameServerGroup(
context.Background(), account.Id, "nsGroup", "nsGroup", []nbdns.NameServer{{
IP: netip.MustParseAddr("1.1.1.1"),
NSType: nbdns.UDPNameServerType,
Port: nbdns.DefaultDNSPort,
}},
[]string{"groupC"},
true, []string{}, true, userID, false,
)
require.NoError(t, err)
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
expectedPeerKey := key.PublicKey().String()
peer6, _, _, _, err = manager.AddPeer(context.Background(), "", "", "regularUser3", &nbpeer.Peer{
Key: expectedPeerKey,
LoginExpirationEnabled: true,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Deleting peer with linked group to name server group should update peers in that group, not unrelated peers
step(t, "deleting peer with linked group to name server group", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.DeletePeer(context.Background(), account.Id, peer6.ID, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
})
}
@@ -2703,6 +2642,8 @@ func TestHandleSetupKeyAddedPeer(t *testing.T) {
err = manager.handleSetupKeyAddedPeer(context.Background(), encodedHashedKey, peer, opEvent, config)
require.Error(t, err)
require.IsType(t, &status.Error{}, err)
require.Equal(t, err.(*status.Error).ErrorType, status.PermissionDenied)
assert.Contains(t, err.Error(), "setup key is invalid")
})
@@ -2723,6 +2664,8 @@ func TestHandleSetupKeyAddedPeer(t *testing.T) {
err = manager.handleSetupKeyAddedPeer(context.Background(), encodedHashedKey, peer, opEvent, config)
require.Error(t, err)
require.IsType(t, &status.Error{}, err)
require.Equal(t, err.(*status.Error).ErrorType, status.PermissionDenied)
assert.Contains(t, err.Error(), "setup key is invalid")
})
@@ -2859,45 +2802,47 @@ func TestPeerWillHaveIPv6(t *testing.T) {
// flipping --disable-ipv6) without bumping its WtVersion, other account peers
// receive a fresh network map so their AAAA records for it become unstale.
func TestSyncPeer_IPv6CapabilityChangePropagates(t *testing.T) {
manager, updateManager, _, peer1, peer2, _ := setupNetworkMapTest(t)
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, _, peer1, peer2, _ := setupNetworkMapTest(t)
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// Drain any initial updates from setup.
drain := func() {
for {
select {
case <-updMsg:
case <-time.After(200 * time.Millisecond):
return
// Drain any initial updates from setup.
drain := func() {
for {
select {
case <-updMsg:
case <-time.After(200 * time.Millisecond):
return
}
}
}
}
drain()
drain()
t.Run("no propagation when capabilities are unchanged", func(t *testing.T) {
_, _, _, _, err := manager.SyncPeer(context.Background(), types.PeerSync{
WireGuardPubKey: peer2.Key,
Meta: peer2.Meta,
}, peer2.AccountID)
require.NoError(t, err)
peerShouldNotReceiveUpdate(t, updMsg)
})
step(t, "no propagation when capabilities are unchanged", func(t *testing.T) {
_, _, _, _, err := manager.SyncPeer(context.Background(), types.PeerSync{
WireGuardPubKey: peer2.Key,
Meta: peer2.Meta,
}, peer2.AccountID)
require.NoError(t, err)
peerShouldNotReceiveUpdate(t, updMsg)
})
t.Run("propagation when IPv6 capability is added", func(t *testing.T) {
newMeta := peer2.Meta
newMeta.Capabilities = append([]int32{}, peer2.Meta.Capabilities...)
newMeta.Capabilities = append(newMeta.Capabilities, nbpeer.PeerCapabilityIPv6Overlay)
step(t, "propagation when IPv6 capability is added", func(t *testing.T) {
newMeta := peer2.Meta
newMeta.Capabilities = append([]int32{}, peer2.Meta.Capabilities...)
newMeta.Capabilities = append(newMeta.Capabilities, nbpeer.PeerCapabilityIPv6Overlay)
_, _, _, _, err := manager.SyncPeer(context.Background(), types.PeerSync{
WireGuardPubKey: peer2.Key,
Meta: newMeta,
}, peer2.AccountID)
require.NoError(t, err)
peerShouldReceiveUpdate(t, updMsg)
_, _, _, _, err := manager.SyncPeer(context.Background(), types.PeerSync{
WireGuardPubKey: peer2.Key,
Meta: newMeta,
}, peer2.AccountID)
require.NoError(t, err)
peerShouldReceiveUpdate(t, updMsg)
})
})
}
+278 -274
View File
@@ -1135,287 +1135,291 @@ func sortFunc() func(a *types.FirewallRule, b *types.FirewallRule) int {
}
func TestPolicyAccountPeersUpdate(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
g := []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer3.ID},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{},
},
{
ID: "groupD",
Name: "GroupD",
Peers: []string{peer1.ID, peer2.ID},
},
}
for _, group := range g {
err := manager.CreateGroup(context.Background(), account.Id, userID, group)
assert.NoError(t, err)
}
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
var policyWithGroupRulesNoPeers *types.Policy
var policyWithDestinationPeersOnly *types.Policy
var policyWithSourceAndDestinationPeers *types.Policy
var err error
// Saving policy with rule groups with no peers should not update account's peers and not send peer update
t.Run("saving policy with rule groups with no peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
policyWithGroupRulesNoPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupB"},
Destinations: []string{"groupC"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
g := []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer3.ID},
},
}, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Saving policy with source group containing peers, but destination group without peers should
// update account's peers and send peer update
t.Run("saving policy where source has peers but destination does not", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupB"},
Protocol: types.PolicyRuleProtocolTCP,
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
}, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Saving policy with destination group containing peers, but source group without peers should
// update account's peers and send peer update
t.Run("saving policy where destination has peers but source does not", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithDestinationPeersOnly, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupC"},
Destinations: []string{"groupD"},
Bidirectional: true,
Protocol: types.PolicyRuleProtocolTCP,
Action: types.PolicyTrafficActionAccept,
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{},
},
}, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Saving policy with destination and source groups containing peers should update account's peers
// and send peer update
t.Run("saving policy with source and destination groups with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupD"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
{
ID: "groupD",
Name: "GroupD",
Peers: []string{peer1.ID, peer2.ID},
},
}, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Disabling policy with destination and source groups containing peers should update account's peers
// and send peer update
t.Run("disabling policy with source and destination groups with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithSourceAndDestinationPeers.Enabled = false
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Updating disabled policy with destination and source groups containing peers should still update account's peers
// because affected peer resolution does not filter by policy enabled state
t.Run("updating disabled policy with source and destination groups with peers", func(t *testing.T) {
drainPeerUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithSourceAndDestinationPeers.Description = "updated description"
policyWithSourceAndDestinationPeers.Rules[0].Destinations = []string{"groupA"}
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Enabling policy with destination and source groups containing peers should update account's peers
// and send peer update
t.Run("enabling policy with source and destination groups with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithSourceAndDestinationPeers.Enabled = true
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting policy should trigger account peers update and send peer update
t.Run("deleting policy with source and destination groups with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeletePolicy(context.Background(), account.Id, policyWithSourceAndDestinationPeers.ID, userID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
for _, group := range g {
err := manager.CreateGroup(context.Background(), account.Id, userID, group)
assert.NoError(t, err)
}
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
var policyWithGroupRulesNoPeers *types.Policy
var policyWithDestinationPeersOnly *types.Policy
var policyWithSourceAndDestinationPeers *types.Policy
var err error
// Saving policy with rule groups with no peers should not update account's peers and not send peer update
step(t, "saving policy with rule groups with no peers", func(t *testing.T) {
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
policyWithGroupRulesNoPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupB"},
Destinations: []string{"groupC"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
}, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Saving policy with source group containing peers, but destination group without peers should
// update account's peers and send peer update
step(t, "saving policy where source has peers but destination does not", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupB"},
Protocol: types.PolicyRuleProtocolTCP,
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
}, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Saving policy with destination group containing peers, but source group without peers should
// update account's peers and send peer update
step(t, "saving policy where destination has peers but source does not", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithDestinationPeersOnly, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupC"},
Destinations: []string{"groupD"},
Bidirectional: true,
Protocol: types.PolicyRuleProtocolTCP,
Action: types.PolicyTrafficActionAccept,
},
},
}, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Saving policy with destination and source groups containing peers should update account's peers
// and send peer update
step(t, "saving policy with source and destination groups with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
AccountID: account.Id,
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupD"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
}, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Disabling policy with destination and source groups containing peers should update account's peers
// and send peer update
step(t, "disabling policy with source and destination groups with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithSourceAndDestinationPeers.Enabled = false
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Updating disabled policy with destination and source groups containing peers should still update account's peers
// because affected peer resolution does not filter by policy enabled state
step(t, "updating disabled policy with source and destination groups with peers", func(t *testing.T) {
drainPeerUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithSourceAndDestinationPeers.Description = "updated description"
policyWithSourceAndDestinationPeers.Rules[0].Destinations = []string{"groupA"}
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Enabling policy with destination and source groups containing peers should update account's peers
// and send peer update
step(t, "enabling policy with source and destination groups with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policyWithSourceAndDestinationPeers.Enabled = true
policyWithSourceAndDestinationPeers, err = manager.SavePolicy(context.Background(), account.Id, userID, policyWithSourceAndDestinationPeers, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting policy should trigger account peers update and send peer update
step(t, "deleting policy with source and destination groups with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeletePolicy(context.Background(), account.Id, policyWithSourceAndDestinationPeers.ID, userID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting policy with destination group containing peers, but source group without peers should
// update account's peers and send peer update
step(t, "deleting policy where destination has peers but source does not", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeletePolicy(context.Background(), account.Id, policyWithDestinationPeersOnly.ID, userID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting policy with no peers in groups should not update account's peers and not send peer update
step(t, "deleting policy with no peers in groups", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeletePolicy(context.Background(), account.Id, policyWithGroupRulesNoPeers.ID, userID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
})
// Deleting policy with destination group containing peers, but source group without peers should
// update account's peers and send peer update
t.Run("deleting policy where destination has peers but source does not", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeletePolicy(context.Background(), account.Id, policyWithDestinationPeersOnly.ID, userID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting policy with no peers in groups should not update account's peers and not send peer update
t.Run("deleting policy with no peers in groups", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeletePolicy(context.Background(), account.Id, policyWithGroupRulesNoPeers.ID, userID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
}
+298 -288
View File
@@ -123,324 +123,334 @@ func initTestPostureChecksAccount(am *DefaultAccountManager) (*types.Account, er
}
func TestPostureCheckAccountPeersUpdate(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
g := []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{},
},
}
for _, group := range g {
err := manager.CreateGroup(context.Background(), account.Id, userID, group)
assert.NoError(t, err)
}
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
postureCheckA := &posture.Checks{
Name: "postureCheckA",
AccountID: account.Id,
Checks: posture.ChecksDefinition{
ProcessCheck: &posture.ProcessCheck{
Processes: []posture.Process{
{LinuxPath: "/usr/bin/netbird", MacPath: "/usr/local/bin/netbird"},
},
},
},
}
postureCheckA, err := manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckA, true)
require.NoError(t, err)
postureCheckB := &posture.Checks{
Name: "postureCheckB",
AccountID: account.Id,
Checks: posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.28.0",
},
},
}
// Saving unused posture check should not update account peers and not send peer update
t.Run("saving unused posture check", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
postureCheckB, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Updating unused posture check should not update account peers and not send peer update
t.Run("updating unused posture check", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
postureCheckB.Checks = posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.29.0",
},
}
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
policy := &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
g := []*types.Group{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupA"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
},
},
SourcePostureChecks: []string{postureCheckB.ID},
}
// Linking posture check to policy should trigger update account peers and send peer update
t.Run("linking posture check to policy with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policy, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Updating linked posture checks should update account peers and send peer update
t.Run("updating linked to posture check with peers", func(t *testing.T) {
postureCheckB.Checks = posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.29.0",
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
ProcessCheck: &posture.ProcessCheck{
Processes: []posture.Process{
{LinuxPath: "/usr/bin/netbird", MacPath: "/usr/local/bin/netbird"},
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{},
},
}
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
for _, group := range g {
err := manager.CreateGroup(context.Background(), account.Id, userID, group)
assert.NoError(t, err)
}
})
// Removing posture check from policy should trigger account peers update and send peer update
t.Run("removing posture check from policy", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policy.SourcePostureChecks = []string{}
_, err := manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting unused posture check should not trigger account peers update and not send peer update
t.Run("deleting unused posture check", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeletePostureChecks(context.Background(), account.Id, postureCheckA.ID, userID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
// Updating linked posture check to policy with no peers should not trigger account peers update and not send peer update
t.Run("updating linked posture check to policy with no peers", func(t *testing.T) {
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupB"},
Destinations: []string{"groupC"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
SourcePostureChecks: []string{postureCheckB.ID},
}, true)
assert.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
postureCheckB.Checks = posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.29.0",
},
}
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Updating linked posture check to policy where destination has peers but source does not
// should trigger account peers update and send peer update
t.Run("updating linked posture check to policy where destination has peers but source does not", func(t *testing.T) {
updMsg1 := updateManager.CreateChannel(context.Background(), peer2.ID)
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer2.ID)
updateManager.CloseChannel(context.Background(), peer1.ID)
})
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
postureCheckA := &posture.Checks{
Name: "postureCheckA",
AccountID: account.Id,
Checks: posture.ChecksDefinition{
ProcessCheck: &posture.ProcessCheck{
Processes: []posture.Process{
{LinuxPath: "/usr/bin/netbird", MacPath: "/usr/local/bin/netbird"},
},
},
},
}
postureCheckA, err := manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckA, true)
require.NoError(t, err)
postureCheckB := &posture.Checks{
Name: "postureCheckB",
AccountID: account.Id,
Checks: posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.28.0",
},
},
}
// Saving unused posture check should not update account peers and not send peer update
step(t, "saving unused posture check", func(t *testing.T) {
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
postureCheckB, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Updating unused posture check should not update account peers and not send peer update
step(t, "updating unused posture check", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
postureCheckB.Checks = posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.29.0",
},
}
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
policy := &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupB"},
Sources: []string{"groupA"},
Destinations: []string{"groupA"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
SourcePostureChecks: []string{postureCheckB.ID},
}, true)
assert.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg1)
close(done)
}()
postureCheckB.Checks = posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.29.0",
},
}
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Linking posture check to policy should trigger update account peers and send peer update
step(t, "linking posture check to policy with peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Updating linked client posture check to policy where source has peers but destination does not,
// should trigger account peers update and send peer update
t.Run("updating linked posture check to policy where source has peers but destination does not", func(t *testing.T) {
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupB"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
policy, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Updating linked posture checks should update account peers and send peer update
step(t, "updating linked to posture check with peers", func(t *testing.T) {
postureCheckB.Checks = posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.29.0",
},
},
SourcePostureChecks: []string{postureCheckB.ID},
}, true)
assert.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
postureCheckB.Checks = posture.ChecksDefinition{
ProcessCheck: &posture.ProcessCheck{
Processes: []posture.Process{
{
LinuxPath: "/usr/bin/netbird",
ProcessCheck: &posture.ProcessCheck{
Processes: []posture.Process{
{LinuxPath: "/usr/bin/netbird", MacPath: "/usr/local/bin/netbird"},
},
},
},
}
}
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Removing posture check from policy should trigger account peers update and send peer update
step(t, "removing posture check from policy", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
policy.SourcePostureChecks = []string{}
_, err := manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting unused posture check should not trigger account peers update and not send peer update
step(t, "deleting unused posture check", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeletePostureChecks(context.Background(), account.Id, postureCheckA.ID, userID)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
// Updating linked posture check to policy with no peers should not trigger account peers update and not send peer update
step(t, "updating linked posture check to policy with no peers", func(t *testing.T) {
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupB"},
Destinations: []string{"groupC"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
SourcePostureChecks: []string{postureCheckB.ID},
}, true)
assert.NoError(t, err)
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
postureCheckB.Checks = posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.29.0",
},
}
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Updating linked posture check to policy where destination has peers but source does not
// should trigger account peers update and send peer update
step(t, "updating linked posture check to policy where destination has peers but source does not", func(t *testing.T) {
updMsg1 := updateManager.CreateChannel(context.Background(), peer2.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer2.ID)
})
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupB"},
Destinations: []string{"groupA"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
SourcePostureChecks: []string{postureCheckB.ID},
}, true)
assert.NoError(t, err)
settleAffectedUpdates(updMsg, updMsg1)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg1)
close(done)
}()
postureCheckB.Checks = posture.ChecksDefinition{
NBVersionCheck: &posture.NBVersionCheck{
MinVersion: "0.29.0",
},
}
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Updating linked client posture check to policy where source has peers but destination does not,
// should trigger account peers update and send peer update
step(t, "updating linked posture check to policy where source has peers but destination does not", func(t *testing.T) {
_, err = manager.SavePolicy(context.Background(), account.Id, userID, &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupB"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
SourcePostureChecks: []string{postureCheckB.ID},
}, true)
assert.NoError(t, err)
settleAffectedUpdates(updMsg)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
postureCheckB.Checks = posture.ChecksDefinition{
ProcessCheck: &posture.ProcessCheck{
Processes: []posture.Process{
{
LinuxPath: "/usr/bin/netbird",
},
},
},
}
_, err = manager.SavePostureChecks(context.Background(), account.Id, userID, postureCheckB, true)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
})
}
+253 -246
View File
@@ -6,10 +6,10 @@ import (
"testing"
"time"
"go.uber.org/mock/gomock"
"github.com/rs/xid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
@@ -18,7 +18,6 @@ import (
"github.com/netbirdio/netbird/management/internals/server/config"
"github.com/netbirdio/netbird/management/server/activity"
"github.com/netbirdio/netbird/management/server/cache"
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
"github.com/netbirdio/netbird/management/server/job"
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
@@ -1262,7 +1261,10 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, *update_channel.
}
eventStore := &activity.InMemoryEventStore{}
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
metrics, err := telemetry.NewDefaultAppMetrics(ctx)
require.NoError(t, err)
ctrl := gomock.NewController(t)
@@ -1290,21 +1292,22 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, *update_channel.
permissionsManager := permissions.NewManager(store)
peersManager := peers.NewManager(store, permissionsManager)
ctx := context.Background()
cacheStore, err := cache.NewStore(ctx, 100*time.Millisecond, 300*time.Millisecond, 100)
// A go-cache janitor only stops through a GC finalizer, which would leave synctest bubbles with a goroutine that never exits.
cacheStore, err := cache.NewStore(ctx, 100*time.Millisecond, 0, 100)
if err != nil {
return nil, nil, err
}
updateManager := update_channel.NewPeersUpdateManager(metrics)
requestBuffer := NewAccountRequestBuffer(ctx, store)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", port_forwarding.NewControllerMock(), ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)), &config.Config{}, nil)
networkMapController := controller.NewController(ctx, store, metrics, updateManager, requestBuffer, MockIntegratedValidator{}, settingsMockManager, "netbird.selfhosted", ephemeral_manager.NewEphemeralManager(store, peers.NewManager(store, permissionsManager)), &config.Config{}, nil)
am, err := BuildManager(context.Background(), nil, store, networkMapController, job.NewJobManager(nil, store, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, port_forwarding.NewControllerMock(), settingsMockManager, permissionsManager, false, cacheStore)
am, err := BuildManager(ctx, nil, store, networkMapController, job.NewJobManager(nil, store, peersManager), nil, "", eventStore, nil, false, MockIntegratedValidator{}, metrics, settingsMockManager, permissionsManager, false, cacheStore)
if err != nil {
return nil, nil, err
}
cacheManager := am.cacheManager
t.Cleanup(func() { _ = cacheManager.Close() })
return am, updateManager, nil
}
@@ -1893,265 +1896,269 @@ func TestAccount_getPeersRoutesFirewall(t *testing.T) {
}
func TestRouteAccountPeersUpdate(t *testing.T) {
manager, updateManager, err := createRouterManager(t)
require.NoError(t, err, "failed to create account manager")
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, err := createRouterManager(t)
require.NoError(t, err, "failed to create account manager")
account, err := initTestRouteAccount(t, manager)
require.NoError(t, err, "failed to init testing account")
account, err := initTestRouteAccount(t, manager)
require.NoError(t, err, "failed to init testing account")
g := []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{},
},
}
for _, group := range g {
err = manager.CreateGroup(context.Background(), account.Id, userID, group)
require.NoError(t, err, "failed to create group %s", group.Name)
}
g := []*types.Group{
{
ID: "groupA",
Name: "GroupA",
Peers: []string{},
},
{
ID: "groupB",
Name: "GroupB",
Peers: []string{},
},
{
ID: "groupC",
Name: "GroupC",
Peers: []string{},
},
}
for _, group := range g {
err = manager.CreateGroup(context.Background(), account.Id, userID, group)
require.NoError(t, err, "failed to create group %s", group.Name)
}
updMsg := updateManager.CreateChannel(context.Background(), peer1ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1ID)
})
updMsg := updateManager.CreateChannel(context.Background(), peer1ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1ID)
})
// Creating a route with no routing peer and no peers in PeerGroups or Groups should not update account peers and not send peer update
t.Run("creating route no routing peer and no peers in groups", func(t *testing.T) {
route := route.Route{
ID: "testingRoute1",
Network: netip.MustParsePrefix("100.65.250.202/32"),
// Creating a route with no routing peer and no peers in PeerGroups or Groups should not update account peers and not send peer update
step(t, "creating route no routing peer and no peers in groups", func(t *testing.T) {
settleAffectedUpdates(updMsg)
route := route.Route{
ID: "testingRoute1",
Network: netip.MustParsePrefix("100.65.250.202/32"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{"groupA"},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{"groupA"},
}
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err := manager.CreateRoute(
context.Background(), account.Id, route.Network, route.NetworkType, route.Domains, route.Peer,
route.PeerGroups, route.Description, route.NetID, route.Masquerade, route.Metric,
route.Groups, []string{}, true, userID, route.KeepRoute, route.SkipAutoApply,
)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Creating a route with no routing peer and having peers in groups that don't include peer1 should not send peer1 an update
step(t, "creating a route with peers in PeerGroups and Groups", func(t *testing.T) {
drainPeerUpdates(updMsg)
route := route.Route{
ID: "testingRoute2",
Network: netip.MustParsePrefix("192.0.2.0/32"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{routeGroup3},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{routeGroup3},
}
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err := manager.CreateRoute(
context.Background(), account.Id, route.Network, route.NetworkType, route.Domains, route.Peer,
route.PeerGroups, route.Description, route.NetID, route.Masquerade, route.Metric,
route.Groups, []string{}, true, userID, route.KeepRoute, route.SkipAutoApply,
)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
baseRoute := route.Route{
ID: "testingRoute3",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{"groupA"},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{"groupA"},
}
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err := manager.CreateRoute(
context.Background(), account.Id, route.Network, route.NetworkType, route.Domains, route.Peer,
route.PeerGroups, route.Description, route.NetID, route.Masquerade, route.Metric,
route.Groups, []string{}, true, userID, route.KeepRoute, route.SkipAutoApply,
)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Creating a route with no routing peer and having peers in groups that don't include peer1 should not send peer1 an update
t.Run("creating a route with peers in PeerGroups and Groups", func(t *testing.T) {
drainPeerUpdates(updMsg)
route := route.Route{
ID: "testingRoute2",
Network: netip.MustParsePrefix("192.0.2.0/32"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{routeGroup3},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{routeGroup3},
}
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err := manager.CreateRoute(
context.Background(), account.Id, route.Network, route.NetworkType, route.Domains, route.Peer,
route.PeerGroups, route.Description, route.NetID, route.Masquerade, route.Metric,
route.Groups, []string{}, true, userID, route.KeepRoute, route.SkipAutoApply,
)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
baseRoute := route.Route{
ID: "testingRoute3",
Network: netip.MustParsePrefix("192.168.0.0/16"),
NetID: "superNet",
NetworkType: route.IPv4Network,
Peer: peer1ID,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{routeGroup1},
}
// Creating route should update account peers and send peer update
t.Run("creating route with a routing peer", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
newRoute, err := manager.CreateRoute(
context.Background(), account.Id, baseRoute.Network, baseRoute.NetworkType, baseRoute.Domains, baseRoute.Peer,
baseRoute.PeerGroups, baseRoute.Description, baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric,
baseRoute.Groups, []string{}, true, userID, baseRoute.KeepRoute, !baseRoute.SkipAutoApply,
)
require.NoError(t, err)
baseRoute = *newRoute
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Updating the route should update account peers and send peer update when there is peers in group
t.Run("updating route", func(t *testing.T) {
baseRoute.Groups = []string{routeGroup1, routeGroup2}
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.SaveRoute(context.Background(), account.Id, userID, &baseRoute)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Deleting the route should update account peers and send peer update
t.Run("deleting route", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeleteRoute(context.Background(), account.Id, baseRoute.ID, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Adding peer to route peer groups that do not have any peers should update account peers and send peer update
t.Run("adding peer to route peer groups that do not have any peers", func(t *testing.T) {
newRoute := route.Route{
Network: netip.MustParsePrefix("192.168.12.0/16"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{"groupB"},
Peer: peer1ID,
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{routeGroup1},
}
_, err := manager.CreateRoute(
context.Background(), account.Id, newRoute.Network, newRoute.NetworkType, newRoute.Domains, newRoute.Peer,
newRoute.PeerGroups, newRoute.Description, newRoute.NetID, newRoute.Masquerade, newRoute.Metric,
newRoute.Groups, []string{}, true, userID, newRoute.KeepRoute, !newRoute.SkipAutoApply,
)
require.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Creating route should update account peers and send peer update
step(t, "creating route with a routing peer", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupB",
Name: "GroupB",
Peers: []string{peer1ID},
newRoute, err := manager.CreateRoute(
context.Background(), account.Id, baseRoute.Network, baseRoute.NetworkType, baseRoute.Domains, baseRoute.Peer,
baseRoute.PeerGroups, baseRoute.Description, baseRoute.NetID, baseRoute.Masquerade, baseRoute.Metric,
baseRoute.Groups, []string{}, true, userID, baseRoute.KeepRoute, !baseRoute.SkipAutoApply,
)
require.NoError(t, err)
baseRoute = *newRoute
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Updating the route should update account peers and send peer update when there is peers in group
step(t, "updating route", func(t *testing.T) {
baseRoute.Groups = []string{routeGroup1, routeGroup2}
// Adding peer to route groups that do not have any peers should update account peers and send peer update
t.Run("adding peer to route groups that do not have any peers", func(t *testing.T) {
newRoute := route.Route{
Network: netip.MustParsePrefix("192.168.13.0/16"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{"groupB"},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{"groupC"},
}
_, err := manager.CreateRoute(
context.Background(), account.Id, newRoute.Network, newRoute.NetworkType, newRoute.Domains, newRoute.Peer,
newRoute.PeerGroups, newRoute.Description, newRoute.NetID, newRoute.Masquerade, newRoute.Metric,
newRoute.Groups, []string{}, true, userID, newRoute.KeepRoute, !newRoute.SkipAutoApply,
)
require.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.SaveRoute(context.Background(), account.Id, userID, &baseRoute)
require.NoError(t, err)
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupC",
Name: "GroupC",
Peers: []string{peer1ID},
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
// Deleting the route should update account peers and send peer update
step(t, "deleting route", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err := manager.DeleteRoute(context.Background(), account.Id, baseRoute.ID, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Adding peer to route peer groups that do not have any peers should update account peers and send peer update
step(t, "adding peer to route peer groups that do not have any peers", func(t *testing.T) {
newRoute := route.Route{
Network: netip.MustParsePrefix("192.168.12.0/16"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{"groupB"},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{routeGroup1},
}
_, err := manager.CreateRoute(
context.Background(), account.Id, newRoute.Network, newRoute.NetworkType, newRoute.Domains, newRoute.Peer,
newRoute.PeerGroups, newRoute.Description, newRoute.NetID, newRoute.Masquerade, newRoute.Metric,
newRoute.Groups, []string{}, true, userID, newRoute.KeepRoute, !newRoute.SkipAutoApply,
)
require.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupB",
Name: "GroupB",
Peers: []string{peer1ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// Adding peer to route groups that do not have any peers should update account peers and send peer update
step(t, "adding peer to route groups that do not have any peers", func(t *testing.T) {
newRoute := route.Route{
Network: netip.MustParsePrefix("192.168.13.0/16"),
NetID: "superNet",
NetworkType: route.IPv4Network,
PeerGroups: []string{"groupB"},
Description: "super",
Masquerade: false,
Metric: 9999,
Enabled: true,
Groups: []string{"groupC"},
}
_, err := manager.CreateRoute(
context.Background(), account.Id, newRoute.Network, newRoute.NetworkType, newRoute.Domains, newRoute.Peer,
newRoute.PeerGroups, newRoute.Description, newRoute.NetID, newRoute.Masquerade, newRoute.Metric,
newRoute.Groups, []string{}, true, userID, newRoute.KeepRoute, !newRoute.SkipAutoApply,
)
require.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.UpdateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupC",
Name: "GroupC",
Peers: []string{peer1ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
})
}
+65 -63
View File
@@ -397,75 +397,77 @@ func TestSetupKey_Copy(t *testing.T) {
}
func TestSetupKeyAccountPeersUpdate(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
runPeerUpdateTest(t, func(t *testing.T) {
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
err := manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
})
assert.NoError(t, err)
policy := &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"group"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
}
_, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
require.NoError(t, err)
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// The setup policy above dispatches affected-peer updates asynchronously; drain
// any in-flight ones so the assertions only observe the setup-key operations.
settleAffectedUpdates(updMsg)
var setupKey *types.SetupKey
// Creating setup key should not update account peers and not send peer update
t.Run("creating setup key", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
setupKey, err = manager.CreateSetupKey(context.Background(), account.Id, "key1", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
err := manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
})
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
policy := &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"group"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
}
})
// Saving setup key should not update account peers and not send peer update
t.Run("saving setup key", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.SaveSetupKey(context.Background(), account.Id, setupKey, userID)
_, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// The setup policy above dispatches affected-peer updates asynchronously; drain
// any in-flight ones so the assertions only observe the setup-key operations.
settleAffectedUpdates(updMsg)
var setupKey *types.SetupKey
// Creating setup key should not update account peers and not send peer update
step(t, "creating setup key", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
setupKey, err = manager.CreateSetupKey(context.Background(), account.Id, "key1", types.SetupKeyReusable, time.Hour, nil, 999, userID, false, false)
assert.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// Saving setup key should not update account peers and not send peer update
step(t, "saving setup key", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.SaveSetupKey(context.Background(), account.Id, setupKey, userID)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
})
}
@@ -127,7 +127,7 @@ func (s *SqlStore) GetSetupKeyBySecret(ctx context.Context, lockStrength Locking
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return nil, status.Errorf(status.PreconditionFailed, "setup key not found")
return nil, status.Errorf(status.PermissionDenied, "couldn't add peer: setup key is invalid")
}
log.WithContext(ctx).Errorf("failed to get setup key by secret from store: %v", result.Error)
return nil, status.Errorf(status.Internal, "failed to get setup key by secret from store")
@@ -213,8 +213,7 @@ func twinPeer(p *nbpeer.Peer) *nmdata.Peer {
}
}
// TwinPeer converts a real peer to its slim nmdata twin. Exported for the
// port-forwarding integration, which builds proxy NetworkMaps holding twins.
// TwinPeer converts a real peer to its slim nmdata twin.
func TwinPeer(p *nbpeer.Peer) *nmdata.Peer {
return twinPeer(p)
}
-1
View File
@@ -15,7 +15,6 @@ import (
type FirewallRule = sharedtypes.FirewallRule
type NetworkMap = sharedtypes.NetworkMap
type ForwardingRule = sharedtypes.ForwardingRule
type PolicyTrafficActionType = sharedtypes.PolicyTrafficActionType
type PolicyRuleProtocolType = sharedtypes.PolicyRuleProtocolType
@@ -10,7 +10,6 @@ type (
DNSSettings = types.DNSSettings
FirewallRule = sharedtypes.FirewallRule
ForwardingRule = sharedtypes.ForwardingRule
Group = types.Group
Network = types.Network
Policy = types.Policy
@@ -19,7 +19,6 @@ type NetworkMap struct {
OfflinePeers []*ComponentPeer
FirewallRules []*FirewallRule
RoutesFirewallRules []*RouteFirewallRule
ForwardingRules []*ForwardingRule
AuthorizedUsers map[string]map[string]struct{}
EnableSSH bool
// ForceRoutingPeerDNSResolution forces the peer to run/use routing-peer DNS
@@ -320,7 +320,6 @@ func canonicalize(nm *proto.NetworkMap) {
slices.SortFunc(nm.Routes, cmpRoute)
slices.SortFunc(nm.FirewallRules, cmpFirewallRule)
slices.SortFunc(nm.RoutesFirewallRules, cmpRouteFirewallRule)
slices.SortFunc(nm.ForwardingRules, cmpForwardingRule)
for _, r := range nm.FirewallRules {
slices.SortFunc(r.SourcePrefixes, bytes.Compare)
@@ -550,16 +549,6 @@ func cmpRouteFirewallRule(a, b *proto.RouteFirewallRule) int {
return boolCmp(a.IsDynamic, b.IsDynamic)
}
func cmpForwardingRule(a, b *proto.ForwardingRule) int {
if a == nil || b == nil {
return boolCmp(a == nil, b == nil)
}
if c := cmp.Compare(int32(a.Protocol), int32(b.Protocol)); c != 0 {
return c
}
return bytes.Compare(a.TranslatedAddress, b.TranslatedAddress)
}
func portInfoKey(pi *proto.PortInfo) string {
if pi == nil {
return ""
@@ -591,7 +580,6 @@ func describeDivergence(legacy, updated *proto.NetworkMap, accountID, peerID str
{"Routes", len(legacy.Routes), len(updated.Routes), func() string { return diffLists(legacy.Routes, updated.Routes) }},
{"FirewallRules", len(legacy.FirewallRules), len(updated.FirewallRules), func() string { return diffLists(legacy.FirewallRules, updated.FirewallRules) }},
{"RoutesFirewallRules", len(legacy.RoutesFirewallRules), len(updated.RoutesFirewallRules), func() string { return diffLists(legacy.RoutesFirewallRules, updated.RoutesFirewallRules) }},
{"ForwardingRules", len(legacy.ForwardingRules), len(updated.ForwardingRules), func() string { return diffLists(legacy.ForwardingRules, updated.ForwardingRules) }},
}
for _, l := range lens {
if l.a != l.b {
@@ -186,14 +186,6 @@ func ToProtoNetworkMap(
pm.RoutesFirewallRules = routesFirewallRules
pm.RoutesFirewallRulesIsEmpty = len(routesFirewallRules) == 0
if nm.ForwardingRules != nil {
forwardingRules := make([]*proto.ForwardingRule, 0, len(nm.ForwardingRules))
for _, rule := range nm.ForwardingRules {
forwardingRules = append(forwardingRules, rule.ToProto())
}
pm.ForwardingRules = forwardingRules
}
if nm.AuthorizedUsers != nil {
hashedUsers, machineUsers := networkmap.BuildAuthorizedUsersProto(ctx, nm.AuthorizedUsers)
userIDClaim := auth.DefaultUserIDClaim
+3 -1
View File
@@ -861,9 +861,11 @@ func (am *DefaultAccountManager) processUserUpdate(ctx context.Context, transact
allGroupChanges := slices.Concat(removedGroups, addedGroups)
change.LinkGroups = allGroupChanges
if err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, allGroupChanges); err != nil {
ipv6Changed, err := am.reconcileIPv6ForGroupChanges(ctx, transaction, accountID, allGroupChanges)
if err != nil {
return change, nil, nil, nil, fmt.Errorf("reconcile IPv6 for group changes: %w", err)
}
change.ChangedPeerIDs = append(change.ChangedPeerIDs, ipv6Changed...)
}
userEventsToAdd := am.prepareUserUpdateEvents(ctx, updatedUser.AccountID, initiatorUserId, oldUser, updatedUser, transferredOwnerRole, isNewUser, removedGroups, addedGroups, transaction)
+135 -131
View File
@@ -1544,166 +1544,170 @@ func TestDefaultAccountManager_SaveUser(t *testing.T) {
}
func TestUserAccountPeersUpdate(t *testing.T) {
// account groups propagation is enabled
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
runPeerUpdateTest(t, func(t *testing.T) {
// account groups propagation is enabled
manager, updateManager, account, peer1, peer2, peer3 := setupNetworkMapTest(t)
err := manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
})
require.NoError(t, err)
err := manager.CreateGroup(context.Background(), account.Id, userID, &types.Group{
ID: "groupA",
Name: "GroupA",
Peers: []string{peer1.ID, peer2.ID, peer3.ID},
})
require.NoError(t, err)
policy := &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupA"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
policy := &types.Policy{
Enabled: true,
Rules: []*types.PolicyRule{
{
Enabled: true,
Sources: []string{"groupA"},
Destinations: []string{"groupA"},
Bidirectional: true,
Action: types.PolicyTrafficActionAccept,
},
},
},
}
_, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
require.NoError(t, err)
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// Creating a new regular user should send peer update (as users are not filtered yet)
t.Run("creating new regular user with no groups", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
Id: "regularUser1",
AccountID: account.Id,
Role: types.UserRoleUser,
Issued: types.UserIssuedAPI,
}, true)
}
_, err = manager.SavePolicy(context.Background(), account.Id, userID, policy, true)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
updMsg := updateManager.CreateChannel(context.Background(), peer1.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer1.ID)
})
// updating user with no linked peers should update account peers and send peer update (as users are not filtered yet)
t.Run("updating user with no linked peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
// Creating a new regular user should send peer update (as users are not filtered yet)
step(t, "creating new regular user with no groups", func(t *testing.T) {
settleAffectedUpdates(updMsg)
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
Id: "regularUser1",
AccountID: account.Id,
Role: types.UserRoleUser,
Issued: types.UserIssuedAPI,
}, false)
require.NoError(t, err)
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
Id: "regularUser1",
AccountID: account.Id,
Role: types.UserRoleUser,
Issued: types.UserIssuedAPI,
}, true)
require.NoError(t, err)
// drain any buffered updates from previous subtests
drainPeerUpdates(updMsg)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// deleting user with no linked peers should not update account peers and not send peer update
t.Run("deleting user with no linked peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
// saving an unchanged user with no linked peers should not update account peers and not send peer update
step(t, "updating user with no linked peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
err = manager.DeleteUser(context.Background(), account.Id, userID, "regularUser1")
require.NoError(t, err)
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
Id: "regularUser1",
AccountID: account.Id,
Role: types.UserRoleUser,
Issued: types.UserIssuedAPI,
}, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// create a user and add new peer with the user
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
Id: "regularUser2",
AccountID: account.Id,
Role: types.UserRoleAdmin,
Issued: types.UserIssuedAPI,
}, true)
require.NoError(t, err)
// drain any buffered updates from previous subtests
drainPeerUpdates(updMsg)
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
// deleting user with no linked peers should not update account peers and not send peer update
step(t, "deleting user with no linked peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldNotReceiveUpdate(t, updMsg)
close(done)
}()
expectedPeerKey := key.PublicKey().String()
peer4, _, _, _, err := manager.AddPeer(context.Background(), "", "", "regularUser2", &nbpeer.Peer{
Key: expectedPeerKey,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
err = manager.DeleteUser(context.Background(), account.Id, userID, "regularUser1")
require.NoError(t, err)
// updating user with linked peers should update account peers and send peer update
t.Run("updating user with linked peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
select {
case <-done:
case <-time.After(time.Second):
t.Error("timeout waiting for peerShouldNotReceiveUpdate")
}
})
// create a user and add new peer with the user
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
Id: "regularUser2",
AccountID: account.Id,
Role: types.UserRoleAdmin,
Issued: types.UserIssuedAPI,
}, true)
require.NoError(t, err)
key, err := wgtypes.GeneratePrivateKey()
require.NoError(t, err)
expectedPeerKey := key.PublicKey().String()
peer4, _, _, _, err := manager.AddPeer(context.Background(), "", "", "regularUser2", &nbpeer.Peer{
Key: expectedPeerKey,
Meta: nbpeer.PeerSystemMeta{Hostname: expectedPeerKey},
}, false)
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
// updating user with linked peers should update account peers and send peer update
step(t, "updating user with linked peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, updMsg)
close(done)
}()
peer4UpdMsg := updateManager.CreateChannel(context.Background(), peer4.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer4.ID)
})
_, err = manager.SaveOrAddUser(context.Background(), account.Id, userID, &types.User{
Id: "regularUser2",
AccountID: account.Id,
Role: types.UserRoleAdmin,
Issued: types.UserIssuedAPI,
}, false)
require.NoError(t, err)
// deleting user with linked peers should update account peers and send peer update
t.Run("deleting user with linked peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, peer4UpdMsg)
close(done)
}()
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
err = manager.DeleteUser(context.Background(), account.Id, userID, "regularUser2")
require.NoError(t, err)
peer4UpdMsg := updateManager.CreateChannel(context.Background(), peer4.ID)
t.Cleanup(func() {
updateManager.CloseChannel(context.Background(), peer4.ID)
})
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
// deleting user with linked peers should update account peers and send peer update
step(t, "deleting user with linked peers", func(t *testing.T) {
done := make(chan struct{})
go func() {
peerShouldReceiveUpdate(t, peer4UpdMsg)
close(done)
}()
err = manager.DeleteUser(context.Background(), account.Id, userID, "regularUser2")
require.NoError(t, err)
select {
case <-done:
case <-time.After(peerUpdateTimeout):
t.Error("timeout waiting for peerShouldReceiveUpdate")
}
})
})
}