diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscale/cli/debug.go index fc6d28182..b73578f55 100644 --- a/cmd/tailscale/cli/debug.go +++ b/cmd/tailscale/cli/debug.go @@ -134,6 +134,7 @@ var debugCmd = &ffcli.Command{ fs := newFlagSet("watch-ipn") fs.BoolVar(&watchIPNArgs.netmap, "netmap", true, "include netmap in messages") fs.BoolVar(&watchIPNArgs.initial, "initial", false, "include initial status") + fs.BoolVar(&watchIPNArgs.showPrivateKey, "show-private-key", false, "include node private key in printed netmap") return fs })(), }, @@ -319,8 +320,9 @@ func runPrefs(ctx context.Context, args []string) error { } var watchIPNArgs struct { - netmap bool - initial bool + netmap bool + initial bool + showPrivateKey bool } func runWatchIPN(ctx context.Context, args []string) error { @@ -328,6 +330,9 @@ func runWatchIPN(ctx context.Context, args []string) error { if watchIPNArgs.initial { mask = ipn.NotifyInitialState | ipn.NotifyInitialPrefs | ipn.NotifyInitialNetMap } + if !watchIPNArgs.showPrivateKey { + mask |= ipn.NotifyNoPrivateKeys + } watcher, err := localClient.WatchIPNBus(ctx, mask) if err != nil { return err diff --git a/ipn/backend.go b/ipn/backend.go index 5232f5e67..9b66b6105 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -65,6 +65,8 @@ const ( NotifyInitialState // if set, the first Notify message (sent immediately) will contain the current State + BrowseToURL NotifyInitialPrefs // if set, the first Notify message (sent immediately) will contain the current Prefs NotifyInitialNetMap // if set, the first Notify message (sent immediately) will contain the current NetMap + + NotifyNoPrivateKeys // if set, private keys that would normally be sent in updates are zeroed out ) // Notify is a communication from a backend (e.g. tailscaled) to a frontend diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index f3f463c57..4a0e0e58c 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1742,6 +1742,24 @@ func (b *LocalBackend) readPoller() { func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWatchOpt, fn func(roNotify *ipn.Notify) (keepGoing bool)) { ch := make(chan *ipn.Notify, 128) + origFn := fn + if mask&ipn.NotifyNoPrivateKeys != 0 { + fn = func(n *ipn.Notify) bool { + if n.NetMap == nil || n.NetMap.PrivateKey.IsZero() { + return origFn(n) + } + + // The netmap in n is shared across all watchers, so to mutate it for a + // single watcher we have to clone the notify and the netmap. We can + // make shallow clones, at least. + nm2 := *n.NetMap + n2 := *n + n2.NetMap = &nm2 + n2.NetMap.PrivateKey = key.NodePrivate{} + return origFn(&n2) + } + } + var ini *ipn.Notify b.mu.Lock()