| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310 |
- /*
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package secretmanager
- import (
- "context"
- "encoding/json"
- "fmt"
- secretmanager "cloud.google.com/go/secretmanager/apiv1"
- "github.com/googleapis/gax-go/v2"
- "github.com/tidwall/gjson"
- "golang.org/x/oauth2"
- "golang.org/x/oauth2/google"
- "google.golang.org/api/option"
- secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
- v1 "k8s.io/api/core/v1"
- "k8s.io/apimachinery/pkg/types"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
- esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
- "github.com/external-secrets/external-secrets/pkg/utils"
- )
- 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"
- errInvalidStore = "invalid store"
- errInvalidStoreSpec = "invalid store spec"
- errInvalidStoreProv = "invalid store provider"
- errInvalidGCPProv = "invalid gcp secrets manager provider"
- errInvalidAuthSecretRef = "invalid auth secret ref: %w"
- errInvalidWISARef = "invalid workload identity service account reference: %w"
- )
- type GoogleSecretManagerClient interface {
- AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
- Close() error
- }
- // ProviderGCP is a provider for GCP Secret Manager.
- type ProviderGCP struct {
- projectID string
- SecretManagerClient GoogleSecretManagerClient
- gClient *gClient
- }
- type gClient struct {
- kube kclient.Client
- store *esv1beta1.GCPSMProvider
- namespace string
- storeKind string
- workloadIdentity *workloadIdentity
- }
- func (c *gClient) getTokenSource(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
- ts, err := serviceAccountTokenSource(ctx, store, kube, namespace)
- if ts != nil || err != nil {
- return ts, err
- }
- ts, err = c.workloadIdentity.TokenSource(ctx, store, kube, namespace)
- if ts != nil || err != nil {
- return ts, err
- }
- return google.DefaultTokenSource(ctx, CloudPlatformRole)
- }
- func (c *gClient) Close() error {
- return c.workloadIdentity.Close()
- }
- func serviceAccountTokenSource(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (oauth2.TokenSource, error) {
- spec := store.GetSpec()
- if spec == nil || spec.Provider.GCPSM == nil {
- return nil, fmt.Errorf(errMissingStoreSpec)
- }
- sr := spec.Provider.GCPSM.Auth.SecretRef
- if sr == nil {
- return nil, nil
- }
- storeKind := store.GetObjectKind().GroupVersionKind().Kind
- credentialsSecret := &v1.Secret{}
- credentialsSecretName := sr.SecretAccessKey.Name
- objectKey := types.NamespacedName{
- Name: credentialsSecretName,
- Namespace: namespace,
- }
- // only ClusterStore is allowed to set namespace (and then it's required)
- if storeKind == esv1beta1.ClusterSecretStoreKind {
- if credentialsSecretName != "" && sr.SecretAccessKey.Namespace == nil {
- return nil, fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
- } else if credentialsSecretName != "" {
- objectKey.Namespace = *sr.SecretAccessKey.Namespace
- }
- }
- err := kube.Get(ctx, objectKey, credentialsSecret)
- if err != nil {
- return nil, fmt.Errorf(errFetchSAKSecret, err)
- }
- credentials := credentialsSecret.Data[sr.SecretAccessKey.Key]
- if (credentials == nil) || (len(credentials) == 0) {
- return nil, fmt.Errorf(errMissingSAK)
- }
- config, err := google.JWTConfigFromJSON(credentials, CloudPlatformRole)
- if err != nil {
- return nil, fmt.Errorf(errUnableProcessJSONCredentials, err)
- }
- return config.TokenSource(ctx), nil
- }
- // NewClient constructs a GCP Provider.
- func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube kclient.Client, namespace string) (esv1beta1.SecretsClient, error) {
- storeSpec := store.GetSpec()
- if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
- return nil, fmt.Errorf(errGCPSMStore)
- }
- storeSpecGCPSM := storeSpec.Provider.GCPSM
- wi, err := newWorkloadIdentity(ctx)
- if err != nil {
- return nil, fmt.Errorf("unable to initialize workload identity")
- }
- cliStore := gClient{
- kube: kube,
- store: storeSpecGCPSM,
- namespace: namespace,
- storeKind: store.GetObjectKind().GroupVersionKind().Kind,
- workloadIdentity: wi,
- }
- sm.gClient = &cliStore
- defer func() {
- // closes IAMClient to prevent gRPC connection leak in case of an error.
- if sm.SecretManagerClient == nil {
- _ = sm.gClient.Close()
- }
- }()
- sm.projectID = cliStore.store.ProjectID
- ts, err := cliStore.getTokenSource(ctx, store, kube, namespace)
- if err != nil {
- return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
- }
- // check if we can get credentials
- _, err = ts.Token()
- if err != nil {
- return nil, fmt.Errorf(errUnableGetCredentials, err)
- }
- clientGCPSM, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
- if err != nil {
- return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
- }
- sm.SecretManagerClient = clientGCPSM
- return sm, nil
- }
- // Empty GetAllSecrets.
- func (sm *ProviderGCP) GetAllSecrets(ctx context.Context, ref esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
- // TO be implemented
- return nil, fmt.Errorf("GetAllSecrets not implemented")
- }
- // GetSecret returns a single secret from the provider.
- func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
- if utils.IsNil(sm.SecretManagerClient) || sm.projectID == "" {
- return nil, fmt.Errorf(errUninitalizedGCPProvider)
- }
- version := ref.Version
- if version == "" {
- version = defaultVersion
- }
- req := &secretmanagerpb.AccessSecretVersionRequest{
- 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, fmt.Errorf(errClientGetSecretAccess, err)
- }
- if ref.Property == "" {
- if result.Payload.Data != nil {
- return result.Payload.Data, nil
- }
- return nil, fmt.Errorf("invalid secret received. no secret string for key: %s", ref.Key)
- }
- var payload string
- if result.Payload.Data != nil {
- payload = string(result.Payload.Data)
- }
- val := gjson.Get(payload, ref.Property)
- if !val.Exists() {
- return nil, fmt.Errorf("key %s does not exist in secret %s", ref.Property, ref.Key)
- }
- return []byte(val.String()), nil
- }
- // GetSecretMap returns multiple k/v pairs from the provider.
- func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
- if sm.SecretManagerClient == nil || sm.projectID == "" {
- return nil, fmt.Errorf(errUninitalizedGCPProvider)
- }
- data, err := sm.GetSecret(ctx, ref)
- if err != nil {
- return nil, err
- }
- kv := make(map[string]json.RawMessage)
- err = json.Unmarshal(data, &kv)
- if err != nil {
- return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
- }
- secretData := make(map[string][]byte)
- for k, v := range kv {
- var strVal string
- err = json.Unmarshal(v, &strVal)
- if err == nil {
- secretData[k] = []byte(strVal)
- } else {
- secretData[k] = v
- }
- }
- return secretData, nil
- }
- func (sm *ProviderGCP) Close(ctx context.Context) error {
- err := sm.SecretManagerClient.Close()
- if sm.gClient != nil {
- err = sm.gClient.Close()
- }
- if err != nil {
- return fmt.Errorf(errClientClose, err)
- }
- return nil
- }
- func (sm *ProviderGCP) Validate() error {
- return nil
- }
- func (sm *ProviderGCP) ValidateStore(store esv1beta1.GenericStore) error {
- if store == nil {
- return fmt.Errorf(errInvalidStore)
- }
- spc := store.GetSpec()
- if spc == nil {
- return fmt.Errorf(errInvalidStoreSpec)
- }
- if spc.Provider == nil {
- return fmt.Errorf(errInvalidStoreProv)
- }
- p := spc.Provider.GCPSM
- if p == nil {
- return fmt.Errorf(errInvalidGCPProv)
- }
- if p.Auth.SecretRef != nil {
- if err := utils.ValidateSecretSelector(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 {
- return fmt.Errorf(errInvalidWISARef, err)
- }
- }
- return nil
- }
- func init() {
- esv1beta1.Register(&ProviderGCP{}, &esv1beta1.SecretStoreProvider{
- GCPSM: &esv1beta1.GCPSMProvider{},
- })
- }
|