client.go 8.3 KB

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