webhook.go 12 KB

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