mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-05 12:16:44 +02:00
ipn/ipnlocal,control/controlclient: track control-plane DisableLogTail per-instance
When Headscale sends MapResponse.Debug.DisableLogTail, the client previously called envknob.SetNoLogsNoSupport(), permanently latching the global no-logs-no-support flag for the process lifetime. This meant users switching from a Headscale tailnet to a Tailscale tailnet with Network Flow Logs could not connect without restarting tailscaled. Replace the global envknob.SetNoLogsNoSupport() call in handleDebugMessage with a per-instance callback (OnDisableLogTail) that sets an atomic.Bool on LocalBackend. This flag follows the control client lifecycle: it is cleared in startLocked() when a new control client is created (profile switch, re-login, etc.). If the new control server also wants logging disabled, it will re-send DisableLogTail in its first MapResponse. The global user-set flag (--no-logs-no-support / TS_NO_LOGS_NO_SUPPORT) is unchanged and always takes precedence. Also improve the error message shown when a tailnet requires logging: differentiate between the user-explicit case (tells user to restart without the flag) and the control-server case (tells user to restart). Fixes #15048
This commit is contained in:
parent
da7d38f5fa
commit
6fd492b7c8
@ -82,7 +82,8 @@ type Direct struct {
|
||||
debugFlags []string
|
||||
skipIPForwardingCheck bool
|
||||
pinger Pinger
|
||||
popBrowser func(url string) // or nil
|
||||
popBrowser func(url string) // or nil
|
||||
onDisableLogTail func() // or nil; called when control sends DisableLogTail
|
||||
polc policyclient.Client // always non-nil
|
||||
c2nHandler http.Handler // or nil
|
||||
panicOnUse bool // if true, panic if client is used (for testing)
|
||||
@ -180,6 +181,13 @@ type Options struct {
|
||||
// attempted. It is used to allow the client to clean up any resources or complete any
|
||||
// tasks that are dependent on a live client.
|
||||
Shutdown func()
|
||||
|
||||
// OnDisableLogTail, if non-nil, is called when the control plane
|
||||
// requests logging to be disabled via MapResponse.Debug.DisableLogTail.
|
||||
// This is primarily used by Headscale. The callback allows the caller
|
||||
// (e.g., LocalBackend) to track this state per-instance rather than
|
||||
// relying on global state.
|
||||
OnDisableLogTail func()
|
||||
}
|
||||
|
||||
// ControlDialPlanner is the interface optionally supplied when creating a
|
||||
@ -323,6 +331,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
||||
pinger: opts.Pinger,
|
||||
polc: cmp.Or(opts.PolicyClient, policyclient.Client(policyclient.NoPolicyClient{})),
|
||||
popBrowser: opts.PopBrowserURL,
|
||||
onDisableLogTail: opts.OnDisableLogTail,
|
||||
c2nHandler: opts.C2NHandler,
|
||||
dialer: opts.Dialer,
|
||||
dnsCache: dnsCache,
|
||||
@ -1252,7 +1261,9 @@ func (c *Direct) handleDebugMessage(ctx context.Context, debug *tailcfg.Debug) e
|
||||
}
|
||||
if buildfeatures.HasLogTail && debug.DisableLogTail {
|
||||
logtail.Disable()
|
||||
envknob.SetNoLogsNoSupport()
|
||||
if c.onDisableLogTail != nil {
|
||||
c.onDisableLogTail()
|
||||
}
|
||||
}
|
||||
if sleep := time.Duration(debug.SleepSeconds * float64(time.Second)); sleep > 0 {
|
||||
if err := sleepAsRequested(ctx, c.logf, sleep, c.clock); err != nil {
|
||||
|
||||
@ -54,6 +54,7 @@ import (
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/log/sockstatlog"
|
||||
"tailscale.com/logpolicy"
|
||||
"tailscale.com/logtail"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/dnscache"
|
||||
"tailscale.com/net/dnsfallback"
|
||||
@ -214,7 +215,14 @@ type LocalBackend struct {
|
||||
// Tailscale on port 5252.
|
||||
exposeRemoteWebClientAtomicBool atomic.Bool // TODO(nickkhyl): move to nodeBackend
|
||||
shutdownCalled bool // if Shutdown has been called
|
||||
debugSink packet.CaptureSink
|
||||
// noLogsFromControl tracks whether the current control plane has
|
||||
// requested logging to be disabled via MapResponse.Debug.DisableLogTail.
|
||||
// This is primarily set by Headscale. It follows the control client's
|
||||
// lifecycle: it is cleared when switching profiles or control servers.
|
||||
// The global envknob.NoLogsNoSupport (user-set via CLI/env) always
|
||||
// takes precedence over this per-instance flag.
|
||||
noLogsFromControl atomic.Bool
|
||||
debugSink packet.CaptureSink
|
||||
sockstatLogger *sockstatlog.Logger
|
||||
|
||||
// getTCPHandlerForFunnelFlow returns a handler for an incoming TCP flow for
|
||||
@ -1780,8 +1788,18 @@ func (b *LocalBackend) setControlClientStatusLocked(c controlclient.Client, st c
|
||||
|
||||
// Now complete the lock-free parts of what we started while locked.
|
||||
if st.NetMap != nil {
|
||||
if envknob.NoLogsNoSupport() && st.NetMap.HasCap(tailcfg.CapabilityDataPlaneAuditLogs) {
|
||||
msg := "tailnet requires logging to be enabled. Remove --no-logs-no-support from tailscaled command line."
|
||||
if b.NoLogsNoSupport() && st.NetMap.HasCap(tailcfg.CapabilityDataPlaneAuditLogs) {
|
||||
var msg string
|
||||
if envknob.NoLogsNoSupport() {
|
||||
msg = "This tailnet requires logging to be enabled. " +
|
||||
"Tailscale is running with logging explicitly disabled " +
|
||||
"(via --no-logs-no-support or TS_NO_LOGS_NO_SUPPORT). " +
|
||||
"Restart Tailscale without this setting to connect to this tailnet."
|
||||
} else {
|
||||
msg = "This tailnet requires logging to be enabled. " +
|
||||
"Logging was disabled by the control server. " +
|
||||
"Restart Tailscale to connect to this tailnet."
|
||||
}
|
||||
b.health.SetLocalLogConfigHealth(errors.New(msg))
|
||||
// Get the current prefs again, since we unlocked above.
|
||||
prefs := b.pm.CurrentPrefs().AsStruct()
|
||||
@ -2607,6 +2625,16 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error {
|
||||
c2nHandler = http.HandlerFunc(b.handleC2N)
|
||||
}
|
||||
|
||||
// If no-logs-no-support was set by the previous control plane
|
||||
// (e.g., Headscale's DisableLogTail), clear it before connecting to
|
||||
// the new control server. If the new control plane also wants logging
|
||||
// disabled, it will send DisableLogTail in its first MapResponse.
|
||||
// The global user-set flag (envknob) always takes precedence.
|
||||
if b.noLogsFromControl.Swap(false) && !envknob.NoLogsNoSupport() {
|
||||
logtail.Enable()
|
||||
b.logf("re-enabled logtail; was disabled by previous control server")
|
||||
}
|
||||
|
||||
// TODO(apenwarr): The only way to change the ServerURL is to
|
||||
// re-run b.Start, because this is the only place we create a
|
||||
// new controlclient. EditPrefs allows you to overwrite ServerURL,
|
||||
@ -2625,6 +2653,7 @@ func (b *LocalBackend) startLocked(opts ipn.Options) error {
|
||||
PolicyClient: b.sys.PolicyClientOrDefault(),
|
||||
Pinger: b,
|
||||
PopBrowserURL: b.tellClientToBrowseToURL,
|
||||
OnDisableLogTail: func() { b.noLogsFromControl.Store(true) },
|
||||
Dialer: b.Dialer(),
|
||||
Observer: b,
|
||||
C2NHandler: c2nHandler,
|
||||
@ -4206,6 +4235,14 @@ func (b *LocalBackend) isDefaultServerLocked() bool {
|
||||
return prefs.ControlURLOrDefault(b.polc) == ipn.DefaultControlURL
|
||||
}
|
||||
|
||||
// NoLogsNoSupport reports whether logging is effectively disabled for this
|
||||
// LocalBackend instance, either by the user (CLI flag or environment variable)
|
||||
// or by the current control plane (via MapResponse.Debug.DisableLogTail).
|
||||
// The global user-set flag always takes precedence.
|
||||
func (b *LocalBackend) NoLogsNoSupport() bool {
|
||||
return envknob.NoLogsNoSupport() || b.noLogsFromControl.Load()
|
||||
}
|
||||
|
||||
func (b *LocalBackend) checkExitNodePrefsLocked(p *ipn.Prefs) error {
|
||||
tryingToUseExitNode := p.ExitNodeIP.IsValid() || p.ExitNodeID != ""
|
||||
if !tryingToUseExitNode {
|
||||
@ -5636,6 +5673,7 @@ func (b *LocalBackend) applyPrefsToHostinfoLocked(hi *tailcfg.Hostinfo, prefs ip
|
||||
hi.RequestTags = prefs.AdvertiseTags().AsSlice()
|
||||
hi.ShieldsUp = prefs.ShieldsUp()
|
||||
hi.AllowsUpdate = buildfeatures.HasClientUpdate && (envknob.AllowsRemoteUpdate() || prefs.AutoUpdate().Apply.EqualBool(true))
|
||||
hi.NoLogsNoSupport = b.NoLogsNoSupport()
|
||||
|
||||
if buildfeatures.HasAdvertiseRoutes {
|
||||
b.metrics.advertisedRoutes.Set(float64(tsaddr.WithoutExitRoute(prefs.AdvertiseRoutes()).Len()))
|
||||
|
||||
@ -398,7 +398,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
|
||||
logMarker := func() string {
|
||||
return fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, h.clock.Now().UTC().Format("20060102150405Z"), rands.HexString(16))
|
||||
}
|
||||
if envknob.NoLogsNoSupport() {
|
||||
if h.b.NoLogsNoSupport() {
|
||||
logMarker = func() string { return "BUG-NO-LOGS-NO-SUPPORT-this-node-has-had-its-logging-disabled" }
|
||||
}
|
||||
|
||||
|
||||
@ -2326,8 +2326,11 @@ type Debug struct {
|
||||
// state machine.
|
||||
SleepSeconds float64 `json:",omitempty"`
|
||||
|
||||
// DisableLogTail disables the logtail package. Once disabled it can't be
|
||||
// re-enabled for the lifetime of the process.
|
||||
// DisableLogTail disables the logtail package. When set by the control
|
||||
// plane, logging is automatically re-enabled when switching to a
|
||||
// different control server. When set by the user (via CLI flag or
|
||||
// environment variable), it cannot be re-enabled for the lifetime of
|
||||
// the process.
|
||||
//
|
||||
// This is primarily used by Headscale.
|
||||
DisableLogTail bool `json:",omitempty"`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user