webhook.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package webhook
  13. import (
  14. "context"
  15. "encoding/json"
  16. "fmt"
  17. "strconv"
  18. "time"
  19. "github.com/PaesslerAG/jsonpath"
  20. corev1 "k8s.io/api/core/v1"
  21. "sigs.k8s.io/controller-runtime/pkg/client"
  22. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  23. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  24. "github.com/external-secrets/external-secrets/pkg/common/webhook"
  25. "github.com/external-secrets/external-secrets/pkg/utils"
  26. )
  27. // https://github.com/external-secrets/external-secrets/issues/644
  28. var _ esv1beta1.SecretsClient = &WebHook{}
  29. var _ esv1beta1.Provider = &Provider{}
  30. // Provider satisfies the provider interface.
  31. type Provider struct{}
  32. type WebHook struct {
  33. wh webhook.Webhook
  34. store esv1beta1.GenericStore
  35. storeKind string
  36. url string
  37. }
  38. func init() {
  39. esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
  40. Webhook: &esv1beta1.WebhookProvider{},
  41. })
  42. }
  43. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  44. func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
  45. return esv1beta1.SecretStoreReadOnly
  46. }
  47. func (p *Provider) NewClient(_ context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  48. wh := webhook.Webhook{
  49. Kube: kube,
  50. Namespace: namespace,
  51. }
  52. whClient := &WebHook{
  53. store: store,
  54. wh: wh,
  55. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  56. }
  57. if whClient.storeKind == esv1beta1.ClusterSecretStoreKind {
  58. whClient.wh.ClusterScoped = true
  59. }
  60. provider, err := getProvider(store)
  61. if err != nil {
  62. return nil, err
  63. }
  64. whClient.url = provider.URL
  65. whClient.wh.HTTP, err = whClient.wh.GetHTTPClient(provider)
  66. if err != nil {
  67. return nil, err
  68. }
  69. return whClient, nil
  70. }
  71. func (p *Provider) ValidateStore(_ esv1beta1.GenericStore) (admission.Warnings, error) {
  72. return nil, nil
  73. }
  74. func getProvider(store esv1beta1.GenericStore) (*webhook.Spec, error) {
  75. spc := store.GetSpec()
  76. if spc == nil || spc.Provider == nil || spc.Provider.Webhook == nil {
  77. return nil, fmt.Errorf("missing store provider webhook")
  78. }
  79. out := webhook.Spec{}
  80. d, err := json.Marshal(spc.Provider.Webhook)
  81. if err != nil {
  82. return nil, err
  83. }
  84. err = json.Unmarshal(d, &out)
  85. return &out, err
  86. }
  87. func (w *WebHook) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
  88. return fmt.Errorf("not implemented")
  89. }
  90. // Not Implemented PushSecret.
  91. func (w *WebHook) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
  92. return fmt.Errorf("not implemented")
  93. }
  94. // Empty GetAllSecrets.
  95. func (w *WebHook) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  96. // TO be implemented
  97. return nil, fmt.Errorf("GetAllSecrets not implemented")
  98. }
  99. func (w *WebHook) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  100. provider, err := getProvider(w.store)
  101. if err != nil {
  102. return nil, fmt.Errorf("failed to get store: %w", err)
  103. }
  104. result, err := w.wh.GetWebhookData(ctx, provider, &ref)
  105. if err != nil {
  106. return nil, err
  107. }
  108. // Only parse as json if we have a jsonpath set
  109. data, err := w.wh.GetTemplateData(ctx, &ref, provider.Secrets)
  110. if err != nil {
  111. return nil, err
  112. }
  113. resultJSONPath, err := webhook.ExecuteTemplateString(provider.Result.JSONPath, data)
  114. if err != nil {
  115. return nil, err
  116. }
  117. if resultJSONPath != "" {
  118. jsondata := interface{}(nil)
  119. if err := json.Unmarshal(result, &jsondata); err != nil {
  120. return nil, fmt.Errorf("failed to parse response json: %w", err)
  121. }
  122. jsondata, err = jsonpath.Get(resultJSONPath, jsondata)
  123. if err != nil {
  124. return nil, fmt.Errorf("failed to get response path %s: %w", resultJSONPath, err)
  125. }
  126. return extractSecretData(jsondata)
  127. }
  128. return result, nil
  129. }
  130. // tries to extract data from an interface{}
  131. // it is supposed to return a single value.
  132. func extractSecretData(jsondata any) ([]byte, error) {
  133. switch val := jsondata.(type) {
  134. case bool:
  135. return []byte(strconv.FormatBool(val)), nil
  136. case nil:
  137. return []byte{}, nil
  138. case int:
  139. return []byte(strconv.Itoa(val)), nil
  140. case float64:
  141. return []byte(strconv.FormatFloat(val, 'f', 0, 64)), nil
  142. case []byte:
  143. return val, nil
  144. case string:
  145. return []byte(val), nil
  146. // due to backwards compatibility we must keep this!
  147. // in case we see a []something we pick the first element and return it
  148. case []any:
  149. if len(val) == 0 {
  150. return nil, fmt.Errorf("filter worked but didn't get any result")
  151. }
  152. return extractSecretData(val[0])
  153. // in case we encounter a map we serialize it instead of erroring out
  154. // The user should use that data from within a template and figure
  155. // out how to deal with it.
  156. case map[string]any:
  157. return json.Marshal(val)
  158. default:
  159. return nil, fmt.Errorf("failed to get response (wrong type: %T)", jsondata)
  160. }
  161. }
  162. func (w *WebHook) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  163. provider, err := getProvider(w.store)
  164. if err != nil {
  165. return nil, fmt.Errorf("failed to get store: %w", err)
  166. }
  167. return w.wh.GetSecretMap(ctx, provider, &ref)
  168. }
  169. func (w *WebHook) Close(_ context.Context) error {
  170. return nil
  171. }
  172. func (w *WebHook) Validate() (esv1beta1.ValidationResult, error) {
  173. timeout := 15 * time.Second
  174. url := w.url
  175. if err := utils.NetworkValidate(url, timeout); err != nil {
  176. return esv1beta1.ValidationResultError, err
  177. }
  178. return esv1beta1.ValidationResultReady, nil
  179. }