/* Copyright © The ESO Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package esutils import ( "context" "errors" "testing" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" kclient "sigs.k8s.io/controller-runtime/pkg/client" fclient "sigs.k8s.io/controller-runtime/pkg/client/fake" esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1" v1 "github.com/external-secrets/external-secrets/apis/meta/v1" utilfake "github.com/external-secrets/external-secrets/runtime/util/fake" ) const ( caCert = `-----BEGIN CERTIFICATE----- MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS 7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp 0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 DXZDjC5Ty3zfDBeWUA== -----END CERTIFICATE----- ` authTestKubeConfig = `apiVersion: v1 clusters: - cluster: server: https://api.my-domain.tld certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNHVENDQVorZ0F3SUJBZ0lRQ2VDVFphejMyY2k1UGh3TEJDb3U4ekFLQmdncWhrak9QUVFEQXpCT01Rc3cKQ1FZRFZRUUdFd0pWVXpFWE1CVUdBMVVFQ2hNT1JHbG5hVU5sY25Rc0lFbHVZeTR4SmpBa0JnTlZCQU1USFVScApaMmxEWlhKMElGUk1VeUJGUTBNZ1VETTROQ0JTYjI5MElFYzFNQjRYRFRJeE1ERXhOVEF3TURBd01Gb1hEVFEyCk1ERXhOREl6TlRrMU9Wb3dUakVMTUFrR0ExVUVCaE1DVlZNeEZ6QVZCZ05WQkFvVERrUnBaMmxEWlhKMExDQkoKYm1NdU1TWXdKQVlEVlFRREV4MUVhV2RwUTJWeWRDQlVURk1nUlVORElGQXpPRFFnVW05dmRDQkhOVEIyTUJBRwpCeXFHU000OUFnRUdCU3VCQkFBaUEySUFCTUZFb2M4UmwxQ2EzaU9DTlFmTjBNc1luZEx4ZjNjMVR6dmRsSEpTCjdjSTcrT3o2ZTJ0WUlPeVpyc244YUxOMXVkc0o3TWdUOVU3R0NoMW1NRXk3SDBjS1BHRVFRaWw4cFFnTzRDTHAKMHpWb3pwdGpuNFMxbVUxWW9JNzFWT2VWeWFOQ01FQXdIUVlEVlIwT0JCWUVGTUZSUlZCWnF6N25MRnI2SUNJUwpCNENJZkJGcU1BNEdBMVVkRHdFQi93UUVBd0lCaGpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUFvR0NDcUdTTTQ5CkJBTURBMmdBTUdVQ01RQ0phbzFINSt6OGJsVUQyV2RzSms2RHh2M0oreXNUdkxkNmpMUmwwbWxwWXhOak95WlEKTGdHaGVRYVJuVWkvd3I0Q01FZkRGWHV4b0pHWlNaT29QSHpvUmdhTExQSXhBSlNkWXNpSnZSbUVGT21sK3dHNApEWFpEakM1VHkzemZEQmVXVUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== name: mycluster contexts: - context: cluster: mycluster user: myuser name: mycontext current-context: mycontext kind: Config preferences: {} users: - name: myuser user: token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE3MTkzOTY4OTksImV4cCI6MTc1MDkzMjg4NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.xXrfIl0akhfjWU_BDl7Ad54SXje0YlJdnugzwh96VmM ` serverURL = "https://my.test.tld" ) func TestBuildRESTConfigFromKubernetesConnection(t *testing.T) { type fields struct { kube kclient.Client kubeclientset typedcorev1.CoreV1Interface store *esv1.KubernetesProvider namespace string storeKind string } type want = rest.Config tests := []struct { name string fields fields want *want wantErr bool wantErrIs error }{ { name: "should return err if no ca provided", fields: fields{ store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{}, }, }, want: nil, wantErr: true, }, { name: "should return err if no auth provided", fields: fields{ store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ CABundle: []byte(caCert), }, }, }, want: nil, wantErr: true, }, { name: "should fetch ca from Secret", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foobar", Namespace: "default", }, Data: map[string][]byte{ "cert": []byte(caCert), "token": []byte("mytoken"), }, }).Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CAProvider: &esv1.CAProvider{ Type: esv1.CAProviderTypeSecret, Name: "foobar", Key: "cert", }, }, Auth: &esv1.KubernetesAuth{ Token: &esv1.TokenAuth{ BearerToken: v1.SecretKeySelector{ Name: "foobar", Namespace: new("shouldnotberelevant"), Key: "token", }, }, }, }, }, want: &want{ Host: serverURL, BearerToken: "mytoken", TLSClientConfig: rest.TLSClientConfig{ CAData: []byte(caCert), }, }, wantErr: false, }, { name: "should fetch ca from ConfigMap", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foobar", Namespace: "default", }, Data: map[string][]byte{ "token": []byte("mytoken"), }, }, &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: "foobar", Namespace: "default", }, Data: map[string]string{ "cert": "1234", }, }).Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CAProvider: &esv1.CAProvider{ Type: esv1.CAProviderTypeConfigMap, Name: "foobar", Key: "cert", }, }, Auth: &esv1.KubernetesAuth{ Token: &esv1.TokenAuth{ BearerToken: v1.SecretKeySelector{ Name: "foobar", Namespace: new("shouldnotberelevant"), Key: "token", }, }, }, }, }, want: &want{ Host: serverURL, BearerToken: "mytoken", TLSClientConfig: rest.TLSClientConfig{ CAData: []byte("1234"), }, }, wantErr: false, }, { name: "should use system ca roots when no ca configured", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foobar", Namespace: "default", }, Data: map[string][]byte{ "token": []byte("mytoken"), }, }).Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, }, Auth: &esv1.KubernetesAuth{ Token: &esv1.TokenAuth{ BearerToken: v1.SecretKeySelector{ Name: "foobar", Key: "token", }, }, }, }, }, want: &want{ Host: serverURL, BearerToken: "mytoken", TLSClientConfig: rest.TLSClientConfig{ Insecure: false, CAData: nil, }, }, wantErr: false, }, { name: "should set token from secret", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foobar", Namespace: "default", }, Data: map[string][]byte{ "token": []byte("mytoken"), }, }).Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CABundle: []byte(caCert), }, Auth: &esv1.KubernetesAuth{ Token: &esv1.TokenAuth{ BearerToken: v1.SecretKeySelector{ Name: "foobar", Namespace: new("shouldnotberelevant"), Key: "token", }, }, }, }, }, want: &want{ Host: serverURL, BearerToken: "mytoken", TLSClientConfig: rest.TLSClientConfig{ CAData: []byte(caCert), }, }, wantErr: false, }, { name: "should set client cert from secret", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "mycert", Namespace: "default", }, Data: map[string][]byte{ "cert": []byte("my-cert"), "key": []byte("my-key"), }, }).Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CABundle: []byte(caCert), }, Auth: &esv1.KubernetesAuth{ Cert: &esv1.CertAuth{ ClientCert: v1.SecretKeySelector{ Name: "mycert", Key: "cert", }, ClientKey: v1.SecretKeySelector{ Name: "mycert", Key: "key", }, }, }, }, }, want: &want{ Host: serverURL, TLSClientConfig: rest.TLSClientConfig{ CAData: []byte(caCert), CertData: []byte("my-cert"), KeyData: []byte("my-key"), }, }, wantErr: false, }, { name: "should set token from service account", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().WithObjects(&corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "my-sa", Namespace: "default", }, }).Build(), kubeclientset: utilfake.NewCreateTokenMock().WithToken("my-sa-token"), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CABundle: []byte(caCert), }, Auth: &esv1.KubernetesAuth{ ServiceAccount: &v1.ServiceAccountSelector{ Name: "my-sa", Namespace: new("shouldnotberelevant"), }, }, }, }, want: &want{ Host: serverURL, BearerToken: "my-sa-token", TLSClientConfig: rest.TLSClientConfig{ CAData: []byte(caCert), }, }, wantErr: false, }, { name: "should fail with missing URL", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().WithObjects(&corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "my-sa", Namespace: "default", }, }).Build(), kubeclientset: utilfake.NewCreateTokenMock().WithToken("my-sa-token"), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ CABundle: []byte(caCert), }, Auth: &esv1.KubernetesAuth{ ServiceAccount: &v1.ServiceAccountSelector{ Name: "my-sa", Namespace: new("shouldnotberelevant"), }, }, }, }, want: nil, wantErr: true, }, { name: "should error when Token and ServiceAccount are both set", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CABundle: []byte(caCert), }, Auth: &esv1.KubernetesAuth{ Token: &esv1.TokenAuth{ BearerToken: v1.SecretKeySelector{Name: "tok", Key: "token"}, }, ServiceAccount: &v1.ServiceAccountSelector{Name: "my-sa"}, }, }, }, want: nil, wantErr: true, wantErrIs: ErrMultipleAuthMethods, }, { name: "should error when Token and Cert are both set", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CABundle: []byte(caCert), }, Auth: &esv1.KubernetesAuth{ Token: &esv1.TokenAuth{ BearerToken: v1.SecretKeySelector{Name: "tok", Key: "token"}, }, Cert: &esv1.CertAuth{ ClientCert: v1.SecretKeySelector{Name: "mycert", Key: "cert"}, ClientKey: v1.SecretKeySelector{Name: "mycert", Key: "key"}, }, }, }, }, want: nil, wantErr: true, wantErrIs: ErrMultipleAuthMethods, }, { name: "should error when ServiceAccount and Cert are both set", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CABundle: []byte(caCert), }, Auth: &esv1.KubernetesAuth{ ServiceAccount: &v1.ServiceAccountSelector{Name: "my-sa"}, Cert: &esv1.CertAuth{ ClientCert: v1.SecretKeySelector{Name: "mycert", Key: "cert"}, ClientKey: v1.SecretKeySelector{Name: "mycert", Key: "key"}, }, }, }, }, want: nil, wantErr: true, wantErrIs: ErrMultipleAuthMethods, }, { name: "should error when all three auth methods are set", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CABundle: []byte(caCert), }, Auth: &esv1.KubernetesAuth{ Token: &esv1.TokenAuth{ BearerToken: v1.SecretKeySelector{Name: "tok", Key: "token"}, }, ServiceAccount: &v1.ServiceAccountSelector{Name: "my-sa"}, Cert: &esv1.CertAuth{ ClientCert: v1.SecretKeySelector{Name: "mycert", Key: "cert"}, ClientKey: v1.SecretKeySelector{Name: "mycert", Key: "key"}, }, }, }, }, want: nil, wantErr: true, wantErrIs: ErrMultipleAuthMethods, }, { name: "should error when authRef and inline auth are both set", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foobar", Namespace: "default", }, Data: map[string][]byte{ "config": []byte(authTestKubeConfig), "token": []byte("mytoken"), }, }).Build(), store: &esv1.KubernetesProvider{ Server: esv1.KubernetesServer{ URL: serverURL, CABundle: []byte(caCert), }, Auth: &esv1.KubernetesAuth{ Token: &esv1.TokenAuth{ BearerToken: v1.SecretKeySelector{Name: "foobar", Key: "token"}, }, }, AuthRef: &v1.SecretKeySelector{ Name: "foobar", Namespace: new("default"), Key: "config", }, }, }, want: nil, wantErr: true, wantErrIs: ErrAuthRefWithInlineAuth, }, { name: "should read config from secret", fields: fields{ namespace: "default", kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "foobar", Namespace: "default", }, Data: map[string][]byte{ "config": []byte(authTestKubeConfig), }, }).Build(), store: &esv1.KubernetesProvider{ AuthRef: &v1.SecretKeySelector{ Name: "foobar", Namespace: new("default"), Key: "config", }, }, }, want: &want{ Host: "https://api.my-domain.tld", BearerToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE3MTkzOTY4OTksImV4cCI6MTc1MDkzMjg4NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.xXrfIl0akhfjWU_BDl7Ad54SXje0YlJdnugzwh96VmM", TLSClientConfig: rest.TLSClientConfig{ CAData: []byte(caCert), }, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { storeKind := tt.fields.storeKind if storeKind == "" { storeKind = esv1.SecretStoreKind } cfg, err := BuildRESTConfigFromKubernetesConnection( context.Background(), tt.fields.kube, tt.fields.kubeclientset, storeKind, tt.fields.namespace, tt.fields.store.Server, tt.fields.store.Auth, tt.fields.store.AuthRef, ) if (err != nil) != tt.wantErr { t.Errorf("BuildRESTConfigFromKubernetesConnection() error = %v, wantErr %v", err, tt.wantErr) } if tt.wantErrIs != nil { assert.True(t, errors.Is(err, tt.wantErrIs), "expected errors.Is(%v, %v)", err, tt.wantErrIs) } assert.Equal(t, tt.want, cfg) }) } }