provider.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  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 doppler
  14. import (
  15. "context"
  16. "errors"
  17. "fmt"
  18. "os"
  19. "strconv"
  20. "github.com/spf13/pflag"
  21. "k8s.io/client-go/kubernetes"
  22. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  23. "sigs.k8s.io/controller-runtime/pkg/client/config"
  24. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  25. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  26. "github.com/external-secrets/external-secrets/runtime/cache"
  27. "github.com/external-secrets/external-secrets/runtime/esutils"
  28. "github.com/external-secrets/external-secrets/runtime/feature"
  29. dclient "github.com/external-secrets/external-secrets/providers/v1/doppler/client"
  30. )
  31. const (
  32. errNewClient = "unable to create DopplerClient : %s"
  33. errInvalidStore = "invalid store: %s"
  34. errDopplerStore = "missing or invalid Doppler SecretStore"
  35. )
  36. // Provider is a Doppler secrets provider implementing NewClient and ValidateStore for the esv1.Provider interface.
  37. type Provider struct{}
  38. // https://github.com/external-secrets/external-secrets/issues/644
  39. var _ esv1.SecretsClient = &Client{}
  40. var _ esv1.Provider = &Provider{}
  41. var (
  42. oidcClientCache *cache.Cache[esv1.SecretsClient]
  43. defaultCacheSize = 2 << 17
  44. )
  45. func initCache(cacheSize int) {
  46. if oidcClientCache == nil && cacheSize > 0 {
  47. oidcClientCache = cache.Must(cacheSize, func(_ esv1.SecretsClient) {
  48. // No cleanup is needed when evicting OIDC clients from cache
  49. })
  50. }
  51. }
  52. // InitializeFlags registers Doppler-specific flags with the feature system.
  53. func InitializeFlags() *feature.Feature {
  54. var dopplerOIDCCacheSize int
  55. fs := pflag.NewFlagSet("doppler", pflag.ExitOnError)
  56. fs.IntVar(&dopplerOIDCCacheSize, "doppler-oidc-cache-size", defaultCacheSize,
  57. "Maximum size of Doppler OIDC provider cache. Set to 0 to disable caching.")
  58. return &feature.Feature{
  59. Flags: fs,
  60. Initialize: func() {
  61. initCache(dopplerOIDCCacheSize)
  62. },
  63. }
  64. }
  65. // Capabilities returns the provider's supported capabilities.
  66. func (p *Provider) Capabilities() esv1.SecretStoreCapabilities {
  67. return esv1.SecretStoreReadOnly
  68. }
  69. // NewClient creates a new Doppler client.
  70. func (p *Provider) NewClient(ctx context.Context, store esv1.GenericStore, kube kclient.Client, namespace string) (esv1.SecretsClient, error) {
  71. storeSpec := store.GetSpec()
  72. if storeSpec == nil || storeSpec.Provider == nil || storeSpec.Provider.Doppler == nil {
  73. return nil, errors.New(errDopplerStore)
  74. }
  75. dopplerStoreSpec := storeSpec.Provider.Doppler
  76. useCache := dopplerStoreSpec.Auth.OIDCConfig != nil && oidcClientCache != nil
  77. key := cache.Key{
  78. Name: store.GetObjectMeta().Name,
  79. Namespace: namespace,
  80. Kind: store.GetTypeMeta().Kind,
  81. }
  82. if useCache {
  83. if cachedClient, ok := oidcClientCache.Get(store.GetObjectMeta().ResourceVersion, key); ok {
  84. return cachedClient, nil
  85. }
  86. }
  87. client := &Client{
  88. kube: kube,
  89. store: dopplerStoreSpec,
  90. namespace: namespace,
  91. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  92. }
  93. if err := p.setupClientAuth(ctx, client, dopplerStoreSpec, store, namespace); err != nil {
  94. return nil, err
  95. }
  96. if err := p.configureDopplerClient(client); err != nil {
  97. return nil, err
  98. }
  99. if useCache {
  100. oidcClientCache.Add(store.GetObjectMeta().ResourceVersion, key, client)
  101. }
  102. return client, nil
  103. }
  104. func (p *Provider) setupClientAuth(ctx context.Context, client *Client, dopplerStoreSpec *esv1.DopplerProvider, store esv1.GenericStore, namespace string) error {
  105. if dopplerStoreSpec.Auth.SecretRef != nil {
  106. if dopplerStoreSpec.Auth.SecretRef.DopplerToken.Key == "" {
  107. dopplerStoreSpec.Auth.SecretRef.DopplerToken.Key = "dopplerToken"
  108. }
  109. } else if dopplerStoreSpec.Auth.OIDCConfig != nil {
  110. if err := p.setupOIDCAuth(client, dopplerStoreSpec, store, namespace); err != nil {
  111. return err
  112. }
  113. }
  114. return client.setAuth(ctx)
  115. }
  116. func (p *Provider) setupOIDCAuth(client *Client, dopplerStoreSpec *esv1.DopplerProvider, store esv1.GenericStore, namespace string) error {
  117. cfg, err := config.GetConfig()
  118. if err != nil {
  119. return fmt.Errorf("failed to get kubernetes config: %w", err)
  120. }
  121. clientset, err := kubernetes.NewForConfig(cfg)
  122. if err != nil {
  123. return fmt.Errorf("failed to create kubernetes clientset: %w", err)
  124. }
  125. client.corev1 = clientset.CoreV1()
  126. client.oidcManager = NewOIDCTokenManager(
  127. client.corev1,
  128. dopplerStoreSpec,
  129. namespace,
  130. store.GetObjectKind().GroupVersionKind().Kind,
  131. store.GetObjectMeta().Name,
  132. )
  133. return nil
  134. }
  135. func (p *Provider) configureDopplerClient(client *Client) error {
  136. doppler, err := dclient.NewDopplerClient(client.dopplerToken)
  137. if err != nil {
  138. return fmt.Errorf(errNewClient, err)
  139. }
  140. if customBaseURL, found := os.LookupEnv(customBaseURLEnvVar); found {
  141. if err := doppler.SetBaseURL(customBaseURL); err != nil {
  142. return fmt.Errorf(errNewClient, err)
  143. }
  144. }
  145. if customVerifyTLS, found := os.LookupEnv(verifyTLSOverrideEnvVar); found {
  146. customVerifyTLS, err := strconv.ParseBool(customVerifyTLS)
  147. if err == nil {
  148. doppler.VerifyTLS = customVerifyTLS
  149. }
  150. }
  151. client.doppler = doppler
  152. client.project = client.store.Project
  153. client.config = client.store.Config
  154. client.nameTransformer = client.store.NameTransformer
  155. client.format = client.store.Format
  156. return nil
  157. }
  158. // ValidateStore validates the Doppler provider configuration.
  159. func (p *Provider) ValidateStore(store esv1.GenericStore) (admission.Warnings, error) {
  160. storeSpec := store.GetSpec()
  161. dopplerStoreSpec := storeSpec.Provider.Doppler
  162. if dopplerStoreSpec.Auth.SecretRef != nil {
  163. dopplerTokenSecretRef := dopplerStoreSpec.Auth.SecretRef.DopplerToken
  164. if err := esutils.ValidateSecretSelector(store, dopplerTokenSecretRef); err != nil {
  165. return nil, fmt.Errorf(errInvalidStore, err)
  166. }
  167. if dopplerTokenSecretRef.Name == "" {
  168. return nil, fmt.Errorf(errInvalidStore, "dopplerToken.name cannot be empty")
  169. }
  170. } else if dopplerStoreSpec.Auth.OIDCConfig != nil {
  171. oidcAuth := dopplerStoreSpec.Auth.OIDCConfig
  172. if oidcAuth.Identity == "" {
  173. return nil, fmt.Errorf(errInvalidStore, "oidcConfig.identity cannot be empty")
  174. }
  175. if oidcAuth.ServiceAccountRef.Name == "" {
  176. return nil, fmt.Errorf(errInvalidStore, "oidcConfig.serviceAccountRef.name cannot be empty")
  177. }
  178. if err := esutils.ValidateServiceAccountSelector(store, oidcAuth.ServiceAccountRef); err != nil {
  179. return nil, fmt.Errorf(errInvalidStore, err)
  180. }
  181. } else {
  182. return nil, fmt.Errorf(errInvalidStore, "either auth.secretRef or auth.oidcConfig must be specified")
  183. }
  184. return nil, nil
  185. }
  186. // NewProvider creates a new Provider instance.
  187. func NewProvider() esv1.Provider {
  188. return &Provider{}
  189. }
  190. // ProviderSpec returns the provider specification for registration.
  191. func ProviderSpec() *esv1.SecretStoreProvider {
  192. return &esv1.SecretStoreProvider{
  193. Doppler: &esv1.DopplerProvider{},
  194. }
  195. }
  196. // MaintenanceStatus returns the maintenance status of the provider.
  197. func MaintenanceStatus() esv1.MaintenanceStatus {
  198. return esv1.MaintenanceStatusMaintained
  199. }