client.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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 implements a provider for Doppler secrets management.
  14. package doppler
  15. import (
  16. "context"
  17. "encoding/json"
  18. "errors"
  19. "fmt"
  20. "net/url"
  21. "strings"
  22. "time"
  23. "github.com/external-secrets/external-secrets/pkg/find"
  24. corev1 "k8s.io/api/core/v1"
  25. kclient "sigs.k8s.io/controller-runtime/pkg/client"
  26. esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
  27. "github.com/external-secrets/external-secrets/pkg/esutils"
  28. "github.com/external-secrets/external-secrets/pkg/esutils/resolvers"
  29. dclient "github.com/external-secrets/external-secrets/pkg/provider/doppler/client"
  30. )
  31. const (
  32. customBaseURLEnvVar = "DOPPLER_BASE_URL"
  33. verifyTLSOverrideEnvVar = "DOPPLER_VERIFY_TLS"
  34. errGetSecret = "could not get secret %s: %w"
  35. errGetSecrets = "could not get secrets %w"
  36. errDeleteSecrets = "could not delete secrets %s: %w"
  37. errPushSecrets = "could not push secrets %s: %w"
  38. errUnmarshalSecretMap = "unable to unmarshal secret %s: %w"
  39. secretsDownloadFileKey = "DOPPLER_SECRETS_FILE"
  40. errDopplerTokenSecretName = "missing auth.secretRef.dopplerToken.name"
  41. errInvalidClusterStoreMissingDopplerTokenNamespace = "missing auth.secretRef.dopplerToken.namespace"
  42. )
  43. // Client implements the SecretsClient interface for Doppler.
  44. type Client struct {
  45. doppler SecretsClientInterface
  46. dopplerToken string
  47. project string
  48. config string
  49. nameTransformer string
  50. format string
  51. kube kclient.Client
  52. store *esv1.DopplerProvider
  53. namespace string
  54. storeKind string
  55. }
  56. // SecretsClientInterface defines the required Doppler Client methods.
  57. type SecretsClientInterface interface {
  58. BaseURL() *url.URL
  59. Authenticate() error
  60. GetSecret(request dclient.SecretRequest) (*dclient.SecretResponse, error)
  61. GetSecrets(request dclient.SecretsRequest) (*dclient.SecretsResponse, error)
  62. UpdateSecrets(request dclient.UpdateSecretsRequest) error
  63. }
  64. func (c *Client) setAuth(ctx context.Context) error {
  65. token, err := resolvers.SecretKeyRef(
  66. ctx,
  67. c.kube,
  68. c.storeKind,
  69. c.namespace,
  70. &c.store.Auth.SecretRef.DopplerToken)
  71. if err != nil {
  72. return err
  73. }
  74. c.dopplerToken = token
  75. return nil
  76. }
  77. // Validate validates the Doppler client configuration.
  78. func (c *Client) Validate() (esv1.ValidationResult, error) {
  79. timeout := 15 * time.Second
  80. clientURL := c.doppler.BaseURL().String()
  81. if err := esutils.NetworkValidate(clientURL, timeout); err != nil {
  82. return esv1.ValidationResultError, err
  83. }
  84. if err := c.doppler.Authenticate(); err != nil {
  85. return esv1.ValidationResultError, err
  86. }
  87. return esv1.ValidationResultReady, nil
  88. }
  89. // DeleteSecret removes a secret from Doppler.
  90. func (c *Client) DeleteSecret(_ context.Context, ref esv1.PushSecretRemoteRef) error {
  91. request := dclient.UpdateSecretsRequest{
  92. ChangeRequests: []dclient.Change{
  93. {
  94. Name: ref.GetRemoteKey(),
  95. OriginalName: ref.GetRemoteKey(),
  96. ShouldDelete: true,
  97. },
  98. },
  99. Project: c.project,
  100. Config: c.config,
  101. }
  102. err := c.doppler.UpdateSecrets(request)
  103. if err != nil {
  104. return fmt.Errorf(errDeleteSecrets, ref.GetRemoteKey(), err)
  105. }
  106. return nil
  107. }
  108. // SecretExists checks if a secret exists in Doppler.
  109. func (c *Client) SecretExists(_ context.Context, _ esv1.PushSecretRemoteRef) (bool, error) {
  110. return false, errors.New("not implemented")
  111. }
  112. // PushSecret creates or updates a secret in Doppler.
  113. func (c *Client) PushSecret(_ context.Context, secret *corev1.Secret, data esv1.PushSecretData) error {
  114. value := secret.Data[data.GetSecretKey()]
  115. request := dclient.UpdateSecretsRequest{
  116. Secrets: dclient.Secrets{
  117. data.GetRemoteKey(): string(value),
  118. },
  119. Project: c.project,
  120. Config: c.config,
  121. }
  122. err := c.doppler.UpdateSecrets(request)
  123. if err != nil {
  124. return fmt.Errorf(errPushSecrets, data.GetRemoteKey(), err)
  125. }
  126. return nil
  127. }
  128. // GetSecret retrieves a secret from Doppler.
  129. func (c *Client) GetSecret(_ context.Context, ref esv1.ExternalSecretDataRemoteRef) ([]byte, error) {
  130. request := dclient.SecretRequest{
  131. Name: ref.Key,
  132. Project: c.project,
  133. Config: c.config,
  134. }
  135. secret, err := c.doppler.GetSecret(request)
  136. if err != nil {
  137. return nil, fmt.Errorf(errGetSecret, ref.Key, err)
  138. }
  139. return []byte(secret.Value), nil
  140. }
  141. // GetSecretMap retrieves a secret from Doppler and returns it as a map.
  142. func (c *Client) GetSecretMap(ctx context.Context, ref esv1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  143. data, err := c.GetSecret(ctx, ref)
  144. if err != nil {
  145. return nil, err
  146. }
  147. kv := make(map[string]json.RawMessage)
  148. err = json.Unmarshal(data, &kv)
  149. if err != nil {
  150. return nil, fmt.Errorf(errUnmarshalSecretMap, ref.Key, err)
  151. }
  152. secretData := make(map[string][]byte)
  153. for k, v := range kv {
  154. var strVal string
  155. err = json.Unmarshal(v, &strVal)
  156. if err == nil {
  157. secretData[k] = []byte(strVal)
  158. } else {
  159. secretData[k] = v
  160. }
  161. }
  162. return secretData, nil
  163. }
  164. // GetAllSecrets retrieves all secrets from Doppler that match the given criteria.
  165. func (c *Client) GetAllSecrets(ctx context.Context, ref esv1.ExternalSecretFind) (map[string][]byte, error) {
  166. secrets, err := c.getSecrets(ctx)
  167. selected := map[string][]byte{}
  168. if err != nil {
  169. return nil, err
  170. }
  171. if ref.Name == nil && ref.Path == nil {
  172. return secrets, nil
  173. }
  174. var matcher *find.Matcher
  175. if ref.Name != nil {
  176. m, err := find.New(*ref.Name)
  177. if err != nil {
  178. return nil, err
  179. }
  180. matcher = m
  181. }
  182. for key, value := range secrets {
  183. if (matcher != nil && !matcher.MatchName(key)) || (ref.Path != nil && !strings.HasPrefix(key, *ref.Path)) {
  184. continue
  185. }
  186. selected[key] = value
  187. }
  188. return selected, nil
  189. }
  190. // Close implements cleanup operations for the Doppler client.
  191. func (c *Client) Close(_ context.Context) error {
  192. return nil
  193. }
  194. func (c *Client) getSecrets(_ context.Context) (map[string][]byte, error) {
  195. request := dclient.SecretsRequest{
  196. Project: c.project,
  197. Config: c.config,
  198. NameTransformer: c.nameTransformer,
  199. Format: c.format,
  200. }
  201. response, err := c.doppler.GetSecrets(request)
  202. if err != nil {
  203. return nil, fmt.Errorf(errGetSecrets, err)
  204. }
  205. if c.format != "" {
  206. return map[string][]byte{
  207. secretsDownloadFileKey: response.Body,
  208. }, nil
  209. }
  210. return externalSecretsFormat(response.Secrets), nil
  211. }
  212. func externalSecretsFormat(secrets dclient.Secrets) map[string][]byte {
  213. converted := make(map[string][]byte, len(secrets))
  214. for key, value := range secrets {
  215. converted[key] = []byte(value)
  216. }
  217. return converted
  218. }