mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-25 23:06:09 -04:00
Simplifies cmd/containerboot env var parsing. Most of the private helpers did not earn their abstraction: defaultEnv(name, "") is just os.Getenv(name), and the rest collapse into cmp.Or and the existing def.Bool. defaultEnv, defaultEnvs and defaultBool are gone. Adds def.LookupEnv, the env companion to def.Bool, for the one case that needs it: TS_KUBE_SECRET, where an explicit "" disables Kubernetes secret storage and must stay distinct from unset (cmp.Or cannot express that). Updates #20018 Signed-off-by: Nick Rossi <nrossi0530@gmail.com>
330 lines
8.3 KiB
Go
330 lines
8.3 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux
|
|
|
|
package main
|
|
|
|
import (
|
|
"net/netip"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func Test_parseAcceptDNS(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
extraArgs string
|
|
acceptDNS bool
|
|
wantExtraArgs string
|
|
wantAcceptDNS bool
|
|
}{
|
|
{
|
|
name: "false_extra_args_unset",
|
|
extraArgs: "",
|
|
wantExtraArgs: "",
|
|
wantAcceptDNS: false,
|
|
},
|
|
{
|
|
name: "false_unrelated_args_set",
|
|
extraArgs: "--accept-routes=true --advertise-routes=10.0.0.1/32",
|
|
wantExtraArgs: "--accept-routes=true --advertise-routes=10.0.0.1/32",
|
|
wantAcceptDNS: false,
|
|
},
|
|
{
|
|
name: "true_extra_args_unset",
|
|
extraArgs: "",
|
|
acceptDNS: true,
|
|
wantExtraArgs: "",
|
|
wantAcceptDNS: true,
|
|
},
|
|
{
|
|
name: "true_unrelated_args_set",
|
|
acceptDNS: true,
|
|
extraArgs: "--accept-routes=true --advertise-routes=10.0.0.1/32",
|
|
wantExtraArgs: "--accept-routes=true --advertise-routes=10.0.0.1/32",
|
|
wantAcceptDNS: true,
|
|
},
|
|
{
|
|
name: "false_extra_args_set_to_false",
|
|
extraArgs: "--accept-dns=false",
|
|
wantExtraArgs: "",
|
|
wantAcceptDNS: false,
|
|
},
|
|
{
|
|
name: "false_extra_args_set_to_true",
|
|
extraArgs: "--accept-dns=true",
|
|
wantExtraArgs: "",
|
|
wantAcceptDNS: true,
|
|
},
|
|
{
|
|
name: "true_extra_args_set_to_false",
|
|
extraArgs: "--accept-dns=false",
|
|
acceptDNS: true,
|
|
wantExtraArgs: "",
|
|
wantAcceptDNS: false,
|
|
},
|
|
{
|
|
name: "true_extra_args_set_to_true",
|
|
extraArgs: "--accept-dns=true",
|
|
acceptDNS: true,
|
|
wantExtraArgs: "",
|
|
wantAcceptDNS: true,
|
|
},
|
|
{
|
|
name: "false_extra_args_set_to_true_implicitly",
|
|
extraArgs: "--accept-dns",
|
|
wantExtraArgs: "",
|
|
wantAcceptDNS: true,
|
|
},
|
|
{
|
|
name: "false_extra_args_set_to_true_implicitly_with_unrelated_args",
|
|
extraArgs: "--accept-dns --accept-routes --advertise-routes=10.0.0.1/32",
|
|
wantExtraArgs: "--accept-routes --advertise-routes=10.0.0.1/32",
|
|
wantAcceptDNS: true,
|
|
},
|
|
{
|
|
name: "false_extra_args_set_to_true_implicitly_surrounded_with_unrelated_args",
|
|
extraArgs: "--accept-routes --accept-dns --advertise-routes=10.0.0.1/32",
|
|
wantExtraArgs: "--accept-routes --advertise-routes=10.0.0.1/32",
|
|
wantAcceptDNS: true,
|
|
},
|
|
{
|
|
name: "true_extra_args_set_to_false_with_unrelated_args",
|
|
extraArgs: "--accept-routes --accept-dns=false",
|
|
acceptDNS: true,
|
|
wantExtraArgs: "--accept-routes",
|
|
wantAcceptDNS: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotExtraArgs, gotAcceptDNS := parseAcceptDNS(tt.extraArgs, tt.acceptDNS)
|
|
if gotExtraArgs != tt.wantExtraArgs {
|
|
t.Errorf("parseAcceptDNS() gotExtraArgs = %v, want %v", gotExtraArgs, tt.wantExtraArgs)
|
|
}
|
|
if gotAcceptDNS != tt.wantAcceptDNS {
|
|
t.Errorf("parseAcceptDNS() gotAcceptDNS = %v, want %v", gotAcceptDNS, tt.wantAcceptDNS)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateAuthMethods(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
authKey string
|
|
clientID string
|
|
clientSecret string
|
|
idToken string
|
|
audience string
|
|
errContains string
|
|
}{
|
|
{
|
|
name: "no_auth_method",
|
|
},
|
|
{
|
|
name: "authkey_only",
|
|
authKey: "tskey-auth-xxx",
|
|
},
|
|
{
|
|
name: "client_secret_only",
|
|
clientSecret: "tskey-client-xxx",
|
|
},
|
|
{
|
|
name: "client_id_alone",
|
|
clientID: "client-id",
|
|
},
|
|
{
|
|
name: "oauth_client_id_and_secret",
|
|
clientID: "client-id",
|
|
clientSecret: "tskey-client-xxx",
|
|
},
|
|
{
|
|
name: "wif_client_id_and_id_token",
|
|
clientID: "client-id",
|
|
idToken: "id-token",
|
|
},
|
|
{
|
|
name: "wif_client_id_and_audience",
|
|
clientID: "client-id",
|
|
audience: "audience",
|
|
},
|
|
{
|
|
name: "id_token_without_client_id",
|
|
idToken: "id-token",
|
|
errContains: "TS_ID_TOKEN is set but TS_CLIENT_ID is not set",
|
|
},
|
|
{
|
|
name: "audience_without_client_id",
|
|
audience: "audience",
|
|
errContains: "TS_AUDIENCE is set but TS_CLIENT_ID is not set",
|
|
},
|
|
{
|
|
name: "authkey_with_client_secret",
|
|
authKey: "tskey-auth-xxx",
|
|
clientSecret: "tskey-client-xxx",
|
|
errContains: "TS_AUTHKEY cannot be used with",
|
|
},
|
|
{
|
|
name: "authkey_with_id_token",
|
|
authKey: "tskey-auth-xxx",
|
|
clientID: "client-id",
|
|
idToken: "id-token",
|
|
errContains: "TS_AUTHKEY cannot be used with",
|
|
},
|
|
{
|
|
name: "authkey_with_audience",
|
|
authKey: "tskey-auth-xxx",
|
|
clientID: "client-id",
|
|
audience: "audience",
|
|
errContains: "TS_AUTHKEY cannot be used with",
|
|
},
|
|
{
|
|
name: "id_token_with_client_secret",
|
|
clientID: "client-id",
|
|
clientSecret: "tskey-client-xxx",
|
|
idToken: "id-token",
|
|
errContains: "TS_ID_TOKEN and TS_CLIENT_SECRET cannot both be set",
|
|
},
|
|
{
|
|
name: "id_token_with_audience",
|
|
clientID: "client-id",
|
|
idToken: "id-token",
|
|
audience: "audience",
|
|
errContains: "TS_ID_TOKEN and TS_AUDIENCE cannot both be set",
|
|
},
|
|
{
|
|
name: "audience_with_client_secret",
|
|
clientID: "client-id",
|
|
clientSecret: "tskey-client-xxx",
|
|
audience: "audience",
|
|
errContains: "TS_AUDIENCE and TS_CLIENT_SECRET cannot both be set",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
s := &settings{
|
|
AuthKey: tt.authKey,
|
|
ClientID: tt.clientID,
|
|
ClientSecret: tt.clientSecret,
|
|
IDToken: tt.idToken,
|
|
Audience: tt.audience,
|
|
}
|
|
err := s.validate()
|
|
if tt.errContains != "" {
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), tt.errContains) {
|
|
t.Errorf("error %q does not contain %q", err.Error(), tt.errContains)
|
|
}
|
|
} else if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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")
|
|
|
|
cfg, err := configFromEnv()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if cfg.LocalAddrPort != "[fd7a:115c:a1e0::6c34:352]:9002" {
|
|
t.Errorf("LocalAddrPort is not set correctly")
|
|
}
|
|
|
|
parsed, err := netip.ParseAddrPort(cfg.LocalAddrPort)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !parsed.Addr().Is6() {
|
|
t.Errorf("expected v6 address but got %s", parsed)
|
|
}
|
|
|
|
if parsed.Port() != 9002 {
|
|
t.Errorf("expected port 9002 but got %d", parsed.Port())
|
|
}
|
|
}
|