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>
This commit is contained in:
Joan López de la Franca Beltran
2021-10-07 17:33:50 +03:00
committed by GitHub
co-authored by Emil Tullstedt Tania B
parent da813877fb
commit 722c414fef
141 changed files with 1968 additions and 1197 deletions
@@ -4,11 +4,12 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption"
)
// Provision alert notifiers
func Provision(configDirectory string) error {
dc := newNotificationProvisioner(log.New("provisioning.notifiers"))
func Provision(configDirectory string, encryptionService encryption.Service) error {
dc := newNotificationProvisioner(encryptionService, log.New("provisioning.notifiers"))
return dc.applyChanges(configDirectory)
}
@@ -18,10 +19,13 @@ type NotificationProvisioner struct {
cfgProvider *configReader
}
func newNotificationProvisioner(log log.Logger) NotificationProvisioner {
func newNotificationProvisioner(encryptionService encryption.Service, log log.Logger) NotificationProvisioner {
return NotificationProvisioner{
log: log,
cfgProvider: &configReader{log: log},
log: log,
cfgProvider: &configReader{
encryptionService: encryptionService,
log: log,
},
}
}
@@ -1,22 +1,25 @@
package notifiers
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/provisioning/utils"
"github.com/grafana/grafana/pkg/setting"
"gopkg.in/yaml.v2"
)
type configReader struct {
log log.Logger
encryptionService encryption.Service
log log.Logger
}
func (cr *configReader) readConfig(path string) ([]*notificationsAsConfig, error) {
@@ -44,15 +47,15 @@ func (cr *configReader) readConfig(path string) ([]*notificationsAsConfig, error
}
cr.log.Debug("Validating alert notifications")
if err = validateRequiredField(notifications); err != nil {
if err = cr.validateRequiredField(notifications); err != nil {
return nil, err
}
if err := checkOrgIDAndOrgName(notifications); err != nil {
if err := cr.checkOrgIDAndOrgName(notifications); err != nil {
return nil, err
}
if err := validateNotifications(notifications); err != nil {
if err := cr.validateNotifications(notifications); err != nil {
return nil, err
}
@@ -78,7 +81,7 @@ func (cr *configReader) parseNotificationConfig(path string, file os.FileInfo) (
return cfg.mapToNotificationFromConfig(), nil
}
func checkOrgIDAndOrgName(notifications []*notificationsAsConfig) error {
func (cr *configReader) checkOrgIDAndOrgName(notifications []*notificationsAsConfig) error {
for i := range notifications {
for _, notification := range notifications[i].Notifications {
if notification.OrgID < 1 {
@@ -107,7 +110,7 @@ func checkOrgIDAndOrgName(notifications []*notificationsAsConfig) error {
return nil
}
func validateRequiredField(notifications []*notificationsAsConfig) error {
func (cr *configReader) validateRequiredField(notifications []*notificationsAsConfig) error {
for i := range notifications {
var errStrings []string
for index, notification := range notifications[i].Notifications {
@@ -150,19 +153,29 @@ func validateRequiredField(notifications []*notificationsAsConfig) error {
return nil
}
func validateNotifications(notifications []*notificationsAsConfig) error {
func (cr *configReader) validateNotifications(notifications []*notificationsAsConfig) error {
for i := range notifications {
if notifications[i].Notifications == nil {
continue
}
for _, notification := range notifications[i].Notifications {
_, err := alerting.InitNotifier(&models.AlertNotification{
encryptedSecureSettings, err := cr.encryptionService.EncryptJsonData(
context.Background(),
notification.SecureSettings,
setting.SecretKey,
)
if err != nil {
return err
}
_, err = alerting.InitNotifier(&models.AlertNotification{
Name: notification.Name,
Settings: notification.SettingsToJSON(),
SecureSettings: securejsondata.GetEncryptedJsonData(notification.SecureSettings),
SecureSettings: encryptedSecureSettings,
Type: notification.Type,
})
}, cr.encryptionService.GetDecryptedValue)
if err != nil {
return err
@@ -5,10 +5,12 @@ import (
"os"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/sqlstore"
. "github.com/smartystreets/goconvey/convey"
)
@@ -30,7 +32,8 @@ func TestNotificationAsConfig(t *testing.T) {
logger := log.New("fake.log")
Convey("Testing notification as configuration", t, func() {
sqlstore.InitTestDB(t)
sqlStore := sqlstore.InitTestDB(t)
setupBusHandlers(sqlStore)
for i := 1; i < 5; i++ {
orgCommand := models.CreateOrgCommand{Name: fmt.Sprintf("Main Org. %v", i)}
@@ -52,7 +55,11 @@ func TestNotificationAsConfig(t *testing.T) {
Convey("Can read correct properties", func() {
_ = os.Setenv("TEST_VAR", "default")
cfgProvider := &configReader{log: log.New("test logger")}
cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
cfg, err := cfgProvider.readConfig(correctProperties)
_ = os.Unsetenv("TEST_VAR")
if err != nil {
@@ -125,13 +132,14 @@ func TestNotificationAsConfig(t *testing.T) {
Convey("One configured notification", func() {
Convey("no notification in database", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(twoNotificationsConfig)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 2)
@@ -144,22 +152,22 @@ func TestNotificationAsConfig(t *testing.T) {
Uid: "notifier1",
Type: "slack",
}
err := sqlstore.CreateAlertNotificationCommand(&existingNotificationCmd)
err := sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil)
So(existingNotificationCmd.Result, ShouldNotBeNil)
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 1)
Convey("should update one notification", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err = dc.applyChanges(twoNotificationsConfig)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 2)
@@ -177,12 +185,12 @@ func TestNotificationAsConfig(t *testing.T) {
})
})
Convey("Two notifications with is_default", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(doubleNotificationsConfig)
Convey("should both be inserted", func() {
So(err, ShouldBeNil)
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 2)
@@ -201,7 +209,7 @@ func TestNotificationAsConfig(t *testing.T) {
Uid: "notifier0",
Type: "slack",
}
err := sqlstore.CreateAlertNotificationCommand(&existingNotificationCmd)
err := sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil)
existingNotificationCmd = models.CreateAlertNotificationCommand{
Name: "channel3",
@@ -209,23 +217,23 @@ func TestNotificationAsConfig(t *testing.T) {
Uid: "notifier3",
Type: "slack",
}
err = sqlstore.CreateAlertNotificationCommand(&existingNotificationCmd)
err = sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil)
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 2)
Convey("should have two new notifications", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(twoNotificationsConfig)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
notificationsQuery = models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 4)
@@ -249,17 +257,17 @@ func TestNotificationAsConfig(t *testing.T) {
Uid: "notifier2",
Type: "slack",
}
err = sqlstore.CreateAlertNotificationCommand(&existingNotificationCmd)
err = sqlStore.CreateAlertNotificationCommand(&existingNotificationCmd)
So(err, ShouldBeNil)
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err = dc.applyChanges(correctPropertiesWithOrgName)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: existingOrg2.Result.Id}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldNotBeNil)
So(len(notificationsQuery.Result), ShouldEqual, 1)
@@ -270,7 +278,7 @@ func TestNotificationAsConfig(t *testing.T) {
})
Convey("Config doesn't contain required field", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(noRequiredFields)
So(err, ShouldNotBeNil)
@@ -283,26 +291,34 @@ func TestNotificationAsConfig(t *testing.T) {
Convey("Empty yaml file", func() {
Convey("should have not changed repo", func() {
dc := newNotificationProvisioner(logger)
dc := newNotificationProvisioner(ossencryption.ProvideService(), logger)
err := dc.applyChanges(emptyFile)
if err != nil {
t.Fatalf("applyChanges return an error %v", err)
}
notificationsQuery := models.GetAllAlertNotificationsQuery{OrgId: 1}
err = sqlstore.GetAllAlertNotifications(&notificationsQuery)
err = sqlStore.GetAllAlertNotifications(&notificationsQuery)
So(err, ShouldBeNil)
So(notificationsQuery.Result, ShouldBeEmpty)
})
})
Convey("Broken yaml should return error", func() {
reader := &configReader{log: log.New("test logger")}
reader := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
_, err := reader.readConfig(brokenYaml)
So(err, ShouldNotBeNil)
})
Convey("Skip invalid directory", func() {
cfgProvider := &configReader{log: log.New("test logger")}
cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
cfg, err := cfgProvider.readConfig(emptyFolder)
if err != nil {
t.Fatalf("readConfig return an error %v", err)
@@ -311,17 +327,53 @@ func TestNotificationAsConfig(t *testing.T) {
})
Convey("Unknown notifier should return error", func() {
cfgProvider := &configReader{log: log.New("test logger")}
cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
_, err := cfgProvider.readConfig(unknownNotifier)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, `unsupported notification type "nonexisting"`)
})
Convey("Read incorrect properties", func() {
cfgProvider := &configReader{log: log.New("test logger")}
cfgProvider := &configReader{
encryptionService: ossencryption.ProvideService(),
log: log.New("test logger"),
}
_, err := cfgProvider.readConfig(incorrectSettings)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "alert validation error: token must be specified when using the Slack chat API")
})
})
}
func setupBusHandlers(sqlStore *sqlstore.SQLStore) {
bus.AddHandler("getOrg", func(q *models.GetOrgByNameQuery) error {
return sqlstore.GetOrgByName(q)
})
bus.AddHandler("getAlertNotifications", func(q *models.GetAlertNotificationsWithUidQuery) error {
return sqlStore.GetAlertNotificationsWithUid(q)
})
bus.AddHandler("createAlertNotification", func(cmd *models.CreateAlertNotificationCommand) error {
return sqlStore.CreateAlertNotificationCommand(cmd)
})
bus.AddHandler("updateAlertNotification", func(cmd *models.UpdateAlertNotificationCommand) error {
return sqlStore.UpdateAlertNotification(cmd)
})
bus.AddHandler("updateAlertNotification", func(cmd *models.UpdateAlertNotificationWithUidCommand) error {
return sqlStore.UpdateAlertNotificationWithUid(cmd)
})
bus.AddHandler("deleteAlertNotification", func(cmd *models.DeleteAlertNotificationCommand) error {
return sqlStore.DeleteAlertNotification(cmd)
})
bus.AddHandler("deleteAlertNotification", func(cmd *models.DeleteAlertNotificationWithUidCommand) error {
return sqlStore.DeleteAlertNotificationWithUid(cmd)
})
}
+8 -5
View File
@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
plugifaces "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning/datasources"
"github.com/grafana/grafana/pkg/services/provisioning/notifiers"
@@ -17,12 +18,13 @@ import (
"github.com/grafana/grafana/pkg/util/errutil"
)
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginManager plugifaces.Manager) (
*ProvisioningServiceImpl, error) {
func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginManager plugifaces.Manager,
encryptionService encryption.Service) (*ProvisioningServiceImpl, error) {
s := &ProvisioningServiceImpl{
Cfg: cfg,
SQLStore: sqlStore,
PluginManager: pluginManager,
EncryptionService: encryptionService,
log: log.New("provisioning"),
newDashboardProvisioner: dashboards.New,
provisionNotifiers: notifiers.Provision,
@@ -57,7 +59,7 @@ func NewProvisioningServiceImpl() *ProvisioningServiceImpl {
// Used for testing purposes
func newProvisioningServiceImpl(
newDashboardProvisioner dashboards.DashboardProvisionerFactory,
provisionNotifiers func(string) error,
provisionNotifiers func(string, encryption.Service) error,
provisionDatasources func(string) error,
provisionPlugins func(string, plugifaces.Manager) error,
) *ProvisioningServiceImpl {
@@ -74,11 +76,12 @@ type ProvisioningServiceImpl struct {
Cfg *setting.Cfg
SQLStore *sqlstore.SQLStore
PluginManager plugifaces.Manager
EncryptionService encryption.Service
log log.Logger
pollingCtxCancel context.CancelFunc
newDashboardProvisioner dashboards.DashboardProvisionerFactory
dashboardProvisioner dashboards.DashboardProvisioner
provisionNotifiers func(string) error
provisionNotifiers func(string, encryption.Service) error
provisionDatasources func(string) error
provisionPlugins func(string, plugifaces.Manager) error
mutex sync.Mutex
@@ -146,7 +149,7 @@ func (ps *ProvisioningServiceImpl) ProvisionPlugins() error {
func (ps *ProvisioningServiceImpl) ProvisionNotifications() error {
alertNotificationsPath := filepath.Join(ps.Cfg.ProvisioningPath, "notifiers")
err := ps.provisionNotifiers(alertNotificationsPath)
err := ps.provisionNotifiers(alertNotificationsPath, ps.EncryptionService)
return errutil.Wrap("Alert notification provisioning error", err)
}