webhook.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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. "errors"
  17. "fmt"
  18. "strconv"
  19. "time"
  20. "github.com/PaesslerAG/jsonpath"
  21. corev1 "k8s.io/api/core/v1"
  22. "sigs.k8s.io/controller-runtime/pkg/client"
  23. "sigs.k8s.io/controller-runtime/pkg/webhook/admission"
  24. esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
  25. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  26. "github.com/external-secrets/external-secrets/pkg/common/webhook"
  27. "github.com/external-secrets/external-secrets/pkg/utils"
  28. )
  29. const (
  30. errNotImplemented = "not implemented"
  31. )
  32. // https://github.com/external-secrets/external-secrets/issues/644
  33. var _ esv1beta1.SecretsClient = &WebHook{}
  34. var _ esv1beta1.Provider = &Provider{}
  35. // Provider satisfies the provider interface.
  36. type Provider struct{}
  37. type WebHook struct {
  38. wh webhook.Webhook
  39. store esv1beta1.GenericStore
  40. storeKind string
  41. url string
  42. }
  43. func init() {
  44. esv1beta1.Register(&Provider{}, &esv1beta1.SecretStoreProvider{
  45. Webhook: &esv1beta1.WebhookProvider{},
  46. })
  47. }
  48. // Capabilities return the provider supported capabilities (ReadOnly, WriteOnly, ReadWrite).
  49. func (p *Provider) Capabilities() esv1beta1.SecretStoreCapabilities {
  50. return esv1beta1.SecretStoreReadOnly
  51. }
  52. func (p *Provider) ApplyReferent(spec client.Object, _ esmeta.ReferentCallOrigin, _ string) (client.Object, error) {
  53. return spec, nil
  54. }
  55. func (p *Provider) Convert(_ esv1beta1.GenericStore) (client.Object, error) {
  56. return nil, nil
  57. }
  58. func (p *Provider) NewClientFromObj(_ context.Context, _ client.Object, _ client.Client, _ string) (esv1beta1.SecretsClient, error) {
  59. return nil, fmt.Errorf("not implemented")
  60. }
  61. func (p *Provider) NewClient(ctx context.Context, store esv1beta1.GenericStore, kube client.Client, namespace string) (esv1beta1.SecretsClient, error) {
  62. wh := webhook.Webhook{
  63. Kube: kube,
  64. Namespace: namespace,
  65. }
  66. whClient := &WebHook{
  67. store: store,
  68. wh: wh,
  69. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  70. }
  71. whClient.wh.EnforceLabels = true
  72. if whClient.storeKind == esv1beta1.ClusterSecretStoreKind {
  73. whClient.wh.ClusterScoped = true
  74. }
  75. provider, err := getProvider(store)
  76. if err != nil {
  77. return nil, err
  78. }
  79. whClient.url = provider.URL
  80. whClient.wh.HTTP, err = whClient.wh.GetHTTPClient(ctx, provider)
  81. if err != nil {
  82. return nil, err
  83. }
  84. return whClient, nil
  85. }
  86. func (p *Provider) ValidateStore(_ esv1beta1.GenericStore) (admission.Warnings, error) {
  87. return nil, nil
  88. }
  89. func getProvider(store esv1beta1.GenericStore) (*webhook.Spec, error) {
  90. spc := store.GetSpec()
  91. if spc == nil || spc.Provider == nil || spc.Provider.Webhook == nil {
  92. return nil, errors.New("missing store provider webhook")
  93. }
  94. out := webhook.Spec{}
  95. d, err := json.Marshal(spc.Provider.Webhook)
  96. if err != nil {
  97. return nil, err
  98. }
  99. err = json.Unmarshal(d, &out)
  100. return &out, err
  101. }
  102. func (w *WebHook) DeleteSecret(_ context.Context, _ esv1beta1.PushSecretRemoteRef) error {
  103. return errors.New(errNotImplemented)
  104. }
  105. func (w *WebHook) SecretExists(_ context.Context, _ esv1beta1.PushSecretRemoteRef) (bool, error) {
  106. return false, errors.New(errNotImplemented)
  107. }
  108. // PushSecret not implement.
  109. func (w *WebHook) PushSecret(_ context.Context, _ *corev1.Secret, _ esv1beta1.PushSecretData) error {
  110. return errors.New(errNotImplemented)
  111. }
  112. // GetAllSecrets Empty .
  113. func (w *WebHook) GetAllSecrets(_ context.Context, _ esv1beta1.ExternalSecretFind) (map[string][]byte, error) {
  114. // TO be implemented
  115. return nil, errors.New(errNotImplemented)
  116. }
  117. func (w *WebHook) GetSecret(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) ([]byte, error) {
  118. provider, err := getProvider(w.store)
  119. if err != nil {
  120. return nil, fmt.Errorf("failed to get store: %w", err)
  121. }
  122. result, err := w.wh.GetWebhookData(ctx, provider, &ref)
  123. if err != nil {
  124. return nil, err
  125. }
  126. // Only parse as json if we have a jsonpath set
  127. data, err := w.wh.GetTemplateData(ctx, &ref, provider.Secrets, false)
  128. if err != nil {
  129. return nil, err
  130. }
  131. resultJSONPath, err := webhook.ExecuteTemplateString(provider.Result.JSONPath, data)
  132. if err != nil {
  133. return nil, err
  134. }
  135. if resultJSONPath != "" {
  136. jsondata := any(nil)
  137. if err := json.Unmarshal(result, &jsondata); err != nil {
  138. return nil, fmt.Errorf("failed to parse response json: %w", err)
  139. }
  140. jsondata, err = jsonpath.Get(resultJSONPath, jsondata)
  141. if err != nil {
  142. return nil, fmt.Errorf("failed to get response path %s: %w", resultJSONPath, err)
  143. }
  144. return extractSecretData(jsondata)
  145. }
  146. return result, nil
  147. }
  148. // tries to extract data from an any
  149. // it is supposed to return a single value.
  150. func extractSecretData(jsondata any) ([]byte, error) {
  151. switch val := jsondata.(type) {
  152. case bool:
  153. return []byte(strconv.FormatBool(val)), nil
  154. case nil:
  155. return []byte{}, nil
  156. case int:
  157. return []byte(strconv.Itoa(val)), nil
  158. case float64:
  159. return []byte(strconv.FormatFloat(val, 'f', 0, 64)), nil
  160. case []byte:
  161. return val, nil
  162. case string:
  163. return []byte(val), nil
  164. // due to backwards compatibility we must keep this!
  165. // in case we see a []something we pick the first element and return it
  166. case []any:
  167. if len(val) == 0 {
  168. return nil, errors.New("filter worked but didn't get any result")
  169. }
  170. return extractSecretData(val[0])
  171. // in case we encounter a map we serialize it instead of erroring out
  172. // The user should use that data from within a template and figure
  173. // out how to deal with it.
  174. case map[string]any:
  175. return json.Marshal(val)
  176. default:
  177. return nil, fmt.Errorf("failed to get response (wrong type: %T)", jsondata)
  178. }
  179. }
  180. func (w *WebHook) GetSecretMap(ctx context.Context, ref esv1beta1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  181. provider, err := getProvider(w.store)
  182. if err != nil {
  183. return nil, fmt.Errorf("failed to get store: %w", err)
  184. }
  185. return w.wh.GetSecretMap(ctx, provider, &ref)
  186. }
  187. func (w *WebHook) Close(_ context.Context) error {
  188. return nil
  189. }
  190. func (w *WebHook) Validate() (esv1beta1.ValidationResult, error) {
  191. timeout := 15 * time.Second
  192. url := w.url
  193. if err := utils.NetworkValidate(url, timeout); err != nil {
  194. return esv1beta1.ValidationResultError, err
  195. }
  196. return esv1beta1.ValidationResultReady, nil
  197. }