Browse Source

Refactor gcp secretmanager

* Create store client struct
* Add authentication method
* Use error handling constants
* Add GetSecretMap functionality
Gabi 4 years ago
parent
commit
bf0e5c4c52
1 changed files with 96 additions and 33 deletions
  1. 96 33
      pkg/provider/gcp/secretmanager/secretsmanager.go

+ 96 - 33
pkg/provider/gcp/secretmanager/secretsmanager.go

@@ -15,8 +15,8 @@ package secretmanager
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
-	"log"
 
 	secretmanager "cloud.google.com/go/secretmanager/apiv1"
 	"github.com/googleapis/gax-go"
@@ -25,7 +25,7 @@ import (
 	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/client"
+	kclient "sigs.k8s.io/controller-runtime/pkg/client"
 
 	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
 	"github.com/external-secrets/external-secrets/pkg/provider"
@@ -35,6 +35,18 @@ import (
 const (
 	cloudPlatformRole = "https://www.googleapis.com/auth/cloud-platform"
 	defaultVersion    = "latest"
+
+	errGCPSMStore                             = "received invalid GCPSM SecretStore resource"
+	errGCPSMCredSecretName                    = "invalid GCPSM SecretStore resource: missing GCP Secret Access Key"
+	errInvalidClusterStoreMissingSAKNamespace = "invalid ClusterSecretStore: missing GCP SecretAccessKey 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"
+	errClientClose                            = "unable to close SecretManager client: %w"
+	errJSONSecretUnmarshal                    = "unable to unmarshal secret: %w"
 )
 
 type GoogleSecretManagerClient interface {
@@ -48,47 +60,84 @@ type ProviderGCP struct {
 	SecretManagerClient GoogleSecretManagerClient
 }
 
-// NewClient constructs a GCP Provider.
-func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
-	// Fetch credential Secret
+type gClient struct {
+	kube        kclient.Client
+	store       *esv1alpha1.GCPSMProvider
+	namespace   string
+	storeKind   string
+	credentials []byte
+}
+
+func (c *gClient) setAuth(ctx context.Context) error {
 	credentialsSecret := &corev1.Secret{}
-	credentialsSecretName := store.GetSpec().Provider.GCPSM.Auth.SecretRef.SecretAccessKey.Name
-	objectKey := types.NamespacedName{Name: credentialsSecretName, Namespace: store.GetNamespace()}
-	err := kube.Get(ctx, objectKey, credentialsSecret)
+	credentialsSecretName := c.store.Auth.SecretRef.SecretAccessKey.Name
+	if credentialsSecretName == "" {
+		return fmt.Errorf(errGCPSMCredSecretName)
+	}
+	objectKey := types.NamespacedName{
+		Name:      credentialsSecretName,
+		Namespace: c.namespace,
+	}
+
+	// only ClusterStore is allowed to set namespace (and then it's required)
+	if c.storeKind == esv1alpha1.ClusterSecretStoreKind {
+		if c.store.Auth.SecretRef.SecretAccessKey.Namespace == nil {
+			return fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
+		}
+		objectKey.Namespace = *c.store.Auth.SecretRef.SecretAccessKey.Namespace
+	}
+
+	err := c.kube.Get(ctx, objectKey, credentialsSecret)
 	if err != nil {
-		log.Panicf(err.Error(), "Failed to get credentials Secret")
-		return nil, err
+		return fmt.Errorf(errFetchSAKSecret, err)
 	}
 
-	credentials := credentialsSecret.Data[store.GetSpec().Provider.GCPSM.Auth.SecretRef.SecretAccessKey.Key]
-	if len(credentials) == 0 {
-		return nil, fmt.Errorf("credentials GCP invalid/not provided")
+	c.credentials = credentialsSecret.Data[c.store.Auth.SecretRef.SecretAccessKey.Key]
+	if (c.credentials == nil) || (len(c.credentials) == 0) {
+		return fmt.Errorf(errMissingSAK)
 	}
+	return nil
+}
 
-	projectID := store.GetSpec().Provider.GCPSM.ProjectID
+// NewClient constructs a GCP Provider.
+func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
+	storeSpec := store.GetSpec()
+	if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
+		return nil, fmt.Errorf(errGCPSMStore)
+	}
+	storeSpecGCPSM := storeSpec.Provider.GCPSM
 
-	sm.projectID = projectID
+	cliStore := gClient{
+		kube:      kube,
+		store:     storeSpecGCPSM,
+		namespace: namespace,
+		storeKind: store.GetObjectKind().GroupVersionKind().Kind,
+	}
 
-	config, err := google.JWTConfigFromJSON(credentials, cloudPlatformRole)
-	if err != nil {
-		log.Panicf(err.Error(), "Failed to process the provided JSON credentials")
+	if err := cliStore.setAuth(ctx); err != nil {
 		return nil, err
 	}
 
+	sm.projectID = cliStore.store.ProjectID
+
+	config, err := google.JWTConfigFromJSON(cliStore.credentials, cloudPlatformRole)
+	if err != nil {
+		return nil, fmt.Errorf(errUnableProcessJSONCredentials, err)
+	}
 	ts := config.TokenSource(ctx)
 
-	client, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
+	clientGCPSM, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
 	if err != nil {
-		return nil, fmt.Errorf("failed to create GCP secretmanager client: %w", err)
+		return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
 	}
-	sm.SecretManagerClient = client
+	sm.SecretManagerClient = clientGCPSM
 	return sm, nil
 }
 
 // GetSecret returns a single secret from the provider.
 func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
 	if sm.SecretManagerClient == nil || sm.projectID == "" {
-		return nil, fmt.Errorf("provider GCP is not initialized")
+		return nil, fmt.Errorf(errUninitalizedGCPProvider)
 	}
 
 	version := ref.Version
@@ -96,31 +145,45 @@ func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSec
 		version = defaultVersion
 	}
 
-	resourceName := fmt.Sprintf("projects/%s/secrets/%s/versions/%s", sm.projectID, ref.Key, version)
-
 	req := &secretmanagerpb.AccessSecretVersionRequest{
-		Name: resourceName,
+		Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", sm.projectID, ref.Key, version),
 	}
-
 	result, err := sm.SecretManagerClient.AccessSecretVersion(ctx, req)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf(errClientGetSecretAccess, err)
 	}
+
 	err = sm.SecretManagerClient.Close()
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf(errClientClose, err)
 	}
-	return []byte(string(result.Payload.Data)), nil
+
+	return result.Payload.Data, nil
 }
 
 // GetSecretMap returns multiple k/v pairs from the provider.
 func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
 	if sm.SecretManagerClient == nil || sm.projectID == "" {
-		return nil, fmt.Errorf("provider GCP is not initialized")
+		return nil, fmt.Errorf(errUninitalizedGCPProvider)
 	}
-	return map[string][]byte{
-		"noop": []byte("NOOP"),
-	}, nil
+
+	data, err := sm.GetSecret(ctx, ref)
+	if err != nil {
+		return nil, err
+	}
+
+	kv := make(map[string]string)
+	err = json.Unmarshal(data, &kv)
+	if err != nil {
+		return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
+	}
+
+	secretData := make(map[string][]byte)
+	for k, v := range kv {
+		secretData[k] = []byte(v)
+	}
+
+	return secretData, nil
 }
 
 func init() {