Browse Source

fix(provider): configure TLS for secret server provider (#5558)

* fix(provider): initial tls fix

Signed-off-by: Olumide Ogundele <olumideralph@gmail.com>

* fix(provider): run make generate

Signed-off-by: Olumide Ogundele <olumideralph@gmail.com>

* fix(provider): write tests

Signed-off-by: Olumide Ogundele <olumideralph@gmail.com>

* fix(provider): update test crds

Signed-off-by: Olumide Ogundele <olumideralph@gmail.com>

---------

Signed-off-by: Olumide Ogundele <olumideralph@gmail.com>
Ogundele Olumide 5 months ago
parent
commit
d4d773494b

+ 10 - 1
apis/externalsecrets/v1/secretsstore_secretserver_types.go

@@ -34,7 +34,6 @@ type SecretServerProviderRef struct {
 // SecretServerProvider provides access to authenticate to a secrets provider server.
 // See: https://github.com/DelineaXPM/tss-sdk-go/blob/main/server/server.go.
 type SecretServerProvider struct {
-
 	// Username is the secret server account username.
 	// +required
 	Username *SecretServerProviderRef `json:"username"`
@@ -51,4 +50,14 @@ type SecretServerProvider struct {
 	// URL to your secret server installation
 	// +required
 	ServerURL string `json:"serverURL"`
+
+	// PEM/base64 encoded CA bundle used to validate Secret ServerURL. Only used
+	// if the ServerURL URL is using HTTPS protocol. If not set the system root certificates
+	// are used to validate the TLS connection.
+	// +optional
+	CABundle []byte `json:"caBundle,omitempty"`
+
+	// The provider for the CA bundle to use to validate Secret ServerURL certificate.
+	// +optional
+	CAProvider *CAProvider `json:"caProvider,omitempty"`
 }

+ 10 - 0
apis/externalsecrets/v1/zz_generated.deepcopy.go

@@ -3122,6 +3122,16 @@ func (in *SecretServerProvider) DeepCopyInto(out *SecretServerProvider) {
 		*out = new(SecretServerProviderRef)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.CABundle != nil {
+		in, out := &in.CABundle, &out.CABundle
+		*out = make([]byte, len(*in))
+		copy(*out, *in)
+	}
+	if in.CAProvider != nil {
+		in, out := &in.CAProvider, &out.CAProvider
+		*out = new(CAProvider)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretServerProvider.

+ 44 - 0
config/crds/bases/external-secrets.io_clustersecretstores.yaml

@@ -4176,6 +4176,50 @@ spec:
                       SecretServer configures this store to sync secrets using SecretServer provider
                       https://docs.delinea.com/online-help/secret-server/start.htm
                     properties:
+                      caBundle:
+                        description: |-
+                          PEM/base64 encoded CA bundle used to validate Secret ServerURL. Only used
+                          if the ServerURL URL is using HTTPS protocol. If not set the system root certificates
+                          are used to validate the TLS connection.
+                        format: byte
+                        type: string
+                      caProvider:
+                        description: The provider for the CA bundle to use to validate
+                          Secret ServerURL certificate.
+                        properties:
+                          key:
+                            description: The key where the CA certificate can be found
+                              in the Secret or ConfigMap.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace the Provider type is in.
+                              Can only be defined when used in a ClusterSecretStore.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - type
+                        type: object
                       domain:
                         description: Domain is the secret server domain.
                         type: string

+ 44 - 0
config/crds/bases/external-secrets.io_secretstores.yaml

@@ -4176,6 +4176,50 @@ spec:
                       SecretServer configures this store to sync secrets using SecretServer provider
                       https://docs.delinea.com/online-help/secret-server/start.htm
                     properties:
+                      caBundle:
+                        description: |-
+                          PEM/base64 encoded CA bundle used to validate Secret ServerURL. Only used
+                          if the ServerURL URL is using HTTPS protocol. If not set the system root certificates
+                          are used to validate the TLS connection.
+                        format: byte
+                        type: string
+                      caProvider:
+                        description: The provider for the CA bundle to use to validate
+                          Secret ServerURL certificate.
+                        properties:
+                          key:
+                            description: The key where the CA certificate can be found
+                              in the Secret or ConfigMap.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[-._a-zA-Z0-9]+$
+                            type: string
+                          name:
+                            description: The name of the object located at the provider
+                              type.
+                            maxLength: 253
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                            type: string
+                          namespace:
+                            description: |-
+                              The namespace the Provider type is in.
+                              Can only be defined when used in a ClusterSecretStore.
+                            maxLength: 63
+                            minLength: 1
+                            pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                            type: string
+                          type:
+                            description: The type of provider to use such as "Secret",
+                              or "ConfigMap".
+                            enum:
+                            - Secret
+                            - ConfigMap
+                            type: string
+                        required:
+                        - name
+                        - type
+                        type: object
                       domain:
                         description: Domain is the secret server domain.
                         type: string

+ 80 - 0
deploy/crds/bundle.yaml

@@ -5977,6 +5977,46 @@ spec:
                         SecretServer configures this store to sync secrets using SecretServer provider
                         https://docs.delinea.com/online-help/secret-server/start.htm
                       properties:
+                        caBundle:
+                          description: |-
+                            PEM/base64 encoded CA bundle used to validate Secret ServerURL. Only used
+                            if the ServerURL URL is using HTTPS protocol. If not set the system root certificates
+                            are used to validate the TLS connection.
+                          format: byte
+                          type: string
+                        caProvider:
+                          description: The provider for the CA bundle to use to validate Secret ServerURL certificate.
+                          properties:
+                            key:
+                              description: The key where the CA certificate can be found in the Secret or ConfigMap.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[-._a-zA-Z0-9]+$
+                              type: string
+                            name:
+                              description: The name of the object located at the provider type.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                              type: string
+                            namespace:
+                              description: |-
+                                The namespace the Provider type is in.
+                                Can only be defined when used in a ClusterSecretStore.
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                              type: string
+                            type:
+                              description: The type of provider to use such as "Secret", or "ConfigMap".
+                              enum:
+                                - Secret
+                                - ConfigMap
+                              type: string
+                          required:
+                            - name
+                            - type
+                          type: object
                         domain:
                           description: Domain is the secret server domain.
                           type: string
@@ -17293,6 +17333,46 @@ spec:
                         SecretServer configures this store to sync secrets using SecretServer provider
                         https://docs.delinea.com/online-help/secret-server/start.htm
                       properties:
+                        caBundle:
+                          description: |-
+                            PEM/base64 encoded CA bundle used to validate Secret ServerURL. Only used
+                            if the ServerURL URL is using HTTPS protocol. If not set the system root certificates
+                            are used to validate the TLS connection.
+                          format: byte
+                          type: string
+                        caProvider:
+                          description: The provider for the CA bundle to use to validate Secret ServerURL certificate.
+                          properties:
+                            key:
+                              description: The key where the CA certificate can be found in the Secret or ConfigMap.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[-._a-zA-Z0-9]+$
+                              type: string
+                            name:
+                              description: The name of the object located at the provider type.
+                              maxLength: 253
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
+                              type: string
+                            namespace:
+                              description: |-
+                                The namespace the Provider type is in.
+                                Can only be defined when used in a ClusterSecretStore.
+                              maxLength: 63
+                              minLength: 1
+                              pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
+                              type: string
+                            type:
+                              description: The type of provider to use such as "Secret", or "ConfigMap".
+                              enum:
+                                - Secret
+                                - ConfigMap
+                              type: string
+                          required:
+                            - name
+                            - type
+                          type: object
                         domain:
                           description: Domain is the secret server domain.
                           type: string

+ 29 - 0
docs/api/spec.md

@@ -1753,6 +1753,7 @@ string
 <a href="#external-secrets.io/v1.ConjurProvider">ConjurProvider</a>, 
 <a href="#external-secrets.io/v1.GitlabProvider">GitlabProvider</a>, 
 <a href="#external-secrets.io/v1.KubernetesServer">KubernetesServer</a>, 
+<a href="#external-secrets.io/v1.SecretServerProvider">SecretServerProvider</a>, 
 <a href="#external-secrets.io/v1.VaultProvider">VaultProvider</a>)
 </p>
 <p>
@@ -8471,6 +8472,34 @@ string
 URL to your secret server installation</p>
 </td>
 </tr>
+<tr>
+<td>
+<code>caBundle</code></br>
+<em>
+[]byte
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>PEM/base64 encoded CA bundle used to validate Secret ServerURL. Only used
+if the ServerURL URL is using HTTPS protocol. If not set the system root certificates
+are used to validate the TLS connection.</p>
+</td>
+</tr>
+<tr>
+<td>
+<code>caProvider</code></br>
+<em>
+<a href="#external-secrets.io/v1.CAProvider">
+CAProvider
+</a>
+</em>
+</td>
+<td>
+<em>(Optional)</em>
+<p>The provider for the CA bundle to use to validate Secret ServerURL certificate.</p>
+</td>
+</tr>
 </tbody>
 </table>
 <h3 id="external-secrets.io/v1.SecretServerProviderRef">SecretServerProviderRef

+ 29 - 2
providers/v1/secretserver/provider.go

@@ -18,6 +18,8 @@ package secretserver
 
 import (
 	"context"
+	"crypto/tls"
+	"crypto/x509"
 	"errors"
 
 	"github.com/DelineaXPM/tss-sdk-go/v3/server"
@@ -72,14 +74,39 @@ func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube
 		return nil, err
 	}
 
-	secretServer, err := server.New(server.Configuration{
+	ssConfig := server.Configuration{
 		Credentials: server.UserCredential{
 			Username: username,
 			Password: password,
 			Domain:   cfg.Domain,
 		},
 		ServerURL: cfg.ServerURL,
-	})
+	}
+
+	if len(cfg.CABundle) > 0 || cfg.CAProvider != nil {
+		cert, err := esutils.FetchCACertFromSource(ctx, esutils.CreateCertOpts{
+			StoreKind:  store.GetKind(),
+			Client:     kube,
+			Namespace:  namespace,
+			CABundle:   cfg.CABundle,
+			CAProvider: cfg.CAProvider,
+		})
+		if err != nil {
+			return nil, err
+		}
+
+		caCertPool := x509.NewCertPool()
+		if !caCertPool.AppendCertsFromPEM(cert) {
+			return nil, errors.New("failed to append caBundle")
+		}
+
+		ssConfig.TLSClientConfig = &tls.Config{
+			RootCAs:    caCertPool,
+			MinVersion: tls.VersionTLS12,
+		}
+	}
+
+	secretServer, err := server.New(ssConfig)
 	if err != nil {
 		return nil, err
 	}

+ 158 - 0
providers/v1/secretserver/provider_test.go

@@ -202,6 +202,45 @@ func TestNewClient(t *testing.T) {
 		ServerURL: "https://example.com",
 	}
 
+	// Valid test CA certificate
+	testCABundle := []byte(`-----BEGIN CERTIFICATE-----
+MIIDHTCCAgWgAwIBAgIRAKC4yxy9QGocND+6avTf7BgwDQYJKoZIhvcNAQELBQAw
+EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yMTAzMjAyMDA4MDhaFw0yMTAzMjAyMDM4
+MDhaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+ggEKAoIBAQC3o6/JdZEqNbqNRkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4Nzj
+aG15owr92/11W0pxPUliRLti3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvW
+Y8jh8A0LQALZiV/9QsrJdXZdS47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE
+1gEDqnKfRxXI8DEQKXr+CKPUwCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4e
+ugHe52vXHdh/HJ9VjNp0xOH1waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJa
+YOOonQSEswveSv6PcG9AHvpNPot2Xs6hAgMBAAGjbjBsMA4GA1UdDwEB/wQEAwIC
+pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBR00805mrpoonp95RmC3B6oLl+cGTAVBgNVHREEDjAMggpnb29ibGUuY29tMA0G
+CSqGSIb3DQEBCwUAA4IBAQAipc1b6JrEDayPjpz5GM5krcI8dCWVd8re0a9bGjjN
+ioWGlu/eTr5El0ffwCNZ2WLmL9rewfHf/bMvYz3ioFZJ2OTxfazqYXNggQz6cMfa
+lbedDCdt5XLVX2TyerGvFram+9Uyvk3l0uM7rZnwAmdirG4Tv94QRaD3q4xTj/c0
+mv+AggtK0aRFb9o47z/BypLdk5mhbf3Mmr88C8XBzEnfdYyf4JpTlZrYLBmDCu5d
+9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC
+QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
+-----END CERTIFICATE-----`)
+
+	caSecretName := "ca-secret"
+	caSecretKey := "ca.crt"
+	caSecret := &corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{Name: caSecretName, Namespace: "default"},
+		Data: map[string][]byte{
+			caSecretKey: testCABundle,
+		},
+	}
+
+	caConfigMapName := "ca-configmap"
+	caConfigMapKey := "ca.crt"
+	caConfigMap := &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{Name: caConfigMapName, Namespace: "default"},
+		Data: map[string]string{
+			caConfigMapKey: string(testCABundle),
+		},
+	}
+
 	tests := map[string]struct {
 		store    esv1.GenericStore          // leave nil for namespaced store
 		provider *esv1.SecretServerProvider // discarded when store is set
@@ -325,6 +364,125 @@ func TestNewClient(t *testing.T) {
 			},
 			kube: clientfake.NewClientBuilder().WithObjects(clientSecret, clientSecretWithDomain).Build(),
 		},
+		"valid with CABundle and CAProvider using Secret": {
+			provider: &esv1.SecretServerProvider{
+				Username:  validProvider.Username,
+				Password:  validProvider.Password,
+				ServerURL: validProvider.ServerURL,
+				CABundle:  testCABundle,
+				CAProvider: &esv1.CAProvider{
+					Type: esv1.CAProviderTypeSecret,
+					Name: caSecretName,
+					Key:  caSecretKey,
+				},
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret, caSecret).Build(),
+		},
+		"valid with CABundle and CAProvider using ConfigMap": {
+			provider: &esv1.SecretServerProvider{
+				Username:  validProvider.Username,
+				Password:  validProvider.Password,
+				ServerURL: validProvider.ServerURL,
+				CABundle:  testCABundle,
+				CAProvider: &esv1.CAProvider{
+					Type: esv1.CAProviderTypeConfigMap,
+					Name: caConfigMapName,
+					Key:  caConfigMapKey,
+				},
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret, caConfigMap).Build(),
+		},
+		"CABundle without CAProvider is ignored": {
+			provider: &esv1.SecretServerProvider{
+				Username:  validProvider.Username,
+				Password:  validProvider.Password,
+				ServerURL: validProvider.ServerURL,
+				CABundle:  testCABundle,
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+		},
+		"CAProvider without CABundle is ignored": {
+			provider: &esv1.SecretServerProvider{
+				Username:  validProvider.Username,
+				Password:  validProvider.Password,
+				ServerURL: validProvider.ServerURL,
+				CAProvider: &esv1.CAProvider{
+					Type: esv1.CAProviderTypeSecret,
+					Name: caSecretName,
+					Key:  caSecretKey,
+				},
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret, caSecret).Build(),
+		},
+		"invalid CABundle format with CAProvider": {
+			provider: &esv1.SecretServerProvider{
+				Username:  validProvider.Username,
+				Password:  validProvider.Password,
+				ServerURL: validProvider.ServerURL,
+				CABundle:  []byte("invalid certificate data"),
+				CAProvider: &esv1.CAProvider{
+					Type: esv1.CAProviderTypeSecret,
+					Name: caSecretName,
+					Key:  caSecretKey,
+				},
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret, caSecret).Build(),
+			errCheck: func(t *testing.T, err error) {
+				assert.Error(t, err)
+				assert.Contains(t, err.Error(), "failed to decode ca bundle")
+			},
+		},
+		"missing CAProvider Secret with valid CABundle": {
+			provider: &esv1.SecretServerProvider{
+				Username:  validProvider.Username,
+				Password:  validProvider.Password,
+				ServerURL: validProvider.ServerURL,
+				CABundle:  testCABundle,
+				CAProvider: &esv1.CAProvider{
+					Type: esv1.CAProviderTypeSecret,
+					Name: "non-existent-secret",
+					Key:  caSecretKey,
+				},
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret).Build(),
+			// CABundle takes precedence, so even if the secret doesn't exist, CABundle is used
+		},
+		"only CAProvider without CABundle is ignored": {
+			provider: &esv1.SecretServerProvider{
+				Username:  validProvider.Username,
+				Password:  validProvider.Password,
+				ServerURL: validProvider.ServerURL,
+				CAProvider: &esv1.CAProvider{
+					Type: esv1.CAProviderTypeSecret,
+					Name: caSecretName,
+					Key:  caSecretKey,
+				},
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret, caSecret).Build(),
+			// No error expected because both CABundle AND CAProvider must be set for TLS config
+		},
+		"cluster secret store with CABundle and CAProvider": {
+			store: &esv1.ClusterSecretStore{
+				TypeMeta: metav1.TypeMeta{Kind: esv1.ClusterSecretStoreKind},
+				Spec: esv1.SecretStoreSpec{
+					Provider: &esv1.SecretStoreProvider{
+						SecretServer: &esv1.SecretServerProvider{
+							Username:  makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, userNameKey),
+							Password:  makeSecretRefUsingNamespacedRef(clientSecret.Namespace, clientSecret.Name, passwordKey),
+							ServerURL: validProvider.ServerURL,
+							CABundle:  testCABundle,
+							CAProvider: &esv1.CAProvider{
+								Type:      esv1.CAProviderTypeSecret,
+								Name:      caSecretName,
+								Key:       caSecretKey,
+								Namespace: esutils.Ptr("default"),
+							},
+						},
+					},
+				},
+			},
+			kube: clientfake.NewClientBuilder().WithObjects(clientSecret, caSecret).Build(),
+		},
 	}
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {

+ 6 - 0
tests/__snapshot__/clustersecretstore-v1.yaml

@@ -633,6 +633,12 @@ spec:
           namespace: string
         value: string
     secretserver:
+      caBundle: c3RyaW5n
+      caProvider:
+        key: string
+        name: string
+        namespace: string
+        type: "Secret" # "Secret", "ConfigMap"
       domain: string
       password:
         secretRef:

+ 6 - 0
tests/__snapshot__/secretstore-v1.yaml

@@ -633,6 +633,12 @@ spec:
           namespace: string
         value: string
     secretserver:
+      caBundle: c3RyaW5n
+      caProvider:
+        key: string
+        name: string
+        namespace: string
+        type: "Secret" # "Secret", "ConfigMap"
       domain: string
       password:
         secretRef: