Repository: grafana/terraform-provider-grafana

Issue:
Support disabling TLS verification for Webhook Contact Points
### Feature Request

Grafana recently [added support for webhooks to configure TLS verification](https://github.com/grafana/grafana/pull/93513). I'd love for that to be available via the terraform provider.

Discussion:
@titolins: Hey @aldman123 , tks for raising this. It's definitely a good point, we should support this 👍 

I've moved it to the backlog and have it in my radar, but I'll probably only be able to tackle this in a couple of weeks. In the meantime, please feel free to submit any PR's if you'd like to give it a shot 😄 @titolins: Hey @aldman123 👋 

This should be done now. I've tested on my instance and it seems to be working well. Would be nice if you could confirm from your side as well though.

In case it doesn't, please feel free to ping me and/or reopen this issue (or open a new one).

Thanks!@aldman123: Thank you so much! Once the next release comes out I'll give it a shot on my end. I'll let you know if I run into issues.

Solution patch:
diff --git a/docs/resources/contact_point.md b/docs/resources/contact_point.md
index 273d6503..c3a21660 100644
--- a/docs/resources/contact_point.md
+++ b/docs/resources/contact_point.md
@@ -525,6 +525,7 @@ Optional:
 - `message` (String) Custom message. You can use template variables.
 - `settings` (Map of String, Sensitive) Additional custom properties to attach to the notifier. Defaults to `map[]`.
 - `title` (String) Templated title of the message.
+- `tls_config` (Map of String, Sensitive) Allows configuring TLS for the webhook notifier.
 
 Read-Only:
 
diff --git a/examples/resources/grafana_contact_point/_acc_receiver_types.tf b/examples/resources/grafana_contact_point/_acc_receiver_types.tf
index b16748e6..cf5db0a9 100644
--- a/examples/resources/grafana_contact_point/_acc_receiver_types.tf
+++ b/examples/resources/grafana_contact_point/_acc_receiver_types.tf
@@ -169,6 +169,12 @@ resource "grafana_contact_point" "receiver_types" {
     max_alerts          = 100
     message             = "Custom message"
     title               = "Custom title"
+    tls_config = {
+      insecure_skip_verify = true
+      ca_certificate       = "ca.crt"
+      client_certificate   = "client.crt"
+      client_key           = "client.key"
+    }
   }
 
   wecom {
diff --git a/internal/resources/grafana/resource_alerting_contact_point.go b/internal/resources/grafana/resource_alerting_contact_point.go
index 9f43d236..b99af794 100644
--- a/internal/resources/grafana/resource_alerting_contact_point.go
+++ b/internal/resources/grafana/resource_alerting_contact_point.go
@@ -3,6 +3,7 @@ package grafana
 import (
 	"context"
 	"fmt"
+	"log"
 	"reflect"
 	"strconv"
 	"time"
@@ -422,7 +423,7 @@ func packNotifierStringField(gfSettings, tfSettings *map[string]interface{}, gfK
 func packSecureFields(tfSettings, state map[string]interface{}, secureFields []string) {
 	for _, tfKey := range secureFields {
 		if v, ok := state[tfKey]; ok && v != nil {
-			tfSettings[tfKey] = v.(string)
+			tfSettings[tfKey] = v
 		}
 	}
 }
@@ -445,3 +446,67 @@ func getNotifierConfigFromStateWithUID(data *schema.ResourceData, n notifier, ui
 
 	return nil
 }
+
+func unpackTLSConfig(tfSettings, gfSettings map[string]interface{}) {
+	tlsConfig, ok := tfSettings["tls_config"].(map[string]any)
+	if !ok || len(tlsConfig) == 0 {
+		return
+	}
+
+	gfTLSConfig := make(map[string]any)
+
+	if is, ok := tlsConfig["insecure_skip_verify"].(string); ok {
+		if insecureSkipVerify, err := strconv.ParseBool(is); err != nil {
+			log.Printf("[WARN] failed to parse 'insecure_skip_verify': %s", err)
+		} else {
+			gfTLSConfig["insecureSkipVerify"] = insecureSkipVerify
+		}
+	}
+
+	if caCertificate, ok := tlsConfig["ca_certificate"].(string); ok {
+		gfTLSConfig["caCertificate"] = caCertificate
+	}
+
+	if clientCertificate, ok := tlsConfig["client_certificate"].(string); ok {
+		gfTLSConfig["clientCertificate"] = clientCertificate
+	}
+
+	if clientKey, ok := tlsConfig["client_key"].(string); ok {
+		gfTLSConfig["clientKey"] = clientKey
+	}
+
+	gfSettings["tlsConfig"] = gfTLSConfig
+}
+
+func packTLSConfig(gfSettings, tfSettings map[string]interface{}) {
+	tlsConfig, ok := gfSettings["tlsConfig"].(map[string]any)
+	if !ok || len(tlsConfig) == 0 {
+		return
+	}
+
+	tfTLSConfig := make(map[string]any)
+
+	if is, ok := tlsConfig["insecureSkipVerify"].(string); ok {
+		if insecureSkipVerify, err := strconv.ParseBool(is); err != nil {
+			log.Printf("[WARN] failed to parse 'insecure_skip_verify': %s", err)
+		} else {
+			tfTLSConfig["insecure_skip_verify"] = insecureSkipVerify
+		}
+	}
+
+	if caCertificate, ok := tlsConfig["caCertificate"].(string); ok {
+		tfTLSConfig["ca_certificate"] = caCertificate
+	}
+
+	if clientCertificate, ok := tlsConfig["clientCertificate"].(string); ok {
+		tfTLSConfig["client_certificate"] = clientCertificate
+	}
+
+	if clientKey, ok := tlsConfig["clientKey"].(string); ok {
+		tfTLSConfig["client_key"] = clientKey
+	}
+
+	delete(gfSettings, "tlsConfig")
+
+	tfSettings["tls_config"] = tfTLSConfig
+}
diff --git a/internal/resources/grafana/resource_alerting_contact_point_notifiers.go b/internal/resources/grafana/resource_alerting_contact_point_notifiers.go
index 1127bf85..e0283458 100644
--- a/internal/resources/grafana/resource_alerting_contact_point_notifiers.go
+++ b/internal/resources/grafana/resource_alerting_contact_point_notifiers.go
@@ -623,8 +623,7 @@ func (o lineNotifier) unpack(raw interface{}, name string) *models.EmbeddedConta
 	}
 }
 
-type oncallNotifier struct {
-}
+type oncallNotifier struct{}
 
 var _ notifier = (*oncallNotifier)(nil)
 
@@ -2112,7 +2111,7 @@ func (w webhookNotifier) meta() notifierMeta {
 		field:        "webhook",
 		typeStr:      "webhook",
 		desc:         "A contact point that sends notifications to an arbitrary webhook, using the Prometheus webhook format defined here: https://prometheus.io/docs/alerting/latest/configuration/#webhook_config",
-		secureFields: []string{"basic_auth_password", "authorization_credentials"},
+		secureFields: []string{"basic_auth_password", "authorization_credentials", "tls_config"},
 	}
 }
 
@@ -2165,6 +2164,12 @@ func (w webhookNotifier) schema() *schema.Resource {
 		Optional:    true,
 		Description: "Templated title of the message.",
 	}
+	r.Schema["tls_config"] = &schema.Schema{
+		Type:        schema.TypeMap,
+		Optional:    true,
+		Sensitive:   true,
+		Description: "Allows configuring TLS for the webhook notifier.",
+	}
 	return r
 }
 
