provider.go 7.6 KB

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