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>
This commit is contained in:
Brad Fitzpatrick 2022-09-13 07:09:57 -07:00 committed by Brad Fitzpatrick
parent f6da2220d3
commit 45a3de14a6
10 changed files with 59 additions and 16 deletions

View File

@ -113,6 +113,7 @@ var args struct {
verbose int
socksAddr string // listen address for SOCKS5 server
httpProxyAddr string // listen address for HTTP proxy server
disableLogs bool
}
var (
@ -144,6 +145,7 @@ func main() {
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.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 {
beCLI()
@ -199,6 +201,10 @@ func main() {
args.statepath = paths.DefaultTailscaledStateFile()
}
if args.disableLogs {
envknob.SetNoLogsNoSupport()
}
if beWindowsSubprocess() {
return
}

View File

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

View File

@ -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.
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")
}

View File

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

View File

@ -25,6 +25,7 @@ import (
"time"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnlocal"
"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))
if envknob.NoLogsNoSupport() {
logMarker = "BUG-NO-LOGS-NO-SUPPORT-this-node-has-had-its-logging-disabled"
}
h.logf("user bugreport: %s", logMarker)
if note := r.FormValue("note"); len(note) > 0 {
h.logf("user bugreport note: %s", note)

View File

@ -539,7 +539,10 @@ func New(collection string) *Policy {
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.")
conf.BaseURL = val
u, _ := url.Parse(val)
@ -735,3 +738,14 @@ func goVersion() string {
}
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
}

View File

@ -495,6 +495,7 @@ type Hostinfo struct {
Hostname string `json:",omitempty"` // name of the host the client runs on
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
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)
GoVersion string `json:",omitempty"` // Go version binary was built with
RoutableIPs []netip.Prefix `json:",omitempty"` // set of IP ranges this client can route

View File

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

View File

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

View File

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