1
0
Fork 0
mirror of https://github.com/external-secrets/external-secrets.git synced 2024-12-15 17:51:01 +00:00

add AuthRef to kubernetes provider fixes #3627 (#3628)

* add AuthRef to kubernetes provider fixes #3627

Signed-off-by: kaedwen <kaedwen@heinrich.blue>

* run make reviewable

Signed-off-by: kaedwen <kaedwen@heinrich.blue>

* fix validation for given authRef

Signed-off-by: kaedwen <kaedwen@heinrich.blue>

* refactor kubernetes provider auth

Signed-off-by: kaedwen <kaedwen@heinrich.blue>

* satisfy linter

Signed-off-by: kaedwen <kaedwen@heinrich.blue>

* add URL for kubernetes provider tests

Signed-off-by: kaedwen <kaedwen@heinrich.blue>

---------

Signed-off-by: kaedwen <kaedwen@heinrich.blue>
This commit is contained in:
kaedwen 2024-07-01 23:31:10 +02:00 committed by GitHub
parent c6bafe8c61
commit 48cccaeded
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 401 additions and 94 deletions

View file

@ -37,11 +37,17 @@ type KubernetesServer struct {
// Configures a store to sync secrets with a Kubernetes instance. // Configures a store to sync secrets with a Kubernetes instance.
type KubernetesProvider struct { type KubernetesProvider struct {
// configures the Kubernetes server Address. // configures the Kubernetes server Address.
// +optional
Server KubernetesServer `json:"server,omitempty"` Server KubernetesServer `json:"server,omitempty"`
// Auth configures how secret-manager authenticates with a Kubernetes instance. // Auth configures how secret-manager authenticates with a Kubernetes instance.
// +optional
Auth KubernetesAuth `json:"auth"` Auth KubernetesAuth `json:"auth"`
// A reference to a secret that contains the auth information.
// +optional
AuthRef *esmeta.SecretKeySelector `json:"authRef,omitempty"`
// Remote namespace to fetch the secrets from // Remote namespace to fetch the secrets from
// +kubebuilder:default= default // +kubebuilder:default= default
// +optional // +optional

View file

@ -1858,6 +1858,11 @@ func (in *KubernetesProvider) DeepCopyInto(out *KubernetesProvider) {
*out = *in *out = *in
in.Server.DeepCopyInto(&out.Server) in.Server.DeepCopyInto(&out.Server)
in.Auth.DeepCopyInto(&out.Auth) in.Auth.DeepCopyInto(&out.Auth)
if in.AuthRef != nil {
in, out := &in.AuthRef, &out.AuthRef
*out = new(metav1.SecretKeySelector)
(*in).DeepCopyInto(*out)
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesProvider. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KubernetesProvider.

View file

@ -3199,6 +3199,25 @@ spec:
type: object type: object
type: object type: object
type: object type: object
authRef:
description: A reference to a secret that contains the auth
information.
properties:
key:
description: |-
The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
defaulted, in others it may be required.
type: string
name:
description: The name of the Secret resource being referred
to.
type: string
namespace:
description: |-
Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
to the namespace of the referent.
type: string
type: object
remoteNamespace: remoteNamespace:
default: default default: default
description: Remote namespace to fetch the secrets from description: Remote namespace to fetch the secrets from
@ -3242,8 +3261,6 @@ spec:
description: configures the Kubernetes server Address. description: configures the Kubernetes server Address.
type: string type: string
type: object type: object
required:
- auth
type: object type: object
onboardbase: onboardbase:
description: Onboardbase configures this store to sync secrets description: Onboardbase configures this store to sync secrets

View file

@ -3199,6 +3199,25 @@ spec:
type: object type: object
type: object type: object
type: object type: object
authRef:
description: A reference to a secret that contains the auth
information.
properties:
key:
description: |-
The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
defaulted, in others it may be required.
type: string
name:
description: The name of the Secret resource being referred
to.
type: string
namespace:
description: |-
Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
to the namespace of the referent.
type: string
type: object
remoteNamespace: remoteNamespace:
default: default default: default
description: Remote namespace to fetch the secrets from description: Remote namespace to fetch the secrets from
@ -3242,8 +3261,6 @@ spec:
description: configures the Kubernetes server Address. description: configures the Kubernetes server Address.
type: string type: string
type: object type: object
required:
- auth
type: object type: object
onboardbase: onboardbase:
description: Onboardbase configures this store to sync secrets description: Onboardbase configures this store to sync secrets

View file

@ -3634,6 +3634,23 @@ spec:
type: object type: object
type: object type: object
type: object type: object
authRef:
description: A reference to a secret that contains the auth information.
properties:
key:
description: |-
The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
defaulted, in others it may be required.
type: string
name:
description: The name of the Secret resource being referred to.
type: string
namespace:
description: |-
Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
to the namespace of the referent.
type: string
type: object
remoteNamespace: remoteNamespace:
default: default default: default
description: Remote namespace to fetch the secrets from description: Remote namespace to fetch the secrets from
@ -3674,8 +3691,6 @@ spec:
description: configures the Kubernetes server Address. description: configures the Kubernetes server Address.
type: string type: string
type: object type: object
required:
- auth
type: object type: object
onboardbase: onboardbase:
description: Onboardbase configures this store to sync secrets using the Onboardbase provider description: Onboardbase configures this store to sync secrets using the Onboardbase provider
@ -9182,6 +9197,23 @@ spec:
type: object type: object
type: object type: object
type: object type: object
authRef:
description: A reference to a secret that contains the auth information.
properties:
key:
description: |-
The key of the entry in the Secret resource's `data` field to be used. Some instances of this field may be
defaulted, in others it may be required.
type: string
name:
description: The name of the Secret resource being referred to.
type: string
namespace:
description: |-
Namespace of the resource being referred to. Ignored if referent is not cluster-scoped. cluster-scoped defaults
to the namespace of the referent.
type: string
type: object
remoteNamespace: remoteNamespace:
default: default default: default
description: Remote namespace to fetch the secrets from description: Remote namespace to fetch the secrets from
@ -9222,8 +9254,6 @@ spec:
description: configures the Kubernetes server Address. description: configures the Kubernetes server Address.
type: string type: string
type: object type: object
required:
- auth
type: object type: object
onboardbase: onboardbase:
description: Onboardbase configures this store to sync secrets using the Onboardbase provider description: Onboardbase configures this store to sync secrets using the Onboardbase provider

View file

@ -4864,6 +4864,7 @@ KubernetesServer
</em> </em>
</td> </td>
<td> <td>
<em>(Optional)</em>
<p>configures the Kubernetes server Address.</p> <p>configures the Kubernetes server Address.</p>
</td> </td>
</tr> </tr>
@ -4877,11 +4878,26 @@ KubernetesAuth
</em> </em>
</td> </td>
<td> <td>
<em>(Optional)</em>
<p>Auth configures how secret-manager authenticates with a Kubernetes instance.</p> <p>Auth configures how secret-manager authenticates with a Kubernetes instance.</p>
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<code>authRef</code></br>
<em>
<a href="https://pkg.go.dev/github.com/external-secrets/external-secrets/apis/meta/v1#SecretKeySelector">
External Secrets meta/v1.SecretKeySelector
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>A reference to a secret that contains the auth information.</p>
</td>
</tr>
<tr>
<td>
<code>remoteNamespace</code></br> <code>remoteNamespace</code></br>
<em> <em>
string string

View file

@ -22,6 +22,8 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1" esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
esmeta "github.com/external-secrets/external-secrets/apis/meta/v1" esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
@ -36,35 +38,63 @@ const (
errUnableCreateToken = "cannot create service account token: %q" errUnableCreateToken = "cannot create service account token: %q"
) )
func (c *Client) setAuth(ctx context.Context) error { func (c *Client) getAuth(ctx context.Context) (*rest.Config, error) {
err := c.setCA(ctx) if c.store.AuthRef != nil {
cfg, err := c.fetchSecretKey(ctx, *c.store.AuthRef)
if err != nil { if err != nil {
return err return nil, err
} }
return clientcmd.RESTConfigFromKubeConfig(cfg)
}
ca, err := c.getCA(ctx)
if err != nil {
return nil, err
}
var token []byte
if c.store.Auth.Token != nil { if c.store.Auth.Token != nil {
c.BearerToken, err = c.fetchSecretKey(ctx, c.store.Auth.Token.BearerToken) token, err = c.fetchSecretKey(ctx, c.store.Auth.Token.BearerToken)
if err != nil { if err != nil {
return fmt.Errorf("could not fetch Auth.Token.BearerToken: %w", err) return nil, fmt.Errorf("could not fetch Auth.Token.BearerToken: %w", err)
} }
return nil } else if c.store.Auth.ServiceAccount != nil {
} token, err = c.serviceAccountToken(ctx, c.store.Auth.ServiceAccount)
if c.store.Auth.ServiceAccount != nil {
c.BearerToken, err = c.serviceAccountToken(ctx, c.store.Auth.ServiceAccount)
if err != nil { if err != nil {
return fmt.Errorf("could not fetch Auth.ServiceAccount: %w", err) return nil, fmt.Errorf("could not fetch Auth.ServiceAccount: %w", err)
} }
return nil } else {
return nil, fmt.Errorf("no auth provider given")
} }
var key, cert []byte
if c.store.Auth.Cert != nil { if c.store.Auth.Cert != nil {
return c.setClientCert(ctx) key, cert, err = c.getClientKeyAndCert(ctx)
if err != nil {
return nil, fmt.Errorf("could not fetch client key and cert: %w", err)
} }
return fmt.Errorf("no credentials provided") }
if c.store.Server.URL == "" {
return nil, fmt.Errorf("no server URL provided")
}
return &rest.Config{
Host: c.store.Server.URL,
BearerToken: string(token),
TLSClientConfig: rest.TLSClientConfig{
Insecure: false,
CertData: cert,
KeyData: key,
CAData: ca,
},
}, nil
} }
func (c *Client) setCA(ctx context.Context) error { func (c *Client) getCA(ctx context.Context) ([]byte, error) {
if c.store.Server.CABundle != nil { if c.store.Server.CABundle != nil {
c.CA = c.store.Server.CABundle return c.store.Server.CABundle, nil
return nil
} }
if c.store.Server.CAProvider != nil { if c.store.Server.CAProvider != nil {
var ca []byte var ca []byte
@ -78,7 +108,7 @@ func (c *Client) setCA(ctx context.Context) error {
} }
ca, err = c.fetchConfigMapKey(ctx, keySelector) ca, err = c.fetchConfigMapKey(ctx, keySelector)
if err != nil { if err != nil {
return fmt.Errorf("unable to fetch Server.CAProvider ConfigMap: %w", err) return nil, fmt.Errorf("unable to fetch Server.CAProvider ConfigMap: %w", err)
} }
case esv1beta1.CAProviderTypeSecret: case esv1beta1.CAProviderTypeSecret:
keySelector := esmeta.SecretKeySelector{ keySelector := esmeta.SecretKeySelector{
@ -88,26 +118,25 @@ func (c *Client) setCA(ctx context.Context) error {
} }
ca, err = c.fetchSecretKey(ctx, keySelector) ca, err = c.fetchSecretKey(ctx, keySelector)
if err != nil { if err != nil {
return fmt.Errorf("unable to fetch Server.CAProvider Secret: %w", err) return nil, fmt.Errorf("unable to fetch Server.CAProvider Secret: %w", err)
} }
} }
c.CA = ca return ca, nil
return nil
} }
return fmt.Errorf("no Certificate Authority provided") return nil, fmt.Errorf("no Certificate Authority provided")
} }
func (c *Client) setClientCert(ctx context.Context) error { func (c *Client) getClientKeyAndCert(ctx context.Context) ([]byte, []byte, error) {
var err error var err error
c.Certificate, err = c.fetchSecretKey(ctx, c.store.Auth.Cert.ClientCert) cert, err := c.fetchSecretKey(ctx, c.store.Auth.Cert.ClientCert)
if err != nil { if err != nil {
return fmt.Errorf("unable to fetch client certificate: %w", err) return nil, nil, fmt.Errorf("unable to fetch client certificate: %w", err)
} }
c.Key, err = c.fetchSecretKey(ctx, c.store.Auth.Cert.ClientKey) key, err := c.fetchSecretKey(ctx, c.store.Auth.Cert.ClientKey)
if err != nil { if err != nil {
return fmt.Errorf("unable to fetch client key: %w", err) return nil, nil, fmt.Errorf("unable to fetch client key: %w", err)
} }
return nil return key, cert, nil
} }
func (c *Client) serviceAccountToken(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) ([]byte, error) { func (c *Client) serviceAccountToken(ctx context.Context, serviceAccountRef *esmeta.ServiceAccountSelector) ([]byte, error) {

View file

@ -22,6 +22,7 @@ import (
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
pointer "k8s.io/utils/ptr" pointer "k8s.io/utils/ptr"
kclient "sigs.k8s.io/controller-runtime/pkg/client" kclient "sigs.k8s.io/controller-runtime/pkg/client"
fclient "sigs.k8s.io/controller-runtime/pkg/client/fake" fclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
@ -31,6 +32,43 @@ import (
utilfake "github.com/external-secrets/external-secrets/pkg/provider/util/fake" utilfake "github.com/external-secrets/external-secrets/pkg/provider/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
`
)
func TestSetAuth(t *testing.T) { func TestSetAuth(t *testing.T) {
type fields struct { type fields struct {
kube kclient.Client kube kclient.Client
@ -39,16 +77,11 @@ func TestSetAuth(t *testing.T) {
namespace string namespace string
storeKind string storeKind string
} }
type want struct { type want = rest.Config
Certificate []byte
Key []byte
CA []byte
BearerToken []byte
}
tests := []struct { tests := []struct {
name string name string
fields fields fields fields
want want want *want
wantErr bool wantErr bool
}{ }{
{ {
@ -58,7 +91,7 @@ func TestSetAuth(t *testing.T) {
Server: esv1beta1.KubernetesServer{}, Server: esv1beta1.KubernetesServer{},
}, },
}, },
want: want{}, want: nil,
wantErr: true, wantErr: true,
}, },
{ {
@ -70,9 +103,7 @@ func TestSetAuth(t *testing.T) {
}, },
}, },
}, },
want: want{ want: nil,
CA: []byte("1234"),
},
wantErr: true, wantErr: true,
}, },
{ {
@ -86,28 +117,51 @@ func TestSetAuth(t *testing.T) {
}, },
Data: map[string][]byte{ Data: map[string][]byte{
"cert": []byte("1234"), "cert": []byte("1234"),
"token": []byte("mytoken"),
}, },
}).Build(), }).Build(),
store: &esv1beta1.KubernetesProvider{ store: &esv1beta1.KubernetesProvider{
Server: esv1beta1.KubernetesServer{ Server: esv1beta1.KubernetesServer{
URL: "https://my.test.tld",
CAProvider: &esv1beta1.CAProvider{ CAProvider: &esv1beta1.CAProvider{
Type: esv1beta1.CAProviderTypeSecret, Type: esv1beta1.CAProviderTypeSecret,
Name: "foobar", Name: "foobar",
Key: "cert", Key: "cert",
}, },
}, },
Auth: esv1beta1.KubernetesAuth{
Token: &esv1beta1.TokenAuth{
BearerToken: v1.SecretKeySelector{
Name: "foobar",
Namespace: pointer.To("shouldnotberelevant"),
Key: "token",
}, },
}, },
want: want{
CA: []byte("1234"),
}, },
wantErr: true, },
},
want: &want{
Host: "https://my.test.tld",
BearerToken: "mytoken",
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte("1234"),
},
},
wantErr: false,
}, },
{ {
name: "should fetch ca from ConfigMap", name: "should fetch ca from ConfigMap",
fields: fields{ fields: fields{
namespace: "default", namespace: "default",
kube: fclient.NewClientBuilder().WithObjects(&corev1.ConfigMap{ 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{ ObjectMeta: metav1.ObjectMeta{
Name: "foobar", Name: "foobar",
Namespace: "default", Namespace: "default",
@ -118,18 +172,32 @@ func TestSetAuth(t *testing.T) {
}).Build(), }).Build(),
store: &esv1beta1.KubernetesProvider{ store: &esv1beta1.KubernetesProvider{
Server: esv1beta1.KubernetesServer{ Server: esv1beta1.KubernetesServer{
URL: "https://my.test.tld",
CAProvider: &esv1beta1.CAProvider{ CAProvider: &esv1beta1.CAProvider{
Type: esv1beta1.CAProviderTypeConfigMap, Type: esv1beta1.CAProviderTypeConfigMap,
Name: "foobar", Name: "foobar",
Key: "cert", Key: "cert",
}, },
}, },
Auth: esv1beta1.KubernetesAuth{
Token: &esv1beta1.TokenAuth{
BearerToken: v1.SecretKeySelector{
Name: "foobar",
Namespace: pointer.To("shouldnotberelevant"),
Key: "token",
}, },
}, },
want: want{
CA: []byte("1234"),
}, },
wantErr: true, },
},
want: &want{
Host: "https://my.test.tld",
BearerToken: "mytoken",
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte("1234"),
},
},
wantErr: false,
}, },
{ {
name: "should set token from secret", name: "should set token from secret",
@ -146,6 +214,7 @@ func TestSetAuth(t *testing.T) {
}).Build(), }).Build(),
store: &esv1beta1.KubernetesProvider{ store: &esv1beta1.KubernetesProvider{
Server: esv1beta1.KubernetesServer{ Server: esv1beta1.KubernetesServer{
URL: "https://my.test.tld",
CABundle: []byte("1234"), CABundle: []byte("1234"),
}, },
Auth: esv1beta1.KubernetesAuth{ Auth: esv1beta1.KubernetesAuth{
@ -159,9 +228,12 @@ func TestSetAuth(t *testing.T) {
}, },
}, },
}, },
want: want{ want: &want{
CA: []byte("1234"), Host: "https://my.test.tld",
BearerToken: []byte("mytoken"), BearerToken: "mytoken",
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte("1234"),
},
}, },
wantErr: false, wantErr: false,
}, },
@ -178,12 +250,28 @@ func TestSetAuth(t *testing.T) {
"cert": []byte("my-cert"), "cert": []byte("my-cert"),
"key": []byte("my-key"), "key": []byte("my-key"),
}, },
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foobar",
Namespace: "default",
},
Data: map[string][]byte{
"token": []byte("mytoken"),
},
}).Build(), }).Build(),
store: &esv1beta1.KubernetesProvider{ store: &esv1beta1.KubernetesProvider{
Server: esv1beta1.KubernetesServer{ Server: esv1beta1.KubernetesServer{
URL: "https://my.test.tld",
CABundle: []byte("1234"), CABundle: []byte("1234"),
}, },
Auth: esv1beta1.KubernetesAuth{ Auth: esv1beta1.KubernetesAuth{
Token: &esv1beta1.TokenAuth{
BearerToken: v1.SecretKeySelector{
Name: "foobar",
Namespace: pointer.To("shouldnotberelevant"),
Key: "token",
},
},
Cert: &esv1beta1.CertAuth{ Cert: &esv1beta1.CertAuth{
ClientCert: v1.SecretKeySelector{ ClientCert: v1.SecretKeySelector{
Name: "mycert", Name: "mycert",
@ -197,15 +285,52 @@ func TestSetAuth(t *testing.T) {
}, },
}, },
}, },
want: want{ want: &want{
CA: []byte("1234"), Host: "https://my.test.tld",
Certificate: []byte("my-cert"), BearerToken: "mytoken",
Key: []byte("my-key"), TLSClientConfig: rest.TLSClientConfig{
CAData: []byte("1234"),
CertData: []byte("my-cert"),
KeyData: []byte("my-key"),
},
}, },
wantErr: false, wantErr: false,
}, },
{ {
name: "should set token from service account", 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: &esv1beta1.KubernetesProvider{
Server: esv1beta1.KubernetesServer{
URL: "https://my.test.tld",
CABundle: []byte("1234"),
},
Auth: esv1beta1.KubernetesAuth{
ServiceAccount: &v1.ServiceAccountSelector{
Name: "my-sa",
Namespace: pointer.To("shouldnotberelevant"),
},
},
},
},
want: &want{
Host: "https://my.test.tld",
BearerToken: "my-sa-token",
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte("1234"),
},
},
wantErr: false,
},
{
name: "should fail with missing URL",
fields: fields{ fields: fields{
namespace: "default", namespace: "default",
kube: fclient.NewClientBuilder().WithObjects(&corev1.ServiceAccount{ kube: fclient.NewClientBuilder().WithObjects(&corev1.ServiceAccount{
@ -227,9 +352,36 @@ func TestSetAuth(t *testing.T) {
}, },
}, },
}, },
want: want{ want: nil,
CA: []byte("1234"), wantErr: true,
BearerToken: []byte("my-sa-token"), },
{
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: &esv1beta1.KubernetesProvider{
AuthRef: &v1.SecretKeySelector{
Name: "foobar",
Namespace: pointer.To("default"),
Key: "config",
},
},
},
want: &want{
Host: "https://api.my-domain.tld",
BearerToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE3MTkzOTY4OTksImV4cCI6MTc1MDkzMjg4NywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.xXrfIl0akhfjWU_BDl7Ad54SXje0YlJdnugzwh96VmM",
TLSClientConfig: rest.TLSClientConfig{
CAData: []byte(caCert),
},
}, },
wantErr: false, wantErr: false,
}, },
@ -243,17 +395,12 @@ func TestSetAuth(t *testing.T) {
namespace: tt.fields.namespace, namespace: tt.fields.namespace,
storeKind: tt.fields.storeKind, storeKind: tt.fields.storeKind,
} }
if err := k.setAuth(context.Background()); (err != nil) != tt.wantErr { cfg, err := k.getAuth(context.Background())
if (err != nil) != tt.wantErr {
t.Errorf("BaseClient.setAuth() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("BaseClient.setAuth() error = %v, wantErr %v", err, tt.wantErr)
} }
w := want{ if !cmp.Equal(cfg, tt.want) {
Certificate: k.Certificate, t.Errorf("unexpected value: expected %#v, got %#v", tt.want, cfg)
Key: k.Key,
CA: k.CA,
BearerToken: k.BearerToken,
}
if !cmp.Equal(w, tt.want) {
t.Errorf("unexpected value: expected %#v, got %#v", tt.want, w)
} }
}) })
} }

View file

@ -23,7 +23,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
kclient "sigs.k8s.io/controller-runtime/pkg/client" kclient "sigs.k8s.io/controller-runtime/pkg/client"
ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config" ctrlcfg "sigs.k8s.io/controller-runtime/pkg/client/config"
@ -74,10 +73,6 @@ type Client struct {
// namespace is the namespace of the // namespace is the namespace of the
// ExternalSecret referencing this provider. // ExternalSecret referencing this provider.
namespace string namespace string
Certificate []byte
Key []byte
CA []byte
BearerToken []byte
} }
func init() { func init() {
@ -123,22 +118,12 @@ func (p *Provider) newClient(ctx context.Context, store esv1beta1.GenericStore,
return client, nil return client, nil
} }
if err := client.setAuth(ctx); err != nil { cfg, err := client.getAuth(ctx)
return nil, err if err != nil {
return nil, fmt.Errorf("failed to prepare auth: %w", err)
} }
config := &rest.Config{ userClientset, err := kubernetes.NewForConfig(cfg)
Host: client.store.Server.URL,
BearerToken: string(client.BearerToken),
TLSClientConfig: rest.TLSClientConfig{
Insecure: false,
CertData: client.Certificate,
KeyData: client.Key,
CAData: client.CA,
},
}
userClientset, err := kubernetes.NewForConfig(config)
if err != nil { if err != nil {
return nil, fmt.Errorf("error configuring clientset: %w", err) return nil, fmt.Errorf("error configuring clientset: %w", err)
} }

View file

@ -51,6 +51,24 @@ mv+AggtK0aRFb9o47z/BypLdk5mhbf3Mmr88C8XBzEnfdYyf4JpTlZrYLBmDCu5d
9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC 9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC
QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
-----END CERTIFICATE-----` -----END CERTIFICATE-----`
testKubeConfig = `apiVersion: v1
clusters:
- cluster:
server: https://api.my-domain.tld
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
`
) )
func TestNewClient(t *testing.T) { func TestNewClient(t *testing.T) {
@ -88,6 +106,40 @@ func TestNewClient(t *testing.T) {
}, },
wantErr: true, wantErr: true,
}, },
{
name: "test auth ref",
fields: fields{},
args: args{
store: &esv1beta1.ClusterSecretStore{
TypeMeta: metav1.TypeMeta{
Kind: esv1beta1.ClusterSecretStoreKind,
},
Spec: esv1beta1.SecretStoreSpec{
Provider: &esv1beta1.SecretStoreProvider{
Kubernetes: &esv1beta1.KubernetesProvider{
AuthRef: &v1.SecretKeySelector{
Name: "foo",
Namespace: pointer.To("default"),
Key: "config",
},
},
},
},
},
namespace: "",
kube: fclient.NewClientBuilder().WithObjects(&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "default",
},
Data: map[string][]byte{
"config": []byte(testKubeConfig),
},
}).Build(),
clientset: clientgofake.NewSimpleClientset(),
},
want: true,
},
{ {
name: "test referent auth return", name: "test referent auth return",
fields: fields{}, fields: fields{},
@ -100,6 +152,7 @@ func TestNewClient(t *testing.T) {
Provider: &esv1beta1.SecretStoreProvider{ Provider: &esv1beta1.SecretStoreProvider{
Kubernetes: &esv1beta1.KubernetesProvider{ Kubernetes: &esv1beta1.KubernetesProvider{
Server: esv1beta1.KubernetesServer{ Server: esv1beta1.KubernetesServer{
URL: "https://my.test.tld",
CABundle: []byte(testCertificate), CABundle: []byte(testCertificate),
}, },
Auth: esv1beta1.KubernetesAuth{ Auth: esv1beta1.KubernetesAuth{
@ -132,6 +185,7 @@ func TestNewClient(t *testing.T) {
Provider: &esv1beta1.SecretStoreProvider{ Provider: &esv1beta1.SecretStoreProvider{
Kubernetes: &esv1beta1.KubernetesProvider{ Kubernetes: &esv1beta1.KubernetesProvider{
Server: esv1beta1.KubernetesServer{ Server: esv1beta1.KubernetesServer{
URL: "https://my.test.tld",
CABundle: []byte(testCertificate), CABundle: []byte(testCertificate),
}, },
RemoteNamespace: "remote", RemoteNamespace: "remote",
@ -166,6 +220,7 @@ func TestNewClient(t *testing.T) {
Provider: &esv1beta1.SecretStoreProvider{ Provider: &esv1beta1.SecretStoreProvider{
Kubernetes: &esv1beta1.KubernetesProvider{ Kubernetes: &esv1beta1.KubernetesProvider{
Server: esv1beta1.KubernetesServer{ Server: esv1beta1.KubernetesServer{
URL: "https://my.test.tld",
CABundle: []byte(testCertificate), CABundle: []byte(testCertificate),
}, },
RemoteNamespace: "remote", RemoteNamespace: "remote",

View file

@ -31,7 +31,7 @@ import (
func (p *Provider) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) { func (p *Provider) ValidateStore(store esv1beta1.GenericStore) (admission.Warnings, error) {
storeSpec := store.GetSpec() storeSpec := store.GetSpec()
k8sSpec := storeSpec.Provider.Kubernetes k8sSpec := storeSpec.Provider.Kubernetes
if k8sSpec.Server.CABundle == nil && k8sSpec.Server.CAProvider == nil { if k8sSpec.AuthRef == nil && k8sSpec.Server.CABundle == nil && k8sSpec.Server.CAProvider == nil {
return nil, fmt.Errorf("a CABundle or CAProvider is required") return nil, fmt.Errorf("a CABundle or CAProvider is required")
} }
if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind && if store.GetObjectKind().GroupVersionKind().Kind == esv1beta1.ClusterSecretStoreKind &&