Alerting: Remove invalid Slack URL as we migrate notification channels (#40344)

* Alerting: Remove invalid Slack URL as we migrate notification channels

Grafana will accept any type of utf8 valid string as the Slack URL and will simply fail as we try to deliver the notification of the channel. The Alertmanager will fail to apply a configuration if the URL of the Slack Receiver is invalid.

This change takes that into account by removing the URL for the receiver as we migrate notification channels that do not pass the url validation. As we assume the notification was not being delivered to being with.

* Add a log line when we modify the channel

Co-authored-by: Yuriy Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
gotjosh 2021-10-12 23:55:39 +01:00 committed by GitHub
parent 47f09fca8d
commit 2448123a65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 136 additions and 0 deletions

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/url"
"sort"
"strings"
@ -127,6 +128,22 @@ func (m *migration) makeReceiverAndRoute(ruleUid string, orgID int64, channelUid
if err != nil {
return err
}
// Grafana accepts any type of string as a URL for the Slack notification channel.
// However, the Alertmanager will fail if provided with an invalid URL we have two options at this point:
// Either we fail the migration or remove the URL, we've chosen the latter and assume that the notification
// channel was broken to begin with.
if c.Type == "slack" {
u, ok := decryptedSecureSettings["url"]
if ok {
_, err := url.Parse(u)
if err != nil {
m.mg.Logger.Warn("slack notification channel had invalid URL, removing", "name", c.Name, "uid", c.Uid, "org", c.OrgID)
delete(decryptedSecureSettings, "url")
}
}
}
portedChannels = append(portedChannels, &PostableGrafanaReceiver{
UID: uid,
Name: c.Name,

View File

@ -0,0 +1,119 @@
package ualert
import (
"fmt"
"math/rand"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/util"
)
func Test_makeReceiverAndRoute(t *testing.T) {
emptyMigration := func() *migration {
return &migration{
mg: &migrator.Migrator{
Logger: log.New("test"),
},
migratedChannelsPerOrg: make(map[int64]map[*notificationChannel]struct{}),
portedChannelGroupsPerOrg: make(map[int64]map[string]string),
seenChannelUIDs: make(map[string]struct{}),
}
}
generateChannel := func(channelType string, settings map[string]interface{}, secureSettings map[string]string) *notificationChannel {
uid := util.GenerateShortUID()
return &notificationChannel{
ID: rand.Int63(),
OrgID: rand.Int63(),
Uid: uid,
Name: fmt.Sprintf("Test-%s", uid),
Type: channelType,
DisableResolveMessage: rand.Int63()%2 == 0,
IsDefault: rand.Int63()%2 == 0,
Settings: simplejson.NewFromAny(settings),
SecureSettings: GetEncryptedJsonData(secureSettings),
}
}
t.Run("Slack channel is migrated", func(t *testing.T) {
t.Run("url is removed if it is invalid (secure settings)", func(t *testing.T) {
secureSettings := map[string]string{
"url": invalidUri,
"token": util.GenerateShortUID(),
}
settings := map[string]interface{}{
"test": "data",
"some_map": map[string]interface{}{
"test": rand.Int63(),
},
}
channel := generateChannel("slack", settings, secureSettings)
channelsUid := []interface{}{
channel.Uid,
}
defaultChannels := make([]*notificationChannel, 0)
allChannels := map[interface{}]*notificationChannel{
channel.Uid: channel,
}
apiReceiver, _, err := emptyMigration().makeReceiverAndRoute(util.GenerateShortUID(), channel.OrgID, channelsUid, defaultChannels, allChannels)
require.NoError(t, err)
require.Len(t, apiReceiver.GrafanaManagedReceivers, 1)
receiver := apiReceiver.GrafanaManagedReceivers[0]
require.NotContains(t, receiver.SecureSettings, "url")
require.Contains(t, receiver.SecureSettings, "token")
require.Equal(t, secureSettings["token"], receiver.SecureSettings["token"])
actualSettings, err := receiver.Settings.Map()
require.NoError(t, err)
require.Equal(t, settings, actualSettings)
})
t.Run("url is removed if it is invalid (settings)", func(t *testing.T) {
secureSettings := map[string]string{
"token": util.GenerateShortUID(),
}
settings := map[string]interface{}{
"url": invalidUri,
"test": "data",
"some_map": map[string]interface{}{
"test": rand.Int63(),
},
}
channel := generateChannel("slack", settings, secureSettings)
channelsUid := []interface{}{
channel.Uid,
}
defaultChannels := make([]*notificationChannel, 0)
allChannels := map[interface{}]*notificationChannel{
channel.Uid: channel,
}
apiReceiver, _, err := emptyMigration().makeReceiverAndRoute(util.GenerateShortUID(), channel.OrgID, channelsUid, defaultChannels, allChannels)
require.NoError(t, err)
require.Len(t, apiReceiver.GrafanaManagedReceivers, 1)
receiver := apiReceiver.GrafanaManagedReceivers[0]
require.NotContains(t, receiver.SecureSettings, "url")
require.Contains(t, receiver.SecureSettings, "token")
require.Equal(t, secureSettings["token"], receiver.SecureSettings["token"])
actualSettings, err := receiver.Settings.Map()
require.NoError(t, err)
delete(settings, "url")
require.Equal(t, settings, actualSettings)
})
})
}
const invalidUri = "<22>6<EFBFBD>M<EFBFBD><4D>)uk譹1(<28>h`$<24>o<EFBFBD>N>mĕ<6D><C495><EFBFBD><EFBFBD>cS2<53>dh![ę<> <09><><EFBFBD>`csB<73>!<21><>OSxP<78>{<7B>"