Browse Source

wip: referent cluster stores for gcp

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
Gustavo Carvalho 3 years ago
parent
commit
7b3bdc68a9

+ 27 - 0
e2e/suites/provider/cases/gcp/gcp.go

@@ -29,12 +29,34 @@ import (
 	"github.com/external-secrets/external-secrets/e2e/suites/provider/cases/common"
 )
 
+const (
+	withReferent = "using referent Cluster Secret Store"
+)
+
 // This test uses the global ESO.
 var _ = Describe("[gcp]", Label("gcp", "secretsmanager"), func() {
 	f := framework.New("eso-gcp")
 	prov := NewFromEnv(f, "")
 
 	DescribeTable("sync secrets", framework.TableFunc(f, prov),
+		// using referent cluster secret store
+		framework.Compose(withReferent, f, common.SimpleDataSync, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.JSONDataWithProperty, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.JSONDataFromSync, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.NestedJSONWithGJSON, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.JSONDataWithTemplate, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.DockerJSONConfig, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.DataPropertyDockerconfigJSON, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.SSHKeySync, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.SSHKeySyncDataProperty, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.SyncWithoutTargetName, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.JSONDataWithoutTargetName, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.FindByName, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.FindByNameWithPath, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.FindByTag, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.FindByTagWithPath, useReferentKeyStore),
+		framework.Compose(withReferent, f, common.SyncV1Alpha1, useReferentKeyStore),
+		// using simple secret store
 		Entry(common.SimpleDataSync(f)),
 		Entry(common.JSONDataWithProperty(f)),
 		Entry(common.JSONDataFromSync(f)),
@@ -55,6 +77,11 @@ var _ = Describe("[gcp]", Label("gcp", "secretsmanager"), func() {
 	)
 })
 
+func useReferentKeyStore(f *framework.TestCase) {
+	f.ExternalSecret.Spec.SecretStoreRef.Kind = "ClusterSecretStore"
+	f.ExternalSecret.Spec.SecretStoreRef.Name = fmt.Sprintf("referent-%v", f.ExternalSecret.Namespace)
+}
+
 // P12Cert case creates a secret with a p12 cert containing a privkey and cert bundled together.
 // It uses templating to generate a k8s secret of type tls with pem values.
 var p12Cert = func(tc *framework.TestCase) {

+ 44 - 0
e2e/suites/provider/cases/gcp/provider.go

@@ -72,6 +72,7 @@ func NewGCPProvider(f *framework.Framework, credentials, projectID string,
 
 	BeforeEach(func() {
 		prov.CreateSAKeyStore(f.Namespace.Name)
+		prov.CreateReferentKeyStore()
 		prov.CreateSpecifcSASecretStore(f.Namespace.Name)
 		prov.CreatePodIDStore(f.Namespace.Name)
 	})
@@ -163,6 +164,21 @@ func makeStore(s *GcpProvider) *esv1beta1.SecretStore {
 	}
 }
 
+func makeClusterStore(s *GcpProvider) *esv1beta1.ClusterSecretStore {
+	return &esv1beta1.ClusterSecretStore{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: fmt.Sprintf("referent-%v", s.framework.Namespace.Name),
+		},
+		Spec: esv1beta1.SecretStoreSpec{
+			Controller: s.controllerClass,
+			Provider: &esv1beta1.SecretStoreProvider{
+				GCPSM: &esv1beta1.GCPSMProvider{
+					ProjectID: s.projectID,
+				},
+			},
+		},
+	}
+}
 func (s *GcpProvider) CreateSAKeyStore(ns string) {
 	gcpCreds := &v1.Secret{
 		ObjectMeta: metav1.ObjectMeta{
@@ -191,6 +207,34 @@ func (s *GcpProvider) CreateSAKeyStore(ns string) {
 	Expect(err).ToNot(HaveOccurred())
 }
 
+func (s *GcpProvider) CreateReferentKeyStore() {
+	gcpCreds := &v1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      staticCredentialsSecretName,
+			Namespace: s.framework.Namespace.Name,
+		},
+		StringData: map[string]string{
+			"secret-access-credentials": s.credentials,
+		},
+	}
+	err := s.framework.CRClient.Create(context.Background(), gcpCreds)
+	if err != nil {
+		err = s.framework.CRClient.Update(context.Background(), gcpCreds)
+		Expect(err).ToNot(HaveOccurred())
+	}
+	secretStore := makeClusterStore(s)
+	secretStore.Spec.Provider.GCPSM.Auth = esv1beta1.GCPSMAuth{
+		SecretRef: &esv1beta1.GCPSMAuthSecretRef{
+			SecretAccessKey: esmeta.SecretKeySelector{
+				Name: staticCredentialsSecretName,
+				Key:  "secret-access-credentials",
+			},
+		},
+	}
+	err = s.framework.CRClient.Create(context.Background(), secretStore)
+	Expect(err).ToNot(HaveOccurred())
+}
+
 func (s *GcpProvider) CreatePodIDStore(ns string) {
 	secretStore := makeStore(s)
 	secretStore.ObjectMeta.Name = PodIDSecretStoreName

+ 31 - 22
pkg/provider/gcp/secretmanager/secretsmanager.go

@@ -41,21 +41,19 @@ import (
 )
 
 const (
-	CloudPlatformRole                         = "https://www.googleapis.com/auth/cloud-platform"
-	defaultVersion                            = "latest"
-	errGCPSMStore                             = "received invalid GCPSM SecretStore resource"
-	errUnableGetCredentials                   = "unable to get credentials: %w"
-	errClientClose                            = "unable to close SecretManager client: %w"
-	errMissingStoreSpec                       = "invalid: missing store spec"
-	errInvalidClusterStoreMissingSAKNamespace = "invalid ClusterSecretStore: missing GCP SecretAccessKey Namespace"
-	errInvalidClusterStoreMissingSANamespace  = "invalid ClusterSecretStore: missing GCP Service Account Namespace"
-	errFetchSAKSecret                         = "could not fetch SecretAccessKey secret: %w"
-	errMissingSAK                             = "missing SecretAccessKey"
-	errUnableProcessJSONCredentials           = "failed to process the provided JSON credentials: %w"
-	errUnableCreateGCPSMClient                = "failed to create GCP secretmanager client: %w"
-	errUninitalizedGCPProvider                = "provider GCP is not initialized"
-	errClientGetSecretAccess                  = "unable to access Secret from SecretManager Client: %w"
-	errJSONSecretUnmarshal                    = "unable to unmarshal secret: %w"
+	CloudPlatformRole               = "https://www.googleapis.com/auth/cloud-platform"
+	defaultVersion                  = "latest"
+	errGCPSMStore                   = "received invalid GCPSM SecretStore resource"
+	errUnableGetCredentials         = "unable to get credentials: %w"
+	errClientClose                  = "unable to close SecretManager client: %w"
+	errMissingStoreSpec             = "invalid: missing store spec"
+	errFetchSAKSecret               = "could not fetch SecretAccessKey secret: %w"
+	errMissingSAK                   = "missing SecretAccessKey"
+	errUnableProcessJSONCredentials = "failed to process the provided JSON credentials: %w"
+	errUnableCreateGCPSMClient      = "failed to create GCP secretmanager client: %w"
+	errUninitalizedGCPProvider      = "provider GCP is not initialized"
+	errClientGetSecretAccess        = "unable to access Secret from SecretManager Client: %w"
+	errJSONSecretUnmarshal          = "unable to unmarshal secret: %w"
 
 	errInvalidStore           = "invalid store"
 	errInvalidStoreSpec       = "invalid store spec"
@@ -137,11 +135,9 @@ func serviceAccountTokenSource(ctx context.Context, store esv1beta1.GenericStore
 		Namespace: namespace,
 	}
 
-	// only ClusterStore is allowed to set namespace (and then it's required)
+	// only ClusterStore is allowed to set namespace. If none provided, use the referent.
 	if storeKind == esv1beta1.ClusterSecretStoreKind {
-		if credentialsSecretName != "" && sr.SecretAccessKey.Namespace == nil {
-			return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
-		} else if credentialsSecretName != "" {
+		if sr.SecretAccessKey.Namespace != nil {
 			objectKey.Namespace = *sr.SecretAccessKey.Namespace
 		}
 	}
@@ -191,7 +187,10 @@ func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1beta1.GenericSto
 	}()
 
 	sm.projectID = cliStore.store.ProjectID
-
+	// if reconciling a ClusterSecretStore with referent configuration, don't generate credentials.
+	if sm.gClient.storeKind == esv1beta1.ClusterSecretStoreKind && isReferent(sm.gClient.store) && namespace == "" {
+		return sm, nil
+	}
 	ts, err := cliStore.getTokenSource(ctx, store, kube, namespace)
 	if err != nil {
 		useMu.Unlock()
@@ -443,12 +442,12 @@ func (sm *ProviderGCP) ValidateStore(store esv1beta1.GenericStore) error {
 		return fmt.Errorf(errInvalidGCPProv)
 	}
 	if p.Auth.SecretRef != nil {
-		if err := utils.ValidateSecretSelector(store, p.Auth.SecretRef.SecretAccessKey); err != nil {
+		if err := utils.ValidateReferentSecretSelector(store, p.Auth.SecretRef.SecretAccessKey); err != nil {
 			return fmt.Errorf(errInvalidAuthSecretRef, err)
 		}
 	}
 	if p.Auth.WorkloadIdentity != nil {
-		if err := utils.ValidateServiceAccountSelector(store, p.Auth.WorkloadIdentity.ServiceAccountRef); err != nil {
+		if err := utils.ValidateReferentServiceAccountSelector(store, p.Auth.WorkloadIdentity.ServiceAccountRef); err != nil {
 			return fmt.Errorf(errInvalidWISARef, err)
 		}
 	}
@@ -460,3 +459,13 @@ func init() {
 		GCPSM: &esv1beta1.GCPSMProvider{},
 	})
 }
+
+func isReferent(spec *esv1beta1.GCPSMProvider) bool {
+	if spec.Auth.SecretRef != nil && spec.Auth.SecretRef.SecretAccessKey.Namespace == nil {
+		return true
+	}
+	if spec.Auth.WorkloadIdentity != nil && spec.Auth.WorkloadIdentity.ServiceAccountRef.Namespace == nil {
+		return true
+	}
+	return false
+}

+ 3 - 4
pkg/provider/gcp/secretmanager/secretsmanager_workload_identity.go

@@ -107,12 +107,11 @@ func (w *workloadIdentity) TokenSource(ctx context.Context, store esv1beta1.Gene
 		Namespace: namespace,
 	}
 
-	// only ClusterStore is allowed to set namespace (and then it's required)
+	// only ClusterStore is allowed to set namespace. If not provided, use the referent.
 	if storeKind == esv1beta1.ClusterSecretStoreKind {
-		if wi.ServiceAccountRef.Namespace == nil {
-			return nil, fmt.Errorf(errInvalidClusterStoreMissingSANamespace)
+		if wi.ServiceAccountRef.Namespace != nil {
+			saKey.Namespace = *wi.ServiceAccountRef.Namespace
 		}
-		saKey.Namespace = *wi.ServiceAccountRef.Namespace
 	}
 
 	clusterProjectID, err := clusterProjectID(spec)

+ 3 - 8
pkg/provider/gcp/secretmanager/secretsmanager_workload_identity_test.go

@@ -88,8 +88,9 @@ func TestWorkloadIdentity(t *testing.T) {
 			}),
 		),
 		composeTestcase(
-			defaultTestCase("invalid ClusterSecretStore: missing service account namespace"),
-			expErr("invalid ClusterSecretStore: missing GCP Service Account Namespace"),
+			defaultTestCase("Referent ClusterSecretStore: missing service account namespace points to default bind"),
+			expTokenSource(),
+			expectToken(defaultIDBindToken),
 			withStore(
 				composeStore(defaultClusterStore()),
 			),
@@ -239,12 +240,6 @@ func expectToken(token string) testCaseMutator {
 	}
 }
 
-func expErr(err string) testCaseMutator {
-	return func(tc *workloadIdentityTest) {
-		tc.expErr = err
-	}
-}
-
 func withK8sResources(objs []client.Object) testCaseMutator {
 	return func(tc *workloadIdentityTest) {
 		tc.kubeObjects = objs