diff --git a/client/internal/connect.go b/client/internal/connect.go index 88d829d2f..6ca5f196a 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -457,7 +457,7 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan c.engine = engine c.engineMutex.Unlock() - if err := engine.Start(loginResp.GetNetbirdConfig(), c.config.ManagementURL); err != nil { + if err := engine.Start(loginResp.GetNetbirdConfig(), loginResp.GetPeerConfig(), c.config.ManagementURL); err != nil { log.Errorf("error while starting Netbird Connection Engine: %s", err) return wrapErr(err) } diff --git a/client/internal/engine.go b/client/internal/engine.go index a15450526..b157e1383 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -548,7 +548,7 @@ func waitWithContext(ctx context.Context, wg *sync.WaitGroup) error { // Start creates a new WireGuard tunnel interface and listens to events from Signal and Management services // Connections to remote peers are not established here. // However, they will be established once an event with a list of peers to connect to will be received from Management Service -func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, mgmtURL *url.URL) (err error) { +func (e *Engine) Start(netbirdConfig *mgmProto.NetbirdConfig, peerConfig *mgmProto.PeerConfig, mgmtURL *url.URL) (err error) { e.syncMsgMux.Lock() defer e.syncMsgMux.Unlock() diff --git a/client/internal/engine_privileged_test.go b/client/internal/engine_privileged_test.go index 1b047e017..d213e70e5 100644 --- a/client/internal/engine_privileged_test.go +++ b/client/internal/engine_privileged_test.go @@ -12,12 +12,12 @@ import ( "testing" "time" - "go.uber.org/mock/gomock" "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel" + "go.uber.org/mock/gomock" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" "google.golang.org/grpc" "google.golang.org/grpc/keepalive" @@ -98,7 +98,7 @@ func TestEngine_SSH(t *testing.T) { UpdateDNSServerFunc: func(serial uint64, update nbdns.Config) error { return nil }, } - err = engine.Start(nil, nil) + err = engine.Start(nil, nil, nil) require.NoError(t, err) defer func() { @@ -228,7 +228,7 @@ func TestEngine_Sync(t *testing.T) { } }() - err = engine.Start(nil, nil) + err = engine.Start(nil, nil, nil) if err != nil { t.Fatal(err) return @@ -312,7 +312,7 @@ func TestEngine_MultiplePeers(t *testing.T) { defer mu.Unlock() guid := fmt.Sprintf("{%s}", uuid.New().String()) device.CustomWindowsGUIDString = strings.ToLower(guid) - err = engine.Start(nil, nil) + err = engine.Start(nil, nil, nil) if err != nil { t.Errorf("unable to start engine for peer %d with error %v", j, err) wg.Done() diff --git a/management/internals/controllers/network_map/controller/controller.go b/management/internals/controllers/network_map/controller/controller.go index 9727ff958..f734f7383 100644 --- a/management/internals/controllers/network_map/controller/controller.go +++ b/management/internals/controllers/network_map/controller/controller.go @@ -297,7 +297,7 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin // 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, proxyNetworkMap, dnsDomain, postureChecks, types.TwinAccountSettings(account.Settings), extraSetting, maps.Keys(peerGroups), dnsFwdPort, c.config.DebugUpload.URL) c.metrics.CountToComponentSyncResponseDuration(time.Since(start)) c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{ @@ -318,7 +318,7 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin } 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) + 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.config.DebugUpload.URL) c.metrics.CountToSyncResponseDuration(time.Since(start)) c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{ @@ -451,7 +451,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, nil, dnsDomain, postureChecks, nmData.AccountSettings, extraSettings, peerGroups, dnsFwdPort, c.config.DebugUpload.URL) c.metrics.CountToComponentSyncResponseDuration(time.Since(start)) c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{ @@ -467,7 +467,7 @@ func (c *Controller) sendUpdatesFromData(ctx context.Context, accountID string, c.metrics.CountCalcPeerNetworkMapDuration(time.Since(start)) start = time.Now() - update = grpc.ToSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, p, nil, nil, nmap, dnsDomain, postureChecks, dnsCache, nmData.AccountSettings, extraSettings, peerGroups, dnsFwdPort) + update = grpc.ToSyncResponse(ctx, nil, c.config.HttpConfig, c.config.DeviceAuthorizationFlow, p, nil, nil, nmap, dnsDomain, postureChecks, dnsCache, nmData.AccountSettings, extraSettings, peerGroups, dnsFwdPort, c.config.DebugUpload.URL) c.metrics.CountToSyncResponseDuration(time.Since(start)) c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{ @@ -746,7 +746,7 @@ func (c *Controller) sendUpdateForAffectedPeers(ctx context.Context, accountID s // 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, proxyNetworkMap, dnsDomain, postureChecks, types.TwinAccountSettings(account.Settings), extraSetting, maps.Keys(peerGroups), dnsFwdPort, c.config.DebugUpload.URL) c.metrics.CountToComponentSyncResponseDuration(time.Since(start)) c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{ @@ -767,7 +767,7 @@ func (c *Controller) sendUpdateForAffectedPeers(ctx context.Context, accountID s } 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) + 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.config.DebugUpload.URL) c.metrics.CountToSyncResponseDuration(time.Since(start)) c.peersUpdateManager.SendUpdate(ctx, p.ID, &network_map.UpdateMessage{ @@ -884,7 +884,7 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe // 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, proxyNetworkMap, dnsDomain, postureChecks, types.TwinAccountSettings(account.Settings), extraSettings, maps.Keys(peerGroups), dnsFwdPort, c.config.DebugUpload.URL) c.peersUpdateManager.SendUpdate(ctx, peer.ID, &network_map.UpdateMessage{ Update: update, @@ -901,7 +901,7 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe 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) + 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.config.DebugUpload.URL) c.peersUpdateManager.SendUpdate(ctx, peer.ID, &network_map.UpdateMessage{ Update: update, diff --git a/management/internals/controllers/network_map/nmaptest/runner.go b/management/internals/controllers/network_map/nmaptest/runner.go index ffce6483e..f37e3d5f2 100644 --- a/management/internals/controllers/network_map/nmaptest/runner.go +++ b/management/internals/controllers/network_map/nmaptest/runner.go @@ -239,12 +239,12 @@ func computeMode(t *testing.T, ctx context.Context, mode Mode, nmData *networkma case ModeFull: nmap := controller.NetworkMapFromData(ctx, nmData, peerID, zone, nil) return mgmtgrpc.ToSyncResponse(ctx, nil, nil, nil, peer, nil, nil, nmap, dnsDomain, nil, - &cache.DNSConfigCache{}, nmData.AccountSettings, nil, nil, dnsFwdPort).NetworkMap + &cache.DNSConfigCache{}, nmData.AccountSettings, nil, nil, dnsFwdPort, "").NetworkMap 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, - dnsDomain, nil, nmData.AccountSettings, nil, peerGroups, dnsFwdPort) + dnsDomain, nil, nmData.AccountSettings, nil, peerGroups, dnsFwdPort, "") res, err := networkmap.EnvelopeToNetworkMap(ctx, resp.NetworkMapEnvelope, peer.Key, dnsDomain, false) require.NoError(t, err, "expand envelope") return res.NetworkMap diff --git a/management/internals/shared/grpc/components_envelope_response.go b/management/internals/shared/grpc/components_envelope_response.go index cdd2a7f37..fe4efabe5 100644 --- a/management/internals/shared/grpc/components_envelope_response.go +++ b/management/internals/shared/grpc/components_envelope_response.go @@ -41,6 +41,7 @@ func ToComponentSyncResponse( extraSettings *types.ExtraSettings, peerGroups []string, dnsFwdPort int64, + debugUploadDefault string, ) *proto.SyncResponse { // // 'component' parameter is expected to never be nil @@ -75,7 +76,7 @@ func ToComponentSyncResponse( Version: int32(sharedgrpc.ComponentNetworkMap), } - nbConfig := toNetbirdConfig(config, turnCredentials, relayCredentials, extraSettings, settings) + nbConfig := toNetbirdConfig(config, turnCredentials, relayCredentials, extraSettings, settings, debugUploadDefault) resp.NetbirdConfig = integrationsConfig.ExtendNetBirdConfig(peer.ID, peerGroups, nbConfig, extraSettings) // settings == nil → field stays nil → "no info in this snapshot", client diff --git a/management/internals/shared/grpc/conversion.go b/management/internals/shared/grpc/conversion.go index 9fb4fccb5..89c81ce15 100644 --- a/management/internals/shared/grpc/conversion.go +++ b/management/internals/shared/grpc/conversion.go @@ -46,7 +46,7 @@ func init() { // nil when no server config is set (the fan-out network-map path) because clients treat any // non-nil config as authoritative: a config without a relay section is interpreted as relay // disabled and wipes the clients' relay URLs. -func toNetbirdConfig(config *nbconfig.Config, turnCredentials *Token, relayToken *Token, extraSettings *types.ExtraSettings, settings *nmdata.AccountSettingsInfo) *proto.NetbirdConfig { +func toNetbirdConfig(config *nbconfig.Config, turnCredentials *Token, relayToken *Token, extraSettings *types.ExtraSettings, settings *nmdata.AccountSettingsInfo, debugUploadDefault string) *proto.NetbirdConfig { if config == nil { return nil } @@ -115,22 +115,26 @@ func toNetbirdConfig(config *nbconfig.Config, turnCredentials *Token, relayToken } } - // The account setting wins, the server config is the deployment-wide value a - // self-hosted install can set once for every account. Both are - // https-validated where they are written. Neither set publishes nothing, and - // the peers fall back to the service NetBird runs. - debugUploadURL := config.DebugUpload.URL - if settings != nil && settings.DebugBundleUploadURL != "" { - debugUploadURL = settings.DebugBundleUploadURL - } // Always sent, empty included: this is a full config, so the peer can tell an // operator clearing the destination from the partial updates that carry only // TURN or relay credentials and say nothing about it. - nbConfig.Debug = &proto.DebugConfig{UploadUrl: debugUploadURL} + nbConfig.Debug = &proto.DebugConfig{UploadUrl: resolveDebugUploadURL(settings, debugUploadDefault)} return nbConfig } +// resolveDebugUploadURL picks the debug-bundle destination a peer is told to +// use. The account setting wins; deploymentDefault is the server-config value a +// self-hosted install sets once for every account. Both are https-validated +// where they are written, and neither set means the peer falls back to the +// service NetBird runs. +func resolveDebugUploadURL(settings *nmdata.AccountSettingsInfo, deploymentDefault string) string { + if settings != nil && settings.DebugBundleUploadURL != "" { + return settings.DebugBundleUploadURL + } + return deploymentDefault +} + func toPeerConfig(peer *nmdata.Peer, network *nmdata.Network, dnsName string, settings *nmdata.AccountSettingsInfo, httpConfig *nbconfig.HttpServerConfig, deviceFlowConfig *nbconfig.DeviceAuthorizationFlow, enableSSH bool, forceRoutingPeerDNS bool) *proto.PeerConfig { netmask, _ := network.Net.Mask.Size() fqdn := peer.FQDN(dnsName) @@ -166,7 +170,7 @@ func toPeerConfig(peer *nmdata.Peer, network *nmdata.Network, dnsName string, se return peerConfig } -func ToSyncResponse(ctx context.Context, config *nbconfig.Config, httpConfig *nbconfig.HttpServerConfig, deviceFlowConfig *nbconfig.DeviceAuthorizationFlow, peer *nmdata.Peer, turnCredentials *Token, relayCredentials *Token, networkMap *types.NetworkMap, dnsName string, checks []*nmdata.PostureChecks, dnsCache *cache.DNSConfigCache, settings *nmdata.AccountSettingsInfo, extraSettings *types.ExtraSettings, peerGroups []string, dnsFwdPort int64) *proto.SyncResponse { +func ToSyncResponse(ctx context.Context, config *nbconfig.Config, httpConfig *nbconfig.HttpServerConfig, deviceFlowConfig *nbconfig.DeviceAuthorizationFlow, peer *nmdata.Peer, turnCredentials *Token, relayCredentials *Token, networkMap *types.NetworkMap, dnsName string, checks []*nmdata.PostureChecks, dnsCache *cache.DNSConfigCache, settings *nmdata.AccountSettingsInfo, extraSettings *types.ExtraSettings, peerGroups []string, dnsFwdPort int64, debugUploadDefault string) *proto.SyncResponse { // IPv6 data in AllowedIPs and SourcePrefixes wildcard expansion depends on // whether the target peer supports IPv6. Routes and firewall rules are already // filtered at the source (network map builder). @@ -185,7 +189,7 @@ func ToSyncResponse(ctx context.Context, config *nbconfig.Config, httpConfig *nb Checks: toProtocolChecks(ctx, checks), } - nbConfig := toNetbirdConfig(config, turnCredentials, relayCredentials, extraSettings, settings) + nbConfig := toNetbirdConfig(config, turnCredentials, relayCredentials, extraSettings, settings, debugUploadDefault) extendedConfig := integrationsConfig.ExtendNetBirdConfig(peer.ID, peerGroups, nbConfig, extraSettings) response.NetbirdConfig = extendedConfig diff --git a/management/internals/shared/grpc/conversion_test.go b/management/internals/shared/grpc/conversion_test.go index 559699d8c..ace6d26d0 100644 --- a/management/internals/shared/grpc/conversion_test.go +++ b/management/internals/shared/grpc/conversion_test.go @@ -278,7 +278,7 @@ func TestToNetbirdConfig_RelayInvariant(t *testing.T) { settings := &types.Settings{MetricsPushEnabled: true} t.Run("nil server config returns nil config", func(t *testing.T) { - nbCfg := toNetbirdConfig(nil, nil, nil, nil, types.TwinAccountSettings(settings)) + nbCfg := toNetbirdConfig(nil, nil, nil, nil, types.TwinAccountSettings(settings), "") assert.Nil(t, nbCfg, "fan-out updates must not carry a partial NetbirdConfig even when settings are present") }) @@ -293,7 +293,7 @@ func TestToNetbirdConfig_RelayInvariant(t *testing.T) { } relayToken := &Token{Payload: "token-payload", Signature: "token-signature"} - nbCfg := toNetbirdConfig(cfg, nil, relayToken, nil, types.TwinAccountSettings(settings)) + nbCfg := toNetbirdConfig(cfg, nil, relayToken, nil, types.TwinAccountSettings(settings), "") require.NotNil(t, nbCfg) require.NotNil(t, nbCfg.Relay, "non-nil NetbirdConfig must include the relay section") assert.Equal(t, cfg.Relay.Addresses, nbCfg.Relay.Urls, "relay URLs should match the server config") diff --git a/management/internals/shared/grpc/server.go b/management/internals/shared/grpc/server.go index c178b6fa1..a52566ba7 100644 --- a/management/internals/shared/grpc/server.go +++ b/management/internals/shared/grpc/server.go @@ -828,7 +828,7 @@ func (s *Server) prepareLoginResponse(ctx context.Context, peer *nbpeer.Peer, ne // if peer has reached this point then it has logged in loginResp := &proto.LoginResponse{ - NetbirdConfig: toNetbirdConfig(s.config, nil, relayToken, nil, types.TwinAccountSettings(settings)), + NetbirdConfig: toNetbirdConfig(s.config, nil, relayToken, nil, types.TwinAccountSettings(settings), s.config.DebugUpload.URL), PeerConfig: toPeerConfig(types.TwinPeer(peer), types.TwinNetwork(network), s.networkMapController.GetDNSDomain(settings), types.TwinAccountSettings(settings), s.config.HttpConfig, s.config.DeviceAuthorizationFlow, enableSSH, false), Checks: toProtocolChecks(ctx, postureChecks), } @@ -960,9 +960,9 @@ func (s *Server) sendInitialSync(ctx context.Context, peerKey wgtypes.Key, peer 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, proxyPatch, dnsName, freshPostureChecks, types.TwinAccountSettings(settings), settings.Extra, peerGroups, freshDnsFwdPort, s.config.DebugUpload.URL) } 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) + 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, s.config.DebugUpload.URL) } key, err := s.secretsManager.GetWGKey() diff --git a/management/server/peer_test.go b/management/server/peer_test.go index 22f2b9b6f..913bccc6e 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -1181,7 +1181,7 @@ func TestToSyncResponse(t *testing.T) { } dnsCache := &cache.DNSConfigCache{} accountSettings := &types.Settings{RoutingPeerDNSResolutionEnabled: true} - response := grpc.ToSyncResponse(context.Background(), config, config.HttpConfig, config.DeviceAuthorizationFlow, types.TwinPeer(peer), turnRelayToken, turnRelayToken, networkMap, dnsName, checks, dnsCache, types.TwinAccountSettings(accountSettings), nil, []string{}, int64(dnsForwarderPort)) + response := grpc.ToSyncResponse(context.Background(), config, config.HttpConfig, config.DeviceAuthorizationFlow, types.TwinPeer(peer), turnRelayToken, turnRelayToken, networkMap, dnsName, checks, dnsCache, types.TwinAccountSettings(accountSettings), nil, []string{}, int64(dnsForwarderPort), "") assert.NotNil(t, response) // assert peer config