Alerting: Support tls config for webhook receiver (#93513)

Adds the ability to configure tls settings on the webhook receiver (e.g. to skip server certificate validation)
This commit is contained in:
Tito Lins
2024-10-22 12:44:32 +02:00
committed by GitHub
parent d722a25084
commit 71d04a326b
19 changed files with 178 additions and 48 deletions

View File

@@ -289,14 +289,15 @@ type WebhookIntegration struct {
URL string `json:"url" yaml:"url" hcl:"url"`
HTTPMethod *string `json:"httpMethod,omitempty" yaml:"httpMethod,omitempty" hcl:"http_method"`
MaxAlerts *int64 `json:"maxAlerts,omitempty" yaml:"maxAlerts,omitempty" hcl:"max_alerts"`
AuthorizationScheme *string `json:"authorization_scheme,omitempty" yaml:"authorization_scheme,omitempty" hcl:"authorization_scheme"`
AuthorizationCredentials *Secret `json:"authorization_credentials,omitempty" yaml:"authorization_credentials,omitempty" hcl:"authorization_credentials"`
User *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"basic_auth_user"`
Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"basic_auth_password"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
HTTPMethod *string `json:"httpMethod,omitempty" yaml:"httpMethod,omitempty" hcl:"http_method"`
MaxAlerts *int64 `json:"maxAlerts,omitempty" yaml:"maxAlerts,omitempty" hcl:"max_alerts"`
AuthorizationScheme *string `json:"authorization_scheme,omitempty" yaml:"authorization_scheme,omitempty" hcl:"authorization_scheme"`
AuthorizationCredentials *Secret `json:"authorization_credentials,omitempty" yaml:"authorization_credentials,omitempty" hcl:"authorization_credentials"`
User *string `json:"username,omitempty" yaml:"username,omitempty" hcl:"basic_auth_user"`
Password *Secret `json:"password,omitempty" yaml:"password,omitempty" hcl:"basic_auth_password"`
Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"`
Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"`
TLSConfig *TLSConfig `json:"tlsConfig,omitempty" yaml:"tlsConfig,omitempty" hcl:"tlsConfig,block"`
}
type WecomIntegration struct {

View File

@@ -964,6 +964,48 @@ func GetAvailableNotifiers() []*NotifierPlugin {
PropertyName: "message",
Placeholder: alertingTemplates.DefaultMessageEmbed,
},
{
Label: "TLS",
PropertyName: "tlsConfig",
Description: "TLS configuration options",
Element: ElementTypeSubform,
SubformOptions: []NotifierOption{
{
Label: "Disable certificate verification",
Element: ElementTypeCheckbox,
Description: "Do not verify the server's certificate chain and host name.",
PropertyName: "insecureSkipVerify",
Required: false,
},
{
Label: "CA Certificate",
Element: ElementTypeTextArea,
Description: "Certificate in PEM format to use when verifying the server's certificate chain.",
InputType: InputTypeText,
PropertyName: "caCertificate",
Required: false,
Secure: true,
},
{
Label: "Client Certificate",
Element: ElementTypeTextArea,
Description: "Client certificate in PEM format to use when connecting to the server.",
InputType: InputTypeText,
PropertyName: "clientCertificate",
Required: false,
Secure: true,
},
{
Label: "Client Key",
Element: ElementTypeTextArea,
Description: "Client key in PEM format to use when connecting to the server.",
InputType: InputTypeText,
PropertyName: "clientKey",
Required: false,
Secure: true,
},
},
},
},
},
{

View File

@@ -22,7 +22,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) {
{receiverType: "sensugo", expectedSecretFields: []string{"apikey"}},
{receiverType: "teams", expectedSecretFields: []string{}},
{receiverType: "telegram", expectedSecretFields: []string{"bottoken"}},
{receiverType: "webhook", expectedSecretFields: []string{"password", "authorization_credentials"}},
{receiverType: "webhook", expectedSecretFields: []string{"password", "authorization_credentials", "tlsConfig.caCertificate", "tlsConfig.clientCertificate", "tlsConfig.clientKey"}},
{receiverType: "wecom", expectedSecretFields: []string{"url", "secret"}},
{receiverType: "prometheus-alertmanager", expectedSecretFields: []string{"basicAuthPassword"}},
{receiverType: "discord", expectedSecretFields: []string{"url"}},

View File

@@ -22,6 +22,7 @@ func (s sender) SendWebhook(ctx context.Context, cmd *receivers.SendWebhookSetti
HttpHeader: cmd.HTTPHeader,
ContentType: cmd.ContentType,
Validation: cmd.Validation,
TLSConfig: cmd.TLSConfig,
})
}

