2024-12-11 14:48:57 +00:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package e2e
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"log"
|
|
|
|
|
"os"
|
|
|
|
|
"strings"
|
|
|
|
|
"testing"
|
2025-09-10 13:02:59 +01:00
|
|
|
"time"
|
2024-12-11 14:48:57 +00:00
|
|
|
|
|
|
|
|
"golang.org/x/oauth2/clientcredentials"
|
2025-09-10 13:02:59 +01:00
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
2024-12-11 14:48:57 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
2025-02-07 09:09:36 -06:00
|
|
|
"tailscale.com/internal/client/tailscale"
|
2025-09-10 13:02:59 +01:00
|
|
|
"tailscale.com/ipn/store/mem"
|
|
|
|
|
"tailscale.com/tsnet"
|
2024-12-11 14:48:57 +00:00
|
|
|
)
|
|
|
|
|
|
2025-09-10 13:02:59 +01:00
|
|
|
// This test suite is currently not run in CI.
|
|
|
|
|
// It requires some setup not handled by this code:
|
|
|
|
|
// - Kubernetes cluster with local kubeconfig for it (direct connection, no API server proxy)
|
|
|
|
|
// - Tailscale operator installed with --set apiServerProxyConfig.mode="true"
|
|
|
|
|
// - ACLs from acl.hujson
|
|
|
|
|
// - OAuth client secret in TS_API_CLIENT_SECRET env, with at least auth_keys write scope and tag:k8s tag
|
2024-12-11 14:48:57 +00:00
|
|
|
var (
|
2025-09-10 13:02:59 +01:00
|
|
|
apiClient *tailscale.Client // For API calls to control.
|
|
|
|
|
tailnetClient *tsnet.Server // For testing real tailnet traffic.
|
2024-12-11 14:48:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
|
code, err := runTests(m)
|
|
|
|
|
if err != nil {
|
2025-09-10 13:02:59 +01:00
|
|
|
log.Printf("Error: %v", err)
|
|
|
|
|
os.Exit(1)
|
2024-12-11 14:48:57 +00:00
|
|
|
}
|
|
|
|
|
os.Exit(code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runTests(m *testing.M) (int, error) {
|
2025-09-10 13:02:59 +01:00
|
|
|
secret := os.Getenv("TS_API_CLIENT_SECRET")
|
|
|
|
|
if secret != "" {
|
|
|
|
|
secretParts := strings.Split(secret, "-")
|
|
|
|
|
if len(secretParts) != 4 {
|
|
|
|
|
return 0, errors.New("TS_API_CLIENT_SECRET is not valid")
|
|
|
|
|
}
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
credentials := clientcredentials.Config{
|
|
|
|
|
ClientID: secretParts[2],
|
|
|
|
|
ClientSecret: secret,
|
|
|
|
|
TokenURL: "https://login.tailscale.com/api/v2/oauth/token",
|
|
|
|
|
Scopes: []string{"auth_keys"},
|
|
|
|
|
}
|
|
|
|
|
apiClient = tailscale.NewClient("-", nil)
|
|
|
|
|
apiClient.HTTPClient = credentials.Client(ctx)
|
|
|
|
|
|
|
|
|
|
caps := tailscale.KeyCapabilities{
|
|
|
|
|
Devices: tailscale.KeyDeviceCapabilities{
|
|
|
|
|
Create: tailscale.KeyDeviceCreateCapabilities{
|
|
|
|
|
Reusable: false,
|
|
|
|
|
Preauthorized: true,
|
|
|
|
|
Ephemeral: true,
|
|
|
|
|
Tags: []string{"tag:k8s"},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
2024-12-11 14:48:57 +00:00
|
|
|
|
2025-09-10 13:02:59 +01:00
|
|
|
authKey, authKeyMeta, err := apiClient.CreateKeyWithExpiry(ctx, caps, 10*time.Minute)
|
2024-12-11 14:48:57 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
2025-09-10 13:02:59 +01:00
|
|
|
defer apiClient.DeleteKey(context.Background(), authKeyMeta.ID)
|
2024-12-11 14:48:57 +00:00
|
|
|
|
2025-09-10 13:02:59 +01:00
|
|
|
tailnetClient = &tsnet.Server{
|
|
|
|
|
Hostname: "test-proxy",
|
|
|
|
|
Ephemeral: true,
|
|
|
|
|
Store: &mem.Store{},
|
|
|
|
|
AuthKey: authKey,
|
2024-12-11 14:48:57 +00:00
|
|
|
}
|
2025-09-10 13:02:59 +01:00
|
|
|
_, err = tailnetClient.Up(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
2024-12-11 14:48:57 +00:00
|
|
|
}
|
2025-09-10 13:02:59 +01:00
|
|
|
defer tailnetClient.Close()
|
2024-12-11 14:48:57 +00:00
|
|
|
}
|
|
|
|
|
|
2025-09-10 13:02:59 +01:00
|
|
|
return m.Run(), nil
|
2024-12-11 14:48:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func objectMeta(namespace, name string) metav1.ObjectMeta {
|
|
|
|
|
return metav1.ObjectMeta{
|
|
|
|
|
Namespace: namespace,
|
|
|
|
|
Name: name,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-10 13:02:59 +01:00
|
|
|
func createAndCleanup(t *testing.T, cl client.Client, obj client.Object) {
|
2024-12-11 14:48:57 +00:00
|
|
|
t.Helper()
|
2025-09-10 13:02:59 +01:00
|
|
|
|
|
|
|
|
// Try to create the object first
|
|
|
|
|
err := cl.Create(t.Context(), obj)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if apierrors.IsAlreadyExists(err) {
|
|
|
|
|
if updateErr := cl.Update(t.Context(), obj); updateErr != nil {
|
|
|
|
|
t.Fatal(updateErr)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2024-12-11 14:48:57 +00:00
|
|
|
}
|
2025-09-10 13:02:59 +01:00
|
|
|
|
2024-12-11 14:48:57 +00:00
|
|
|
t.Cleanup(func() {
|
2025-09-10 13:02:59 +01:00
|
|
|
// Use context.Background() for cleanup, as t.Context() is cancelled
|
|
|
|
|
// just before cleanup functions are called.
|
|
|
|
|
if err := cl.Delete(context.Background(), obj); err != nil {
|
2024-12-11 14:48:57 +00:00
|
|
|
t.Errorf("error cleaning up %s %s/%s: %s", obj.GetObjectKind().GroupVersionKind(), obj.GetNamespace(), obj.GetName(), err)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func get(ctx context.Context, cl client.Client, obj client.Object) error {
|
|
|
|
|
return cl.Get(ctx, client.ObjectKeyFromObject(obj), obj)
|
|
|
|
|
}
|