webhook.go 12 KB

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