@ -3,11 +3,13 @@ package notifier
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/secrets"
)
@ -57,17 +59,90 @@ func (c *alertmanagerCrypto) ProcessSecureSettings(ctx context.Context, orgId in
// EncryptReceiverConfigs encrypts all SecureSettings in the given receivers.
func EncryptReceiverConfigs ( c [ ] * definitions . PostableApiReceiver , encrypt definitions . EncryptFn ) error {
return encryptReceiverConfigs ( c , encrypt , true )
}
func EncryptReceiverConfigSettings ( c [ ] * definitions . PostableApiReceiver , encrypt definitions . EncryptFn ) error {
return encryptReceiverConfigs ( c , encrypt , false )
}
// encryptReceiverConfigs encrypts all SecureSettings in the given receivers.
// encryptExisting determines whether to encrypt existing secure settings.
func encryptReceiverConfigs ( c [ ] * definitions . PostableApiReceiver , encrypt definitions . EncryptFn , encryptExisting bool ) error {
// encrypt secure settings for storing them in DB
for _ , r := range c {
switch r . Type ( ) {
case definitions . GrafanaReceiverType :
for _ , gr := range r . PostableGrafanaReceivers . GrafanaManagedReceivers {
for k , v := range gr . SecureSettings {
encryptedData , err := encrypt ( context . Background ( ) , [ ] byte ( v ) )
if encryptExisting {
for k , v := range gr . SecureSettings {
encryptedData , err := encrypt ( context . Background ( ) , [ ] byte ( v ) )
if err != nil {
return fmt . Errorf ( "failed to encrypt secure settings: %w" , err )
}
gr . SecureSettings [ k ] = base64 . StdEncoding . EncodeToString ( encryptedData )
}
}
if len ( gr . Settings ) > 0 {
// We need to parse the settings to check for secret keys. If we find any, we encrypt them and
// store them in SecureSettings. This can happen from incorrect configuration or when an integration
// definition is updated to make a field secure.
settings := make ( map [ string ] any )
if err := json . Unmarshal ( gr . Settings , & settings ) ; err != nil {
return fmt . Errorf ( "integration '%s' of receiver '%s' has settings that cannot be parsed as JSON: %w" , gr . Type , gr . Name , err )
}
secretKeys , err := channels_config . GetSecretKeysForContactPointType ( gr . Type )
if err != nil {
return fmt . Errorf ( "failed to encrypt secure settings: %w" , err )
return fmt . Errorf ( "failed to get secret keys for contact point type %s: %w" , gr . Type , err )
}
secureSettings := gr . SecureSettings
if secureSettings == nil {
secureSettings = make ( map [ string ] string )
}
settingsChanged := false
secureSettingsChanged := false
for _ , secretKey := range secretKeys {
settingsValue , ok := settings [ secretKey ]
if ! ok {
continue
}
// Secrets should not be stored in settings regardless.
delete ( settings , secretKey )
settingsChanged = true
// If the secret is already encrypted, we don't need to encrypt it again.
if _ , ok := secureSettings [ secretKey ] ; ok {
continue
}
if strVal , isString := settingsValue . ( string ) ; isString {
encrypted , err := encrypt ( context . Background ( ) , [ ] byte ( strVal ) )
if err != nil {
return fmt . Errorf ( "failed to encrypt secure settings: %w" , err )
}
secureSettings [ secretKey ] = base64 . StdEncoding . EncodeToString ( encrypted )
secureSettingsChanged = true
}
}
// Defensive checks to limit the risk of unintentional edge case changes in this legacy API.
if settingsChanged {
// If we removed any secret keys from settings, we need to save the updated settings.
jsonBytes , err := json . Marshal ( settings )
if err != nil {
return err
}
gr . Settings = jsonBytes
}
if secureSettingsChanged {
// If we added any secure settings, we need to save the updated secure settings.
gr . SecureSettings = secureSettings
}
gr . SecureSettings [ k ] = base64 . StdEncoding . EncodeToString ( encryptedData )
}
}
default :
@ -94,6 +169,14 @@ func (c *alertmanagerCrypto) LoadSecureSettings(ctx context.Context, orgId int64
if err != nil {
c . log . Warn ( "Last known alertmanager configuration was invalid. Overwriting..." )
} else {
// First we encrypt the secure settings in the existing configuration.
// This is done to ensure that any secure settings incorrectly stored in Settings are encrypted and moved to
// SecureSettings. This can happen if an integration definition is updated to make a field secure.
if err := EncryptReceiverConfigSettings ( currentConfig . AlertmanagerConfig . Receivers , func ( ctx context . Context , payload [ ] byte ) ( [ ] byte , error ) {
return c . Encrypt ( ctx , payload , secrets . WithoutScope ( ) )
} ) ; err != nil {
return fmt . Errorf ( "failed to encrypt receivers: %w" , err )
}
currentReceiverMap = currentConfig . GetGrafanaReceiverMap ( )
}
}