cmd/tailscaled, tailcfg, hostinfo: add flag to disable logging + support

As noted in #5617, our documented method of blocking log.tailscale.io
DNS no longer works due to bootstrap DNS.

Instead, provide an explicit flag (--no-logs-no-support) and/or env
variable (TS_NO_LOGS_NO_SUPPORT=true) to explicitly disable logcatcher
uploads. It also sets a bit on Hostinfo to say that the node is in that
mode so we can end any support tickets from such nodes more quickly.

This does not yet provide an easy mechanism for users on some
platforms (such as Windows, macOS, Synology) to set flags/env. On
Linux you'd used /etc/default/tailscaled typically. Making it easier
to set flags for other platforms is tracked in #5114.

Fixes #5617
Fixes tailscale/corp#1475

Change-Id: I72404e1789f9e56ec47f9b7021b44c025f7a373a
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/5626/head
Brad Fitzpatrick 2 years ago committed by Brad Fitzpatrick
parent f6da2220d3
commit 45a3de14a6

@ -113,6 +113,7 @@ var args struct {
verbose int verbose int
socksAddr string // listen address for SOCKS5 server socksAddr string // listen address for SOCKS5 server
httpProxyAddr string // listen address for HTTP proxy server httpProxyAddr string // listen address for HTTP proxy server
disableLogs bool
} }
var ( var (
@ -144,6 +145,7 @@ func main() {
flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket") flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket") flag.StringVar(&args.birdSocketPath, "bird-socket", "", "path of the bird unix socket")
flag.BoolVar(&printVersion, "version", false, "print version information and exit") flag.BoolVar(&printVersion, "version", false, "print version information and exit")
flag.BoolVar(&args.disableLogs, "no-logs-no-support", false, "disable log uploads; this also disables any technical support")
if len(os.Args) > 0 && filepath.Base(os.Args[0]) == "tailscale" && beCLI != nil { if len(os.Args) > 0 && filepath.Base(os.Args[0]) == "tailscale" && beCLI != nil {
beCLI() beCLI()
@ -199,6 +201,10 @@ func main() {
args.statepath = paths.DefaultTailscaledStateFile() args.statepath = paths.DefaultTailscaledStateFile()
} }
if args.disableLogs {
envknob.SetNoLogsNoSupport()
}
if beWindowsSubprocess() { if beWindowsSubprocess() {
return return
} }

@ -937,6 +937,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, readOnly bool
} }
if resp.Debug.DisableLogTail { if resp.Debug.DisableLogTail {
logtail.Disable() logtail.Disable()
envknob.SetNoLogsNoSupport()
} }
if resp.Debug.LogHeapPprof { if resp.Debug.LogHeapPprof {
go logheap.LogHeap(resp.Debug.LogHeapURL) go logheap.LogHeap(resp.Debug.LogHeapURL)

@ -155,3 +155,14 @@ func SSHPolicyFile() string { return String("TS_DEBUG_SSH_POLICY_FILE") }
// SSHIgnoreTailnetPolicy is whether to ignore the Tailnet SSH policy for development. // SSHIgnoreTailnetPolicy is whether to ignore the Tailnet SSH policy for development.
func SSHIgnoreTailnetPolicy() bool { return Bool("TS_DEBUG_SSH_IGNORE_TAILNET_POLICY") } func SSHIgnoreTailnetPolicy() bool { return Bool("TS_DEBUG_SSH_IGNORE_TAILNET_POLICY") }
// NoLogsNoSupport reports whether the client's opted out of log uploads and
// technical support.
func NoLogsNoSupport() bool {
return Bool("TS_NO_LOGS_NO_SUPPORT")
}
// SetNoLogsNoSupport enables no-logs-no-support mode.
func SetNoLogsNoSupport() {
os.Setenv("TS_NO_LOGS_NO_SUPPORT", "true")
}

@ -17,6 +17,7 @@ import (
"time" "time"
"go4.org/mem" "go4.org/mem"
"tailscale.com/envknob"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/types/opt" "tailscale.com/types/opt"
"tailscale.com/util/cloudenv" "tailscale.com/util/cloudenv"
@ -32,21 +33,22 @@ func New() *tailcfg.Hostinfo {
hostname, _ := os.Hostname() hostname, _ := os.Hostname()
hostname = dnsname.FirstLabel(hostname) hostname = dnsname.FirstLabel(hostname)
return &tailcfg.Hostinfo{ return &tailcfg.Hostinfo{
IPNVersion: version.Long, IPNVersion: version.Long,
Hostname: hostname, Hostname: hostname,
OS: version.OS(), OS: version.OS(),
OSVersion: GetOSVersion(), OSVersion: GetOSVersion(),
Container: lazyInContainer.Get(), Container: lazyInContainer.Get(),
Distro: condCall(distroName), Distro: condCall(distroName),
DistroVersion: condCall(distroVersion), DistroVersion: condCall(distroVersion),
DistroCodeName: condCall(distroCodeName), DistroCodeName: condCall(distroCodeName),
Env: string(GetEnvType()), Env: string(GetEnvType()),
Desktop: desktop(), Desktop: desktop(),
Package: packageTypeCached(), Package: packageTypeCached(),
GoArch: runtime.GOARCH, GoArch: runtime.GOARCH,
GoVersion: runtime.Version(), GoVersion: runtime.Version(),
DeviceModel: deviceModel(), DeviceModel: deviceModel(),
Cloud: string(cloudenv.Get()), Cloud: string(cloudenv.Get()),
NoLogsNoSupport: envknob.NoLogsNoSupport(),
} }
} }

@ -25,6 +25,7 @@ import (
"time" "time"
"tailscale.com/client/tailscale/apitype" "tailscale.com/client/tailscale/apitype"
"tailscale.com/envknob"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
@ -213,6 +214,9 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) {
} }
logMarker := fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, time.Now().UTC().Format("20060102150405Z"), randHex(8)) logMarker := fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, time.Now().UTC().Format("20060102150405Z"), randHex(8))
if envknob.NoLogsNoSupport() {
logMarker = "BUG-NO-LOGS-NO-SUPPORT-this-node-has-had-its-logging-disabled"
}
h.logf("user bugreport: %s", logMarker) h.logf("user bugreport: %s", logMarker)
if note := r.FormValue("note"); len(note) > 0 { if note := r.FormValue("note"); len(note) > 0 {
h.logf("user bugreport note: %s", note) h.logf("user bugreport note: %s", note)

@ -539,7 +539,10 @@ func New(collection string) *Policy {
conf.IncludeProcSequence = true conf.IncludeProcSequence = true
} }
if val := getLogTarget(); val != "" { if envknob.NoLogsNoSupport() {
log.Println("You have disabled logging. Tailscale will not be able to provide support.")
conf.HTTPC = &http.Client{Transport: noopPretendSuccessTransport{}}
} else if val := getLogTarget(); val != "" {
log.Println("You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult.") log.Println("You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult.")
conf.BaseURL = val conf.BaseURL = val
u, _ := url.Parse(val) u, _ := url.Parse(val)
@ -735,3 +738,14 @@ func goVersion() string {
} }
return v return v
} }
type noopPretendSuccessTransport struct{}
func (noopPretendSuccessTransport) RoundTrip(req *http.Request) (*http.Response, error) {
io.ReadAll(req.Body)
req.Body.Close()
return &http.Response{
StatusCode: 200,
Status: "200 OK",
}, nil
}

