Files
tailscale/cmd/tailscaled/deps_test.go
T
Brad FitzpatrickandBrad Fitzpatrick 0433cc6929 feature/syslog, cmd/tailscaled, logpolicy: add optional --syslog flag
Add a new modular syslog feature providing a tailscaled --syslog flag
that sends the daemon's logs to the system syslog daemon instead of
stderr, which is useful when running as a daemon without a service
manager that captures stderr (e.g. OpenWrt's procd).

The feature package registers two new hooks: one to register its flag
before flag parsing, and one that tailscaled calls early in main to
redirect the standard library's default logger. Because logpolicy later
points the default logger at logtail, whose local console copy writes
to stderr, logpolicy now also consults the hook and sends its console
copy to the same sink (with timestamps disabled, as syslog records its
own).

The feature is linked by default only on Linux, FreeBSD, and OpenBSD,
and can be removed with the ts_omit_syslog build tag. If connecting to
the syslog daemon fails at startup, tailscaled logs a warning and
continues logging to stderr.

Fixes #16270

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: I8f3a92d4c1e6b70a5d29e4f61b3c874250a9de13
2026-07-17 13:55:23 -07:00

346 lines
9.1 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package main
import (
"maps"
"slices"
"strings"
"testing"
"tailscale.com/feature/featuretags"
"tailscale.com/tstest/deptest"
)
func TestOmitSSH(t *testing.T) {
const msg = "unexpected with ts_omit_ssh"
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_ssh,ts_include_cli",
BadDeps: map[string]string{
"golang.org/x/crypto/ssh": msg,
"tailscale.com/ssh/tailssh": msg,
"tailscale.com/sessionrecording": msg,
"github.com/anmitsu/go-shlex": msg,
"github.com/creack/pty": msg,
"github.com/kr/fs": msg,
"github.com/pkg/sftp": msg,
"github.com/u-root/u-root/pkg/termios": msg,
"tempfork/gliderlabs/ssh": msg,
},
}.Check(t)
}
func TestOmitSyslog(t *testing.T) {
const msg = "unexpected syslog usage with ts_omit_syslog"
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
// Tailscale SSH's incubator also uses log/syslog, so omit
// SSH too to lock down the standard library package.
Tags: "ts_omit_syslog,ts_omit_ssh,ts_include_cli",
BadDeps: map[string]string{
"log/syslog": msg,
"tailscale.com/feature/syslog": msg,
},
}.Check(t)
}
func TestOmitSyspolicy(t *testing.T) {
const msg = "unexpected syspolicy usage with ts_omit_syspolicy"
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_syspolicy,ts_include_cli",
BadDeps: map[string]string{
"tailscale.com/util/syspolicy": msg,
"tailscale.com/util/syspolicy/setting": msg,
"tailscale.com/util/syspolicy/rsop": msg,
},
}.Check(t)
}
func TestOmitLocalClient(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_webclient,ts_omit_relayserver,ts_omit_oauthkey,ts_omit_acme",
BadDeps: map[string]string{
"tailscale.com/client/local": "unexpected",
},
}.Check(t)
}
// Test that we can build a binary without reflect.MethodByName.
// See https://github.com/tailscale/tailscale/issues/17063
func TestOmitReflectThings(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_include_cli,ts_omit_systray,ts_omit_debugeventbus,ts_omit_webclient",
BadDeps: map[string]string{
"text/template": "unexpected text/template usage",
"html/template": "unexpected text/template usage",
},
OnDep: func(dep string) {
if strings.Contains(dep, "systray") {
t.Errorf("unexpected systray dep %q", dep)
}
},
}.Check(t)
}
func TestOmitDrive(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_drive,ts_include_cli",
OnDep: func(dep string) {
if strings.Contains(dep, "driveimpl") {
t.Errorf("unexpected dep with ts_omit_drive: %q", dep)
}
if strings.Contains(dep, "webdav") {
t.Errorf("unexpected dep with ts_omit_drive: %q", dep)
}
},
}.Check(t)
}
func TestOmitPortmapper(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_portmapper,ts_include_cli,ts_omit_debugportmapper",
OnDep: func(dep string) {
if dep == "tailscale.com/net/portmapper" {
t.Errorf("unexpected dep with ts_omit_portmapper: %q", dep)
return
}
if strings.Contains(dep, "goupnp") || strings.Contains(dep, "/soap") ||
strings.Contains(dep, "internetgateway2") {
t.Errorf("unexpected dep with ts_omit_portmapper: %q", dep)
}
},
}.Check(t)
}
func TestOmitACME(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_acme,ts_include_cli",
OnDep: func(dep string) {
if strings.Contains(dep, "/acme") {
t.Errorf("unexpected dep with ts_omit_acme: %q", dep)
}
},
}.Check(t)
}
func TestOmitCaptivePortal(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_captiveportal,ts_include_cli",
OnDep: func(dep string) {
if strings.Contains(dep, "captive") {
t.Errorf("unexpected dep with ts_omit_captiveportal: %q", dep)
}
},
}.Check(t)
}
func TestOmitBird(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_bird,ts_include_cli",
OnDep: func(dep string) {
switch dep {
case "tailscale.com/chirp", "tailscale.com/feature/bird":
t.Errorf("unexpected dep with ts_omit_bird: %q", dep)
}
},
}.Check(t)
}
func TestOmitAuth(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_oauthkey,ts_omit_identityfederation,ts_include_cli",
OnDep: func(dep string) {
if strings.HasPrefix(dep, "golang.org/x/oauth2") {
t.Errorf("unexpected oauth2 dep: %q", dep)
}
},
}.Check(t)
}
func TestOmitOutboundProxy(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_outboundproxy,ts_include_cli",
OnDep: func(dep string) {
if strings.Contains(dep, "socks5") || strings.Contains(dep, "proxymux") {
t.Errorf("unexpected dep with ts_omit_outboundproxy: %q", dep)
}
},
}.Check(t)
}
func TestOmitDBus(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_networkmanager,ts_omit_dbus,ts_omit_resolved,ts_omit_systray,ts_omit_ssh,ts_include_cli",
OnDep: func(dep string) {
if strings.Contains(dep, "dbus") {
t.Errorf("unexpected DBus dep: %q", dep)
}
},
}.Check(t)
}
func TestNetstack(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_gro,ts_omit_netstack,ts_omit_outboundproxy,ts_omit_serve,ts_omit_ssh,ts_omit_webclient,ts_omit_tap",
OnDep: func(dep string) {
if strings.Contains(dep, "gvisor") {
t.Errorf("unexpected gvisor dep: %q", dep)
}
},
}.Check(t)
}
func TestOmitPortlist(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_portlist,ts_include_cli",
OnDep: func(dep string) {
if strings.Contains(dep, "portlist") {
t.Errorf("unexpected dep: %q", dep)
}
},
}.Check(t)
}
func TestOmitRouteCheck(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_routecheck,ts_include_cli",
OnDep: func(dep string) {
if strings.Contains(dep, "routecheck") && !strings.HasSuffix(dep, "/peernode") {
t.Errorf("unexpected dep: %q", dep)
}
},
}.Check(t)
}
func TestOmitGRO(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_gro,ts_include_cli",
BadDeps: map[string]string{
"gvisor.dev/gvisor/pkg/tcpip/stack/gro": "unexpected dep with ts_omit_gro",
},
}.Check(t)
}
func TestOmitUseProxy(t *testing.T) {
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: "ts_omit_useproxy,ts_include_cli",
OnDep: func(dep string) {
if strings.Contains(dep, "tshttproxy") {
t.Errorf("unexpected dep: %q", dep)
}
},
}.Check(t)
}
func minTags() string {
var tags []string
for _, f := range slices.Sorted(maps.Keys(featuretags.Features)) {
if f.IsOmittable() {
tags = append(tags, f.OmitTag())
}
}
return strings.Join(tags, ",")
}
func TestMinTailscaledNoCLI(t *testing.T) {
badSubstrs := []string{
"cbor",
"regexp",
"golang.org/x/net/proxy",
"internal/socks",
"github.com/tailscale/peercred",
"tailscale.com/types/netlogtype",
"deephash",
"util/hashx",
}
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: minTags(),
OnDep: func(dep string) {
for _, bad := range badSubstrs {
if strings.Contains(dep, bad) {
t.Errorf("unexpected dep: %q", dep)
}
}
},
}.Check(t)
}
func TestMinTailscaledWithCLI(t *testing.T) {
badSubstrs := []string{
"cbor",
"hujson",
"multierr", // https://github.com/tailscale/tailscale/pull/17379
"tailscale.com/metrics",
"tailscale.com/tsweb/varz",
"dirwalk",
"deephash",
"util/hashx",
}
deptest.DepChecker{
GOOS: "linux",
GOARCH: "amd64",
Tags: minTags() + ",ts_include_cli",
OnDep: func(dep string) {
for _, bad := range badSubstrs {
if strings.Contains(dep, bad) {
t.Errorf("unexpected dep: %q", dep)
}
}
},
BadDeps: map[string]string{
"golang.org/x/net/http2": "unexpected x/net/http2 dep; tailscale/tailscale#17305",
"expvar": "unexpected expvar dep",
"runtime/pprof": "unexpected runtime/pprof dep",
"net/http/pprof": "unexpected net/http/pprof dep",
"github.com/mdlayher/genetlink": "unexpected genetlink dep",
"tailscale.com/clientupdate": "unexpected clientupdate dep",
"filippo.io/edwards25519": "unexpected edwards25519 dep",
"github.com/hdevalence/ed25519consensus": "unexpected ed25519consensus dep",
"tailscale.com/clientupdate/distsign": "unexpected distsign dep",
"archive/tar": "unexpected archive/tar dep",
"tailscale.com/feature/conn25": "unexpected conn25 dep",
"regexp": "unexpected regexp dep; bloats binary",
"github.com/toqueteos/webbrowser": "unexpected webbrowser dep with ts_omit_webbrowser",
"github.com/mattn/go-colorable": "unexpected go-colorable dep with ts_omit_colorable",
},
}.Check(t)
}