|
@@ -15,8 +15,8 @@ package secretmanager
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
"context"
|
|
"context"
|
|
|
|
|
+ "encoding/json"
|
|
|
"fmt"
|
|
"fmt"
|
|
|
- "log"
|
|
|
|
|
|
|
|
|
|
secretmanager "cloud.google.com/go/secretmanager/apiv1"
|
|
secretmanager "cloud.google.com/go/secretmanager/apiv1"
|
|
|
"github.com/googleapis/gax-go"
|
|
"github.com/googleapis/gax-go"
|
|
@@ -25,7 +25,7 @@ import (
|
|
|
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
|
|
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
|
|
|
corev1 "k8s.io/api/core/v1"
|
|
corev1 "k8s.io/api/core/v1"
|
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"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"
|
|
esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
|
|
|
"github.com/external-secrets/external-secrets/pkg/provider"
|
|
"github.com/external-secrets/external-secrets/pkg/provider"
|
|
@@ -35,6 +35,18 @@ import (
|
|
|
const (
|
|
const (
|
|
|
cloudPlatformRole = "https://www.googleapis.com/auth/cloud-platform"
|
|
cloudPlatformRole = "https://www.googleapis.com/auth/cloud-platform"
|
|
|
defaultVersion = "latest"
|
|
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 {
|
|
type GoogleSecretManagerClient interface {
|
|
@@ -48,47 +60,84 @@ type ProviderGCP struct {
|
|
|
SecretManagerClient GoogleSecretManagerClient
|
|
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{}
|
|
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 {
|
|
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
|
|
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)
|
|
ts := config.TokenSource(ctx)
|
|
|
|
|
|
|
|
- client, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
|
|
|
|
|
|
|
+ clientGCPSM, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
|
|
|
if err != nil {
|
|
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
|
|
return sm, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// GetSecret returns a single secret from the provider.
|
|
// GetSecret returns a single secret from the provider.
|
|
|
func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
|
func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
|
|
|
if sm.SecretManagerClient == nil || sm.projectID == "" {
|
|
if sm.SecretManagerClient == nil || sm.projectID == "" {
|
|
|
- return nil, fmt.Errorf("provider GCP is not initialized")
|
|
|
|
|
|
|
+ return nil, fmt.Errorf(errUninitalizedGCPProvider)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
version := ref.Version
|
|
version := ref.Version
|
|
@@ -96,31 +145,45 @@ func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSec
|
|
|
version = defaultVersion
|
|
version = defaultVersion
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- resourceName := fmt.Sprintf("projects/%s/secrets/%s/versions/%s", sm.projectID, ref.Key, version)
|
|
|
|
|
-
|
|
|
|
|
req := &secretmanagerpb.AccessSecretVersionRequest{
|
|
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)
|
|
result, err := sm.SecretManagerClient.AccessSecretVersion(ctx, req)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, err
|
|
|
|
|
|
|
+ return nil, fmt.Errorf(errClientGetSecretAccess, err)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
err = sm.SecretManagerClient.Close()
|
|
err = sm.SecretManagerClient.Close()
|
|
|
if err != nil {
|
|
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.
|
|
// GetSecretMap returns multiple k/v pairs from the provider.
|
|
|
func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
|
func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
|
|
|
if sm.SecretManagerClient == nil || sm.projectID == "" {
|
|
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() {
|
|
func init() {
|