Skip to content

Commit

Permalink
Move buildTLSConfig to receivers.TLSConfig.ToTLSConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akhmetov committed Sep 12, 2024
1 parent b5db19e commit ca50133
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 39 deletions.
7 changes: 7 additions & 0 deletions receivers/mqtt/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"math/rand"
"net/url"

"github.com/grafana/alerting/receivers"
"github.com/grafana/alerting/templates"
Expand Down Expand Up @@ -76,5 +77,11 @@ func NewConfig(jsonData json.RawMessage, decryptFn receivers.DecryptFunc) (Confi
settings.TLSConfig.ClientCertificate = decryptFn("tlsConfig.clientCertificate", settings.TLSConfig.ClientCertificate)
settings.TLSConfig.ClientKey = decryptFn("tlsConfig.clientKey", settings.TLSConfig.ClientKey)

parsedURL, err := url.Parse(settings.BrokerURL)
if err != nil {
return Config{}, errors.New("Failed to parse broker URL")
}
settings.TLSConfig.ServerName = parsedURL.Hostname()

return settings, nil
}
14 changes: 11 additions & 3 deletions receivers/mqtt/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ func TestNewConfig(t *testing.T) {
BrokerURL: "tcp://localhost:1883",
Topic: "grafana/alerts",
MessageFormat: MessageFormatJSON,
TLSConfig: &receivers.TLSConfig{},
TLSConfig: &receivers.TLSConfig{
ServerName: "localhost",
},
},
},
{
Expand All @@ -60,6 +62,7 @@ func TestNewConfig(t *testing.T) {
MessageFormat: MessageFormatJSON,
TLSConfig: &receivers.TLSConfig{
InsecureSkipVerify: true,
ServerName: "localhost",
},
},
},
Expand All @@ -72,7 +75,9 @@ func TestNewConfig(t *testing.T) {
Topic: "grafana/alerts",
MessageFormat: MessageFormatJSON,
ClientID: "test-client-id",
TLSConfig: &receivers.TLSConfig{},
TLSConfig: &receivers.TLSConfig{
ServerName: "localhost",
},
},
},
{
Expand All @@ -88,7 +93,9 @@ func TestNewConfig(t *testing.T) {
MessageFormat: MessageFormatJSON,
Username: "grafana",
Password: "testpasswd",
TLSConfig: &receivers.TLSConfig{},
TLSConfig: &receivers.TLSConfig{
ServerName: "localhost",
},
},
},
{
Expand All @@ -106,6 +113,7 @@ func TestNewConfig(t *testing.T) {
MessageFormat: MessageFormatJSON,
TLSConfig: &receivers.TLSConfig{
InsecureSkipVerify: false,
ServerName: "localhost",
CACertificate: "test-ca-cert",
ClientKey: "test-client-key",
ClientCertificate: "test-client-cert",
Expand Down
40 changes: 4 additions & 36 deletions receivers/mqtt/mqtt.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ package mqtt
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"net/url"

"github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types"
Expand Down Expand Up @@ -71,7 +69,10 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
return false, err
}

tlsCfg, err := n.buildTLSConfig()
var tlsCfg *tls.Config
if n.settings.TLSConfig != nil {
tlsCfg, err = n.settings.TLSConfig.ToTLSConfig()
}
if err != nil {
n.log.Error("Failed to build TLS config", "error", err.Error())
return false, fmt.Errorf("failed to build TLS config: %s", err.Error())
Expand Down Expand Up @@ -113,39 +114,6 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error)
return true, nil
}

func (n *Notifier) buildTLSConfig() (*tls.Config, error) {
if n.settings.TLSConfig == nil {
return nil, nil
}

parsedURL, err := url.Parse(n.settings.BrokerURL)
if err != nil {
n.log.Error("Failed to parse broker URL", "error", err.Error())
return nil, err
}

tlsCfg := &tls.Config{
InsecureSkipVerify: n.settings.TLSConfig.InsecureSkipVerify,
ServerName: parsedURL.Hostname(),
}

if n.settings.TLSConfig.CACertificate != "" {
tlsCfg.RootCAs = x509.NewCertPool()
tlsCfg.RootCAs.AppendCertsFromPEM([]byte(n.settings.TLSConfig.CACertificate))
}

if n.settings.TLSConfig.ClientCertificate != "" || n.settings.TLSConfig.ClientKey != "" {
cert, err := tls.X509KeyPair([]byte(n.settings.TLSConfig.ClientCertificate), []byte(n.settings.TLSConfig.ClientKey))
if err != nil {
n.log.Error("Failed to load client certificate", "error", err.Error())
return nil, err
}
tlsCfg.Certificates = append(tlsCfg.Certificates, cert)
}

return tlsCfg, nil
}

func (n *Notifier) buildMessage(ctx context.Context, as ...*types.Alert) (string, error) {
groupKey, err := notify.ExtractGroupKey(ctx)
if err != nil {
Expand Down
28 changes: 28 additions & 0 deletions receivers/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bytes"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"net"
Expand Down Expand Up @@ -46,6 +48,32 @@ type TLSConfig struct {
ClientCertificate string `json:"clientCertificate,omitempty" yaml:"clientCertificate,omitempty"`
ClientKey string `json:"clientKey,omitempty" yaml:"clientKey,omitempty"`
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty"`
ServerName string
}

func (cfg *TLSConfig) ToTLSConfig() (*tls.Config, error) {
tlsCfg := &tls.Config{
InsecureSkipVerify: cfg.InsecureSkipVerify,
ServerName: cfg.ServerName,
}

if cfg.CACertificate != "" {
tlsCfg.RootCAs = x509.NewCertPool()
ok := tlsCfg.RootCAs.AppendCertsFromPEM([]byte(cfg.CACertificate))
if !ok {
return nil, errors.New("Unable to use the provided CA certificate")
}
}

if cfg.ClientCertificate != "" || cfg.ClientKey != "" {
cert, err := tls.X509KeyPair([]byte(cfg.ClientCertificate), []byte(cfg.ClientKey))
if err != nil {
return nil, fmt.Errorf("failed to load client certificate: %w", err)
}
tlsCfg.Certificates = append(tlsCfg.Certificates, cert)
}

return tlsCfg, nil
}

// SendHTTPRequest sends an HTTP request.
Expand Down
110 changes: 110 additions & 0 deletions receivers/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package receivers

import (
"crypto/tls"
"testing"

"github.com/stretchr/testify/require"
)

// Test certificates from https://github.com/golang/go/blob/4f852b9734249c063928b34a02dd689e03a8ab2c/src/crypto/tls/tls_test.go#L34
const (
testRsaCertPem = `-----BEGIN CERTIFICATE-----
MIIB0zCCAX2gAwIBAgIJAI/M7BYjwB+uMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTIwOTEyMjE1MjAyWhcNMTUwOTEyMjE1MjAyWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANLJ
hPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wok/4xIA+ui35/MmNa
rtNuC+BdZ1tMuVCPFZcCAwEAAaNQME4wHQYDVR0OBBYEFJvKs8RfJaXTH08W+SGv
zQyKn0H8MB8GA1UdIwQYMBaAFJvKs8RfJaXTH08W+SGvzQyKn0H8MAwGA1UdEwQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADQQBJlffJHybjDGxRMqaRmDhX0+6v02TUKZsW
r5QuVbpQhH6u+0UgcW0jp9QwpxoPTLTWGXEWBBBurxFwiCBhkQ+V
-----END CERTIFICATE-----`

testRsaKeyPem = `-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANLJhPHhITqQbPklG3ibCVxwGMRfp/v4XqhfdQHdcVfHap6NQ5Wo
k/4xIA+ui35/MmNartNuC+BdZ1tMuVCPFZcCAwEAAQJAEJ2N+zsR0Xn8/Q6twa4G
6OB1M1WO+k+ztnX/1SvNeWu8D6GImtupLTYgjZcHufykj09jiHmjHx8u8ZZB/o1N
MQIhAPW+eyZo7ay3lMz1V01WVjNKK9QSn1MJlb06h/LuYv9FAiEA25WPedKgVyCW
SmUwbPw8fnTcpqDWE3yTO3vKcebqMSsCIBF3UmVue8YU3jybC3NxuXq3wNm34R8T
xVLHwDXh/6NJAiEAl2oHGGLz64BuAfjKrqwz7qMYr9HCLIe/YsoWq/olzScCIQDi
D2lWusoe2/nEqfDVVWGWlyJ7yOmqaVm/iNUN9B2N2g==
-----END RSA PRIVATE KEY-----`
)

func TestNewTLSConfig(t *testing.T) {
tests := []struct {
name string
cfg TLSConfig
expectedTLSConfig tls.Config
expectError bool
}{
{
name: "empty TLSConfig",
cfg: TLSConfig{},
expectError: false,
},
{
name: "valid CA certificate",
cfg: TLSConfig{
CACertificate: string(testRsaCertPem),
},
expectError: false,
},
{
name: "invalid CA certificate",
cfg: TLSConfig{
CACertificate: "invalid-cert",
},
expectError: true,
},
{
name: "valid client certificate and key",
cfg: TLSConfig{
ClientCertificate: string(testRsaCertPem),
ClientKey: string(testRsaKeyPem),
},
expectError: false,
},
{
name: "invalid client certificate",
cfg: TLSConfig{
ClientCertificate: string(testRsaCertPem),
},
expectError: true,
},
{
name: "set InsecureSkipVerify and ServerName",
cfg: TLSConfig{
InsecureSkipVerify: true,
ServerName: "example.com",
},
expectError: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tlsCfg, err := tt.cfg.ToTLSConfig()

if tt.expectError {
require.Error(t, err)
require.Nil(t, tlsCfg)
} else {
require.NoError(t, err)

require.Equal(t, tt.cfg.InsecureSkipVerify, tlsCfg.InsecureSkipVerify, "InsecureSkipVerify mismatch")
require.Equal(t, tt.cfg.ServerName, tlsCfg.ServerName, "ServerName mismatch")

if tt.cfg.CACertificate != "" {
require.NotNil(t, tlsCfg.RootCAs, "expected RootCAs to be initialized, but it was nil")
}

if tt.cfg.ClientCertificate != "" && tt.cfg.ClientKey != "" {
require.NotEmpty(t, tlsCfg.Certificates, "expected Certificates to be set, but it was empty")
}
}
})
}
}

0 comments on commit ca50133

Please sign in to comment.