@@ -2180,6 +2185,7 @@ func (w webhookNotifier) pack(p *models.EmbeddedContactPoint, data *schema.Resou
 	packNotifierStringField(&settings, &notifier, "authorization_credentials", "authorization_credentials")
 	packNotifierStringField(&settings, &notifier, "message", "message")
 	packNotifierStringField(&settings, &notifier, "title", "title")
+	packTLSConfig(settings, notifier)
 	if v, ok := settings["maxAlerts"]; ok && v != nil {
 		switch typ := v.(type) {
 		case int:
@@ -2216,6 +2222,7 @@ func (w webhookNotifier) unpack(raw interface{}, name string) *models.EmbeddedCo
 	unpackNotifierStringField(&json, &settings, "authorization_credentials", "authorization_credentials")
 	unpackNotifierStringField(&json, &settings, "message", "message")
 	unpackNotifierStringField(&json, &settings, "title", "title")
+	unpackTLSConfig(json, settings)
 	if v, ok := json["max_alerts"]; ok && v != nil {
 		switch typ := v.(type) {
 		case int:



Test patch:
diff --git a/internal/resources/grafana/resource_alerting_contact_point_test.go b/internal/resources/grafana/resource_alerting_contact_point_test.go
index 5ebda694..2f46f0aa 100644
--- a/internal/resources/grafana/resource_alerting_contact_point_test.go
+++ b/internal/resources/grafana/resource_alerting_contact_point_test.go
@@ -304,6 +304,10 @@ func TestAccContactPoint_notifiers(t *testing.T) {
 					resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.max_alerts", "100"),
 					resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.message", "Custom message"),
 					resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.title", "Custom title"),
+					resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.insecure_skip_verify", "true"),
+					resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.ca_certificate", "ca.crt"),
+					resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.client_certificate", "client.crt"),
+					resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "webhook.0.tls_config.client_key", "client.key"),
 					// wecom
 					resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "wecom.#", "1"),
 					resource.TestCheckResourceAttr("grafana_contact_point.receiver_types", "wecom.0.url", "http://wecom-url"),