secretsmanager.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  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. "github.com/tidwall/gjson"
  20. "golang.org/x/oauth2/google"
  21. "google.golang.org/api/option"
  22. secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
  23. corev1 "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/types"
  25. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  26. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  27. "github.com/external-secrets/external-secrets/pkg/provider"
  28. "github.com/external-secrets/external-secrets/pkg/provider/schema"
  29. )
  30. const (
  31. CloudPlatformRole = "https://www.googleapis.com/auth/cloud-platform"
  32. defaultVersion = "latest"
  33. errGCPSMStore = "received invalid GCPSM SecretStore resource"
  34. errGCPSMCredSecretName = "invalid GCPSM SecretStore resource: missing GCP Secret Access Key"
  35. errClientClose = "unable to close SecretManager client: %w"
  36. errInvalidClusterStoreMissingSAKNamespace = "invalid ClusterSecretStore: missing GCP SecretAccessKey Namespace"
  37. errFetchSAKSecret = "could not fetch SecretAccessKey secret: %w"
  38. errMissingSAK = "missing SecretAccessKey"
  39. errUnableProcessJSONCredentials = "failed to process the provided JSON credentials: %w"
  40. errUnableCreateGCPSMClient = "failed to create GCP secretmanager client: %w"
  41. errUninitalizedGCPProvider = "provider GCP is not initialized"
  42. errClientGetSecretAccess = "unable to access Secret from SecretManager Client: %w"
  43. errJSONSecretUnmarshal = "unable to unmarshal secret: %w"
  44. )
  45. type GoogleSecretManagerClient interface {
  46. AccessSecretVersion(ctx context.Context, req *secretmanagerpb.AccessSecretVersionRequest, opts ...gax.CallOption) (*secretmanagerpb.AccessSecretVersionResponse, error)
  47. Close() error
  48. }
  49. // ProviderGCP is a provider for GCP Secret Manager.
  50. type ProviderGCP struct {
  51. projectID string
  52. SecretManagerClient GoogleSecretManagerClient
  53. }
  54. type gClient struct {
  55. kube kclient.Client
  56. store *esv1alpha1.GCPSMProvider
  57. namespace string
  58. storeKind string
  59. credentials []byte
  60. }
  61. func (c *gClient) setAuth(ctx context.Context) error {
  62. credentialsSecret := &corev1.Secret{}
  63. credentialsSecretName := c.store.Auth.SecretRef.SecretAccessKey.Name
  64. if credentialsSecretName == "" {
  65. return fmt.Errorf(errGCPSMCredSecretName)
  66. }
  67. objectKey := types.NamespacedName{
  68. Name: credentialsSecretName,
  69. Namespace: c.namespace,
  70. }
  71. // only ClusterStore is allowed to set namespace (and then it's required)
  72. if c.storeKind == esv1alpha1.ClusterSecretStoreKind {
  73. if c.store.Auth.SecretRef.SecretAccessKey.Namespace == nil {
  74. return fmt.Errorf(errInvalidClusterStoreMissingSAKNamespace)
  75. }
  76. objectKey.Namespace = *c.store.Auth.SecretRef.SecretAccessKey.Namespace
  77. }
  78. err := c.kube.Get(ctx, objectKey, credentialsSecret)
  79. if err != nil {
  80. return fmt.Errorf(errFetchSAKSecret, err)
  81. }
  82. c.credentials = credentialsSecret.Data[c.store.Auth.SecretRef.SecretAccessKey.Key]
  83. if (c.credentials == nil) || (len(c.credentials) == 0) {
  84. return fmt.Errorf(errMissingSAK)
  85. }
  86. return nil
  87. }
  88. // NewClient constructs a GCP Provider.
  89. func (sm *ProviderGCP) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube kclient.Client, namespace string) (provider.SecretsClient, error) {
  90. storeSpec := store.GetSpec()
  91. if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.GCPSM == nil {
  92. return nil, fmt.Errorf(errGCPSMStore)
  93. }
  94. storeSpecGCPSM := storeSpec.Provider.GCPSM
  95. cliStore := gClient{
  96. kube: kube,
  97. store: storeSpecGCPSM,
  98. namespace: namespace,
  99. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  100. }
  101. if err := cliStore.setAuth(ctx); err != nil {
  102. return nil, err
  103. }
  104. sm.projectID = cliStore.store.ProjectID
  105. config, err := google.JWTConfigFromJSON(cliStore.credentials, CloudPlatformRole)
  106. if err != nil {
  107. return nil, fmt.Errorf(errUnableProcessJSONCredentials, err)
  108. }
  109. ts := config.TokenSource(ctx)
  110. clientGCPSM, err := secretmanager.NewClient(ctx, option.WithTokenSource(ts))
  111. if err != nil {
  112. return nil, fmt.Errorf(errUnableCreateGCPSMClient, err)
  113. }
  114. sm.SecretManagerClient = clientGCPSM
  115. return sm, nil
  116. }
  117. // GetSecret returns a single secret from the provider.
  118. func (sm *ProviderGCP) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
  119. if sm.SecretManagerClient == nil || sm.projectID == "" {
  120. return nil, fmt.Errorf(errUninitalizedGCPProvider)
  121. }
  122. version := ref.Version
  123. if version == "" {
  124. version = defaultVersion
  125. }
  126. req := &secretmanagerpb.AccessSecretVersionRequest{
  127. Name: fmt.Sprintf("projects/%s/secrets/%s/versions/%s", sm.projectID, ref.Key, version),
  128. }
  129. result, err := sm.SecretManagerClient.AccessSecretVersion(ctx, req)
  130. if err != nil {
  131. return nil, fmt.Errorf(errClientGetSecretAccess, err)
  132. }
  133. if ref.Property == "" {
  134. if result.Payload.Data != nil {
  135. return result.Payload.Data, nil
  136. }
  137. return nil, fmt.Errorf("invalid secret received. no secret string for key: %s", ref.Key)
  138. }
  139. var payload string
  140. if result.Payload.Data != nil {
  141. payload = string(result.Payload.Data)
  142. }
  143. val := gjson.Get(payload, ref.Property)
  144. return []byte(val.String()), nil
  145. }
  146. // GetSecretMap returns multiple k/v pairs from the provider.
  147. func (sm *ProviderGCP) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  148. if sm.SecretManagerClient == nil || sm.projectID == "" {
  149. return nil, fmt.Errorf(errUninitalizedGCPProvider)
  150. }
  151. data, err := sm.GetSecret(ctx, ref)
  152. if err != nil {
  153. return nil, err
  154. }
  155. kv := make(map[string]string)
  156. err = json.Unmarshal(data, &kv)
  157. if err != nil {
  158. return nil, fmt.Errorf(errJSONSecretUnmarshal, err)
  159. }
  160. secretData := make(map[string][]byte)
  161. for k, v := range kv {
  162. secretData[k] = []byte(v)
  163. }
  164. return secretData, nil
  165. }
  166. func (sm *ProviderGCP) Close() error {
  167. err := sm.SecretManagerClient.Close()
  168. if err != nil {
  169. return fmt.Errorf(errClientClose, err)
  170. }
  171. return nil
  172. }
  173. func init() {
  174. schema.Register(&ProviderGCP{}, &esv1alpha1.SecretStoreProvider{
  175. GCPSM: &esv1alpha1.GCPSMProvider{},
  176. })
  177. }