provider.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package pulumi
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. esc "github.com/pulumi/esc-sdk/sdk/go"
  19. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  20. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  21. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  22. "github.com/external-secrets/external-secrets/runtime/esutils"
  23. "github.com/external-secrets/external-secrets/runtime/esutils/resolvers"
  24. )
  25. // Provider implements the esv1.Provider interface for Pulumi ESC.
  26. type Provider struct{}
  27. var _ esv1.Provider = &Provider{}
  28. const (
  29. errClusterStoreRequiresNamespace = "cluster store requires namespace"
  30. errCannotResolveSecretKeyRef = "cannot resolve secret key ref: %w"
  31. errStoreIsNil = "store is nil"
  32. errNoStoreTypeOrWrongStoreType = "no store type or wrong store type"
  33. errOrganizationIsRequired = "organization is required"
  34. errEnvironmentIsRequired = "environment is required"
  35. errProjectIsRequired = "project is required"
  36. errSecretRefNameIsRequired = "secretRef.name is required"
  37. errSecretRefKeyIsRequired = "secretRef.key is required"
  38. )
  39. // NewClient creates a new Pulumi ESC client.
  40. func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string) (esv1.SecretsClient, error) {
  41. cfg, err := getConfig(store)
  42. if err != nil {
  43. return nil, err
  44. }
  45. storeKind := store.GetKind()
  46. if storeKind == esv1.ClusterSecretStoreKind && doesConfigDependOnNamespace(cfg) {
  47. return nil, errors.New(errClusterStoreRequiresNamespace)
  48. }
  49. accessToken, err := loadAccessTokenSecret(ctx, cfg.AccessToken, kube, storeKind, namespace)
  50. if err != nil {
  51. return nil, err
  52. }
  53. configuration := esc.NewConfiguration()
  54. configuration.UserAgent = "external-secrets-operator"
  55. configuration.Servers = esc.ServerConfigurations{
  56. esc.ServerConfiguration{
  57. URL: cfg.APIURL,
  58. },
  59. }
  60. authCtx := esc.NewAuthContext(accessToken)
  61. escClient := esc.NewClient(configuration)
  62. return &client{
  63. escClient: *escClient,
  64. authCtx: authCtx,
  65. project: cfg.Project,
  66. environment: cfg.Environment,
  67. organization: cfg.Organization,
  68. }, nil
  69. }
  70. func loadAccessTokenSecret(ctx context.Context, ref *esv1.PulumiProviderSecretRef, kube kclient.Client, storeKind, namespace string) (string, error) {
  71. acctoken, err := resolvers.SecretKeyRef(ctx, kube, storeKind, namespace, ref.SecretRef)
  72. if err != nil {
  73. return "", fmt.Errorf(errCannotResolveSecretKeyRef, err)
  74. }
  75. return acctoken, nil
  76. }
  77. func doesConfigDependOnNamespace(cfg *esv1.PulumiProvider) bool {
  78. if cfg.AccessToken.SecretRef != nil && cfg.AccessToken.SecretRef.Namespace == nil {
  79. return true
  80. }
  81. return false
  82. }
  83. func getConfig(store esv1.GenericStore) (*esv1.PulumiProvider, error) {
  84. if store == nil {
  85. return nil, errors.New(errStoreIsNil)
  86. }
  87. spec := store.GetSpec()
  88. if spec == nil || spec.Provider == nil || spec.Provider.Pulumi == nil {
  89. return nil, errors.New(errNoStoreTypeOrWrongStoreType)
  90. }
  91. cfg := spec.Provider.Pulumi
  92. if cfg.APIURL == "" {
  93. cfg.APIURL = "https://api.pulumi.com/api/esc"
  94. }
  95. if cfg.Organization == "" {
  96. return nil, errors.New(errOrganizationIsRequired)
  97. }
  98. if cfg.Environment == "" {
  99. return nil, errors.New(errEnvironmentIsRequired)
  100. }
  101. if cfg.Project == "" {
  102. return nil, errors.New(errProjectIsRequired)
  103. }
  104. err := validateStoreSecretRef(store, cfg.AccessToken)
  105. if err != nil {
  106. return nil, err
  107. }
  108. return cfg, nil
  109. }
  110. func validateStoreSecretRef(store esv1.GenericStore, ref *esv1.PulumiProviderSecretRef) error {
  111. if ref != nil {
  112. if err := esutils.ValidateReferentSecretSelector(store, *ref.SecretRef); err != nil {
  113. return err
  114. }
  115. }
  116. return validateSecretRef(ref)
  117. }
  118. func validateSecretRef(ref *esv1.PulumiProviderSecretRef) error {
  119. if ref.SecretRef != nil {
  120. if ref.SecretRef.Name == "" {
  121. return errors.New(errSecretRefNameIsRequired)
  122. }
  123. if ref.SecretRef.Key == "" {
  124. return errors.New(errSecretRefKeyIsRequired)
  125. }
  126. }
  127. return nil
  128. }
  129. // ValidateStore validates the store's configuration.
  130. func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  131. _, err := getConfig(store)
  132. return nil, err
  133. }
  134. // Capabilities returns the provider's esv1.SecretStoreCapabilities.
  135. func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
  136. return esv1.SecretStoreReadOnly
  137. }
  138. // NewProvider creates a new Provider instance.
  139. func NewProvider() esv1.Provider {
  140. return &Provider{}
  141. }
  142. // ProviderSpec returns the provider specification for registration.
  143. func ProviderSpec() *esv1.SecretStoreProvider {
  144. return &esv1.SecretStoreProvider{
  145. Pulumi: &esv1.PulumiProvider{},
  146. }
  147. }
  148. // MaintenanceStatus returns the maintenance status of the provider.
  149. func MaintenanceStatus() esv1.MaintenanceStatus {
  150. return esv1.MaintenanceStatusMaintained
  151. }