View File

@@ -1,6 +1,7 @@
package notifications
import (
"crypto/tls"
"errors"
"github.com/grafana/grafana/pkg/services/user"
@@ -42,6 +43,7 @@ type SendWebhookSync struct {
HttpHeader map[string]string
ContentType string
Validation func(body []byte, statusCode int) error
TLSConfig *tls.Config
}
type SendResetPasswordEmailCommand struct {

View File

@@ -120,7 +120,6 @@ func (ns *NotificationService) Run(ctx context.Context) error {
select {
case webhook := <-ns.webhookQueue:
err := ns.sendWebRequestSync(context.Background(), webhook)
if err != nil {
ns.log.Error("Failed to send webrequest ", "error", err)
}
@@ -155,6 +154,7 @@ func (ns *NotificationService) SendWebhookSync(ctx context.Context, cmd *SendWeb
HttpMethod: cmd.HttpMethod,
HttpHeader: cmd.HttpHeader,
ContentType: cmd.ContentType,
TLSConfig: cmd.TLSConfig,
Validation: cmd.Validation,
})
}
@@ -211,7 +211,6 @@ func (ns *NotificationService) SendEmailCommandHandlerSync(ctx context.Context,
Subject: cmd.Subject,
ReplyTo: cmd.ReplyTo,
})
if err != nil {
return err
}
@@ -222,7 +221,6 @@ func (ns *NotificationService) SendEmailCommandHandlerSync(ctx context.Context,
func (ns *NotificationService) SendEmailCommandHandler(ctx context.Context, cmd *SendEmailCommand) error {
message, err := ns.buildEmailMessage(cmd)
if err != nil {
return err
}
@@ -304,7 +302,6 @@ func (ns *NotificationService) signUpStartedHandler(ctx context.Context, evt *ev
"SignUpUrl": setting.ToAbsUrl(fmt.Sprintf("signup/?email=%s&code=%s", url.QueryEscape(evt.Email), url.QueryEscape(evt.Code))),
},
})
if err != nil {
return err
}

View File

@@ -33,11 +33,3 @@ func NewFakeDisconnectedMailer() *FakeDisconnectedMailer {
func (fdm *FakeDisconnectedMailer) Send(ctx context.Context, messages ...*Message) (int, error) {
return 0, fmt.Errorf("connect: connection refused")
}
// NetClient is used to export original in test.
var NetClient = &netClient
// SetWebhookClient is used to mock in test.
func SetWebhookClient(client WebhookClient) {
netClient = client
}

View File

@@ -7,10 +7,10 @@ import (
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"time"
alertingReceivers "github.com/grafana/alerting/receivers"
"github.com/grafana/grafana/pkg/util"
)
@@ -23,6 +23,7 @@ type Webhook struct {
HttpMethod string
HttpHeader map[string]string
ContentType string
TLSConfig *tls.Config
// Validation is a function that will validate the response body and statusCode of the webhook. Any returned error will cause the webhook request to be considered failed.
// This can be useful when a webhook service communicates failures in creative ways, such as using the response body instead of the status code.
@@ -34,21 +35,6 @@ type WebhookClient interface {
Do(req *http.Request) (*http.Response, error)
}
var netTransport = &http.Transport{
TLSClientConfig: &tls.Config{
Renegotiation: tls.RenegotiateFreelyAsClient,
},
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 5 * time.Second,
}
var netClient WebhookClient = &http.Client{
Timeout: time.Second * 30,
Transport: netTransport,
}
func (ns *NotificationService) sendWebRequestSync(ctx context.Context, webhook *Webhook) error {
if webhook.HttpMethod == "" {
webhook.HttpMethod = http.MethodPost
@@ -85,7 +71,7 @@ func (ns *NotificationService) sendWebRequestSync(ctx context.Context, webhook *
request.Header.Set(k, v)
}
resp, err := netClient.Do(request)
resp, err := alertingReceivers.NewTLSClient(webhook.TLSConfig).Do(request)
if err != nil {
return redactURL(err)
}