Files

436 lines
12 KiB
Go

package helpers
import (
"context"
"fmt"
"os"
"path/filepath"
"reflect"
"testing"
authv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
testclient "k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/rest"
clienttesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"open-cluster-management.io/sdk-go/pkg/basecontroller/events"
)
func TestAssembleClusterConfig_TLSServerName(t *testing.T) {
tlsName := "kubernetes.default.svc"
t.Run("CAData", func(t *testing.T) {
cfg := &rest.Config{
Host: "https://10.0.0.1:6443",
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte("ca-bytes"),
ServerName: tlsName,
},
}
c, err := assembleClusterConfig(cfg)
if err != nil {
t.Fatal(err)
}
if c.TLSServerName != tlsName {
t.Fatalf("TLSServerName = %q, want %q", c.TLSServerName, tlsName)
}
})
t.Run("CAFile", func(t *testing.T) {
dir := t.TempDir()
caPath := filepath.Join(dir, "ca.crt")
if err := os.WriteFile(caPath, []byte("ca-file-bytes"), 0o600); err != nil {
t.Fatal(err)
}
cfg := &rest.Config{
Host: "https://10.0.0.1:6443",
TLSClientConfig: rest.TLSClientConfig{
CAFile: caPath,
ServerName: tlsName,
},
}
c, err := assembleClusterConfig(cfg)
if err != nil {
t.Fatal(err)
}
if c.TLSServerName != tlsName {
t.Fatalf("TLSServerName = %q, want %q", c.TLSServerName, tlsName)
}
})
t.Run("insecure", func(t *testing.T) {
// No CAData/CAFile selects the InsecureSkipTLSVerify branch in assembleClusterConfig.
cfg := &rest.Config{
Host: "https://10.0.0.1:6443",
TLSClientConfig: rest.TLSClientConfig{
ServerName: tlsName,
},
}
c, err := assembleClusterConfig(cfg)
if err != nil {
t.Fatal(err)
}
if !c.InsecureSkipTLSVerify {
t.Fatalf("InsecureSkipTLSVerify = false, want true")
}
if c.TLSServerName != tlsName {
t.Fatalf("TLSServerName = %q, want %q", c.TLSServerName, tlsName)
}
})
}
func TestSyncKubeConfigSecret_tlsServerNameDrift(t *testing.T) {
tkc := &rest.Config{
Host: "https://api.example:6443",
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte("caData"),
ServerName: "kubernetes.default.svc",
},
}
kubeconfigMissingTLSName, err := clientcmd.Write(clientcmdapi.Config{
Kind: "Config",
APIVersion: "v1",
Clusters: map[string]*clientcmdapi.Cluster{
"cluster": {
Server: tkc.Host,
CertificateAuthorityData: tkc.TLSClientConfig.CAData,
},
},
Contexts: map[string]*clientcmdapi.Context{
"context": {Cluster: "cluster", AuthInfo: "user"},
},
AuthInfos: map[string]*clientcmdapi.AuthInfo{
"user": {Token: "ignored"},
},
CurrentContext: "context",
})
if err != nil {
t.Fatal(err)
}
secretName := "test-secret"
secretNamespace := "test-ns"
client := testclient.NewSimpleClientset(&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: secretName, Namespace: secretNamespace},
Data: map[string][]byte{
"token": []byte("aaa"),
"kubeconfig": kubeconfigMissingTLSName,
},
})
tokenGetter := func() ([]byte, []byte, map[string][]byte, error) {
return []byte("aaa"), nil, nil, nil
}
err = SyncKubeConfigSecret(
context.TODO(), secretName, secretNamespace,
"/tmp/kubeconfig", tkc, client.CoreV1(), tokenGetter,
events.NewContextualLoggingEventRecorder(t.Name()), nil)
if err != nil {
t.Fatal(err)
}
actions := client.Actions()
var lastSecret *corev1.Secret
for i := len(actions) - 1; i >= 0; i-- {
switch a := actions[i].(type) {
case clienttesting.CreateAction:
if s, ok := a.GetObject().(*corev1.Secret); ok {
lastSecret = s
}
case clienttesting.UpdateAction:
if s, ok := a.GetObject().(*corev1.Secret); ok {
lastSecret = s
}
}
if lastSecret != nil {
break
}
}
if lastSecret == nil {
t.Fatalf("expected a create/update secret action, got %#v", actions)
}
loaded, err := clientcmd.Load(lastSecret.Data["kubeconfig"])
if err != nil {
t.Fatal(err)
}
cluster := loaded.Clusters["cluster"]
if cluster == nil {
t.Fatal("cluster context missing")
}
if cluster.TLSServerName != tkc.ServerName {
t.Fatalf("synced TLSServerName = %q, want %q", cluster.TLSServerName, tkc.ServerName)
}
}
func TestTokenGetter(t *testing.T) {
saName := "test-sa"
saNamespace := "test-ns"
saUID := "test-uid"
tests := []struct {
name string
objects []runtime.Object
expirationTime metav1.Time
wantToken []byte
wantErr bool
}{
{
name: "no sa",
wantErr: true,
},
{
name: "no secret",
objects: []runtime.Object{
&corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: saName,
Namespace: saNamespace,
},
Secrets: []corev1.ObjectReference{
{
Name: "test",
},
},
},
},
wantErr: true,
},
{
name: "token request",
objects: []runtime.Object{
&corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: saName,
Namespace: saNamespace,
UID: types.UID(saUID),
},
Secrets: []corev1.ObjectReference{},
},
},
wantToken: []byte("aaa"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := testclient.NewSimpleClientset(tt.objects...)
client.PrependReactor("create", "serviceaccounts/token", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
return true, &authv1.TokenRequest{Status: authv1.TokenRequestStatus{Token: "aaa", ExpirationTimestamp: tt.expirationTime}}, nil
})
tokenGetter := SATokenGetter(context.TODO(), saName, saNamespace, client)
token, _, additionalData, err := tokenGetter()
if err != nil && !tt.wantErr {
t.Error(err)
}
if err == nil && tt.wantErr {
t.Errorf("expect to get error")
}
if !reflect.DeepEqual(token, tt.wantToken) {
t.Errorf("token is not correct, got %s", string(token))
}
if !tt.wantErr {
if additionalData == nil {
t.Errorf("additional data is nil")
}
if !reflect.DeepEqual(additionalData["serviceaccount_namespace"], []byte(saNamespace)) {
t.Errorf("serviceaccount_namespace is not correct, got %s", string(additionalData["serviceaccount_namespace"]))
}
if !reflect.DeepEqual(additionalData["serviceaccount_name"], []byte(saName)) {
t.Errorf("serviceaccount_name is not correct, got %s", string(additionalData["serviceaccount_name"]))
}
if !reflect.DeepEqual(additionalData["serviceaccount_uid"], []byte(saUID)) {
t.Errorf("serviceaccount_uid is not correct, got %s", string(additionalData["serviceaccount_uid"]))
}
}
})
}
}
func TestApplyKubeconfigSecret(t *testing.T) {
tkc := &rest.Config{
Host: "host",
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte("caData"),
},
}
secretName := "test-secret"
secretNamespace := "test-ns"
now, _ := metav1.Now().MarshalText()
tests := []struct {
name string
secrets []runtime.Object
token []byte
additionalData map[string][]byte
tokenGetError error
expiration *metav1.Time
validateActions func(t *testing.T, actions []clienttesting.Action)
wantErr bool
}{
{
name: "create secret if none",
token: []byte("aaa"),
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 3 {
t.Errorf("expect 3 actions, but get %d", len(actions))
}
secret := actions[2].(clienttesting.CreateAction).GetObject().(*corev1.Secret)
if !reflect.DeepEqual(secret.Data["token"], []byte("aaa")) {
t.Errorf("secret update is not correct, got %v", secret.Data)
}
},
},
{
name: "get token fails",
token: []byte("aaa"),
tokenGetError: fmt.Errorf("unexpected error"),
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 1 {
t.Errorf("expect 1 actions, but get %d", len(actions))
}
},
wantErr: true,
},
{
name: "update secret if secret is not correct",
token: []byte("aaa"),
secrets: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: secretNamespace,
},
},
},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 4 {
t.Fatalf("expect 4 actions, but get %d", len(actions))
}
secret := actions[3].(clienttesting.CreateAction).GetObject().(*corev1.Secret)
if !reflect.DeepEqual(secret.Data["token"], []byte("aaa")) {
t.Errorf("secret update is not correct, got %v", secret.Data)
}
},
},
{
name: "update secret if expired",
token: []byte("aaa"),
secrets: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: secretNamespace,
},
Data: map[string][]byte{
"token": []byte("aaa"),
"expiration": now,
},
},
},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 4 {
t.Fatalf("expect 4 actions, but get %d", len(actions))
}
secret := actions[3].(clienttesting.CreateAction).GetObject().(*corev1.Secret)
if !reflect.DeepEqual(secret.Data["token"], []byte("aaa")) {
t.Errorf("secret update is not correct, got %v", secret.Data)
}
},
},
{
name: "update secret if additional data changed",
token: []byte("aaa"),
secrets: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: secretNamespace,
},
Data: map[string][]byte{
"token": []byte("aaa"),
"serviceaccount_name": []byte("test-sa"),
"serviceaccount_namespace": []byte("test-ns"),
},
},
},
additionalData: map[string][]byte{
"serviceaccount_name": []byte("test-sa"),
"serviceaccount_namespace": []byte("test-ns-new"),
},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 4 {
t.Fatalf("expect 4 actions, but get %d", len(actions))
}
secret := actions[3].(clienttesting.CreateAction).GetObject().(*corev1.Secret)
if !reflect.DeepEqual(secret.Data["token"], []byte("aaa")) {
t.Errorf("secret update is not correct, got %v", secret.Data)
}
},
},
{
name: "update secret if new additional data added",
token: []byte("aaa"),
secrets: []runtime.Object{
&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: secretNamespace,
},
Data: map[string][]byte{
"token": []byte("aaa"),
},
},
},
additionalData: map[string][]byte{
"serviceaccount_name": []byte("test-sa"),
"serviceaccount_namespace": []byte("test-ns"),
},
validateActions: func(t *testing.T, actions []clienttesting.Action) {
if len(actions) != 4 {
t.Fatalf("expect 4 actions, but get %d", len(actions))
}
secret := actions[3].(clienttesting.CreateAction).GetObject().(*corev1.Secret)
if !reflect.DeepEqual(secret.Data["token"], []byte("aaa")) {
t.Errorf("secret update is not correct, got %v", secret.Data)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tokenGetter := func() ([]byte, []byte, map[string][]byte, error) {
if tt.expiration == nil {
return tt.token, nil, tt.additionalData, tt.tokenGetError
}
expiration, _ := tt.expiration.MarshalText()
return tt.token, expiration, tt.additionalData, tt.tokenGetError
}
client := testclient.NewSimpleClientset(tt.secrets...)
err := SyncKubeConfigSecret(
context.TODO(), secretName, secretNamespace,
"/tmp/kubeconfig", tkc, client.CoreV1(), tokenGetter,
events.NewContextualLoggingEventRecorder(t.Name()), nil)
if err != nil && !tt.wantErr {
t.Error(err)
}
if err == nil && tt.wantErr {
t.Errorf("expect to get error")
}
tt.validateActions(t, client.Actions())
})
}
}