mirror of https://github.com/grafana/grafana
Encryption: Refactor securejsondata.SecureJsonData to stop relying on global functions (#38865)
* Encryption: Add support to encrypt/decrypt sjd * Add datasources.Service as a proxy to datasources db operations * Encrypt ds.SecureJsonData before calling SQLStore * Move ds cache code into ds service * Fix tlsmanager tests * Fix pluginproxy tests * Remove some securejsondata.GetEncryptedJsonData usages * Add pluginsettings.Service as a proxy for plugin settings db operations * Add AlertNotificationService as a proxy for alert notification db operations * Remove some securejsondata.GetEncryptedJsonData usages * Remove more securejsondata.GetEncryptedJsonData usages * Fix lint errors * Minor fixes * Remove encryption global functions usages from ngalert * Fix lint errors * Minor fixes * Minor fixes * Remove securejsondata.DecryptedValue usage * Refactor the refactor * Remove securejsondata.DecryptedValue usage * Move securejsondata to migrations package * Move securejsondata to migrations package * Minor fix * Fix integration test * Fix integration tests * Undo undesired changes * Fix tests * Add context.Context into encryption methods * Fix tests * Fix tests * Fix tests * Trigger CI * Fix test * Add names to params of encryption service interface * Remove bus from CacheServiceImpl * Add logging * Add keys to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Add missing key to logger Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Undo changes in markdown files * Fix formatting * Add context to secrets service * Rename decryptSecureJsonData to decryptSecureJsonDataFn * Name args in GetDecryptedValueFn * Add template back to NewAlertmanagerNotifier * Copy GetDecryptedValueFn to ngalert * Add logging to pluginsettings * Fix pluginsettings test Co-authored-by: Tania B <yalyna.ts@gmail.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>pull/40153/head
parent
da813877fb
commit
722c414fef
@ -1,23 +0,0 @@ |
||||
package models |
||||
|
||||
var pluginSettingDecryptionCache = secureJSONDecryptionCache{ |
||||
cache: make(map[int64]cachedDecryptedJSON), |
||||
} |
||||
|
||||
// DecryptedValues returns cached decrypted values from secureJsonData.
|
||||
func (ps *PluginSetting) DecryptedValues() map[string]string { |
||||
pluginSettingDecryptionCache.Lock() |
||||
defer pluginSettingDecryptionCache.Unlock() |
||||
|
||||
if item, present := pluginSettingDecryptionCache.cache[ps.Id]; present && ps.Updated.Equal(item.updated) { |
||||
return item.json |
||||
} |
||||
|
||||
json := ps.SecureJsonData.Decrypt() |
||||
pluginSettingDecryptionCache.cache[ps.Id] = cachedDecryptedJSON{ |
||||
updated: ps.Updated, |
||||
json: json, |
||||
} |
||||
|
||||
return json |
||||
} |
@ -1,70 +0,0 @@ |
||||
package models |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/grafana/grafana/pkg/components/securejsondata" |
||||
) |
||||
|
||||
// clearPluginSettingDecryptionCache clears the datasource decryption cache.
|
||||
func clearPluginSettingDecryptionCache() { |
||||
pluginSettingDecryptionCache.Lock() |
||||
defer pluginSettingDecryptionCache.Unlock() |
||||
|
||||
pluginSettingDecryptionCache.cache = make(map[int64]cachedDecryptedJSON) |
||||
} |
||||
|
||||
func TestPluginSettingDecryptionCache(t *testing.T) { |
||||
t.Run("When plugin settings hasn't been updated, encrypted JSON should be fetched from cache", func(t *testing.T) { |
||||
clearPluginSettingDecryptionCache() |
||||
|
||||
ps := PluginSetting{ |
||||
Id: 1, |
||||
JsonData: map[string]interface{}{}, |
||||
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{ |
||||
"password": "password", |
||||
}), |
||||
} |
||||
|
||||
// Populate cache
|
||||
password, ok := ps.DecryptedValues()["password"] |
||||
require.Equal(t, "password", password) |
||||
require.True(t, ok) |
||||
|
||||
ps.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ |
||||
"password": "", |
||||
}) |
||||
|
||||
require.Equal(t, "password", password) |
||||
require.True(t, ok) |
||||
}) |
||||
|
||||
t.Run("When plugin settings is updated, encrypted JSON should not be fetched from cache", func(t *testing.T) { |
||||
clearPluginSettingDecryptionCache() |
||||
|
||||
ps := PluginSetting{ |
||||
Id: 1, |
||||
JsonData: map[string]interface{}{}, |
||||
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{ |
||||
"password": "password", |
||||
}), |
||||
} |
||||
|
||||
// Populate cache
|
||||
password, ok := ps.DecryptedValues()["password"] |
||||
require.Equal(t, "password", password) |
||||
require.True(t, ok) |
||||
|
||||
ps.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ |
||||
"password": "", |
||||
}) |
||||
ps.Updated = time.Now() |
||||
|
||||
password, ok = ps.DecryptedValues()["password"] |
||||
require.Empty(t, password) |
||||
require.True(t, ok) |
||||
}) |
||||
} |
@ -0,0 +1,102 @@ |
||||
package alerting |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/encryption" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
type AlertNotificationService struct { |
||||
Bus bus.Bus |
||||
SQLStore *sqlstore.SQLStore |
||||
EncryptionService encryption.Service |
||||
} |
||||
|
||||
func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService encryption.Service, |
||||
) *AlertNotificationService { |
||||
s := &AlertNotificationService{ |
||||
Bus: bus, |
||||
SQLStore: store, |
||||
EncryptionService: encryptionService, |
||||
} |
||||
|
||||
s.Bus.AddHandler(s.GetAlertNotifications) |
||||
s.Bus.AddHandlerCtx(s.CreateAlertNotificationCommand) |
||||
s.Bus.AddHandlerCtx(s.UpdateAlertNotification) |
||||
s.Bus.AddHandler(s.DeleteAlertNotification) |
||||
s.Bus.AddHandler(s.GetAllAlertNotifications) |
||||
s.Bus.AddHandlerCtx(s.GetOrCreateAlertNotificationState) |
||||
s.Bus.AddHandlerCtx(s.SetAlertNotificationStateToCompleteCommand) |
||||
s.Bus.AddHandlerCtx(s.SetAlertNotificationStateToPendingCommand) |
||||
s.Bus.AddHandler(s.GetAlertNotificationsWithUid) |
||||
s.Bus.AddHandler(s.UpdateAlertNotificationWithUid) |
||||
s.Bus.AddHandler(s.DeleteAlertNotificationWithUid) |
||||
s.Bus.AddHandler(s.GetAlertNotificationsWithUidToSend) |
||||
s.Bus.AddHandlerCtx(s.HandleNotificationTestCommand) |
||||
|
||||
return s |
||||
} |
||||
|
||||
func (s *AlertNotificationService) GetAlertNotifications(query *models.GetAlertNotificationsQuery) error { |
||||
return s.SQLStore.GetAlertNotifications(query) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) CreateAlertNotificationCommand(ctx context.Context, cmd *models.CreateAlertNotificationCommand) error { |
||||
var err error |
||||
cmd.EncryptedSecureSettings, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureSettings, setting.SecretKey) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return s.SQLStore.CreateAlertNotificationCommand(cmd) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) UpdateAlertNotification(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error { |
||||
var err error |
||||
cmd.EncryptedSecureSettings, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureSettings, setting.SecretKey) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return s.SQLStore.UpdateAlertNotification(cmd) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) DeleteAlertNotification(cmd *models.DeleteAlertNotificationCommand) error { |
||||
return s.SQLStore.DeleteAlertNotification(cmd) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) GetAllAlertNotifications(query *models.GetAllAlertNotificationsQuery) error { |
||||
return s.SQLStore.GetAllAlertNotifications(query) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error { |
||||
return s.SQLStore.GetOrCreateAlertNotificationState(ctx, cmd) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) SetAlertNotificationStateToCompleteCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToCompleteCommand) error { |
||||
return s.SQLStore.SetAlertNotificationStateToCompleteCommand(ctx, cmd) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) SetAlertNotificationStateToPendingCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error { |
||||
return s.SQLStore.SetAlertNotificationStateToPendingCommand(ctx, cmd) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) GetAlertNotificationsWithUid(query *models.GetAlertNotificationsWithUidQuery) error { |
||||
return s.SQLStore.GetAlertNotificationsWithUid(query) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) UpdateAlertNotificationWithUid(cmd *models.UpdateAlertNotificationWithUidCommand) error { |
||||
return s.SQLStore.UpdateAlertNotificationWithUid(cmd) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) DeleteAlertNotificationWithUid(cmd *models.DeleteAlertNotificationWithUidCommand) error { |
||||
return s.SQLStore.DeleteAlertNotificationWithUid(cmd) |
||||
} |
||||
|
||||
func (s *AlertNotificationService) GetAlertNotificationsWithUidToSend(query *models.GetAlertNotificationsWithUidToSendQuery) error { |
||||
return s.SQLStore.GetAlertNotificationsWithUidToSend(query) |
||||
} |
@ -0,0 +1,56 @@ |
||||
package alerting |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/bus" |
||||
"github.com/grafana/grafana/pkg/components/simplejson" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
"github.com/grafana/grafana/pkg/services/encryption/ossencryption" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestService(t *testing.T) { |
||||
sqlStore := sqlstore.InitTestDB(t) |
||||
|
||||
s := ProvideService(bus.New(), sqlStore, ossencryption.ProvideService()) |
||||
|
||||
origSecret := setting.SecretKey |
||||
setting.SecretKey = "alert_notification_service_test" |
||||
t.Cleanup(func() { |
||||
setting.SecretKey = origSecret |
||||
}) |
||||
|
||||
var an *models.AlertNotification |
||||
|
||||
t.Run("create alert notification should encrypt the secure json data", func(t *testing.T) { |
||||
ctx := context.Background() |
||||
|
||||
ss := map[string]string{"password": "12345"} |
||||
cmd := models.CreateAlertNotificationCommand{SecureSettings: ss} |
||||
|
||||
err := s.CreateAlertNotificationCommand(ctx, &cmd) |
||||
require.NoError(t, err) |
||||
|
||||
an = cmd.Result |
||||
decrypted, err := s.EncryptionService.DecryptJsonData(ctx, an.SecureSettings, setting.SecretKey) |
||||
require.NoError(t, err) |
||||
require.Equal(t, ss, decrypted) |
||||
}) |
||||
|
||||
t.Run("update alert notification should encrypt the secure json data", func(t *testing.T) { |
||||
ctx := context.Background() |
||||
|
||||
ss := map[string]string{"password": "678910"} |
||||
cmd := models.UpdateAlertNotificationCommand{Id: an.Id, Settings: simplejson.New(), SecureSettings: ss} |
||||
err := s.UpdateAlertNotification(ctx, &cmd) |
||||
require.NoError(t, err) |
||||
|
||||
decrypted, err := s.EncryptionService.DecryptJsonData(ctx, cmd.Result.SecureSettings, setting.SecretKey) |
||||
require.NoError(t, err) |
||||
require.Equal(t, ss, decrypted) |
||||
}) |
||||
} |
@ -1,6 +1,13 @@ |
||||
package encryption |
||||
|
||||
import "context" |
||||
|
||||
type Service interface { |
||||
Encrypt([]byte, string) ([]byte, error) |
||||
Decrypt([]byte, string) ([]byte, error) |
||||
Encrypt(ctx context.Context, payload []byte, secret string) ([]byte, error) |
||||
Decrypt(ctx context.Context, payload []byte, secret string) ([]byte, error) |
||||
|
||||
EncryptJsonData(ctx context.Context, kv map[string]string, secret string) (map[string][]byte, error) |
||||
DecryptJsonData(ctx context.Context, sjd map[string][]byte, secret string) (map[string]string, error) |
||||
|
||||
GetDecryptedValue(ctx context.Context, sjd map[string][]byte, key string, fallback string, secret string) string |
||||
} |
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue