Browse Source

fix: CAProvider cm access (#6246)

* fix: properly scope namespaced ConfigMap access

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>

* fix: reject cross-namespace caProvider refs

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>

---------

Signed-off-by: Moritz Johner <beller.moritz@googlemail.com>
Moritz Johner 1 month ago
parent
commit
9206e26358
2 changed files with 88 additions and 3 deletions
  1. 10 3
      runtime/esutils/utils.go
  2. 78 0
      runtime/esutils/utils_test.go

+ 10 - 3
runtime/esutils/utils.go

@@ -683,6 +683,13 @@ func FetchCACertFromSource(ctx context.Context, opts CreateCertOpts) ([]byte, er
 	}
 
 	if opts.CAProvider != nil &&
+		opts.StoreKind != esv1.ClusterSecretStoreKind &&
+		opts.CAProvider.Namespace != nil &&
+		*opts.CAProvider.Namespace != opts.Namespace {
+		return nil, errNamespaceNotAllowed
+	}
+
+	if opts.CAProvider != nil &&
 		opts.StoreKind == esv1.ClusterSecretStoreKind &&
 		opts.CAProvider.Namespace == nil {
 		return nil, errors.New("missing namespace on caProvider secret")
@@ -697,7 +704,7 @@ func FetchCACertFromSource(ctx context.Context, opts CreateCertOpts) ([]byte, er
 
 		return cert, nil
 	case esv1.CAProviderTypeConfigMap:
-		cert, err := getCertFromConfigMap(ctx, opts.Namespace, opts.Client, opts.CAProvider)
+		cert, err := getCertFromConfigMap(ctx, opts.Namespace, opts.Client, opts.CAProvider, opts.StoreKind)
 		if err != nil {
 			return nil, fmt.Errorf("failed to get cert from configmap: %w", err)
 		}
@@ -810,13 +817,13 @@ func getCertFromSecret(ctx context.Context, c client.Client, provider *esv1.CAPr
 	return []byte(cert), nil
 }
 
-func getCertFromConfigMap(ctx context.Context, namespace string, c client.Client, provider *esv1.CAProvider) ([]byte, error) {
+func getCertFromConfigMap(ctx context.Context, namespace string, c client.Client, provider *esv1.CAProvider, storeKind string) ([]byte, error) {
 	objKey := client.ObjectKey{
 		Name:      provider.Name,
 		Namespace: namespace,
 	}
 
-	if provider.Namespace != nil {
+	if provider.Namespace != nil && storeKind == esv1.ClusterSecretStoreKind {
 		objKey.Namespace = *provider.Namespace
 	}
 

+ 78 - 0
runtime/esutils/utils_test.go

@@ -17,6 +17,7 @@ limitations under the License.
 package esutils
 
 import (
+	"context"
 	"encoding/json"
 	"errors"
 	"reflect"
@@ -29,6 +30,7 @@ import (
 	v1 "k8s.io/api/core/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
@@ -1437,6 +1439,82 @@ func TestValidateReferentServiceAccountSelector(t *testing.T) {
 	}
 }
 
+func TestFetchCACertFromSourceRejectsCrossNamespaceCAProviderConfigMapForSecretStore(t *testing.T) {
+	fakeClient := clientfake.NewClientBuilder().WithObjects(
+		&v1.ConfigMap{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ca-cm",
+				Namespace: "default",
+			},
+			Data: map[string]string{
+				"ca.crt": "local-cert",
+			},
+		},
+		&v1.ConfigMap{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ca-cm",
+				Namespace: "other",
+			},
+			Data: map[string]string{
+				"ca.crt": "other-cert",
+			},
+		},
+	).Build()
+
+	cert, err := FetchCACertFromSource(context.Background(), CreateCertOpts{
+		CAProvider: &esv1.CAProvider{
+			Type:      esv1.CAProviderTypeConfigMap,
+			Name:      "ca-cm",
+			Key:       "ca.crt",
+			Namespace: Ptr("other"),
+		},
+		StoreKind: esv1.SecretStoreKind,
+		Namespace: "default",
+		Client:    fakeClient,
+	})
+
+	assert.ErrorIs(t, err, errNamespaceNotAllowed)
+	assert.Nil(t, cert)
+}
+
+func TestFetchCACertFromSourceRejectsCrossNamespaceCAProviderSecretForSecretStore(t *testing.T) {
+	fakeClient := clientfake.NewClientBuilder().WithObjects(
+		&v1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ca-secret",
+				Namespace: "default",
+			},
+			Data: map[string][]byte{
+				"tls.crt": []byte("local-cert"),
+			},
+		},
+		&v1.Secret{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "ca-secret",
+				Namespace: "other",
+			},
+			Data: map[string][]byte{
+				"tls.crt": []byte("other-cert"),
+			},
+		},
+	).Build()
+
+	cert, err := FetchCACertFromSource(context.Background(), CreateCertOpts{
+		CAProvider: &esv1.CAProvider{
+			Type:      esv1.CAProviderTypeSecret,
+			Name:      "ca-secret",
+			Key:       "tls.crt",
+			Namespace: Ptr("other"),
+		},
+		StoreKind: esv1.SecretStoreKind,
+		Namespace: "default",
+		Client:    fakeClient,
+	})
+
+	assert.ErrorIs(t, err, errNamespaceNotAllowed)
+	assert.Nil(t, cert)
+}
+
 const mockJWTToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZXhwIjoxNzAwMDAwMDAwfQ.signature"
 
 func TestParseJWTClaims(t *testing.T) {