@ -495,6 +495,7 @@ type Hostinfo struct {
Hostname string `json:",omitempty"` // name of the host the client runs on Hostname string `json:",omitempty"` // name of the host the client runs on
ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections
ShareeNode bool `json:",omitempty"` // indicates this node exists in netmap because it's owned by a shared-to user ShareeNode bool `json:",omitempty"` // indicates this node exists in netmap because it's owned by a shared-to user
NoLogsNoSupport bool `json:",omitempty"` // indicates that the user has opted out of sending logs and support
GoArch string `json:",omitempty"` // the host's GOARCH value (of the running binary) GoArch string `json:",omitempty"` // the host's GOARCH value (of the running binary)
GoVersion string `json:",omitempty"` // Go version binary was built with GoVersion string `json:",omitempty"` // Go version binary was built with
RoutableIPs []netip.Prefix `json:",omitempty"` // set of IP ranges this client can route RoutableIPs []netip.Prefix `json:",omitempty"` // set of IP ranges this client can route

@ -131,6 +131,7 @@ var _HostinfoCloneNeedsRegeneration = Hostinfo(struct {
Hostname string Hostname string
ShieldsUp bool ShieldsUp bool
ShareeNode bool ShareeNode bool
NoLogsNoSupport bool
GoArch string GoArch string
GoVersion string GoVersion string
RoutableIPs []netip.Prefix RoutableIPs []netip.Prefix

@ -47,6 +47,7 @@ func TestHostinfoEqual(t *testing.T) {
"Hostname", "Hostname",
"ShieldsUp", "ShieldsUp",
"ShareeNode", "ShareeNode",
"NoLogsNoSupport",
"GoArch", "GoArch",
"GoVersion", "GoVersion",
"RoutableIPs", "RoutableIPs",

@ -266,6 +266,7 @@ func (v HostinfoView) DeviceModel() string { return v.ж.DeviceModel }
func (v HostinfoView) Hostname() string { return v.ж.Hostname } func (v HostinfoView) Hostname() string { return v.ж.Hostname }
func (v HostinfoView) ShieldsUp() bool { return v.ж.ShieldsUp } func (v HostinfoView) ShieldsUp() bool { return v.ж.ShieldsUp }
func (v HostinfoView) ShareeNode() bool { return v.ж.ShareeNode } func (v HostinfoView) ShareeNode() bool { return v.ж.ShareeNode }
func (v HostinfoView) NoLogsNoSupport() bool { return v.ж.NoLogsNoSupport }
func (v HostinfoView) GoArch() string { return v.ж.GoArch } func (v HostinfoView) GoArch() string { return v.ж.GoArch }
func (v HostinfoView) GoVersion() string { return v.ж.GoVersion } func (v HostinfoView) GoVersion() string { return v.ж.GoVersion }
func (v HostinfoView) RoutableIPs() views.IPPrefixSlice { func (v HostinfoView) RoutableIPs() views.IPPrefixSlice {
@ -298,6 +299,7 @@ var _HostinfoViewNeedsRegeneration = Hostinfo(struct {
Hostname string Hostname string
ShieldsUp bool ShieldsUp bool
ShareeNode bool ShareeNode bool
NoLogsNoSupport bool
GoArch string GoArch string
GoVersion string GoVersion string
RoutableIPs []netip.Prefix RoutableIPs []netip.Prefix

Loading…
Cancel
Save