webhook.go 12 KB

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