webhook.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  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. "bytes"
  15. "context"
  16. "crypto/tls"
  17. "crypto/x509"
  18. "fmt"
  19. "io"
  20. "net/http"
  21. "net/url"
  22. "strings"
  23. tpl "text/template"
  24. "github.com/Masterminds/sprig"
  25. "github.com/PaesslerAG/jsonpath"
  26. "gopkg.in/yaml.v3"
  27. corev1 "k8s.io/api/core/v1"
  28. "sigs.k8s.io/controller-runtime/pkg/client"
  29. esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
  30. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  31. "github.com/external-secrets/external-secrets/pkg/provider"
  32. "github.com/external-secrets/external-secrets/pkg/provider/schema"
  33. "github.com/external-secrets/external-secrets/pkg/template"
  34. )
  35. // Provider satisfies the provider interface.
  36. type Provider struct{}
  37. type WebHook struct {
  38. kube client.Client
  39. store esv1alpha1.GenericStore
  40. namespace string
  41. storeKind string
  42. }
  43. func init() {
  44. schema.Register(&Provider{}, &esv1alpha1.SecretStoreProvider{
  45. Webhook: &esv1alpha1.WebhookProvider{},
  46. })
  47. }
  48. func (p *Provider) NewClient(ctx context.Context, store esv1alpha1.GenericStore, kube client.Client, namespace string) (provider.SecretsClient, error) {
  49. whClient := &WebHook{
  50. kube: kube,
  51. store: store,
  52. namespace: namespace,
  53. storeKind: store.GetObjectKind().GroupVersionKind().Kind,
  54. }
  55. return whClient, nil
  56. }
  57. func getProvider(store esv1alpha1.GenericStore) (*esv1alpha1.WebhookProvider, error) {
  58. spc := store.GetSpec()
  59. if spc == nil || spc.Provider == nil || spc.Provider.Webhook == nil {
  60. return nil, fmt.Errorf("missing store provider webhook")
  61. }
  62. return spc.Provider.Webhook, nil
  63. }
  64. func (w *WebHook) getStoreSecret(ctx context.Context, ref esmeta.SecretKeySelector) (*corev1.Secret, error) {
  65. ke := client.ObjectKey{
  66. Name: ref.Name,
  67. Namespace: w.namespace,
  68. }
  69. if w.storeKind == esv1alpha1.ClusterSecretStoreKind {
  70. if ref.Namespace == nil {
  71. return nil, fmt.Errorf("no namespace on ClusterSecretStore webhook secret %s", ref.Name)
  72. }
  73. ke.Namespace = *ref.Namespace
  74. }
  75. secret := &corev1.Secret{}
  76. if err := w.kube.Get(ctx, ke, secret); err != nil {
  77. return nil, fmt.Errorf("failed to get clustersecretstore webhook secret %s: %w", ref.Name, err)
  78. }
  79. return secret, nil
  80. }
  81. func (w *WebHook) GetSecret(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
  82. provider, err := getProvider(w.store)
  83. if err != nil {
  84. return nil, fmt.Errorf("failed to get store: %w", err)
  85. }
  86. result, err := w.getWebhookData(ctx, provider, ref)
  87. if err != nil {
  88. return nil, err
  89. }
  90. if provider.Result.JSONPath != "" {
  91. jsondata := interface{}(nil)
  92. if err := yaml.Unmarshal(result, &jsondata); err != nil {
  93. return nil, fmt.Errorf("failed to parse response json: %w", err)
  94. }
  95. jsondata, err = jsonpath.Get(provider.Result.JSONPath, jsondata)
  96. if err != nil {
  97. return nil, fmt.Errorf("failed to get response path %s: %w", provider.Result.JSONPath, err)
  98. }
  99. jsonvalue, ok := jsondata.(string)
  100. if !ok {
  101. return nil, fmt.Errorf("failed to get response (wrong type: %T)", jsondata)
  102. }
  103. return []byte(jsonvalue), nil
  104. }
  105. return result, nil
  106. }
  107. func (w *WebHook) GetSecretMap(ctx context.Context, ref esv1alpha1.ExternalSecretDataRemoteRef) (map[string][]byte, error) {
  108. provider, err := getProvider(w.store)
  109. if err != nil {
  110. return nil, fmt.Errorf("failed to get store: %w", err)
  111. }
  112. result, err := w.getWebhookData(ctx, provider, ref)
  113. if err != nil {
  114. return nil, err
  115. }
  116. jsondata := interface{}(nil)
  117. var jsonvalue map[string]interface{}
  118. var ok bool
  119. if provider.Result.JSONPath != "" {
  120. if err := yaml.Unmarshal(result, &jsondata); err != nil {
  121. return nil, fmt.Errorf("failed to parse response json: %w", err)
  122. }
  123. jsondata, err = jsonpath.Get(provider.Result.JSONPath, jsondata)
  124. if err != nil {
  125. return nil, fmt.Errorf("failed to get response path %s: %w", provider.Result.JSONPath, err)
  126. }
  127. jsonvalue, ok = jsondata.(map[string]interface{})
  128. if !ok {
  129. jsonstring, ok := jsondata.(string)
  130. if !ok {
  131. return nil, fmt.Errorf("failed to get response (wrong type: %T)", jsondata)
  132. }
  133. if err := yaml.Unmarshal([]byte(jsonstring), &jsondata); err != nil {
  134. return nil, fmt.Errorf("failed to parse data json: %w", err)
  135. }
  136. jsonvalue, ok = jsondata.(map[string]interface{})
  137. if !ok {
  138. return nil, fmt.Errorf("failed to get response (wrong type in data: %T)", jsondata)
  139. }
  140. }
  141. } else {
  142. if err := yaml.Unmarshal(result, &jsondata); err != nil {
  143. return nil, fmt.Errorf("failed to parse data json: %w", err)
  144. }
  145. jsonvalue, ok = jsondata.(map[string]interface{})
  146. if !ok {
  147. return nil, fmt.Errorf("failed to get response (wrong type in body: %T)", jsondata)
  148. }
  149. }
  150. values := make(map[string][]byte)
  151. for rKey, rValue := range jsonvalue {
  152. jVal, ok := rValue.(string)
  153. if !ok {
  154. return nil, fmt.Errorf("failed to get response (wrong type: %T)", rValue)
  155. }
  156. values[rKey] = []byte(jVal)
  157. }
  158. return values, nil
  159. }
  160. func (w *WebHook) getWebhookData(ctx context.Context, provider *esv1alpha1.WebhookProvider, ref esv1alpha1.ExternalSecretDataRemoteRef) ([]byte, error) {
  161. data := map[string]map[string]string{
  162. "remoteRef": {
  163. "key": url.QueryEscape(ref.Key),
  164. "version": url.QueryEscape(ref.Version),
  165. },
  166. }
  167. if provider.Secrets != nil {
  168. for _, secref := range provider.Secrets {
  169. if _, ok := data[secref.Name]; !ok {
  170. data[secref.Name] = make(map[string]string)
  171. }
  172. secret, err := w.getStoreSecret(ctx, secref.SecretRef)
  173. if err != nil {
  174. return nil, err
  175. }
  176. for sKey, sVal := range secret.Data {
  177. data[secref.Name][sKey] = string(sVal)
  178. }
  179. }
  180. }
  181. method := provider.Method
  182. if method == "" {
  183. method = "GET"
  184. }
  185. url, err := executeTemplateString(provider.URL, data)
  186. if err != nil {
  187. return nil, fmt.Errorf("failed to parse url: %w", err)
  188. }
  189. body, err := executeTemplate(provider.Body, data)
  190. if err != nil {
  191. return nil, fmt.Errorf("failed to parse body: %w", err)
  192. }
  193. req, err := http.NewRequestWithContext(ctx, method, url, &body)
  194. if err != nil {
  195. return nil, fmt.Errorf("failed to create request: %w", err)
  196. }
  197. if provider.Headers != nil {
  198. for hKey, hValueTpl := range provider.Headers {
  199. hValue, err := executeTemplateString(hValueTpl, data)
  200. if err != nil {
  201. return nil, fmt.Errorf("failed to parse header %s: %w", hKey, err)
  202. }
  203. req.Header.Add(hKey, hValue)
  204. }
  205. }
  206. client, err := w.getHTTPClient(ctx, provider)
  207. if err != nil {
  208. return nil, fmt.Errorf("failed to call endpoint: %w", err)
  209. }
  210. resp, err := client.Do(req)
  211. if err != nil {
  212. return nil, fmt.Errorf("failed to call endpoint: %w", err)
  213. }
  214. defer resp.Body.Close()
  215. if resp.StatusCode < 200 || resp.StatusCode >= 300 {
  216. return nil, fmt.Errorf("endpoint gave error %s", resp.Status)
  217. }
  218. return io.ReadAll(resp.Body)
  219. }
  220. func (w *WebHook) getHTTPClient(_ context.Context, provider *esv1alpha1.WebhookProvider) (*http.Client, error) {
  221. client := &http.Client{}
  222. if provider.Timeout != nil {
  223. client.Timeout = provider.Timeout.Duration
  224. }
  225. if len(provider.CABundle) != 0 || provider.CAProvider != nil {
  226. caCertPool := x509.NewCertPool()
  227. if len(provider.CABundle) > 0 {
  228. ok := caCertPool.AppendCertsFromPEM(provider.CABundle)
  229. if !ok {
  230. return nil, fmt.Errorf("failed to append cabundle")
  231. }
  232. }
  233. if provider.CAProvider != nil && w.storeKind == esv1alpha1.ClusterSecretStoreKind && provider.CAProvider.Namespace == nil {
  234. return nil, fmt.Errorf("missing namespace on CAProvider secret")
  235. }
  236. if provider.CAProvider != nil {
  237. var cert []byte
  238. var err error
  239. switch provider.CAProvider.Type {
  240. case esv1alpha1.WebhookCAProviderTypeSecret:
  241. cert, err = w.getCertFromSecret(provider)
  242. case esv1alpha1.WebhookCAProviderTypeConfigMap:
  243. cert, err = w.getCertFromConfigMap(provider)
  244. default:
  245. return nil, fmt.Errorf("unknown caprovider type: %s", provider.CAProvider.Type)
  246. }
  247. if err != nil {
  248. return nil, err
  249. }
  250. ok := caCertPool.AppendCertsFromPEM(cert)
  251. if !ok {
  252. return nil, fmt.Errorf("failed to append cabundle")
  253. }
  254. }
  255. tlsConf := &tls.Config{
  256. RootCAs: caCertPool,
  257. MinVersion: tls.VersionTLS12,
  258. }
  259. client.Transport = &http.Transport{TLSClientConfig: tlsConf}
  260. }
  261. return client, nil
  262. }
  263. func (w *WebHook) getCertFromSecret(provider *esv1alpha1.WebhookProvider) ([]byte, error) {
  264. secretRef := esmeta.SecretKeySelector{
  265. Name: provider.CAProvider.Name,
  266. Key: provider.CAProvider.Key,
  267. }
  268. if provider.CAProvider.Namespace != nil {
  269. secretRef.Namespace = provider.CAProvider.Namespace
  270. }
  271. ctx := context.Background()
  272. res, err := w.secretKeyRef(ctx, &secretRef)
  273. if err != nil {
  274. return nil, err
  275. }
  276. return []byte(res), nil
  277. }
  278. func (w *WebHook) secretKeyRef(ctx context.Context, secretRef *esmeta.SecretKeySelector) (string, error) {
  279. secret := &corev1.Secret{}
  280. ref := client.ObjectKey{
  281. Namespace: w.namespace,
  282. Name: secretRef.Name,
  283. }
  284. if (w.storeKind == esv1alpha1.ClusterSecretStoreKind) &&
  285. (secretRef.Namespace != nil) {
  286. ref.Namespace = *secretRef.Namespace
  287. }
  288. err := w.kube.Get(ctx, ref, secret)
  289. if err != nil {
  290. return "", err
  291. }
  292. keyBytes, ok := secret.Data[secretRef.Key]
  293. if !ok {
  294. return "", err
  295. }
  296. value := string(keyBytes)
  297. valueStr := strings.TrimSpace(value)
  298. return valueStr, nil
  299. }
  300. func (w *WebHook) getCertFromConfigMap(provider *esv1alpha1.WebhookProvider) ([]byte, error) {
  301. objKey := client.ObjectKey{
  302. Name: provider.CAProvider.Name,
  303. }
  304. if provider.CAProvider.Namespace != nil {
  305. objKey.Namespace = *provider.CAProvider.Namespace
  306. }
  307. configMapRef := &corev1.ConfigMap{}
  308. ctx := context.Background()
  309. err := w.kube.Get(ctx, objKey, configMapRef)
  310. if err != nil {
  311. return nil, fmt.Errorf("failed to get caprovider secret %s: %w", objKey.Name, err)
  312. }
  313. val, ok := configMapRef.Data[provider.CAProvider.Key]
  314. if !ok {
  315. return nil, fmt.Errorf("failed to get caprovider configmap %s -> %s", objKey.Name, provider.CAProvider.Key)
  316. }
  317. return []byte(val), nil
  318. }
  319. func (w *WebHook) Close(ctx context.Context) error {
  320. return nil
  321. }
  322. func executeTemplateString(tmpl string, data map[string]map[string]string) (string, error) {
  323. result, err := executeTemplate(tmpl, data)
  324. if err != nil {
  325. return "", err
  326. }
  327. return result.String(), nil
  328. }
  329. func executeTemplate(tmpl string, data map[string]map[string]string) (bytes.Buffer, error) {
  330. var result bytes.Buffer
  331. if tmpl == "" {
  332. return result, nil
  333. }
  334. urlt, err := tpl.New("webhooktemplate").Funcs(sprig.TxtFuncMap()).Funcs(template.FuncMap()).Parse(tmpl)
  335. if err != nil {
  336. return result, err
  337. }
  338. if err := urlt.Execute(&result, data); err != nil {
  339. return result, err
  340. }
  341. return result, nil
  342. }