secretsmanager.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package secretmanager
  13. import (
  14. "context"
  15. "encoding/json"
  16. "fmt"
  17. secretmanager "cloud.google.com/go/secretmanager/apiv1"
  18. "github.com/googleapis/gax-go"
  19. "golang.org/x/oauth2/google"
  20. "google.golang.org/api/option"
  21. secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
  22. corev1 "k8s.io/api/core/v1"
  23. "k8s.io/apimachinery/pkg/types"
  24. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  25. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  26. "github.com/external-secrets/external-secrets/pkg/provider"
  27. "github.com/external-secrets/external-secrets/pkg/provider/schema"
  28. )
  29. const (
  30. CloudPlatformRole = "https://www.googleapis.com/auth/cloud-platform"
  31. defaultVersion = "latest"
  32. errGCPSMStore = "received invalid GCPSM SecretStore resource"
  33. errGCPSMCredSecretName = "invalid GCPSM SecretStore resource: missing GCP Secret Access Key"
  34. errInvalidClusterStoreMissingSAKNamespace = "invalid ClusterSecretStore: missing GCP SecretAccessKey Namespace"
  35. errFetchSAKSecret = "could not fetch SecretAccessKey secret: %w"
  36. errMissingSAK = "missing SecretAccessKey"
  37. errUnableProcessJSONCredentials = "failed to process the provided JSON credentials: %w"
  38. errUnableCreateGCPSMClient = "failed to create GCP secretmanager client: %w"
  39. errUninitalizedGCPProvider = "provider GCP is not initialized"
  40. errClientGetSecretAccess = "unable to access Secret from SecretManager Client: %w"
  41. errClientClose = "unable to close SecretManager client: %w"
  42. errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
  43. )
  44. type GoogleSecretManagerClient interface {
  45. AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
  46. Close() error
  47. }
  48. // ProviderGCP is a provider for GCP Secret Manager.
  49. type ProviderGCP struct {
  50. projectID string
  51. SecretManagerClient GoogleSecretManagerClient
  52. }
  53. type gClient struct {
  54. kube kclient.Client
  55. store *esv1alpha1.GCPSMProvider
  56. namespace string
  57. storeKind string
  58. credentials []byte
  59. }
  60. func (c *gClient) setAuth(ctx context.Context) error {
  61. credentialsSecret := &corev1.Secret{}
  62. credentialsSecretName := c.store.Auth.SecretRef.SecretAccessKey.Name
  63. if credentialsSecretName == "" {
  64. return fmt.Errorf(errGCPSMCredSecretName)
  65. }
  66. objectKey := types.NamespacedName{
  67. Name: credentialsSecretName,
  68. Namespace: c.namespace,
  69. }
  70. // only ClusterStore is allowed to set namespace (and then it's required)
  71. if c.storeKind == esv1alpha1.ClusterSecretStoreKind {
  72. if c.store.Auth.SecretRef.SecretAccessKey.Namespace == nil {
  73. return fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
  74. }
  75. objectKey.Namespace = *c.store.Auth.SecretRef.SecretAccessKey.Namespace
  76. }
  77. err := c.kube.Get(ctx, objectKey, credentialsSecret)
  78. if err != nil {
  79. return fmt.Errorf(errFetchSAKSecret, err)
  80. }
  81. c.credentials = credentialsSecret.Data[c.store.Auth.SecretRef.SecretAccessKey.Key]
  82. if (c.credentials == nil) || (len(c.credentials) == 0) {
  83. return fmt.Errorf(errMissingSAK)
  84. }
  85. return nil
  86. }
  87. // NewClient constructs a GCP Provider.
  88. func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
  89. storeSpec := store.GetSpec()
  90. if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
  91. return nil, fmt.Errorf(errGCPSMStore)
  92. }
  93. storeSpecGCPSM := storeSpec.Provider.GCPSM
  94. cliStore := gClient{
  95. kube: kube,
  96. store: storeSpecGCPSM,
  97. namespace: namespace,
  98. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  99. }
  100. if err := cliStore.setAuth(ctx); err != nil {
  101. return nil, err
  102. }
  103. sm.projectID = cliStore.store.ProjectID
  104. config, err := google.JWTConfigFromJSON(cliStore.credentials, CloudPlatformRole)
  105. if err != nil {
  106. return nil, fmt.Errorf(errUnableProcessJSONCredentials, err)
  107. }
  108. ts := config.TokenSource(ctx)
  109. clientGCPSM, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
  110. if err != nil {
  111. return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
  112. }
  113. sm.SecretManagerClient = clientGCPSM
  114. return sm, nil
  115. }
  116. // GetSecret returns a single secret from the provider.
  117. func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
  118. if sm.SecretManagerClient == nil || sm.projectID == "" {
  119. return nil, fmt.Errorf(errUninitalizedGCPProvider)
  120. }
  121. version := ref.Version
  122. if version == "" {
  123. version = defaultVersion
  124. }
  125. req := &secretmanagerpb.AccessSecretVersionRequest{
  126. Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", sm.projectID, ref.Key, version),
  127. }
  128. result, err := sm.SecretManagerClient.AccessSecretVersion(ctx, req)
  129. if err != nil {
  130. return nil, fmt.Errorf(errClientGetSecretAccess, err)
  131. }
  132. err = sm.SecretManagerClient.Close()
  133. if err != nil {
  134. return nil, fmt.Errorf(errClientClose, err)
  135. }
  136. return result.Payload.Data, nil
  137. }
  138. // GetSecretMap returns multiple k/v pairs from the provider.
  139. func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  140. if sm.SecretManagerClient == nil || sm.projectID == "" {
  141. return nil, fmt.Errorf(errUninitalizedGCPProvider)
  142. }
  143. data, err := sm.GetSecret(ctx, ref)
  144. if err != nil {
  145. return nil, err
  146. }
  147. kv := make(map[string]string)
  148. err = json.Unmarshal(data, &kv)
  149. if err != nil {
  150. return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
  151. }
  152. secretData := make(map[string][]byte)
  153. for k, v := range kv {
  154. secretData[k] = []byte(v)
  155. }
  156. return secretData, nil
  157. }
  158. func init() {
  159. schema.Register(&ProviderGCP{}, &esv1alpha1.SecretStoreProvider{
  160. GCPSM: &esv1alpha1.GCPSMProvider{},
  161. })
  162. }