WIP: rebase fork onto upstream/main (v1.103.0) #15

Closed
codinget wants to merge 670 commits from webnet into save/webnet-2026-07-29
5 changed files with 155 additions and 67 deletions
Showing only changes of commit b91e844014 - Show all commits
+3 -2
View File
@@ -154,19 +154,20 @@ import (
"tailscale.com/types/logger"
"tailscale.com/types/views"
"tailscale.com/util/deephash"
"tailscale.com/util/def"
"tailscale.com/util/dnsname"
"tailscale.com/util/linuxfw"
)
func newNetfilterRunner(logf logger.Logf) (linuxfw.NetfilterRunner, error) {
if defaultBool("TS_TEST_FAKE_NETFILTER", false) {
if def.Bool(os.Getenv("TS_TEST_FAKE_NETFILTER"), false) {
return linuxfw.NewFakeIPTablesRunner(), nil
}
return linuxfw.New(logf, "")
}
func getAutoAdvertiseBool() bool {
return defaultBool("TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT", true)
return def.Bool(os.Getenv("TS_EXPERIMENTAL_SERVICE_AUTO_ADVERTISEMENT"), true)
}
const containerbootWatchMask = ipn.NotifyInitialStatus |
+40 -64
View File
@@ -6,6 +6,7 @@
package main
import (
"cmp"
"context"
"errors"
"fmt"
@@ -18,6 +19,7 @@ import (
"tailscale.com/ipn/conffile"
"tailscale.com/kube/kubeclient"
"tailscale.com/util/def"
)
// settings is all the configuration for containerboot.
@@ -89,47 +91,50 @@ type settings struct {
func configFromEnv() (*settings, error) {
cfg := &settings{
AuthKey: defaultEnvs([]string{"TS_AUTHKEY", "TS_AUTH_KEY"}, ""),
ClientID: defaultEnv("TS_CLIENT_ID", ""),
ClientSecret: defaultEnv("TS_CLIENT_SECRET", ""),
IDToken: defaultEnv("TS_ID_TOKEN", ""),
Audience: defaultEnv("TS_AUDIENCE", ""),
Hostname: defaultEnv("TS_HOSTNAME", ""),
AuthKey: cmp.Or(os.Getenv("TS_AUTHKEY"), os.Getenv("TS_AUTH_KEY")),
ClientID: os.Getenv("TS_CLIENT_ID"),
ClientSecret: os.Getenv("TS_CLIENT_SECRET"),
IDToken: os.Getenv("TS_ID_TOKEN"),
Audience: os.Getenv("TS_AUDIENCE"),
Hostname: os.Getenv("TS_HOSTNAME"),
Routes: defaultEnvStringPointer("TS_ROUTES"),
ServeConfigPath: defaultEnv("TS_SERVE_CONFIG", ""),
ProxyTargetIP: defaultEnv("TS_DEST_IP", ""),
ProxyTargetDNSName: defaultEnv("TS_EXPERIMENTAL_DEST_DNS_NAME", ""),
TailnetTargetIP: defaultEnv("TS_TAILNET_TARGET_IP", ""),
TailnetTargetFQDN: defaultEnv("TS_TAILNET_TARGET_FQDN", ""),
DaemonExtraArgs: defaultEnv("TS_TAILSCALED_EXTRA_ARGS", ""),
ExtraArgs: defaultEnv("TS_EXTRA_ARGS", ""),
ServeConfigPath: os.Getenv("TS_SERVE_CONFIG"),
ProxyTargetIP: os.Getenv("TS_DEST_IP"),
ProxyTargetDNSName: os.Getenv("TS_EXPERIMENTAL_DEST_DNS_NAME"),
TailnetTargetIP: os.Getenv("TS_TAILNET_TARGET_IP"),
TailnetTargetFQDN: os.Getenv("TS_TAILNET_TARGET_FQDN"),
DaemonExtraArgs: os.Getenv("TS_TAILSCALED_EXTRA_ARGS"),
ExtraArgs: os.Getenv("TS_EXTRA_ARGS"),
InKubernetes: os.Getenv("KUBERNETES_SERVICE_HOST") != "",
UserspaceMode: defaultBool("TS_USERSPACE", true),
StateDir: defaultEnv("TS_STATE_DIR", ""),
UserspaceMode: def.Bool(os.Getenv("TS_USERSPACE"), true),
StateDir: os.Getenv("TS_STATE_DIR"),
AcceptDNS: defaultEnvBoolPointer("TS_ACCEPT_DNS"),
KubeSecret: func() string {
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
return defaultEnv("TS_KUBE_SECRET", "tailscale")
if os.Getenv("KUBERNETES_SERVICE_HOST") == "" {
return os.Getenv("TS_KUBE_SECRET")
}
return defaultEnv("TS_KUBE_SECRET", "")
// An explicitly empty TS_KUBE_SECRET disables Secret storage, so
// unset and empty must stay distinguishable: def.LookupEnv keeps
// an explicit "" rather than falling back to the default.
return def.LookupEnv("TS_KUBE_SECRET", "tailscale")
}(),
SOCKSProxyAddr: defaultEnv("TS_SOCKS5_SERVER", ""),
HTTPProxyAddr: defaultEnv("TS_OUTBOUND_HTTP_PROXY_LISTEN", ""),
Socket: defaultEnv("TS_SOCKET", "/tmp/tailscaled.sock"),
AuthOnce: defaultBool("TS_AUTH_ONCE", false),
Root: defaultEnv("TS_TEST_ONLY_ROOT", "/"),
SOCKSProxyAddr: os.Getenv("TS_SOCKS5_SERVER"),
HTTPProxyAddr: os.Getenv("TS_OUTBOUND_HTTP_PROXY_LISTEN"),
Socket: cmp.Or(os.Getenv("TS_SOCKET"), "/tmp/tailscaled.sock"),
AuthOnce: def.Bool(os.Getenv("TS_AUTH_ONCE"), false),
Root: cmp.Or(os.Getenv("TS_TEST_ONLY_ROOT"), "/"),
TailscaledConfigFilePath: tailscaledConfigFilePath(),
AllowProxyingClusterTrafficViaIngress: defaultBool("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS", false),
PodIP: defaultEnv("POD_IP", ""),
EnableForwardingOptimizations: defaultBool("TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS", false),
HealthCheckAddrPort: defaultEnv("TS_HEALTHCHECK_ADDR_PORT", ""),
LocalAddrPort: defaultEnv("TS_LOCAL_ADDR_PORT", "[::]:9002"),
MetricsEnabled: defaultBool("TS_ENABLE_METRICS", false),
HealthCheckEnabled: defaultBool("TS_ENABLE_HEALTH_CHECK", false),
DebugAddrPort: defaultEnv("TS_DEBUG_ADDR_PORT", ""),
EgressProxiesCfgPath: defaultEnv("TS_EGRESS_PROXIES_CONFIG_PATH", ""),
IngressProxiesCfgPath: defaultEnv("TS_INGRESS_PROXIES_CONFIG_PATH", ""),
PodUID: defaultEnv("POD_UID", ""),
AllowProxyingClusterTrafficViaIngress: def.Bool(os.Getenv("EXPERIMENTAL_ALLOW_PROXYING_CLUSTER_TRAFFIC_VIA_INGRESS"), false),
PodIP: os.Getenv("POD_IP"),
EnableForwardingOptimizations: def.Bool(os.Getenv("TS_EXPERIMENTAL_ENABLE_FORWARDING_OPTIMIZATIONS"), false),
HealthCheckAddrPort: os.Getenv("TS_HEALTHCHECK_ADDR_PORT"),
LocalAddrPort: cmp.Or(os.Getenv("TS_LOCAL_ADDR_PORT"), "[::]:9002"),
MetricsEnabled: def.Bool(os.Getenv("TS_ENABLE_METRICS"), false),
HealthCheckEnabled: def.Bool(os.Getenv("TS_ENABLE_HEALTH_CHECK"), false),
DebugAddrPort: os.Getenv("TS_DEBUG_ADDR_PORT"),
EgressProxiesCfgPath: os.Getenv("TS_EGRESS_PROXIES_CONFIG_PATH"),
IngressProxiesCfgPath: os.Getenv("TS_INGRESS_PROXIES_CONFIG_PATH"),
PodUID: os.Getenv("POD_UID"),
}
podIPs, ok := os.LookupEnv("POD_IPS")
@@ -153,7 +158,7 @@ func configFromEnv() (*settings, error) {
// If cert share is enabled, set the replica as read or write. Only 0th
// replica should be able to write.
isInCertShareMode := defaultBool("TS_EXPERIMENTAL_CERT_SHARE", false)
isInCertShareMode := def.Bool(os.Getenv("TS_EXPERIMENTAL_CERT_SHARE"), false)
if isInCertShareMode {
cfg.CertShareMode = "ro"
podName := os.Getenv("POD_NAME")
@@ -454,15 +459,6 @@ func (cfg *settings) egressSvcsTerminateEPEnabled() bool {
return cfg.LocalAddrPort != "" && cfg.EgressProxiesCfgPath != ""
}
// defaultEnv returns the value of the given envvar name, or defVal if
// unset.
func defaultEnv(name, defVal string) string {
if v, ok := os.LookupEnv(name); ok {
return v
}
return defVal
}
// defaultEnvStringPointer returns a pointer to the given envvar value if set, else
// returns nil. This is useful in cases where we need to distinguish between a
// variable being set to empty string vs unset.
@@ -484,23 +480,3 @@ func defaultEnvBoolPointer(name string) *bool {
}
return &ret
}
func defaultEnvs(names []string, defVal string) string {
for _, name := range names {
if v, ok := os.LookupEnv(name); ok {
return v
}
}
return defVal
}
// defaultBool returns the boolean value of the given envvar name, or
// defVal if unset or not a bool.
func defaultBool(name string, defVal bool) bool {
v := os.Getenv(name)
ret, err := strconv.ParseBool(v)
if err != nil {
return defVal
}
return ret
}
+73
View File
@@ -7,6 +7,7 @@ package main
import (
"net/netip"
"os"
"strings"
"testing"
)
@@ -228,6 +229,78 @@ func TestValidateAuthMethods(t *testing.T) {
}
}
func TestConfigFromEnvEmptyDefaults(t *testing.T) {
tests := []struct {
env string
get func(*settings) string
want string
}{
{
env: "TS_SOCKET",
get: func(c *settings) string { return c.Socket },
want: "/tmp/tailscaled.sock",
},
{
env: "TS_LOCAL_ADDR_PORT",
get: func(c *settings) string { return c.LocalAddrPort },
want: "[::]:9002",
},
{
env: "TS_TEST_ONLY_ROOT",
get: func(c *settings) string { return c.Root },
want: "/",
},
}
for _, tt := range tests {
t.Run(tt.env, func(t *testing.T) {
t.Setenv(tt.env, "")
cfg, err := configFromEnv()
if err != nil {
t.Fatal(err)
}
if got := tt.get(cfg); got != tt.want {
t.Errorf(`%s set to empty "": got %q, want default %q`, tt.env, got, tt.want)
}
})
}
}
func TestConfigFromEnvKubeSecret(t *testing.T) {
tests := []struct {
name string
inKubernetes bool
unset bool
value string
want string
}{
{name: "in_kubernetes_unset", inKubernetes: true, unset: true, want: "tailscale"},
{name: "in_kubernetes_empty", inKubernetes: true, value: "", want: ""},
{name: "in_kubernetes_set", inKubernetes: true, value: "custom", want: "custom"},
{name: "not_in_kubernetes_unset", inKubernetes: false, unset: true, want: ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// t.Setenv registers a t.Cleanup to restore the original value, so
// route the unset cases through it rather than a bare os.Unsetenv.
t.Setenv("KUBERNETES_SERVICE_HOST", "10.96.0.1")
if !tt.inKubernetes {
os.Unsetenv("KUBERNETES_SERVICE_HOST")
}
t.Setenv("TS_KUBE_SECRET", tt.value)
if tt.unset {
os.Unsetenv("TS_KUBE_SECRET")
}
cfg, err := configFromEnv()
if err != nil {
t.Fatal(err)
}
if cfg.KubeSecret != tt.want {
t.Errorf("KubeSecret = %q, want %q", cfg.KubeSecret, tt.want)
}
})
}
}
func TestHandlesKubeIPV6(t *testing.T) {
t.Setenv("TS_LOCAL_ADDR_PORT", "fd7a:115c:a1e0::6c34:352:9002")
t.Setenv("POD_IPS", "fd7a:115c:a1e0::6c34:352")
+12 -1
View File
@@ -1,10 +1,11 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package def parses strings with fallback default values.
// Package def parses strings and environment variables with fallback default values.
package def
import (
"os"
"strconv"
"time"
)
@@ -32,3 +33,13 @@ func Duration(s string, def time.Duration) time.Duration {
}
return v
}
// LookupEnv retrieves the value of the environment variable named by the key.
// If the variable is present in the environment the value (which may be
// empty) is returned. Otherwise, it returns def.
func LookupEnv(key, def string) string {
if v, ok := os.LookupEnv(key); ok {
return v
}
return def
}
+27
View File
@@ -4,6 +4,7 @@
package def_test
import (
"os"
"strconv"
"testing"
"time"
@@ -11,6 +12,32 @@ import (
"tailscale.com/util/def"
)
func TestLookupEnv(t *testing.T) {
const key = "TS_DEF_TEST_LOOKUPENV"
tests := []struct {
name string
unset bool
value string
def string
want string
}{
{name: "unset", unset: true, def: "fallback", want: "fallback"},
{name: "set", value: "value", def: "fallback", want: "value"},
{name: "empty", value: "", def: "fallback", want: ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv(key, tt.value)
if tt.unset {
os.Unsetenv(key)
}
if got := def.LookupEnv(key, tt.def); got != tt.want {
t.Errorf("LookupEnv(%q, %q) = %q; want %q", key, tt.def, got, tt.want)
}
})
}
}
func TestBool(t *testing.T) {
tests := []struct {
name string