Skip to content

Commit 3d3c4c5

Browse files
authored
[client] Add full sync response to debug bundle (#4287)
1 parent 92ce5af commit 3d3c4c5

File tree

10 files changed

+298
-221
lines changed

10 files changed

+298
-221
lines changed

client/cmd/debug.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ var forCmd = &cobra.Command{
7777

7878
var persistenceCmd = &cobra.Command{
7979
Use: "persistence [on|off]",
80-
Short: "Set network map memory persistence",
81-
Long: `Configure whether the latest network map should persist in memory. When enabled, the last known network map will be kept in memory.`,
80+
Short: "Set sync response memory persistence",
81+
Long: `Configure whether the latest sync response should persist in memory. When enabled, the last known sync response will be kept in memory.`,
8282
Example: " netbird debug persistence on",
8383
Args: cobra.ExactArgs(1),
84-
RunE: setNetworkMapPersistence,
84+
RunE: setSyncResponsePersistence,
8585
}
8686

8787
func debugBundle(cmd *cobra.Command, _ []string) error {
@@ -206,11 +206,11 @@ func runForDuration(cmd *cobra.Command, args []string) error {
206206

207207
time.Sleep(1 * time.Second)
208208

209-
// Enable network map persistence before bringing the service up
210-
if _, err := client.SetNetworkMapPersistence(cmd.Context(), &proto.SetNetworkMapPersistenceRequest{
209+
// Enable sync response persistence before bringing the service up
210+
if _, err := client.SetSyncResponsePersistence(cmd.Context(), &proto.SetSyncResponsePersistenceRequest{
211211
Enabled: true,
212212
}); err != nil {
213-
return fmt.Errorf("failed to enable network map persistence: %v", status.Convert(err).Message())
213+
return fmt.Errorf("failed to enable sync response persistence: %v", status.Convert(err).Message())
214214
}
215215

216216
if _, err := client.Up(cmd.Context(), &proto.UpRequest{}); err != nil {
@@ -273,7 +273,7 @@ func runForDuration(cmd *cobra.Command, args []string) error {
273273
return nil
274274
}
275275

276-
func setNetworkMapPersistence(cmd *cobra.Command, args []string) error {
276+
func setSyncResponsePersistence(cmd *cobra.Command, args []string) error {
277277
conn, err := getClient(cmd)
278278
if err != nil {
279279
return err
@@ -290,14 +290,14 @@ func setNetworkMapPersistence(cmd *cobra.Command, args []string) error {
290290
}
291291

292292
client := proto.NewDaemonServiceClient(conn)
293-
_, err = client.SetNetworkMapPersistence(cmd.Context(), &proto.SetNetworkMapPersistenceRequest{
293+
_, err = client.SetSyncResponsePersistence(cmd.Context(), &proto.SetSyncResponsePersistenceRequest{
294294
Enabled: persistence == "on",
295295
})
296296
if err != nil {
297-
return fmt.Errorf("failed to set network map persistence: %v", status.Convert(err).Message())
297+
return fmt.Errorf("failed to set sync response persistence: %v", status.Convert(err).Message())
298298
}
299299

300-
cmd.Printf("Network map persistence set to: %s\n", persistence)
300+
cmd.Printf("Sync response persistence set to: %s\n", persistence)
301301
return nil
302302
}
303303

@@ -357,21 +357,21 @@ func formatDuration(d time.Duration) string {
357357
}
358358

359359
func generateDebugBundle(config *profilemanager.Config, recorder *peer.Status, connectClient *internal.ConnectClient, logFilePath string) {
360-
var networkMap *mgmProto.NetworkMap
360+
var syncResponse *mgmProto.SyncResponse
361361
var err error
362362

363363
if connectClient != nil {
364-
networkMap, err = connectClient.GetLatestNetworkMap()
364+
syncResponse, err = connectClient.GetLatestSyncResponse()
365365
if err != nil {
366-
log.Warnf("Failed to get latest network map: %v", err)
366+
log.Warnf("Failed to get latest sync response: %v", err)
367367
}
368368
}
369369

370370
bundleGenerator := debug.NewBundleGenerator(
371371
debug.GeneratorDependencies{
372372
InternalConfig: config,
373373
StatusRecorder: recorder,
374-
NetworkMap: networkMap,
374+
SyncResponse: syncResponse,
375375
LogFile: logFilePath,
376376
},
377377
debug.BundleConfig{

client/internal/connect.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ type ConnectClient struct {
4343
engine *Engine
4444
engineMutex sync.Mutex
4545

46-
persistNetworkMap bool
46+
persistSyncResponse bool
4747
}
4848

4949
func NewConnectClient(
@@ -270,7 +270,7 @@ func (c *ConnectClient) run(mobileDependency MobileDependency, runningChan chan
270270

271271
c.engineMutex.Lock()
272272
c.engine = NewEngine(engineCtx, cancel, signalClient, mgmClient, relayManager, engineConfig, mobileDependency, c.statusRecorder, checks)
273-
c.engine.SetNetworkMapPersistence(c.persistNetworkMap)
273+
c.engine.SetSyncResponsePersistence(c.persistSyncResponse)
274274
c.engineMutex.Unlock()
275275

276276
if err := c.engine.Start(); err != nil {
@@ -349,23 +349,23 @@ func (c *ConnectClient) Engine() *Engine {
349349
return e
350350
}
351351

352-
// GetLatestNetworkMap returns the latest network map from the engine.
353-
func (c *ConnectClient) GetLatestNetworkMap() (*mgmProto.NetworkMap, error) {
352+
// GetLatestSyncResponse returns the latest sync response from the engine.
353+
func (c *ConnectClient) GetLatestSyncResponse() (*mgmProto.SyncResponse, error) {
354354
engine := c.Engine()
355355
if engine == nil {
356356
return nil, errors.New("engine is not initialized")
357357
}
358358

359-
networkMap, err := engine.GetLatestNetworkMap()
359+
syncResponse, err := engine.GetLatestSyncResponse()
360360
if err != nil {
361-
return nil, fmt.Errorf("get latest network map: %w", err)
361+
return nil, fmt.Errorf("get latest sync response: %w", err)
362362
}
363363

364-
if networkMap == nil {
365-
return nil, errors.New("network map is not available")
364+
if syncResponse == nil {
365+
return nil, errors.New("sync response is not available")
366366
}
367367

368-
return networkMap, nil
368+
return syncResponse, nil
369369
}
370370

371371
// Status returns the current client status
@@ -398,18 +398,18 @@ func (c *ConnectClient) Stop() error {
398398
return nil
399399
}
400400

401-
// SetNetworkMapPersistence enables or disables network map persistence.
402-
// When enabled, the last received network map will be stored and can be retrieved
403-
// through the Engine's getLatestNetworkMap method. When disabled, any stored
404-
// network map will be cleared.
405-
func (c *ConnectClient) SetNetworkMapPersistence(enabled bool) {
401+
// SetSyncResponsePersistence enables or disables sync response persistence.
402+
// When enabled, the last received sync response will be stored and can be retrieved
403+
// through the Engine's GetLatestSyncResponse method. When disabled, any stored
404+
// sync response will be cleared.
405+
func (c *ConnectClient) SetSyncResponsePersistence(enabled bool) {
406406
c.engineMutex.Lock()
407-
c.persistNetworkMap = enabled
407+
c.persistSyncResponse = enabled
408408
c.engineMutex.Unlock()
409409

410410
engine := c.Engine()
411411
if engine != nil {
412-
engine.SetNetworkMapPersistence(enabled)
412+
engine.SetSyncResponsePersistence(enabled)
413413
}
414414
}
415415

client/internal/debug/debug.go

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ iptables.txt: Anonymized iptables rules with packet counters, if --system-info f
4646
nftables.txt: Anonymized nftables rules with packet counters, if --system-info flag was provided.
4747
resolved_domains.txt: Anonymized resolved domain IP addresses from the status recorder.
4848
config.txt: Anonymized configuration information of the NetBird client.
49-
network_map.json: Anonymized network map containing peer configurations, routes, DNS settings, and firewall rules.
49+
network_map.json: Anonymized sync response containing peer configurations, routes, DNS settings, and firewall rules.
5050
state.json: Anonymized client state dump containing netbird states.
5151
mutex.prof: Mutex profiling information.
5252
goroutine.prof: Goroutine profiling information.
@@ -73,15 +73,15 @@ Domains
7373
All domain names (except for the netbird domains) are replaced with randomly generated strings ending in ".domain". Anonymized domains are consistent across all files in the bundle.
7474
Reoccuring domain names are replaced with the same anonymized domain.
7575
76-
Network Map
76+
Sync Response
7777
The network_map.json file contains the following anonymized information:
7878
- Peer configurations (addresses, FQDNs, DNS settings)
7979
- Remote and offline peer information (allowed IPs, FQDNs)
8080
- Routes (network ranges, associated domains)
8181
- DNS configuration (nameservers, domains, custom zones)
8282
- Firewall rules (peer IPs, source/destination ranges)
8383
84-
SSH keys in the network map are replaced with a placeholder value. All IP addresses and domains in the network map follow the same anonymization rules as described above.
84+
SSH keys in the sync response are replaced with a placeholder value. All IP addresses and domains in the sync response follow the same anonymization rules as described above.
8585
8686
State File
8787
The state.json file contains anonymized internal state information of the NetBird client, including:
@@ -201,7 +201,7 @@ type BundleGenerator struct {
201201
// deps
202202
internalConfig *profilemanager.Config
203203
statusRecorder *peer.Status
204-
networkMap *mgmProto.NetworkMap
204+
syncResponse *mgmProto.SyncResponse
205205
logFile string
206206

207207
anonymize bool
@@ -222,7 +222,7 @@ type BundleConfig struct {
222222
type GeneratorDependencies struct {
223223
InternalConfig *profilemanager.Config
224224
StatusRecorder *peer.Status
225-
NetworkMap *mgmProto.NetworkMap
225+
SyncResponse *mgmProto.SyncResponse
226226
LogFile string
227227
}
228228

@@ -238,7 +238,7 @@ func NewBundleGenerator(deps GeneratorDependencies, cfg BundleConfig) *BundleGen
238238

239239
internalConfig: deps.InternalConfig,
240240
statusRecorder: deps.StatusRecorder,
241-
networkMap: deps.NetworkMap,
241+
syncResponse: deps.SyncResponse,
242242
logFile: deps.LogFile,
243243

244244
anonymize: cfg.Anonymize,
@@ -311,8 +311,8 @@ func (g *BundleGenerator) createArchive() error {
311311
log.Errorf("failed to add profiles to debug bundle: %v", err)
312312
}
313313

314-
if err := g.addNetworkMap(); err != nil {
315-
return fmt.Errorf("add network map: %w", err)
314+
if err := g.addSyncResponse(); err != nil {
315+
return fmt.Errorf("add sync response: %w", err)
316316
}
317317

318318
if err := g.addStateFile(); err != nil {
@@ -526,15 +526,15 @@ func (g *BundleGenerator) addResolvedDomains() error {
526526
return nil
527527
}
528528

529-
func (g *BundleGenerator) addNetworkMap() error {
530-
if g.networkMap == nil {
531-
log.Debugf("skipping empty network map in debug bundle")
529+
func (g *BundleGenerator) addSyncResponse() error {
530+
if g.syncResponse == nil {
531+
log.Debugf("skipping empty sync response in debug bundle")
532532
return nil
533533
}
534534

535535
if g.anonymize {
536-
if err := anonymizeNetworkMap(g.networkMap, g.anonymizer); err != nil {
537-
return fmt.Errorf("anonymize network map: %w", err)
536+
if err := anonymizeSyncResponse(g.syncResponse, g.anonymizer); err != nil {
537+
return fmt.Errorf("anonymize sync response: %w", err)
538538
}
539539
}
540540

@@ -545,13 +545,13 @@ func (g *BundleGenerator) addNetworkMap() error {
545545
AllowPartial: true,
546546
}
547547

548-
jsonBytes, err := options.Marshal(g.networkMap)
548+
jsonBytes, err := options.Marshal(g.syncResponse)
549549
if err != nil {
550550
return fmt.Errorf("generate json: %w", err)
551551
}
552552

553553
if err := g.addFileToZip(bytes.NewReader(jsonBytes), "network_map.json"); err != nil {
554-
return fmt.Errorf("add network map to zip: %w", err)
554+
return fmt.Errorf("add sync response to zip: %w", err)
555555
}
556556

557557
return nil
@@ -921,6 +921,88 @@ func anonymizeNetworkMap(networkMap *mgmProto.NetworkMap, anonymizer *anonymize.
921921
return nil
922922
}
923923

924+
func anonymizeNetbirdConfig(config *mgmProto.NetbirdConfig, anonymizer *anonymize.Anonymizer) {
925+
for _, stun := range config.Stuns {
926+
if stun.Uri != "" {
927+
stun.Uri = anonymizer.AnonymizeURI(stun.Uri)
928+
}
929+
}
930+
931+
for _, turn := range config.Turns {
932+
if turn.HostConfig != nil && turn.HostConfig.Uri != "" {
933+
turn.HostConfig.Uri = anonymizer.AnonymizeURI(turn.HostConfig.Uri)
934+
}
935+
if turn.User != "" {
936+
turn.User = "turn-user-placeholder"
937+
}
938+
if turn.Password != "" {
939+
turn.Password = "turn-password-placeholder"
940+
}
941+
}
942+
943+
if config.Signal != nil && config.Signal.Uri != "" {
944+
config.Signal.Uri = anonymizer.AnonymizeURI(config.Signal.Uri)
945+
}
946+
947+
if config.Relay != nil {
948+
for i, url := range config.Relay.Urls {
949+
config.Relay.Urls[i] = anonymizer.AnonymizeURI(url)
950+
}
951+
if config.Relay.TokenPayload != "" {
952+
config.Relay.TokenPayload = "relay-token-payload-placeholder"
953+
}
954+
if config.Relay.TokenSignature != "" {
955+
config.Relay.TokenSignature = "relay-token-signature-placeholder"
956+
}
957+
}
958+
959+
if config.Flow != nil {
960+
if config.Flow.Url != "" {
961+
config.Flow.Url = anonymizer.AnonymizeURI(config.Flow.Url)
962+
}
963+
if config.Flow.TokenPayload != "" {
964+
config.Flow.TokenPayload = "flow-token-payload-placeholder"
965+
}
966+
if config.Flow.TokenSignature != "" {
967+
config.Flow.TokenSignature = "flow-token-signature-placeholder"
968+
}
969+
}
970+
}
971+
972+
func anonymizeSyncResponse(syncResponse *mgmProto.SyncResponse, anonymizer *anonymize.Anonymizer) error {
973+
if syncResponse.NetbirdConfig != nil {
974+
anonymizeNetbirdConfig(syncResponse.NetbirdConfig, anonymizer)
975+
}
976+
977+
if syncResponse.PeerConfig != nil {
978+
anonymizePeerConfig(syncResponse.PeerConfig, anonymizer)
979+
}
980+
981+
for _, p := range syncResponse.RemotePeers {
982+
anonymizeRemotePeer(p, anonymizer)
983+
}
984+
985+
if syncResponse.NetworkMap != nil {
986+
if err := anonymizeNetworkMap(syncResponse.NetworkMap, anonymizer); err != nil {
987+
return err
988+
}
989+
}
990+
991+
for _, check := range syncResponse.Checks {
992+
for i, file := range check.Files {
993+
check.Files[i] = anonymizer.AnonymizeString(file)
994+
}
995+
}
996+
997+
return nil
998+
}
999+
1000+
func anonymizeSSHConfig(sshConfig *mgmProto.SSHConfig) {
1001+
if sshConfig != nil && len(sshConfig.SshPubKey) > 0 {
1002+
sshConfig.SshPubKey = []byte("ssh-placeholder-key")
1003+
}
1004+
}
1005+
9241006
func anonymizePeerConfig(config *mgmProto.PeerConfig, anonymizer *anonymize.Anonymizer) {
9251007
if config == nil {
9261008
return
@@ -930,9 +1012,7 @@ func anonymizePeerConfig(config *mgmProto.PeerConfig, anonymizer *anonymize.Anon
9301012
config.Address = anonymizer.AnonymizeIP(addr).String()
9311013
}
9321014

933-
if config.SshConfig != nil && len(config.SshConfig.SshPubKey) > 0 {
934-
config.SshConfig.SshPubKey = []byte("ssh-placeholder-key")
935-
}
1015+
anonymizeSSHConfig(config.SshConfig)
9361016

9371017
config.Dns = anonymizer.AnonymizeString(config.Dns)
9381018
config.Fqdn = anonymizer.AnonymizeDomain(config.Fqdn)
@@ -954,9 +1034,7 @@ func anonymizeRemotePeer(peer *mgmProto.RemotePeerConfig, anonymizer *anonymize.
9541034

9551035
peer.Fqdn = anonymizer.AnonymizeDomain(peer.Fqdn)
9561036

957-
if peer.SshConfig != nil && len(peer.SshConfig.SshPubKey) > 0 {
958-
peer.SshConfig.SshPubKey = []byte("ssh-placeholder-key")
959-
}
1037+
anonymizeSSHConfig(peer.SshConfig)
9601038
}
9611039

9621040
func anonymizeRoute(route *mgmProto.Route, anonymizer *anonymize.Anonymizer) {

0 commit comments

Comments
 (0)