@ -3,6 +3,7 @@ package api
import (
"context"
"crypto/md5"
"encoding/base64"
"encoding/json"
"math/rand"
"net/http"
@ -16,6 +17,7 @@ import (
amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/response"
@ -329,6 +331,118 @@ func TestAlertmanagerConfig(t *testing.T) {
} )
}
func TestGetAlertmanagerConfiguration_NewSecretField ( t * testing . T ) {
// This test has the following goals:
// Given:
// - A saved notifier config with an existing secret field stored unencrypted in Settings.
// Ensure:
// - The secret field is not returned in plaintext.
// - The secret field is returned as a bool in SecureFields.
// - The secret field is correctly saved and encrypted in SecureSettings when saving the notifier config without changes.
// - The secret field is removed from Settings when saving the notifier config.
sut := createSut ( t )
orgId := int64 ( 1 )
// This config has the secret field "integrationKey" stored incorrectly and unencrypted in Settings.
configs := map [ int64 ] * ngmodels . AlertConfiguration {
1 : {
OrgID : orgId ,
AlertmanagerConfiguration : ` {
"alertmanager_config" : {
"route" : {
"receiver" : "configWithNewlySecretSetting"
} ,
"receivers" : [ {
"name" : "configWithNewlySecretSetting" ,
"grafana_managed_receiver_configs" : [ {
"uid" : "configWithNewlySecretSetting-uid" ,
"name" : "configWithNewlySecretSetting" ,
"type" : "pagerduty" ,
"settings" : { "integrationKey" : "unencrypted secure secret" } ,
"secureSettings" : { }
} ]
} ]
}
}
` ,
CreatedAt : time . Now ( ) . Unix ( ) ,
Default : false ,
} ,
}
// Store the config as-is in the database. Bypasses normal save route so it doesn't get pre-emptively fixed.
mam := createMultiOrgAlertmanager ( t , configs )
sut . mam = mam
rc := createRequestCtxInOrg ( orgId )
res := sut . RouteGetAlertingConfig ( rc )
gettable := asGettableUserConfig ( t , res )
integration := gettable . GetGrafanaReceiverMap ( ) [ "configWithNewlySecretSetting-uid" ]
require . NotNil ( t , integration )
var settings map [ string ] string
err := json . Unmarshal ( integration . Settings , & settings )
require . NoError ( t , err )
// The secret field "integrationKey" should not be returned in plaintext.
assert . NotEqual ( t , "unencrypted secure secret" , settings [ "integrationKey" ] )
// Just in case let's look for the unencrypted value anywhere in the settings.
assert . NotContains ( t , string ( integration . Settings ) , "unencrypted" )
// The secret fields should be returned as a bool in SecureFields.
assert . True ( t , integration . SecureFields [ "integrationKey" ] )
// Now we save the config without changes. This should encrypt the field "integrationKey" into SecureSettings and
// remove it from Settings.
// Simulates FE-API interaction, "integrationKey" is not sent in Settings as the caller.
// Instead, it leaves it out of "SecureSettings" to indicate the API should keep the existing value.
var postWithoutChanges = ` {
"alertmanager_config" : {
"route" : {
"receiver" : "configWithNewlySecretSetting"
} ,
"receivers" : [ {
"name" : "configWithNewlySecretSetting" ,
"grafana_managed_receiver_configs" : [ {
"uid" : "configWithNewlySecretSetting-uid" ,
"name" : "configWithNewlySecretSetting" ,
"type" : "pagerduty" ,
"settings" : { } ,
"secureSettings" : { }
} ]
} ]
}
}
`
postable := createAmConfigRequest ( t , postWithoutChanges )
res = sut . RoutePostAlertingConfig ( rc , postable )
require . Equal ( t , 202 , res . Status ( ) )
// Check that the secret field "integrationKey" is now encrypted in SecureSettings.
savedConfig := & apimodels . PostableUserConfig { }
err = json . Unmarshal ( [ ] byte ( configs [ orgId ] . AlertmanagerConfiguration ) , savedConfig )
require . NoError ( t , err )
savedIntegration := savedConfig . GetGrafanaReceiverMap ( ) [ "configWithNewlySecretSetting-uid" ]
require . NotNil ( t , savedIntegration )
// No longer in Settings.
assert . Equal ( t , "{}" , string ( savedIntegration . Settings ) )
// Encrypted in SecureSettings.
secureSecret := savedIntegration . SecureSettings [ "integrationKey" ]
assert . NotEmpty ( t , secureSecret )
encryptedSecret , err := base64 . StdEncoding . DecodeString ( secureSecret )
require . NoError ( t , err )
// No access to .Decrypt, but we can check that it's not the same as the unencrypted value.
assert . NotEqual ( t , "unencrypted secure secret" , string ( encryptedSecret ) )
}
func TestAlertmanagerAutogenConfig ( t * testing . T ) {
createSutForAutogen := func ( t * testing . T ) ( AlertmanagerSrv , map [ int64 ] * ngmodels . AlertConfiguration ) {
sut := createSut ( t )