WIP: rebase fork onto upstream/main (v1.103.0) #15
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user