diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index f2469eac478..90faaf8a12e 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -1,6 +1,7 @@ package api import ( + "context" "errors" "fmt" "strconv" @@ -13,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/search" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" macaron "gopkg.in/macaron.v1" ) @@ -287,10 +289,10 @@ func CreateAlertNotification(c *models.ReqContext, cmd models.CreateAlertNotific return response.JSON(200, dtos.NewAlertNotification(cmd.Result)) } -func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) response.Response { +func (hs *HTTPServer) UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) response.Response { cmd.OrgId = c.OrgId - err := fillWithSecureSettingsData(&cmd) + err := hs.fillWithSecureSettingsData(c.Req.Context(), &cmd) if err != nil { return response.Error(500, "Failed to update alert notification", err) } @@ -314,11 +316,11 @@ func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotific return response.JSON(200, dtos.NewAlertNotification(query.Result)) } -func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) response.Response { +func (hs *HTTPServer) UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) response.Response { cmd.OrgId = c.OrgId cmd.Uid = macaron.Params(c.Req)[":uid"] - err := fillWithSecureSettingsDataByUID(&cmd) + err := hs.fillWithSecureSettingsDataByUID(c.Req.Context(), &cmd) if err != nil { return response.Error(500, "Failed to update alert notification", err) } @@ -342,7 +344,7 @@ func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNo return response.JSON(200, dtos.NewAlertNotification(query.Result)) } -func fillWithSecureSettingsData(cmd *models.UpdateAlertNotificationCommand) error { +func (hs *HTTPServer) fillWithSecureSettingsData(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error { if len(cmd.SecureSettings) == 0 { return nil } @@ -352,11 +354,15 @@ func fillWithSecureSettingsData(cmd *models.UpdateAlertNotificationCommand) erro Id: cmd.Id, } - if err := bus.Dispatch(query); err != nil { + if err := bus.DispatchCtx(ctx, query); err != nil { + return err + } + + secureSettings, err := hs.EncryptionService.DecryptJsonData(ctx, query.Result.SecureSettings, setting.SecretKey) + if err != nil { return err } - secureSettings := query.Result.SecureSettings.Decrypt() for k, v := range secureSettings { if _, ok := cmd.SecureSettings[k]; !ok { cmd.SecureSettings[k] = v @@ -366,7 +372,7 @@ func fillWithSecureSettingsData(cmd *models.UpdateAlertNotificationCommand) erro return nil } -func fillWithSecureSettingsDataByUID(cmd *models.UpdateAlertNotificationWithUidCommand) error { +func (hs *HTTPServer) fillWithSecureSettingsDataByUID(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error { if len(cmd.SecureSettings) == 0 { return nil } @@ -376,11 +382,15 @@ func fillWithSecureSettingsDataByUID(cmd *models.UpdateAlertNotificationWithUidC Uid: cmd.Uid, } - if err := bus.Dispatch(query); err != nil { + if err := bus.DispatchCtx(ctx, query); err != nil { + return err + } + + secureSettings, err := hs.EncryptionService.DecryptJsonData(ctx, query.Result.SecureSettings, setting.SecretKey) + if err != nil { return err } - secureSettings := query.Result.SecureSettings.Decrypt() for k, v := range secureSettings { if _, ok := cmd.SecureSettings[k]; !ok { cmd.SecureSettings[k] = v diff --git a/pkg/api/api.go b/pkg/api/api.go index 58a40a10ac0..e69a1f2dab3 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -394,11 +394,11 @@ func (hs *HTTPServer) registerRoutes() { alertNotifications.Get("/", routing.Wrap(GetAlertNotifications)) alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), routing.Wrap(NotificationTest)) alertNotifications.Post("/", bind(models.CreateAlertNotificationCommand{}), routing.Wrap(CreateAlertNotification)) - alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), routing.Wrap(UpdateAlertNotification)) + alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), routing.Wrap(hs.UpdateAlertNotification)) alertNotifications.Get("/:notificationId", routing.Wrap(GetAlertNotificationByID)) alertNotifications.Delete("/:notificationId", routing.Wrap(DeleteAlertNotification)) alertNotifications.Get("/uid/:uid", routing.Wrap(GetAlertNotificationByUID)) - alertNotifications.Put("/uid/:uid", bind(models.UpdateAlertNotificationWithUidCommand{}), routing.Wrap(UpdateAlertNotificationByUID)) + alertNotifications.Put("/uid/:uid", bind(models.UpdateAlertNotificationWithUidCommand{}), routing.Wrap(hs.UpdateAlertNotificationByUID)) alertNotifications.Delete("/uid/:uid", routing.Wrap(DeleteAlertNotificationByUID)) }, reqEditorRole) diff --git a/pkg/api/app_routes.go b/pkg/api/app_routes.go index ef01ab87f2c..643885715e1 100644 --- a/pkg/api/app_routes.go +++ b/pkg/api/app_routes.go @@ -60,7 +60,7 @@ func AppPluginRoute(route *plugins.AppPluginRoute, appID string, hs *HTTPServer) return func(c *models.ReqContext) { path := macaron.Params(c.Req)["*"] - proxy := pluginproxy.NewApiPluginProxy(c, path, route, appID, hs.Cfg) + proxy := pluginproxy.NewApiPluginProxy(c, path, route, appID, hs.Cfg, hs.EncryptionService) proxy.Transport = pluginProxyTransport proxy.ServeHTTP(c.Resp, c.Req) } diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 1856d168f74..b14c811da50 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -1,11 +1,13 @@ package api import ( + "context" "encoding/json" "errors" "fmt" "sort" + "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -13,10 +15,9 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins/adapters" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" - macaron "gopkg.in/macaron.v1" - - "github.com/grafana/grafana-plugin-sdk-go/backend" + "gopkg.in/macaron.v1" ) var datasourcesLogger = log.New("datasources") @@ -92,7 +93,7 @@ func (hs *HTTPServer) DeleteDataSourceById(c *models.ReqContext) response.Respon return response.Error(400, "Missing valid datasource id", nil) } - ds, err := getRawDataSourceById(id, c.OrgId) + ds, err := getRawDataSourceById(c.Req.Context(), id, c.OrgId) if err != nil { if errors.Is(err, models.ErrDataSourceNotFound) { return response.Error(404, "Data source not found", nil) @@ -106,7 +107,7 @@ func (hs *HTTPServer) DeleteDataSourceById(c *models.ReqContext) response.Respon cmd := &models.DeleteDataSourceCommand{ID: id, OrgID: c.OrgId} - err = bus.Dispatch(cmd) + err = bus.DispatchCtx(c.Req.Context(), cmd) if err != nil { return response.Error(500, "Failed to delete datasource", err) } @@ -240,7 +241,7 @@ func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext, cmd models.UpdateDa return resp } - err := fillWithSecureJSONData(&cmd) + err := hs.fillWithSecureJSONData(c.Req.Context(), &cmd) if err != nil { return response.Error(500, "Failed to update datasource", err) } @@ -277,12 +278,12 @@ func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext, cmd models.UpdateDa }) } -func fillWithSecureJSONData(cmd *models.UpdateDataSourceCommand) error { +func (hs *HTTPServer) fillWithSecureJSONData(ctx context.Context, cmd *models.UpdateDataSourceCommand) error { if len(cmd.SecureJsonData) == 0 { return nil } - ds, err := getRawDataSourceById(cmd.Id, cmd.OrgId) + ds, err := getRawDataSourceById(ctx, cmd.Id, cmd.OrgId) if err != nil { return err } @@ -291,7 +292,11 @@ func fillWithSecureJSONData(cmd *models.UpdateDataSourceCommand) error { return models.ErrDatasourceIsReadOnly } - secureJSONData := ds.SecureJsonData.Decrypt() + secureJSONData, err := hs.EncryptionService.DecryptJsonData(ctx, ds.SecureJsonData, setting.SecretKey) + if err != nil { + return err + } + for k, v := range secureJSONData { if _, ok := cmd.SecureJsonData[k]; !ok { cmd.SecureJsonData[k] = v @@ -301,13 +306,13 @@ func fillWithSecureJSONData(cmd *models.UpdateDataSourceCommand) error { return nil } -func getRawDataSourceById(id int64, orgID int64) (*models.DataSource, error) { +func getRawDataSourceById(ctx context.Context, id int64, orgID int64) (*models.DataSource, error) { query := models.GetDataSourceQuery{ Id: id, OrgId: orgID, } - if err := bus.Dispatch(&query); err != nil { + if err := bus.DispatchCtx(ctx, &query); err != nil { return nil, err } @@ -381,7 +386,7 @@ func (hs *HTTPServer) CallDatasourceResource(c *models.ReqContext) { return } - dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds) + dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds, hs.decryptSecureJsonDataFn()) if err != nil { c.JsonApiErr(500, "Unable to process datasource instance model", err) } @@ -445,7 +450,7 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo return response.Error(500, "Unable to find datasource plugin", err) } - dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds) + dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds, hs.decryptSecureJsonDataFn()) if err != nil { return response.Error(500, "Unable to get datasource model", err) } @@ -483,3 +488,13 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo return response.JSON(200, payload) } + +func (hs *HTTPServer) decryptSecureJsonDataFn() func(map[string][]byte) map[string]string { + return func(m map[string][]byte) map[string]string { + decryptedJsonData, err := hs.EncryptionService.DecryptJsonData(context.Background(), m, setting.SecretKey) + if err != nil { + hs.log.Error("Failed to decrypt secure json data", "error", err) + } + return decryptedJsonData + } +} diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 450cbc3c556..4c766f30473 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -77,7 +77,10 @@ func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins *plu if ds.Access == models.DS_ACCESS_DIRECT { if ds.BasicAuth { - dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.DecryptedBasicAuthPassword()) + dsMap["basicAuth"] = util.GetBasicAuthHeader( + ds.BasicAuthUser, + hs.DataSourcesService.DecryptedBasicAuthPassword(ds), + ) } if ds.WithCredentials { dsMap["withCredentials"] = ds.WithCredentials @@ -85,13 +88,13 @@ func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins *plu if ds.Type == models.DS_INFLUXDB_08 { dsMap["username"] = ds.User - dsMap["password"] = ds.DecryptedPassword() + dsMap["password"] = hs.DataSourcesService.DecryptedPassword(ds) dsMap["url"] = url + "/db/" + ds.Database } if ds.Type == models.DS_INFLUXDB { dsMap["username"] = ds.User - dsMap["password"] = ds.DecryptedPassword() + dsMap["password"] = hs.DataSourcesService.DecryptedPassword(ds) dsMap["url"] = url } } diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 99f8247e228..4df95fa0615 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -106,6 +106,7 @@ type HTTPServer struct { OAuthTokenService oauthtoken.OAuthTokenService Listener net.Listener EncryptionService encryption.Service + DataSourcesService *datasources.Service cleanUpService *cleanup.CleanUpService tracingService *tracing.TracingService internalMetricsSvc *metrics.InternalMetricsService @@ -133,7 +134,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi notificationService *notifications.NotificationService, tracingService *tracing.TracingService, internalMetricsSvc *metrics.InternalMetricsService, quotaService *quota.QuotaService, socialService social.Service, oauthTokenService oauthtoken.OAuthTokenService, - encryptionService encryption.Service, searchUsersService searchusers.Service) (*HTTPServer, error) { + encryptionService encryption.Service, searchUsersService searchusers.Service, + dataSourcesService *datasources.Service) (*HTTPServer, error) { macaron.Env = cfg.Env m := macaron.New() @@ -180,6 +182,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi SocialService: socialService, OAuthTokenService: oauthTokenService, EncryptionService: encryptionService, + DataSourcesService: dataSourcesService, searchUsersService: searchUsersService, } if hs.Listener != nil { diff --git a/pkg/api/login.go b/pkg/api/login.go index eea55a80907..f680c16adab 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -315,12 +315,12 @@ func (hs *HTTPServer) tryGetEncryptedCookie(ctx *models.ReqContext, cookieName s return "", false } - decryptedError, err := hs.EncryptionService.Decrypt(decoded, setting.SecretKey) + decryptedError, err := hs.EncryptionService.Decrypt(ctx.Req.Context(), decoded, setting.SecretKey) return string(decryptedError), err == nil } func (hs *HTTPServer) trySetEncryptedCookie(ctx *models.ReqContext, cookieName string, value string, maxAge int) error { - encryptedError, err := hs.EncryptionService.Encrypt([]byte(value), setting.SecretKey) + encryptedError, err := hs.EncryptionService.Encrypt(ctx.Req.Context(), []byte(value), setting.SecretKey) if err != nil { return err } diff --git a/pkg/api/login_test.go b/pkg/api/login_test.go index 6b4865246d4..7bbbf8c2ed8 100644 --- a/pkg/api/login_test.go +++ b/pkg/api/login_test.go @@ -2,6 +2,7 @@ package api import ( "bytes" + "context" "encoding/hex" "errors" "fmt" @@ -12,8 +13,6 @@ import ( "strings" "testing" - "github.com/grafana/grafana/pkg/services/encryption/ossencryption" - "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" @@ -24,6 +23,7 @@ import ( "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/auth" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" @@ -125,7 +125,7 @@ func TestLoginErrorCookieAPIEndpoint(t *testing.T) { setting.OAuthAutoLogin = true oauthError := errors.New("User not a member of one of the required organizations") - encryptedError, err := hs.EncryptionService.Encrypt([]byte(oauthError.Error()), setting.SecretKey) + encryptedError, err := hs.EncryptionService.Encrypt(context.Background(), []byte(oauthError.Error()), setting.SecretKey) require.NoError(t, err) expCookiePath := "/" if len(setting.AppSubUrl) > 0 { diff --git a/pkg/api/pluginproxy/ds_auth_provider.go b/pkg/api/pluginproxy/ds_auth_provider.go index 71b3f58b6f8..99bb8903ddb 100644 --- a/pkg/api/pluginproxy/ds_auth_provider.go +++ b/pkg/api/pluginproxy/ds_auth_provider.go @@ -9,18 +9,25 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) // ApplyRoute should use the plugin route data to set auth headers and custom headers. func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route *plugins.AppPluginRoute, - ds *models.DataSource, cfg *setting.Cfg) { + ds *models.DataSource, cfg *setting.Cfg, encryptionService encryption.Service) { proxyPath = strings.TrimPrefix(proxyPath, route.Path) + secureJsonData, err := encryptionService.DecryptJsonData(ctx, ds.SecureJsonData, setting.SecretKey) + if err != nil { + logger.Error("Error interpolating proxy url", "error", err) + return + } + data := templateData{ JsonData: ds.JsonData.Interface().(map[string]interface{}), - SecureJsonData: ds.SecureJsonData.Decrypt(), + SecureJsonData: secureJsonData, } if len(route.URL) > 0 { diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index fde878d6479..1f7d0e158db 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -18,6 +18,7 @@ import ( glog "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" @@ -31,15 +32,16 @@ var ( ) type DataSourceProxy struct { - ds *models.DataSource - ctx *models.ReqContext - targetUrl *url.URL - proxyPath string - route *plugins.AppPluginRoute - plugin *plugins.DataSourcePlugin - cfg *setting.Cfg - clientProvider httpclient.Provider - oAuthTokenService oauthtoken.OAuthTokenService + ds *models.DataSource + ctx *models.ReqContext + targetUrl *url.URL + proxyPath string + route *plugins.AppPluginRoute + plugin *plugins.DataSourcePlugin + cfg *setting.Cfg + clientProvider httpclient.Provider + oAuthTokenService oauthtoken.OAuthTokenService + dataSourcesService *datasources.Service } type handleResponseTransport struct { @@ -72,21 +74,23 @@ func (lw *logWrapper) Write(p []byte) (n int, err error) { // NewDataSourceProxy creates a new Datasource proxy func NewDataSourceProxy(ds *models.DataSource, plugin *plugins.DataSourcePlugin, ctx *models.ReqContext, - proxyPath string, cfg *setting.Cfg, clientProvider httpclient.Provider, oAuthTokenService oauthtoken.OAuthTokenService) (*DataSourceProxy, error) { + proxyPath string, cfg *setting.Cfg, clientProvider httpclient.Provider, + oAuthTokenService oauthtoken.OAuthTokenService, dsService *datasources.Service) (*DataSourceProxy, error) { targetURL, err := datasource.ValidateURL(ds.Type, ds.Url) if err != nil { return nil, err } return &DataSourceProxy{ - ds: ds, - plugin: plugin, - ctx: ctx, - proxyPath: proxyPath, - targetUrl: targetURL, - cfg: cfg, - clientProvider: clientProvider, - oAuthTokenService: oAuthTokenService, + ds: ds, + plugin: plugin, + ctx: ctx, + proxyPath: proxyPath, + targetUrl: targetURL, + cfg: cfg, + clientProvider: clientProvider, + oAuthTokenService: oAuthTokenService, + dataSourcesService: dsService, }, nil } @@ -106,7 +110,7 @@ func (proxy *DataSourceProxy) HandleRequest() { proxyErrorLogger := logger.New("userId", proxy.ctx.UserId, "orgId", proxy.ctx.OrgId, "uname", proxy.ctx.Login, "path", proxy.ctx.Req.URL.Path, "remote_addr", proxy.ctx.RemoteAddr(), "referer", proxy.ctx.Req.Referer()) - transport, err := proxy.ds.GetHTTPTransport(proxy.clientProvider) + transport, err := proxy.dataSourcesService.GetHTTPTransport(proxy.ds, proxy.clientProvider) if err != nil { proxy.ctx.JsonApiErr(400, "Unable to load TLS certificate", err) return @@ -186,13 +190,16 @@ func (proxy *DataSourceProxy) director(req *http.Request) { case models.DS_INFLUXDB_08: req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath) reqQueryVals.Add("u", proxy.ds.User) - reqQueryVals.Add("p", proxy.ds.DecryptedPassword()) + reqQueryVals.Add("p", proxy.dataSourcesService.DecryptedPassword(proxy.ds)) req.URL.RawQuery = reqQueryVals.Encode() case models.DS_INFLUXDB: req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath) req.URL.RawQuery = reqQueryVals.Encode() if !proxy.ds.BasicAuth { - req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.DecryptedPassword())) + req.Header.Set( + "Authorization", + util.GetBasicAuthHeader(proxy.ds.User, proxy.dataSourcesService.DecryptedPassword(proxy.ds)), + ) } default: req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath) @@ -208,7 +215,7 @@ func (proxy *DataSourceProxy) director(req *http.Request) { if proxy.ds.BasicAuth { req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser, - proxy.ds.DecryptedBasicAuthPassword())) + proxy.dataSourcesService.DecryptedBasicAuthPassword(proxy.ds))) } dsAuth := req.Header.Get("X-DS-Authorization") @@ -236,7 +243,11 @@ func (proxy *DataSourceProxy) director(req *http.Request) { req.Header.Del("Referer") if proxy.route != nil { - ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, proxy.cfg) + ApplyRoute( + proxy.ctx.Req.Context(), req, proxy.proxyPath, + proxy.route, proxy.ds, proxy.cfg, + proxy.dataSourcesService.EncryptionService, + ) } if proxy.oAuthTokenService.IsOAuthPassThruEnabled(proxy.ds) { diff --git a/pkg/api/pluginproxy/ds_proxy_test.go b/pkg/api/pluginproxy/ds_proxy_test.go index ddf2a417651..978e3f69b16 100644 --- a/pkg/api/pluginproxy/ds_proxy_test.go +++ b/pkg/api/pluginproxy/ds_proxy_test.go @@ -14,18 +14,18 @@ import ( "github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/oauth2" - macaron "gopkg.in/macaron.v1" + "gopkg.in/macaron.v1" ) func TestDataSourceProxy_routeRule(t *testing.T) { @@ -85,7 +85,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { }) setting.SecretKey = "password" //nolint:goconst - key, err := util.Encrypt([]byte("123"), "password") + key, err := ossencryption.ProvideService().Encrypt(context.Background(), []byte("123"), "password") require.NoError(t, err) ds := &models.DataSource{ @@ -113,10 +113,11 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("When matching route path", func(t *testing.T) { ctx, req := setUp() - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) proxy.route = plugin.Routes[0] - ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg) + ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg, ossencryption.ProvideService()) assert.Equal(t, "https://www.google.com/some/method", req.URL.String()) assert.Equal(t, "my secret 123", req.Header.Get("x-header")) @@ -124,10 +125,11 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("When matching route path and has dynamic url", func(t *testing.T) { ctx, req := setUp() - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/common/some/method", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/common/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) proxy.route = plugin.Routes[3] - ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg) + ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg, ossencryption.ProvideService()) assert.Equal(t, "https://dynamic.grafana.com/some/method?apiKey=123", req.URL.String()) assert.Equal(t, "my secret 123", req.Header.Get("x-header")) @@ -135,20 +137,22 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("When matching route path with no url", func(t *testing.T) { ctx, req := setUp() - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) proxy.route = plugin.Routes[4] - ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg) + ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg, ossencryption.ProvideService()) assert.Equal(t, "http://localhost/asd", req.URL.String()) }) t.Run("When matching route path and has dynamic body", func(t *testing.T) { ctx, req := setUp() - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/body", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/body", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) proxy.route = plugin.Routes[5] - ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg) + ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg, ossencryption.ProvideService()) content, err := ioutil.ReadAll(req.Body) require.NoError(t, err) @@ -158,7 +162,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("Validating request", func(t *testing.T) { t.Run("plugin route with valid role", func(t *testing.T) { ctx, _ := setUp() - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) err = proxy.validateRequest() require.NoError(t, err) @@ -166,7 +171,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("plugin route with admin role and user is editor", func(t *testing.T) { ctx, _ := setUp() - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) err = proxy.validateRequest() require.Error(t, err) @@ -175,7 +181,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) { t.Run("plugin route with admin role and user is admin", func(t *testing.T) { ctx, _ := setUp() ctx.SignedInUser.OrgRole = models.ROLE_ADMIN - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) err = proxy.validateRequest() require.NoError(t, err) @@ -221,7 +228,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { }) setting.SecretKey = "password" - key, err := util.Encrypt([]byte("123"), "password") + key, err := ossencryption.ProvideService().Encrypt(context.Background(), []byte("123"), "password") require.NoError(t, err) ds := &models.DataSource{ @@ -255,9 +262,10 @@ func TestDataSourceProxy_routeRule(t *testing.T) { cfg := &setting.Cfg{} - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) - ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds, cfg) + ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds, cfg, ossencryption.ProvideService()) authorizationHeaderCall1 = req.Header.Get("Authorization") assert.Equal(t, "https://api.nr1.io/some/path", req.URL.String()) @@ -270,9 +278,10 @@ func TestDataSourceProxy_routeRule(t *testing.T) { req, err := http.NewRequest("GET", "http://localhost/asd", nil) require.NoError(t, err) client = newFakeHTTPClient(t, json2) - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken2", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken2", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) - ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[1], proxy.ds, cfg) + ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[1], proxy.ds, cfg, ossencryption.ProvideService()) authorizationHeaderCall2 = req.Header.Get("Authorization") @@ -286,9 +295,10 @@ func TestDataSourceProxy_routeRule(t *testing.T) { require.NoError(t, err) client = newFakeHTTPClient(t, []byte{}) - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) - ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds, cfg) + ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds, cfg, ossencryption.ProvideService()) authorizationHeaderCall3 := req.Header.Get("Authorization") assert.Equal(t, "https://api.nr1.io/some/path", req.URL.String()) @@ -306,7 +316,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) { ds := &models.DataSource{Url: "htttp://graphite:8080", Type: models.DS_GRAPHITE} ctx := &models.ReqContext{} - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{BuildVersion: "5.3.0"}, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{BuildVersion: "5.3.0"}, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) require.NoError(t, err) @@ -331,7 +342,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) { } ctx := &models.ReqContext{} - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) @@ -354,7 +366,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) { } ctx := &models.ReqContext{} - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) requestURL, err := url.Parse("http://grafana.com/sub") @@ -381,7 +394,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) { } ctx := &models.ReqContext{} - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) requestURL, err := url.Parse("http://grafana.com/sub") @@ -402,7 +416,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) { Url: "http://host/root/", } ctx := &models.ReqContext{} - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) req.Header.Set("Origin", "grafana.com") @@ -457,7 +472,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) { }, oAuthEnabled: true, } - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &mockAuthToken) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &mockAuthToken, dsService) require.NoError(t, err) req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) require.NoError(t, err) @@ -522,7 +538,6 @@ func TestDataSourceProxy_routeRule(t *testing.T) { createAuthTest(t, models.DS_ES, authTypeBasic, authCheckHeader, true), } for _, test := range tests { - models.ClearDSDecryptionCache() runDatasourceAuthTest(t, test) } }) @@ -584,7 +599,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) { t.Run("When response header Set-Cookie is not set should remove proxied Set-Cookie header", func(t *testing.T) { ctx, ds := setUp(t) - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) proxy.HandleRequest() @@ -599,7 +615,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) { "Set-Cookie": "important_cookie=important_value", }, }) - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) proxy.HandleRequest() @@ -618,7 +635,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) { t.Log("Wrote 401 response") }, }) - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) proxy.HandleRequest() @@ -640,7 +658,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) { }) ctx.Req = httptest.NewRequest("GET", "/api/datasources/proxy/1/path/%2Ftest%2Ftest%2F?query=%2Ftest%2Ftest%2F", nil) - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService) require.NoError(t, err) proxy.HandleRequest() @@ -662,7 +681,8 @@ func TestNewDataSourceProxy_InvalidURL(t *testing.T) { } cfg := setting.Cfg{} plugin := plugins.DataSourcePlugin{} - _, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + _, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService) require.Error(t, err) assert.True(t, strings.HasPrefix(err.Error(), `validation of data source URL "://host/root" failed`)) } @@ -679,7 +699,8 @@ func TestNewDataSourceProxy_ProtocolLessURL(t *testing.T) { cfg := setting.Cfg{} plugin := plugins.DataSourcePlugin{} - _, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + _, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService) require.NoError(t, err) } @@ -717,7 +738,8 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) { Url: tc.url, } - p, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + p, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService) if tc.err == nil { require.NoError(t, err) assert.Equal(t, &url.URL{ @@ -741,7 +763,8 @@ func getDatasourceProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *sett Url: "http://host/root/", } - proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService) require.NoError(t, err) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) require.NoError(t, err) @@ -794,6 +817,8 @@ const ( ) func createAuthTest(t *testing.T, dsType string, authType string, authCheck string, useSecureJsonData bool) *testCase { + ctx := context.Background() + // Basic user:password base64AuthHeader := "Basic dXNlcjpwYXNzd29yZA==" @@ -805,13 +830,16 @@ func createAuthTest(t *testing.T, dsType string, authType string, authCheck stri }, } var message string + var err error if authType == authTypePassword { message = fmt.Sprintf("%v should add username and password", dsType) test.datasource.User = "user" if useSecureJsonData { - test.datasource.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ - "password": "password", - }) + test.datasource.SecureJsonData, err = ossencryption.ProvideService().EncryptJsonData( + ctx, + map[string]string{ + "password": "password", + }, setting.SecretKey) } else { test.datasource.Password = "password" } @@ -820,13 +848,16 @@ func createAuthTest(t *testing.T, dsType string, authType string, authCheck stri test.datasource.BasicAuth = true test.datasource.BasicAuthUser = "user" if useSecureJsonData { - test.datasource.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ - "basicAuthPassword": "password", - }) + test.datasource.SecureJsonData, err = ossencryption.ProvideService().EncryptJsonData( + ctx, + map[string]string{ + "basicAuthPassword": "password", + }, setting.SecretKey) } else { test.datasource.BasicAuthPassword = "password" } } + require.NoError(t, err) if useSecureJsonData { message += " from securejsondata" @@ -852,7 +883,8 @@ func createAuthTest(t *testing.T, dsType string, authType string, authCheck stri func runDatasourceAuthTest(t *testing.T, test *testCase) { plugin := &plugins.DataSourcePlugin{} ctx := &models.ReqContext{} - proxy, err := NewDataSourceProxy(test.datasource, plugin, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(test.datasource, plugin, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService) require.NoError(t, err) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) @@ -891,7 +923,8 @@ func Test_PathCheck(t *testing.T) { return ctx, req } ctx, _ := setUp() - proxy, err := NewDataSourceProxy(&models.DataSource{}, plugin, ctx, "b", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}) + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) + proxy, err := NewDataSourceProxy(&models.DataSource{}, plugin, ctx, "b", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService) require.NoError(t, err) require.Nil(t, proxy.validateRequest()) diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go index 7ca8f662a85..3cd523a87d3 100644 --- a/pkg/api/pluginproxy/pluginproxy.go +++ b/pkg/api/pluginproxy/pluginproxy.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/proxyutil" @@ -21,7 +22,7 @@ type templateData struct { // NewApiPluginProxy create a plugin proxy func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.AppPluginRoute, - appID string, cfg *setting.Cfg) *httputil.ReverseProxy { + appID string, cfg *setting.Cfg, encryptionService encryption.Service) *httputil.ReverseProxy { director := func(req *http.Request) { query := models.GetPluginSettingByIdQuery{OrgId: ctx.OrgId, PluginId: appID} if err := bus.Dispatch(&query); err != nil { @@ -29,9 +30,15 @@ func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins. return } + secureJsonData, err := encryptionService.DecryptJsonData(ctx.Req.Context(), query.Result.SecureJsonData, setting.SecretKey) + if err != nil { + ctx.JsonApiErr(500, "Failed to decrypt plugin settings", err) + return + } + data := templateData{ JsonData: query.Result.JsonData, - SecureJsonData: query.Result.SecureJsonData.Decrypt(), + SecureJsonData: secureJsonData, } interpolatedURL, err := interpolateString(route.URL, data) diff --git a/pkg/api/pluginproxy/pluginproxy_test.go b/pkg/api/pluginproxy/pluginproxy_test.go index 9f8c12a0903..7d5b4aa2d57 100644 --- a/pkg/api/pluginproxy/pluginproxy_test.go +++ b/pkg/api/pluginproxy/pluginproxy_test.go @@ -1,18 +1,19 @@ package pluginproxy import ( + "context" "io/ioutil" "net/http" "testing" "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/macaron.v1" ) func TestPluginProxy(t *testing.T) { @@ -25,8 +26,8 @@ func TestPluginProxy(t *testing.T) { setting.SecretKey = "password" - bus.AddHandler("test", func(query *models.GetPluginSettingByIdQuery) error { - key, err := util.Encrypt([]byte("123"), "password") + bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetPluginSettingByIdQuery) error { + key, err := ossencryption.ProvideService().Encrypt(ctx, []byte("123"), "password") if err != nil { return err } @@ -39,12 +40,18 @@ func TestPluginProxy(t *testing.T) { return nil }) + httpReq, err := http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + req := getPluginProxiedRequest( t, &models.ReqContext{ SignedInUser: &models.SignedInUser{ Login: "test_user", }, + Context: &macaron.Context{ + Req: httpReq, + }, }, &setting.Cfg{SendUserHeader: true}, route, @@ -54,12 +61,18 @@ func TestPluginProxy(t *testing.T) { }) t.Run("When SendUserHeader config is enabled", func(t *testing.T) { + httpReq, err := http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + req := getPluginProxiedRequest( t, &models.ReqContext{ SignedInUser: &models.SignedInUser{ Login: "test_user", }, + Context: &macaron.Context{ + Req: httpReq, + }, }, &setting.Cfg{SendUserHeader: true}, nil, @@ -70,12 +83,18 @@ func TestPluginProxy(t *testing.T) { }) t.Run("When SendUserHeader config is disabled", func(t *testing.T) { + httpReq, err := http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + req := getPluginProxiedRequest( t, &models.ReqContext{ SignedInUser: &models.SignedInUser{ Login: "test_user", }, + Context: &macaron.Context{ + Req: httpReq, + }, }, &setting.Cfg{SendUserHeader: false}, nil, @@ -85,10 +104,16 @@ func TestPluginProxy(t *testing.T) { }) t.Run("When SendUserHeader config is enabled but user is anonymous", func(t *testing.T) { + httpReq, err := http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + req := getPluginProxiedRequest( t, &models.ReqContext{ SignedInUser: &models.SignedInUser{IsAnonymous: true}, + Context: &macaron.Context{ + Req: httpReq, + }, }, &setting.Cfg{SendUserHeader: true}, nil, @@ -104,7 +129,7 @@ func TestPluginProxy(t *testing.T) { Method: "GET", } - bus.AddHandler("test", func(query *models.GetPluginSettingByIdQuery) error { + bus.AddHandlerCtx("test", func(_ context.Context, query *models.GetPluginSettingByIdQuery) error { query.Result = &models.PluginSetting{ JsonData: map[string]interface{}{ "dynamicUrl": "https://dynamic.grafana.com", @@ -113,12 +138,18 @@ func TestPluginProxy(t *testing.T) { return nil }) + httpReq, err := http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + req := getPluginProxiedRequest( t, &models.ReqContext{ SignedInUser: &models.SignedInUser{ Login: "test_user", }, + Context: &macaron.Context{ + Req: httpReq, + }, }, &setting.Cfg{SendUserHeader: true}, route, @@ -138,12 +169,18 @@ func TestPluginProxy(t *testing.T) { return nil }) + httpReq, err := http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + req := getPluginProxiedRequest( t, &models.ReqContext{ SignedInUser: &models.SignedInUser{ Login: "test_user", }, + Context: &macaron.Context{ + Req: httpReq, + }, }, &setting.Cfg{SendUserHeader: true}, route, @@ -158,22 +195,38 @@ func TestPluginProxy(t *testing.T) { Body: []byte(`{ "url": "{{.JsonData.dynamicUrl}}", "secret": "{{.SecureJsonData.key}}" }`), } - bus.AddHandler("test", func(query *models.GetPluginSettingByIdQuery) error { + bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetPluginSettingByIdQuery) error { + encryptedJsonData, err := ossencryption.ProvideService().EncryptJsonData( + ctx, + map[string]string{"key": "123"}, + setting.SecretKey, + ) + + if err != nil { + return err + } + query.Result = &models.PluginSetting{ JsonData: map[string]interface{}{ "dynamicUrl": "https://dynamic.grafana.com", }, - SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{"key": "123"}), + SecureJsonData: encryptedJsonData, } return nil }) + httpReq, err := http.NewRequest(http.MethodGet, "", nil) + require.NoError(t, err) + req := getPluginProxiedRequest( t, &models.ReqContext{ SignedInUser: &models.SignedInUser{ Login: "test_user", }, + Context: &macaron.Context{ + Req: httpReq, + }, }, &setting.Cfg{SendUserHeader: true}, route, @@ -194,7 +247,7 @@ func getPluginProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *setting. ReqRole: models.ROLE_EDITOR, } } - proxy := NewApiPluginProxy(ctx, "", route, "", cfg) + proxy := NewApiPluginProxy(ctx, "", route, "", cfg, ossencryption.ProvideService()) req, err := http.NewRequest(http.MethodGet, "/api/plugin-proxy/grafana-simple-app/api/v4/alerts", nil) require.NoError(t, err) diff --git a/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go index 264acb7c036..b2777128160 100644 --- a/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go +++ b/pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go @@ -6,9 +6,10 @@ import ( "time" "github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/commandstest" - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -30,12 +31,14 @@ func TestPasswordMigrationCommand(t *testing.T) { for _, ds := range datasources { ds.Created = time.Now() ds.Updated = time.Now() + if ds.Name == "elasticsearch" { - ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ - "key": "value", - }) + key, err := util.Encrypt([]byte("value"), setting.SecretKey) + require.NoError(t, err) + + ds.SecureJsonData = map[string][]byte{"key": key} } else { - ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{}) + ds.SecureJsonData = map[string][]byte{} } } @@ -59,7 +62,8 @@ func TestPasswordMigrationCommand(t *testing.T) { assert.Equal(t, len(dss), 4) for _, ds := range dss { - sj := ds.SecureJsonData.Decrypt() + sj, err := DecryptSecureJsonData(ds) + require.NoError(t, err) if ds.Name == "influxdb" { assert.Equal(t, ds.Password, "") @@ -90,3 +94,16 @@ func TestPasswordMigrationCommand(t *testing.T) { } } } + +func DecryptSecureJsonData(ds *models.DataSource) (map[string]string, error) { + decrypted := make(map[string]string) + for key, data := range ds.SecureJsonData { + decryptedData, err := util.Decrypt(data, setting.SecretKey) + if err != nil { + return nil, err + } + + decrypted[key] = string(decryptedData) + } + return decrypted, nil +} diff --git a/pkg/models/alert_notifications.go b/pkg/models/alert_notifications.go index 8b64d07dfcb..b7c20b142ce 100644 --- a/pkg/models/alert_notifications.go +++ b/pkg/models/alert_notifications.go @@ -4,7 +4,6 @@ import ( "errors" "time" - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/simplejson" ) @@ -27,19 +26,19 @@ var ( ) type AlertNotification struct { - Id int64 `json:"id"` - Uid string `json:"-"` - OrgId int64 `json:"-"` - Name string `json:"name"` - Type string `json:"type"` - SendReminder bool `json:"sendReminder"` - DisableResolveMessage bool `json:"disableResolveMessage"` - Frequency time.Duration `json:"frequency"` - IsDefault bool `json:"isDefault"` - Settings *simplejson.Json `json:"settings"` - SecureSettings securejsondata.SecureJsonData `json:"secureSettings"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` + Id int64 `json:"id"` + Uid string `json:"-"` + OrgId int64 `json:"-"` + Name string `json:"name"` + Type string `json:"type"` + SendReminder bool `json:"sendReminder"` + DisableResolveMessage bool `json:"disableResolveMessage"` + Frequency time.Duration `json:"frequency"` + IsDefault bool `json:"isDefault"` + Settings *simplejson.Json `json:"settings"` + SecureSettings map[string][]byte `json:"secureSettings"` + Created time.Time `json:"created"` + Updated time.Time `json:"updated"` } type CreateAlertNotificationCommand struct { @@ -53,7 +52,9 @@ type CreateAlertNotificationCommand struct { Settings *simplejson.Json `json:"settings"` SecureSettings map[string]string `json:"secureSettings"` - OrgId int64 `json:"-"` + OrgId int64 `json:"-"` + EncryptedSecureSettings map[string][]byte `json:"-"` + Result *AlertNotification } @@ -69,7 +70,9 @@ type UpdateAlertNotificationCommand struct { Settings *simplejson.Json `json:"settings" binding:"Required"` SecureSettings map[string]string `json:"secureSettings"` - OrgId int64 `json:"-"` + OrgId int64 `json:"-"` + EncryptedSecureSettings map[string][]byte `json:"-"` + Result *AlertNotification } @@ -166,11 +169,3 @@ type GetOrCreateNotificationStateQuery struct { Result *AlertNotificationState } - -// decryptedValue returns decrypted value from secureSettings -func (an *AlertNotification) DecryptedValue(field string, fallback string) string { - if value, ok := an.SecureSettings.DecryptedValue(field); ok { - return value - } - return fallback -} diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index c3b47fb21e8..6bcf8726da1 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -4,7 +4,6 @@ import ( "errors" "time" - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/simplejson" ) @@ -38,47 +37,27 @@ type DataSource struct { OrgId int64 `json:"orgId"` Version int `json:"version"` - Name string `json:"name"` - Type string `json:"type"` - Access DsAccess `json:"access"` - Url string `json:"url"` - Password string `json:"password"` - User string `json:"user"` - Database string `json:"database"` - BasicAuth bool `json:"basicAuth"` - BasicAuthUser string `json:"basicAuthUser"` - BasicAuthPassword string `json:"basicAuthPassword"` - WithCredentials bool `json:"withCredentials"` - IsDefault bool `json:"isDefault"` - JsonData *simplejson.Json `json:"jsonData"` - SecureJsonData securejsondata.SecureJsonData `json:"secureJsonData"` - ReadOnly bool `json:"readOnly"` - Uid string `json:"uid"` + Name string `json:"name"` + Type string `json:"type"` + Access DsAccess `json:"access"` + Url string `json:"url"` + Password string `json:"password"` + User string `json:"user"` + Database string `json:"database"` + BasicAuth bool `json:"basicAuth"` + BasicAuthUser string `json:"basicAuthUser"` + BasicAuthPassword string `json:"basicAuthPassword"` + WithCredentials bool `json:"withCredentials"` + IsDefault bool `json:"isDefault"` + JsonData *simplejson.Json `json:"jsonData"` + SecureJsonData map[string][]byte `json:"secureJsonData"` + ReadOnly bool `json:"readOnly"` + Uid string `json:"uid"` Created time.Time `json:"created"` Updated time.Time `json:"updated"` } -// DecryptedBasicAuthPassword returns data source basic auth password in plain text. It uses either deprecated -// basic_auth_password field or encrypted secure_json_data[basicAuthPassword] variable. -func (ds *DataSource) DecryptedBasicAuthPassword() string { - return ds.decryptedValue("basicAuthPassword", ds.BasicAuthPassword) -} - -// DecryptedPassword returns data source password in plain text. It uses either deprecated password field -// or encrypted secure_json_data[password] variable. -func (ds *DataSource) DecryptedPassword() string { - return ds.decryptedValue("password", ds.Password) -} - -// decryptedValue returns decrypted value from secureJsonData -func (ds *DataSource) decryptedValue(field string, fallback string) string { - if value, ok := ds.DecryptedValue(field); ok { - return value - } - return fallback -} - // ---------------------- // COMMANDS @@ -100,8 +79,9 @@ type AddDataSourceCommand struct { SecureJsonData map[string]string `json:"secureJsonData"` Uid string `json:"uid"` - OrgId int64 `json:"-"` - ReadOnly bool `json:"-"` + OrgId int64 `json:"-"` + ReadOnly bool `json:"-"` + EncryptedSecureJsonData map[string][]byte `json:"-"` Result *DataSource } @@ -125,9 +105,10 @@ type UpdateDataSourceCommand struct { Version int `json:"version"` Uid string `json:"uid"` - OrgId int64 `json:"-"` - Id int64 `json:"-"` - ReadOnly bool `json:"-"` + OrgId int64 `json:"-"` + Id int64 `json:"-"` + ReadOnly bool `json:"-"` + EncryptedSecureJsonData map[string][]byte `json:"-"` Result *DataSource } diff --git a/pkg/models/plugin_setting_cache.go b/pkg/models/plugin_setting_cache.go deleted file mode 100644 index 9e4034f758a..00000000000 --- a/pkg/models/plugin_setting_cache.go +++ /dev/null @@ -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 -} diff --git a/pkg/models/plugin_setting_cache_test.go b/pkg/models/plugin_setting_cache_test.go deleted file mode 100644 index e327017c074..00000000000 --- a/pkg/models/plugin_setting_cache_test.go +++ /dev/null @@ -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) - }) -} diff --git a/pkg/models/plugin_settings.go b/pkg/models/plugin_settings.go index 97996f8867d..312f4601839 100644 --- a/pkg/models/plugin_settings.go +++ b/pkg/models/plugin_settings.go @@ -3,8 +3,6 @@ package models import ( "errors" "time" - - "github.com/grafana/grafana/pkg/components/securejsondata" ) var ( @@ -18,7 +16,7 @@ type PluginSetting struct { Enabled bool Pinned bool JsonData map[string]interface{} - SecureJsonData securejsondata.SecureJsonData + SecureJsonData map[string][]byte PluginVersion string Created time.Time @@ -36,8 +34,9 @@ type UpdatePluginSettingCmd struct { SecureJsonData map[string]string `json:"secureJsonData"` PluginVersion string `json:"version"` - PluginId string `json:"-"` - OrgId int64 `json:"-"` + PluginId string `json:"-"` + OrgId int64 `json:"-"` + EncryptedSecureJsonData map[string][]byte `json:"-"` } // specific command, will only update version @@ -47,10 +46,6 @@ type UpdatePluginSettingVersionCmd struct { OrgId int64 `json:"-"` } -func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() securejsondata.SecureJsonData { - return securejsondata.GetEncryptedJsonData(cmd.SecureJsonData) -} - // --------------------- // QUERIES diff --git a/pkg/plugins/adapters/adapters.go b/pkg/plugins/adapters/adapters.go index e91e0dedac0..3ab06f9adbd 100644 --- a/pkg/plugins/adapters/adapters.go +++ b/pkg/plugins/adapters/adapters.go @@ -7,7 +7,8 @@ import ( ) // ModelToInstanceSettings converts a models.DataSource to a backend.DataSourceInstanceSettings. -func ModelToInstanceSettings(ds *models.DataSource) (*backend.DataSourceInstanceSettings, error) { +func ModelToInstanceSettings(ds *models.DataSource, decryptFn func(map[string][]byte) map[string]string, +) (*backend.DataSourceInstanceSettings, error) { jsonDataBytes, err := ds.JsonData.MarshalJSON() if err != nil { return nil, err @@ -23,7 +24,7 @@ func ModelToInstanceSettings(ds *models.DataSource) (*backend.DataSourceInstance BasicAuthEnabled: ds.BasicAuth, BasicAuthUser: ds.BasicAuthUser, JSONData: jsonDataBytes, - DecryptedSecureJSONData: ds.DecryptedValues(), + DecryptedSecureJSONData: decryptFn(ds.SecureJsonData), Updated: ds.Updated, }, nil } diff --git a/pkg/plugins/plugincontext/plugincontext.go b/pkg/plugins/plugincontext/plugincontext.go index e5b438c5576..953e5bf2b98 100644 --- a/pkg/plugins/plugincontext/plugincontext.go +++ b/pkg/plugins/plugincontext/plugincontext.go @@ -1,36 +1,47 @@ package plugincontext import ( + "context" "encoding/json" "errors" "time" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/localcache" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/adapters" "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/encryption" + "github.com/grafana/grafana/pkg/services/pluginsettings" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util/errutil" ) func ProvideService(bus bus.Bus, cacheService *localcache.CacheService, pluginManager plugins.Manager, - dataSourceCache datasources.CacheService) *Provider { + dataSourceCache datasources.CacheService, encryptionService encryption.Service, + pluginSettingsService *pluginsettings.Service) *Provider { return &Provider{ - Bus: bus, - CacheService: cacheService, - PluginManager: pluginManager, - DataSourceCache: dataSourceCache, + Bus: bus, + CacheService: cacheService, + PluginManager: pluginManager, + DataSourceCache: dataSourceCache, + EncryptionService: encryptionService, + PluginSettingsService: pluginSettingsService, + logger: log.New("plugincontext"), } } type Provider struct { - Bus bus.Bus - CacheService *localcache.CacheService - PluginManager plugins.Manager - DataSourceCache datasources.CacheService + Bus bus.Bus + CacheService *localcache.CacheService + PluginManager plugins.Manager + DataSourceCache datasources.CacheService + EncryptionService encryption.Service + PluginSettingsService *pluginsettings.Service + logger log.Logger } // Get allows getting plugin context by its ID. If datasourceUID is not empty string @@ -59,7 +70,7 @@ func (p *Provider) Get(pluginID string, datasourceUID string, user *models.Signe if err != nil { return pc, false, errutil.Wrap("Failed to unmarshal plugin json data", err) } - decryptedSecureJSONData = ps.DecryptedValues() + decryptedSecureJSONData = p.PluginSettingsService.DecryptedValues(ps) updated = ps.Updated } @@ -79,7 +90,7 @@ func (p *Provider) Get(pluginID string, datasourceUID string, user *models.Signe if err != nil { return pc, false, errutil.Wrap("Failed to get datasource", err) } - datasourceSettings, err := adapters.ModelToInstanceSettings(ds) + datasourceSettings, err := adapters.ModelToInstanceSettings(ds, p.decryptSecureJsonDataFn()) if err != nil { return pc, false, errutil.Wrap("Failed to convert datasource", err) } @@ -110,3 +121,13 @@ func (p *Provider) getCachedPluginSettings(pluginID string, user *models.SignedI p.CacheService.Set(cacheKey, query.Result, pluginSettingsCacheTTL) return query.Result, nil } + +func (p *Provider) decryptSecureJsonDataFn() func(map[string][]byte) map[string]string { + return func(m map[string][]byte) map[string]string { + decryptedJsonData, err := p.EncryptionService.DecryptJsonData(context.Background(), m, setting.SecretKey) + if err != nil { + p.logger.Error("Failed to decrypt secure json data", "error", err) + } + return decryptedJsonData + } +} diff --git a/pkg/server/backgroundsvcs/background_services.go b/pkg/server/backgroundsvcs/background_services.go index f8fab0d6610..3c185aaf213 100644 --- a/pkg/server/backgroundsvcs/background_services.go +++ b/pkg/server/backgroundsvcs/background_services.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/services/live/pushhttp" "github.com/grafana/grafana/pkg/services/ngalert" "github.com/grafana/grafana/pkg/services/notifications" + "github.com/grafana/grafana/pkg/services/pluginsettings" "github.com/grafana/grafana/pkg/services/provisioning" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/secrets" @@ -49,7 +50,7 @@ func ProvideBackgroundServiceRegistry( _ *influxdb.Service, _ *loki.Service, _ *opentsdb.Service, _ *prometheus.Service, _ *tempo.Service, _ *testdatasource.TestDataPlugin, _ *plugindashboards.Service, _ *dashboardsnapshots.Service, _ secrets.SecretsService, _ *postgres.Service, _ *mysql.Service, _ *mssql.Service, _ *grafanads.Service, - + _ *pluginsettings.Service, _ *alerting.AlertNotificationService, ) *BackgroundServiceRegistry { return NewBackgroundServiceRegistry( httpServer, diff --git a/pkg/server/wire.go b/pkg/server/wire.go index a568c25770d..e9c6e969529 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -33,6 +33,7 @@ import ( "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/datasourceproxy" + "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/librarypanels" @@ -45,6 +46,7 @@ import ( ngmetrics "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/oauthtoken" + "github.com/grafana/grafana/pkg/services/pluginsettings" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/schemaloader" @@ -146,6 +148,9 @@ var wireBasicSet = wire.NewSet( secrets.ProvideSecretsService, grafanads.ProvideService, dashboardsnapshots.ProvideService, + datasources.ProvideService, + pluginsettings.ProvideService, + alerting.ProvideService, ) var wireSet = wire.NewSet( diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index 97adcf42c4f..16a59e134ae 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/setting" "github.com/opentracing/opentracing-go" @@ -47,7 +48,8 @@ func (e *AlertEngine) IsDisabled() bool { // ProvideAlertEngine returns a new AlertEngine. func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidator models.PluginRequestValidator, - dataService plugins.DataRequestHandler, usageStatsService usagestats.Service, cfg *setting.Cfg) *AlertEngine { + dataService plugins.DataRequestHandler, usageStatsService usagestats.Service, encryptionService encryption.Service, + cfg *setting.Cfg) *AlertEngine { e := &AlertEngine{ Cfg: cfg, RenderService: renderer, @@ -62,7 +64,7 @@ func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidato e.evalHandler = NewEvalHandler(e.DataService) e.ruleReader = newRuleReader() e.log = log.New("alerting.engine") - e.resultHandler = newResultHandler(e.RenderService) + e.resultHandler = newResultHandler(e.RenderService, encryptionService.GetDecryptedValue) e.registerUsageMetrics() diff --git a/pkg/services/alerting/engine_integration_test.go b/pkg/services/alerting/engine_integration_test.go index c01563299ee..d604bc1e7d2 100644 --- a/pkg/services/alerting/engine_integration_test.go +++ b/pkg/services/alerting/engine_integration_test.go @@ -13,7 +13,7 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/usagestats" - + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) @@ -21,7 +21,7 @@ import ( func TestEngineTimeouts(t *testing.T) { Convey("Alerting engine timeout tests", t, func() { usMock := &usagestats.UsageStatsMock{T: t} - engine := ProvideAlertEngine(nil, nil, nil, nil, usMock, setting.NewCfg()) + engine := ProvideAlertEngine(nil, nil, nil, nil, usMock, ossencryption.ProvideService(), setting.NewCfg()) setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingMaxAttempts = 3 engine.resultHandler = &FakeResultHandler{} diff --git a/pkg/services/alerting/engine_test.go b/pkg/services/alerting/engine_test.go index 3a87f616d41..703f892338c 100644 --- a/pkg/services/alerting/engine_test.go +++ b/pkg/services/alerting/engine_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) @@ -44,7 +45,7 @@ func TestEngineProcessJob(t *testing.T) { Convey("Alerting engine job processing", t, func() { bus := bus.New() usMock := &usagestats.UsageStatsMock{T: t} - engine := ProvideAlertEngine(nil, bus, nil, nil, usMock, setting.NewCfg()) + engine := ProvideAlertEngine(nil, bus, nil, nil, usMock, ossencryption.ProvideService(), setting.NewCfg()) setting.AlertingEvaluationTimeout = 30 * time.Second setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingMaxAttempts = 3 diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index eb4cf503a9b..bd201049ac8 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -181,12 +181,14 @@ func TestAlertRuleExtraction(t *testing.T) { }) t.Run("Alert notifications are in DB", func(t *testing.T) { - sqlstore.InitTestDB(t) + sqlStore := sqlstore.InitTestDB(t) + firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"} - err = sqlstore.CreateAlertNotificationCommand(&firstNotification) + err = sqlStore.CreateAlertNotificationCommand(&firstNotification) require.Nil(t, err) + secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"} - err = sqlstore.CreateAlertNotificationCommand(&secondNotification) + err = sqlStore.CreateAlertNotificationCommand(&secondNotification) require.Nil(t, err) json, err := ioutil.ReadFile("./testdata/influxdb-alert.json") diff --git a/pkg/services/alerting/notifier.go b/pkg/services/alerting/notifier.go index 42e68c5e60e..2dbbf86eb28 100644 --- a/pkg/services/alerting/notifier.go +++ b/pkg/services/alerting/notifier.go @@ -83,16 +83,18 @@ type ShowWhen struct { Is string `json:"is"` } -func newNotificationService(renderService rendering.Service) *notificationService { +func newNotificationService(renderService rendering.Service, decryptFn GetDecryptedValueFn) *notificationService { return ¬ificationService{ log: log.New("alerting.notifier"), renderService: renderService, + decryptFn: decryptFn, } } type notificationService struct { log log.Logger renderService rendering.Service + decryptFn GetDecryptedValueFn } func (n *notificationService) SendIfNeeded(evalCtx *EvalContext) error { @@ -250,7 +252,7 @@ func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids [ var result notifierStateSlice for _, notification := range query.Result { - not, err := InitNotifier(notification) + not, err := InitNotifier(notification, n.decryptFn) if err != nil { n.log.Error("Could not create notifier", "notifier", notification.Uid, "error", err) continue @@ -280,17 +282,21 @@ func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids [ } // InitNotifier instantiate a new notifier based on the model. -func InitNotifier(model *models.AlertNotification) (Notifier, error) { +func InitNotifier(model *models.AlertNotification, fn GetDecryptedValueFn) (Notifier, error) { notifierPlugin, found := notifierFactories[model.Type] if !found { return nil, fmt.Errorf("unsupported notification type %q", model.Type) } - return notifierPlugin.Factory(model) + return notifierPlugin.Factory(model, fn) } +// GetDecryptedValueFn is a function that returns the decrypted value of +// the given key. If the key is not present, then it returns the fallback value. +type GetDecryptedValueFn func(ctx context.Context, sjd map[string][]byte, key string, fallback string, secret string) string + // NotifierFactory is a signature for creating notifiers. -type NotifierFactory func(notification *models.AlertNotification) (Notifier, error) +type NotifierFactory func(*models.AlertNotification, GetDecryptedValueFn) (Notifier, error) var notifierFactories = make(map[string]*NotifierPlugin) diff --git a/pkg/services/alerting/notifier_test.go b/pkg/services/alerting/notifier_test.go index b1d460fec3c..56fab2e75ad 100644 --- a/pkg/services/alerting/notifier_test.go +++ b/pkg/services/alerting/notifier_test.go @@ -263,7 +263,7 @@ func notificationServiceScenario(t *testing.T, name string, evalCtx *EvalContext }, } - scenarioCtx.notificationService = newNotificationService(renderService) + scenarioCtx.notificationService = newNotificationService(renderService, nil) fn(scenarioCtx) }) } @@ -279,7 +279,7 @@ type testNotifier struct { Frequency time.Duration } -func newTestNotifier(model *models.AlertNotification) (Notifier, error) { +func newTestNotifier(model *models.AlertNotification, _ GetDecryptedValueFn) (Notifier, error) { uploadImage := true value, exist := model.Settings.CheckGet("uploadImage") if exist { diff --git a/pkg/services/alerting/notifiers/alertmanager.go b/pkg/services/alerting/notifiers/alertmanager.go index fc11b9153af..8b999e9a14f 100644 --- a/pkg/services/alerting/notifiers/alertmanager.go +++ b/pkg/services/alerting/notifiers/alertmanager.go @@ -12,6 +12,7 @@ import ( "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/setting" ) func init() { @@ -49,7 +50,7 @@ func init() { } // NewAlertmanagerNotifier returns a new Alertmanager notifier -func NewAlertmanagerNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewAlertmanagerNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { urlString := model.Settings.Get("url").MustString() if urlString == "" { return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} @@ -63,7 +64,7 @@ func NewAlertmanagerNotifier(model *models.AlertNotification) (alerting.Notifier } } basicAuthUser := model.Settings.Get("basicAuthUser").MustString() - basicAuthPassword := model.DecryptedValue("basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString()) + basicAuthPassword := fn(context.Background(), model.SecureSettings, "basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString(), setting.SecretKey) return &AlertmanagerNotifier{ NotifierBase: NewNotifierBase(model), diff --git a/pkg/services/alerting/notifiers/alertmanager_test.go b/pkg/services/alerting/notifiers/alertmanager_test.go index 7f0c5ac12b9..e764d3e6fde 100644 --- a/pkg/services/alerting/notifiers/alertmanager_test.go +++ b/pkg/services/alerting/notifiers/alertmanager_test.go @@ -4,15 +4,14 @@ import ( "context" "testing" - "github.com/grafana/grafana/pkg/services/validations" - - "github.com/stretchr/testify/assert" - "github.com/grafana/grafana/pkg/components/simplejson" "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/ossencryption" + "github.com/grafana/grafana/pkg/services/validations" . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func TestReplaceIllegalCharswithUnderscore(t *testing.T) { @@ -93,7 +92,7 @@ func TestAlertmanagerNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewAlertmanagerNotifier(model) + _, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -107,7 +106,7 @@ func TestAlertmanagerNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewAlertmanagerNotifier(model) + not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue) alertmanagerNotifier := not.(*AlertmanagerNotifier) So(err, ShouldBeNil) @@ -126,7 +125,7 @@ func TestAlertmanagerNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewAlertmanagerNotifier(model) + not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue) alertmanagerNotifier := not.(*AlertmanagerNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/base_test.go b/pkg/services/alerting/notifiers/base_test.go index d85302c8589..24e08b39f23 100644 --- a/pkg/services/alerting/notifiers/base_test.go +++ b/pkg/services/alerting/notifiers/base_test.go @@ -5,14 +5,12 @@ import ( "testing" "time" - "github.com/grafana/grafana/pkg/services/validations" - - "github.com/stretchr/testify/assert" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/validations" . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" ) func TestShouldSendAlertNotification(t *testing.T) { diff --git a/pkg/services/alerting/notifiers/dingding.go b/pkg/services/alerting/notifiers/dingding.go index cad82229253..0ac1913cbbb 100644 --- a/pkg/services/alerting/notifiers/dingding.go +++ b/pkg/services/alerting/notifiers/dingding.go @@ -47,7 +47,7 @@ func init() { }) } -func newDingDingNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func newDingDingNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} diff --git a/pkg/services/alerting/notifiers/dingding_test.go b/pkg/services/alerting/notifiers/dingding_test.go index 4a53a4e4b47..6f193cfeda5 100644 --- a/pkg/services/alerting/notifiers/dingding_test.go +++ b/pkg/services/alerting/notifiers/dingding_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" - "github.com/grafana/grafana/pkg/services/validations" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/grafana/grafana/pkg/services/validations" . "github.com/smartystreets/goconvey/convey" ) @@ -24,7 +24,7 @@ func TestDingDingNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := newDingDingNotifier(model) + _, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) Convey("settings should trigger incident", func() { @@ -37,7 +37,7 @@ func TestDingDingNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := newDingDingNotifier(model) + not, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue) notifier := not.(*DingDingNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/discord.go b/pkg/services/alerting/notifiers/discord.go index 36225dc2fc2..72534fdeee8 100644 --- a/pkg/services/alerting/notifiers/discord.go +++ b/pkg/services/alerting/notifiers/discord.go @@ -51,7 +51,7 @@ func init() { }) } -func newDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func newDiscordNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) { avatar := model.Settings.Get("avatar_url").MustString() content := model.Settings.Get("content").MustString() url := model.Settings.Get("url").MustString() diff --git a/pkg/services/alerting/notifiers/discord_test.go b/pkg/services/alerting/notifiers/discord_test.go index 795454361cd..a50209ea6ab 100644 --- a/pkg/services/alerting/notifiers/discord_test.go +++ b/pkg/services/alerting/notifiers/discord_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" . "github.com/smartystreets/goconvey/convey" ) @@ -21,7 +22,7 @@ func TestDiscordNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := newDiscordNotifier(model) + _, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -40,7 +41,7 @@ func TestDiscordNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := newDiscordNotifier(model) + not, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue) discordNotifier := not.(*DiscordNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/email.go b/pkg/services/alerting/notifiers/email.go index c7f1149f605..59bfb0bd196 100644 --- a/pkg/services/alerting/notifiers/email.go +++ b/pkg/services/alerting/notifiers/email.go @@ -48,7 +48,7 @@ type EmailNotifier struct { // NewEmailNotifier is the constructor function // for the EmailNotifier. -func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewEmailNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) { addressesString := model.Settings.Get("addresses").MustString() singleEmail := model.Settings.Get("singleEmail").MustBool(false) diff --git a/pkg/services/alerting/notifiers/email_test.go b/pkg/services/alerting/notifiers/email_test.go index 62c15e17741..6f01b020e65 100644 --- a/pkg/services/alerting/notifiers/email_test.go +++ b/pkg/services/alerting/notifiers/email_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" . "github.com/smartystreets/goconvey/convey" ) @@ -21,7 +22,7 @@ func TestEmailNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewEmailNotifier(model) + _, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -38,7 +39,7 @@ func TestEmailNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewEmailNotifier(model) + not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue) emailNotifier := not.(*EmailNotifier) So(err, ShouldBeNil) @@ -62,7 +63,7 @@ func TestEmailNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewEmailNotifier(model) + not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue) emailNotifier := not.(*EmailNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/googlechat.go b/pkg/services/alerting/notifiers/googlechat.go index f66d04e486a..49a12479268 100644 --- a/pkg/services/alerting/notifiers/googlechat.go +++ b/pkg/services/alerting/notifiers/googlechat.go @@ -32,7 +32,7 @@ func init() { }) } -func newGoogleChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func newGoogleChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} diff --git a/pkg/services/alerting/notifiers/googlechat_test.go b/pkg/services/alerting/notifiers/googlechat_test.go index 27063851e5e..52b33a2c018 100644 --- a/pkg/services/alerting/notifiers/googlechat_test.go +++ b/pkg/services/alerting/notifiers/googlechat_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" . "github.com/smartystreets/goconvey/convey" ) @@ -21,7 +22,7 @@ func TestGoogleChatNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := newGoogleChatNotifier(model) + _, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -38,7 +39,7 @@ func TestGoogleChatNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := newGoogleChatNotifier(model) + not, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) webhookNotifier := not.(*GoogleChatNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/hipchat.go b/pkg/services/alerting/notifiers/hipchat.go index 909308008df..77520e4b275 100644 --- a/pkg/services/alerting/notifiers/hipchat.go +++ b/pkg/services/alerting/notifiers/hipchat.go @@ -53,7 +53,7 @@ const ( // NewHipChatNotifier is the constructor functions // for the HipChatNotifier -func NewHipChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewHipChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if strings.HasSuffix(url, "/") { url = url[:len(url)-1] diff --git a/pkg/services/alerting/notifiers/hipchat_test.go b/pkg/services/alerting/notifiers/hipchat_test.go index 02ff0305ac5..cd3edc04cdc 100644 --- a/pkg/services/alerting/notifiers/hipchat_test.go +++ b/pkg/services/alerting/notifiers/hipchat_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" . "github.com/smartystreets/goconvey/convey" ) @@ -22,7 +23,7 @@ func TestHipChatNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewHipChatNotifier(model) + _, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -38,7 +39,7 @@ func TestHipChatNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewHipChatNotifier(model) + not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) hipchatNotifier := not.(*HipChatNotifier) So(err, ShouldBeNil) @@ -64,7 +65,7 @@ func TestHipChatNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewHipChatNotifier(model) + not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue) hipchatNotifier := not.(*HipChatNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/kafka.go b/pkg/services/alerting/notifiers/kafka.go index e4bdc355500..ed02f10cd5a 100644 --- a/pkg/services/alerting/notifiers/kafka.go +++ b/pkg/services/alerting/notifiers/kafka.go @@ -41,7 +41,7 @@ func init() { } // NewKafkaNotifier is the constructor function for the Kafka notifier. -func NewKafkaNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewKafkaNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) { endpoint := model.Settings.Get("kafkaRestProxy").MustString() if endpoint == "" { return nil, alerting.ValidationError{Reason: "Could not find kafka rest proxy endpoint property in settings"} diff --git a/pkg/services/alerting/notifiers/kafka_test.go b/pkg/services/alerting/notifiers/kafka_test.go index f22874268c2..1b83e863748 100644 --- a/pkg/services/alerting/notifiers/kafka_test.go +++ b/pkg/services/alerting/notifiers/kafka_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" . "github.com/smartystreets/goconvey/convey" ) @@ -21,7 +22,7 @@ func TestKafkaNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewKafkaNotifier(model) + _, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -39,7 +40,7 @@ func TestKafkaNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewKafkaNotifier(model) + not, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) kafkaNotifier := not.(*KafkaNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/line.go b/pkg/services/alerting/notifiers/line.go index ee6be604f3c..4b760ff4570 100644 --- a/pkg/services/alerting/notifiers/line.go +++ b/pkg/services/alerting/notifiers/line.go @@ -1,6 +1,7 @@ package notifiers import ( + "context" "fmt" "net/url" @@ -8,6 +9,7 @@ import ( "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/setting" ) func init() { @@ -35,8 +37,8 @@ const ( ) // NewLINENotifier is the constructor for the LINE notifier -func NewLINENotifier(model *models.AlertNotification) (alerting.Notifier, error) { - token := model.DecryptedValue("token", model.Settings.Get("token").MustString()) +func NewLINENotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { + token := fn(context.Background(), model.SecureSettings, "token", model.Settings.Get("token").MustString(), setting.SecretKey) if token == "" { return nil, alerting.ValidationError{Reason: "Could not find token in settings"} } diff --git a/pkg/services/alerting/notifiers/line_test.go b/pkg/services/alerting/notifiers/line_test.go index 47438a3b33f..1fc3b0a59fc 100644 --- a/pkg/services/alerting/notifiers/line_test.go +++ b/pkg/services/alerting/notifiers/line_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" . "github.com/smartystreets/goconvey/convey" ) @@ -20,7 +21,7 @@ func TestLineNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewLINENotifier(model) + _, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) Convey("settings should trigger incident", func() { @@ -35,7 +36,7 @@ func TestLineNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewLINENotifier(model) + not, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue) lineNotifier := not.(*LineNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/opsgenie.go b/pkg/services/alerting/notifiers/opsgenie.go index 6cb643fdf54..5f52f045e48 100644 --- a/pkg/services/alerting/notifiers/opsgenie.go +++ b/pkg/services/alerting/notifiers/opsgenie.go @@ -1,6 +1,7 @@ package notifiers import ( + "context" "fmt" "strconv" @@ -9,6 +10,7 @@ import ( "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/setting" ) const ( @@ -82,10 +84,10 @@ const ( ) // NewOpsGenieNotifier is the constructor for OpsGenie. -func NewOpsGenieNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewOpsGenieNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { autoClose := model.Settings.Get("autoClose").MustBool(true) overridePriority := model.Settings.Get("overridePriority").MustBool(true) - apiKey := model.DecryptedValue("apiKey", model.Settings.Get("apiKey").MustString()) + apiKey := fn(context.Background(), model.SecureSettings, "apiKey", model.Settings.Get("apiKey").MustString(), setting.SecretKey) apiURL := model.Settings.Get("apiUrl").MustString() if apiKey == "" { return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"} diff --git a/pkg/services/alerting/notifiers/opsgenie_test.go b/pkg/services/alerting/notifiers/opsgenie_test.go index f3f6783be3e..d220eed8984 100644 --- a/pkg/services/alerting/notifiers/opsgenie_test.go +++ b/pkg/services/alerting/notifiers/opsgenie_test.go @@ -4,12 +4,12 @@ import ( "context" "testing" - "github.com/grafana/grafana/pkg/services/validations" - "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/alerting" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/grafana/grafana/pkg/services/validations" . "github.com/smartystreets/goconvey/convey" ) @@ -26,7 +26,7 @@ func TestOpsGenieNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewOpsGenieNotifier(model) + _, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -43,7 +43,7 @@ func TestOpsGenieNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewOpsGenieNotifier(model) + not, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) opsgenieNotifier := not.(*OpsGenieNotifier) So(err, ShouldBeNil) @@ -67,7 +67,7 @@ func TestOpsGenieNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewOpsGenieNotifier(model) + _, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) So(err, ShouldHaveSameTypeAs, alerting.ValidationError{}) So(err.Error(), ShouldEndWith, "Invalid value for sendTagsAs: \"not_a_valid_value\"") @@ -90,7 +90,7 @@ func TestOpsGenieNotifier(t *testing.T) { Settings: settingsJSON, } - notifier, notifierErr := NewOpsGenieNotifier(model) // unhandled error + notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error opsgenieNotifier := notifier.(*OpsGenieNotifier) @@ -140,7 +140,7 @@ func TestOpsGenieNotifier(t *testing.T) { Settings: settingsJSON, } - notifier, notifierErr := NewOpsGenieNotifier(model) // unhandled error + notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error opsgenieNotifier := notifier.(*OpsGenieNotifier) @@ -190,7 +190,7 @@ func TestOpsGenieNotifier(t *testing.T) { Settings: settingsJSON, } - notifier, notifierErr := NewOpsGenieNotifier(model) // unhandled error + notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error opsgenieNotifier := notifier.(*OpsGenieNotifier) diff --git a/pkg/services/alerting/notifiers/pagerduty.go b/pkg/services/alerting/notifiers/pagerduty.go index 0557f5de733..e82b95b6542 100644 --- a/pkg/services/alerting/notifiers/pagerduty.go +++ b/pkg/services/alerting/notifiers/pagerduty.go @@ -1,6 +1,7 @@ package notifiers import ( + "context" "os" "strconv" "strings" @@ -11,6 +12,7 @@ import ( "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/setting" ) func init() { @@ -74,10 +76,10 @@ var ( ) // NewPagerdutyNotifier is the constructor for the PagerDuty notifier -func NewPagerdutyNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewPagerdutyNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { severity := model.Settings.Get("severity").MustString("critical") autoResolve := model.Settings.Get("autoResolve").MustBool(false) - key := model.DecryptedValue("integrationKey", model.Settings.Get("integrationKey").MustString()) + key := fn(context.Background(), model.SecureSettings, "integrationKey", model.Settings.Get("integrationKey").MustString(), setting.SecretKey) messageInDetails := model.Settings.Get("messageInDetails").MustBool(false) if key == "" { return nil, alerting.ValidationError{Reason: "Could not find integration key property in settings"} diff --git a/pkg/services/alerting/notifiers/pagerduty_test.go b/pkg/services/alerting/notifiers/pagerduty_test.go index 7d4ae74201f..fac067a6717 100644 --- a/pkg/services/alerting/notifiers/pagerduty_test.go +++ b/pkg/services/alerting/notifiers/pagerduty_test.go @@ -5,13 +5,13 @@ import ( "strings" "testing" - "github.com/grafana/grafana/pkg/services/validations" - "github.com/google/go-cmp/cmp" "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/grafana/grafana/pkg/services/validations" . "github.com/smartystreets/goconvey/convey" ) @@ -40,7 +40,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - _, err = NewPagerdutyNotifier(model) + _, err = NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -56,7 +56,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPagerdutyNotifier(model) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) pagerdutyNotifier := not.(*PagerdutyNotifier) So(err, ShouldBeNil) @@ -79,7 +79,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPagerdutyNotifier(model) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) pagerdutyNotifier := not.(*PagerdutyNotifier) So(err, ShouldBeNil) @@ -106,7 +106,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPagerdutyNotifier(model) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) pagerdutyNotifier := not.(*PagerdutyNotifier) So(err, ShouldBeNil) @@ -131,7 +131,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPagerdutyNotifier(model) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldBeNil) pagerdutyNotifier := not.(*PagerdutyNotifier) @@ -188,7 +188,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPagerdutyNotifier(model) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldBeNil) pagerdutyNotifier := not.(*PagerdutyNotifier) @@ -245,7 +245,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPagerdutyNotifier(model) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldBeNil) pagerdutyNotifier := not.(*PagerdutyNotifier) @@ -315,7 +315,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPagerdutyNotifier(model) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldBeNil) pagerdutyNotifier := not.(*PagerdutyNotifier) @@ -395,7 +395,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPagerdutyNotifier(model) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldBeNil) pagerdutyNotifier := not.(*PagerdutyNotifier) @@ -474,7 +474,7 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPagerdutyNotifier(model) + not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldBeNil) pagerdutyNotifier := not.(*PagerdutyNotifier) diff --git a/pkg/services/alerting/notifiers/pushover.go b/pkg/services/alerting/notifiers/pushover.go index 47a6ddec271..23a502739d5 100644 --- a/pkg/services/alerting/notifiers/pushover.go +++ b/pkg/services/alerting/notifiers/pushover.go @@ -2,12 +2,15 @@ package notifiers import ( "bytes" + "context" "fmt" "io" "mime/multipart" "os" "strconv" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" @@ -191,9 +194,9 @@ func init() { } // NewPushoverNotifier is the constructor for the Pushover Notifier -func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, error) { - userKey := model.DecryptedValue("userKey", model.Settings.Get("userKey").MustString()) - APIToken := model.DecryptedValue("apiToken", model.Settings.Get("apiToken").MustString()) +func NewPushoverNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { + userKey := fn(context.Background(), model.SecureSettings, "userKey", model.Settings.Get("userKey").MustString(), setting.SecretKey) + APIToken := fn(context.Background(), model.SecureSettings, "apiToken", model.Settings.Get("apiToken").MustString(), setting.SecretKey) device := model.Settings.Get("device").MustString() alertingPriority, err := strconv.Atoi(model.Settings.Get("priority").MustString("0")) // default Normal if err != nil { diff --git a/pkg/services/alerting/notifiers/pushover_test.go b/pkg/services/alerting/notifiers/pushover_test.go index ae9982e4e00..cef325a87c3 100644 --- a/pkg/services/alerting/notifiers/pushover_test.go +++ b/pkg/services/alerting/notifiers/pushover_test.go @@ -5,12 +5,11 @@ import ( "strings" "testing" - "github.com/grafana/grafana/pkg/services/validations" - - "github.com/grafana/grafana/pkg/services/alerting" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/grafana/grafana/pkg/services/validations" . "github.com/smartystreets/goconvey/convey" ) @@ -27,7 +26,7 @@ func TestPushoverNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewPushoverNotifier(model) + _, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -49,7 +48,7 @@ func TestPushoverNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewPushoverNotifier(model) + not, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue) pushoverNotifier := not.(*PushoverNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/sensu.go b/pkg/services/alerting/notifiers/sensu.go index e5b3d7d2c89..7305105b608 100644 --- a/pkg/services/alerting/notifiers/sensu.go +++ b/pkg/services/alerting/notifiers/sensu.go @@ -1,6 +1,7 @@ package notifiers import ( + "context" "strconv" "strings" @@ -9,6 +10,7 @@ import ( "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/setting" ) func init() { @@ -59,7 +61,7 @@ func init() { } // NewSensuNotifier is the constructor for the Sensu Notifier. -func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewSensuNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} @@ -70,7 +72,7 @@ func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error URL: url, User: model.Settings.Get("username").MustString(), Source: model.Settings.Get("source").MustString(), - Password: model.DecryptedValue("password", model.Settings.Get("password").MustString()), + Password: fn(context.Background(), model.SecureSettings, "password", model.Settings.Get("password").MustString(), setting.SecretKey), Handler: model.Settings.Get("handler").MustString(), log: log.New("alerting.notifier.sensu"), }, nil diff --git a/pkg/services/alerting/notifiers/sensu_test.go b/pkg/services/alerting/notifiers/sensu_test.go index 70ef43b73ef..11684e57e16 100644 --- a/pkg/services/alerting/notifiers/sensu_test.go +++ b/pkg/services/alerting/notifiers/sensu_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" . "github.com/smartystreets/goconvey/convey" ) @@ -21,7 +22,7 @@ func TestSensuNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewSensuNotifier(model) + _, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -40,7 +41,7 @@ func TestSensuNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewSensuNotifier(model) + not, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue) sensuNotifier := not.(*SensuNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/sensugo.go b/pkg/services/alerting/notifiers/sensugo.go index d84dbe00a7c..333ea22156f 100644 --- a/pkg/services/alerting/notifiers/sensugo.go +++ b/pkg/services/alerting/notifiers/sensugo.go @@ -1,6 +1,7 @@ package notifiers import ( + "context" "fmt" "strconv" "strings" @@ -11,6 +12,7 @@ import ( "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/setting" ) func init() { @@ -70,9 +72,9 @@ func init() { } // NewSensuGoNotifier is the constructor for the Sensu Go Notifier. -func NewSensuGoNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewSensuGoNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() - apikey := model.DecryptedValue("apikey", model.Settings.Get("apikey").MustString()) + apikey := fn(context.Background(), model.SecureSettings, "apikey", model.Settings.Get("apikey").MustString(), setting.SecretKey) if url == "" { return nil, alerting.ValidationError{Reason: "Could not find URL property in settings"} diff --git a/pkg/services/alerting/notifiers/sensugo_test.go b/pkg/services/alerting/notifiers/sensugo_test.go index fe8e473fbe4..c1a4eea0a84 100644 --- a/pkg/services/alerting/notifiers/sensugo_test.go +++ b/pkg/services/alerting/notifiers/sensugo_test.go @@ -3,11 +3,11 @@ package notifiers import ( "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSensuGoNotifier(t *testing.T) { @@ -21,7 +21,7 @@ func TestSensuGoNotifier(t *testing.T) { Settings: settingsJSON, } - _, err = NewSensuGoNotifier(model) + _, err = NewSensuGoNotifier(model, ossencryption.ProvideService().GetDecryptedValue) require.Error(t, err) json = ` @@ -42,7 +42,7 @@ func TestSensuGoNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewSensuGoNotifier(model) + not, err := NewSensuGoNotifier(model, ossencryption.ProvideService().GetDecryptedValue) require.NoError(t, err) sensuGoNotifier := not.(*SensuGoNotifier) diff --git a/pkg/services/alerting/notifiers/slack.go b/pkg/services/alerting/notifiers/slack.go index f119800dbed..7b3032c068f 100644 --- a/pkg/services/alerting/notifiers/slack.go +++ b/pkg/services/alerting/notifiers/slack.go @@ -124,8 +124,8 @@ var reRecipient *regexp.Regexp = regexp.MustCompile("^((@[a-z0-9][a-zA-Z0-9._-]* const slackAPIEndpoint = "https://slack.com/api/chat.postMessage" // NewSlackNotifier is the constructor for the Slack notifier. -func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) { - urlStr := model.DecryptedValue("url", model.Settings.Get("url").MustString()) +func NewSlackNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { + urlStr := fn(context.Background(), model.SecureSettings, "url", model.Settings.Get("url").MustString(), setting.SecretKey) if urlStr == "" { urlStr = slackAPIEndpoint } @@ -150,7 +150,7 @@ func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error mentionUsersStr := model.Settings.Get("mentionUsers").MustString() mentionGroupsStr := model.Settings.Get("mentionGroups").MustString() mentionChannel := model.Settings.Get("mentionChannel").MustString() - token := model.DecryptedValue("token", model.Settings.Get("token").MustString()) + token := fn(context.Background(), model.SecureSettings, "token", model.Settings.Get("token").MustString(), setting.SecretKey) if token == "" && apiURL.String() == slackAPIEndpoint { return nil, alerting.ValidationError{ Reason: "token must be specified when using the Slack chat API", diff --git a/pkg/services/alerting/notifiers/slack_test.go b/pkg/services/alerting/notifiers/slack_test.go index a232aac9c2b..dde7cd6c334 100644 --- a/pkg/services/alerting/notifiers/slack_test.go +++ b/pkg/services/alerting/notifiers/slack_test.go @@ -1,11 +1,13 @@ package notifiers import ( + "context" "testing" - "github.com/grafana/grafana/pkg/components/securejsondata" "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/setting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -22,7 +24,7 @@ func TestSlackNotifier(t *testing.T) { Settings: settingsJSON, } - _, err = NewSlackNotifier(model) + _, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue) assert.EqualError(t, err, "alert validation error: recipient must be specified when using the Slack chat API") }) @@ -40,7 +42,7 @@ func TestSlackNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewSlackNotifier(model) + not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue) require.NoError(t, err) slackNotifier := not.(*SlackNotifier) assert.Equal(t, "ops", slackNotifier.Name) @@ -78,7 +80,7 @@ func TestSlackNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewSlackNotifier(model) + not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue) require.NoError(t, err) slackNotifier := not.(*SlackNotifier) assert.Equal(t, "ops", slackNotifier.Name) @@ -110,9 +112,15 @@ func TestSlackNotifier(t *testing.T) { settingsJSON, err := simplejson.NewJson([]byte(json)) require.NoError(t, err) - securedSettingsJSON := securejsondata.GetEncryptedJsonData(map[string]string{ - "token": "xenc-XXXXXXXX-XXXXXXXX-XXXXXXXXXX", - }) + + encryptionService := ossencryption.ProvideService() + securedSettingsJSON, err := encryptionService.EncryptJsonData( + context.Background(), + map[string]string{ + "token": "xenc-XXXXXXXX-XXXXXXXX-XXXXXXXXXX", + }, setting.SecretKey) + require.NoError(t, err) + model := &models.AlertNotification{ Name: "ops", Type: "slack", @@ -120,7 +128,7 @@ func TestSlackNotifier(t *testing.T) { SecureSettings: securedSettingsJSON, } - not, err := NewSlackNotifier(model) + not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue) require.NoError(t, err) slackNotifier := not.(*SlackNotifier) assert.Equal(t, "ops", slackNotifier.Name) @@ -151,7 +159,7 @@ func TestSlackNotifier(t *testing.T) { Settings: settingsJSON, } - _, err = NewSlackNotifier(model) + _, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue) assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"#open tsdb\"") }) @@ -170,7 +178,7 @@ func TestSlackNotifier(t *testing.T) { Settings: settingsJSON, } - _, err = NewSlackNotifier(model) + _, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue) assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"@user name\"") }) @@ -189,7 +197,7 @@ func TestSlackNotifier(t *testing.T) { Settings: settingsJSON, } - _, err = NewSlackNotifier(model) + _, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue) assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"@User\"") }) @@ -208,7 +216,7 @@ func TestSlackNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewSlackNotifier(model) + not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue) require.NoError(t, err) slackNotifier := not.(*SlackNotifier) assert.Equal(t, "1ABCDE", slackNotifier.recipient) diff --git a/pkg/services/alerting/notifiers/teams.go b/pkg/services/alerting/notifiers/teams.go index 5e48a960bca..50f511a81ac 100644 --- a/pkg/services/alerting/notifiers/teams.go +++ b/pkg/services/alerting/notifiers/teams.go @@ -30,7 +30,7 @@ func init() { } // NewTeamsNotifier is the constructor for Teams notifier. -func NewTeamsNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewTeamsNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} diff --git a/pkg/services/alerting/notifiers/teams_test.go b/pkg/services/alerting/notifiers/teams_test.go index 40b56e5b5c4..f1a267a66c2 100644 --- a/pkg/services/alerting/notifiers/teams_test.go +++ b/pkg/services/alerting/notifiers/teams_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" . "github.com/smartystreets/goconvey/convey" ) @@ -21,7 +22,7 @@ func TestTeamsNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewTeamsNotifier(model) + _, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -38,7 +39,7 @@ func TestTeamsNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewTeamsNotifier(model) + not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) teamsNotifier := not.(*TeamsNotifier) So(err, ShouldBeNil) @@ -60,7 +61,7 @@ func TestTeamsNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewTeamsNotifier(model) + not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) teamsNotifier := not.(*TeamsNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/telegram.go b/pkg/services/alerting/notifiers/telegram.go index eef7a6c5530..20c4bcfb656 100644 --- a/pkg/services/alerting/notifiers/telegram.go +++ b/pkg/services/alerting/notifiers/telegram.go @@ -2,6 +2,7 @@ package notifiers import ( "bytes" + "context" "fmt" "io" "mime/multipart" @@ -11,6 +12,7 @@ import ( "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/setting" ) const ( @@ -61,12 +63,12 @@ type TelegramNotifier struct { } // NewTelegramNotifier is the constructor for the Telegram notifier -func NewTelegramNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewTelegramNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { if model.Settings == nil { return nil, alerting.ValidationError{Reason: "No Settings Supplied"} } - botToken := model.DecryptedValue("bottoken", model.Settings.Get("bottoken").MustString()) + botToken := fn(context.Background(), model.SecureSettings, "bottoken", model.Settings.Get("bottoken").MustString(), setting.SecretKey) chatID := model.Settings.Get("chatid").MustString() uploadImage := model.Settings.Get("uploadImage").MustBool() diff --git a/pkg/services/alerting/notifiers/telegram_test.go b/pkg/services/alerting/notifiers/telegram_test.go index 5a2db4c4869..7d2424b1324 100644 --- a/pkg/services/alerting/notifiers/telegram_test.go +++ b/pkg/services/alerting/notifiers/telegram_test.go @@ -4,11 +4,11 @@ import ( "context" "testing" - "github.com/grafana/grafana/pkg/services/validations" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/grafana/grafana/pkg/services/validations" . "github.com/smartystreets/goconvey/convey" ) @@ -25,7 +25,7 @@ func TestTelegramNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewTelegramNotifier(model) + _, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -43,7 +43,7 @@ func TestTelegramNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewTelegramNotifier(model) + not, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue) telegramNotifier := not.(*TelegramNotifier) So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/threema.go b/pkg/services/alerting/notifiers/threema.go index 8b63c0bc183..712da9ffd68 100644 --- a/pkg/services/alerting/notifiers/threema.go +++ b/pkg/services/alerting/notifiers/threema.go @@ -1,6 +1,7 @@ package notifiers import ( + "context" "fmt" "net/url" "strings" @@ -9,6 +10,7 @@ import ( "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/setting" ) var ( @@ -69,14 +71,14 @@ type ThreemaNotifier struct { } // NewThreemaNotifier is the constructor for the Threema notifier -func NewThreemaNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewThreemaNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { if model.Settings == nil { return nil, alerting.ValidationError{Reason: "No Settings Supplied"} } gatewayID := model.Settings.Get("gateway_id").MustString() recipientID := model.Settings.Get("recipient_id").MustString() - apiSecret := model.DecryptedValue("api_secret", model.Settings.Get("api_secret").MustString()) + apiSecret := fn(context.Background(), model.SecureSettings, "api_secret", model.Settings.Get("api_secret").MustString(), setting.SecretKey) // Validation if gatewayID == "" { diff --git a/pkg/services/alerting/notifiers/threema_test.go b/pkg/services/alerting/notifiers/threema_test.go index 4c6b1ed87f3..6ccfcee7f75 100644 --- a/pkg/services/alerting/notifiers/threema_test.go +++ b/pkg/services/alerting/notifiers/threema_test.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" . "github.com/smartystreets/goconvey/convey" ) @@ -23,7 +24,7 @@ func TestThreemaNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewThreemaNotifier(model) + _, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -42,7 +43,7 @@ func TestThreemaNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewThreemaNotifier(model) + not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldBeNil) threemaNotifier := not.(*ThreemaNotifier) @@ -69,7 +70,7 @@ func TestThreemaNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewThreemaNotifier(model) + not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(not, ShouldBeNil) var valErr alerting.ValidationError So(errors.As(err, &valErr), ShouldBeTrue) @@ -91,7 +92,7 @@ func TestThreemaNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewThreemaNotifier(model) + not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(not, ShouldBeNil) var valErr alerting.ValidationError So(errors.As(err, &valErr), ShouldBeTrue) @@ -113,7 +114,7 @@ func TestThreemaNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewThreemaNotifier(model) + not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(not, ShouldBeNil) var valErr alerting.ValidationError So(errors.As(err, &valErr), ShouldBeTrue) diff --git a/pkg/services/alerting/notifiers/victorops.go b/pkg/services/alerting/notifiers/victorops.go index 1d1111deb83..6d7e0379b7a 100644 --- a/pkg/services/alerting/notifiers/victorops.go +++ b/pkg/services/alerting/notifiers/victorops.go @@ -47,7 +47,7 @@ func init() { // NewVictoropsNotifier creates an instance of VictoropsNotifier that // handles posting notifications to Victorops REST API -func NewVictoropsNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewVictoropsNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn) (alerting.Notifier, error) { autoResolve := model.Settings.Get("autoResolve").MustBool(true) url := model.Settings.Get("url").MustString() if url == "" { diff --git a/pkg/services/alerting/notifiers/victorops_test.go b/pkg/services/alerting/notifiers/victorops_test.go index f5669cb9d65..264a85b0907 100644 --- a/pkg/services/alerting/notifiers/victorops_test.go +++ b/pkg/services/alerting/notifiers/victorops_test.go @@ -4,12 +4,12 @@ import ( "context" "testing" - "github.com/grafana/grafana/pkg/services/validations" - "github.com/google/go-cmp/cmp" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/grafana/grafana/pkg/services/validations" . "github.com/smartystreets/goconvey/convey" ) @@ -35,7 +35,7 @@ func TestVictoropsNotifier(t *testing.T) { Settings: settingsJSON, } - _, err := NewVictoropsNotifier(model) + _, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldNotBeNil) }) @@ -52,7 +52,7 @@ func TestVictoropsNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewVictoropsNotifier(model) + not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) victoropsNotifier := not.(*VictoropsNotifier) So(err, ShouldBeNil) @@ -76,7 +76,7 @@ func TestVictoropsNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewVictoropsNotifier(model) + not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldBeNil) victoropsNotifier := not.(*VictoropsNotifier) @@ -124,7 +124,7 @@ func TestVictoropsNotifier(t *testing.T) { Settings: settingsJSON, } - not, err := NewVictoropsNotifier(model) + not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue) So(err, ShouldBeNil) victoropsNotifier := not.(*VictoropsNotifier) diff --git a/pkg/services/alerting/notifiers/webhook.go b/pkg/services/alerting/notifiers/webhook.go index 9a98905c491..c39d677bd11 100644 --- a/pkg/services/alerting/notifiers/webhook.go +++ b/pkg/services/alerting/notifiers/webhook.go @@ -1,12 +1,14 @@ package notifiers import ( + "context" "encoding/json" "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/setting" ) func init() { @@ -58,13 +60,13 @@ func init() { // NewWebHookNotifier is the constructor for // the WebHook notifier. -func NewWebHookNotifier(model *models.AlertNotification) (alerting.Notifier, error) { +func NewWebHookNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} } - password := model.DecryptedValue("password", model.Settings.Get("password").MustString()) + password := fn(context.Background(), model.SecureSettings, "password", model.Settings.Get("password").MustString(), setting.SecretKey) return &WebhookNotifier{ NotifierBase: NewNotifierBase(model), diff --git a/pkg/services/alerting/notifiers/webhook_test.go b/pkg/services/alerting/notifiers/webhook_test.go index 4af905802d1..0054d3f3a4c 100644 --- a/pkg/services/alerting/notifiers/webhook_test.go +++ b/pkg/services/alerting/notifiers/webhook_test.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,7 +22,7 @@ func TestWebhookNotifier_parsingFromSettings(t *testing.T) { Settings: settingsJSON, } - _, err = NewWebHookNotifier(model) + _, err = NewWebHookNotifier(model, ossencryption.ProvideService().GetDecryptedValue) require.Error(t, err) }) @@ -36,7 +37,7 @@ func TestWebhookNotifier_parsingFromSettings(t *testing.T) { Settings: settingsJSON, } - not, err := NewWebHookNotifier(model) + not, err := NewWebHookNotifier(model, ossencryption.ProvideService().GetDecryptedValue) require.NoError(t, err) webhookNotifier := not.(*WebhookNotifier) diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index a6cd961a2a9..b9861e5f770 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -24,10 +24,10 @@ type defaultResultHandler struct { log log.Logger } -func newResultHandler(renderService rendering.Service) *defaultResultHandler { +func newResultHandler(renderService rendering.Service, decryptFn GetDecryptedValueFn) *defaultResultHandler { return &defaultResultHandler{ log: log.New("alerting.resultHandler"), - notifier: newNotificationService(renderService), + notifier: newNotificationService(renderService, decryptFn), } } diff --git a/pkg/services/alerting/rule_test.go b/pkg/services/alerting/rule_test.go index b73a76f7858..0294c16fe70 100644 --- a/pkg/services/alerting/rule_test.go +++ b/pkg/services/alerting/rule_test.go @@ -83,16 +83,16 @@ func TestAlertRuleForParsing(t *testing.T) { } func TestAlertRuleModel(t *testing.T) { - sqlstore.InitTestDB(t) + sqlStore := sqlstore.InitTestDB(t) RegisterCondition("test", func(model *simplejson.Json, index int) (Condition, error) { return &FakeCondition{}, nil }) firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"} - err := sqlstore.CreateAlertNotificationCommand(&firstNotification) + err := sqlStore.CreateAlertNotificationCommand(&firstNotification) require.Nil(t, err) secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"} - err = sqlstore.CreateAlertNotificationCommand(&secondNotification) + err = sqlStore.CreateAlertNotificationCommand(&secondNotification) require.Nil(t, err) t.Run("Testing alert rule with notification id and uid", func(t *testing.T) { diff --git a/pkg/services/alerting/service.go b/pkg/services/alerting/service.go new file mode 100644 index 00000000000..0a6b6e6192d --- /dev/null +++ b/pkg/services/alerting/service.go @@ -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) +} diff --git a/pkg/services/alerting/service_test.go b/pkg/services/alerting/service_test.go new file mode 100644 index 00000000000..7adf84ee453 --- /dev/null +++ b/pkg/services/alerting/service_test.go @@ -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) + }) +} diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go index 4b118652cb4..c6a3aa2f90f 100644 --- a/pkg/services/alerting/test_notification.go +++ b/pkg/services/alerting/test_notification.go @@ -6,13 +6,12 @@ import ( "math/rand" "net/http" - "github.com/grafana/grafana/pkg/components/securejsondata" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" ) // NotificationTestCommand initiates an test @@ -31,12 +30,8 @@ var ( logger = log.New("alerting.testnotification") ) -func init() { - bus.AddHandlerCtx("alerting", handleNotificationTestCommand) -} - -func handleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCommand) error { - notifier := newNotificationService(nil) +func (s *AlertNotificationService) HandleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCommand) error { + notifier := newNotificationService(nil, nil) model := &models.AlertNotification{ Name: cmd.Name, @@ -56,7 +51,11 @@ func handleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCom } if query.Result.SecureSettings != nil { - secureSettingsMap = query.Result.SecureSettings.Decrypt() + var err error + secureSettingsMap, err = s.EncryptionService.DecryptJsonData(ctx, query.Result.SecureSettings, setting.SecretKey) + if err != nil { + return err + } } } @@ -64,10 +63,13 @@ func handleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCom secureSettingsMap[k] = v } - model.SecureSettings = securejsondata.GetEncryptedJsonData(secureSettingsMap) - - notifiers, err := InitNotifier(model) + var err error + model.SecureSettings, err = s.EncryptionService.EncryptJsonData(ctx, secureSettingsMap, setting.SecretKey) + if err != nil { + return err + } + notifiers, err := InitNotifier(model, s.EncryptionService.GetDecryptedValue) if err != nil { logger.Error("Failed to create notifier", "error", err.Error()) return err diff --git a/pkg/services/dashboardsnapshots/dashboardsnapshots.go b/pkg/services/dashboardsnapshots/dashboardsnapshots.go index 680561325de..617f112cc98 100644 --- a/pkg/services/dashboardsnapshots/dashboardsnapshots.go +++ b/pkg/services/dashboardsnapshots/dashboardsnapshots.go @@ -1,6 +1,8 @@ package dashboardsnapshots import ( + "context" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" @@ -22,22 +24,22 @@ func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService enc EncryptionService: encryptionService, } - s.Bus.AddHandler(s.CreateDashboardSnapshot) - s.Bus.AddHandler(s.GetDashboardSnapshot) - s.Bus.AddHandler(s.DeleteDashboardSnapshot) - s.Bus.AddHandler(s.SearchDashboardSnapshots) - s.Bus.AddHandler(s.DeleteExpiredSnapshots) + s.Bus.AddHandlerCtx(s.CreateDashboardSnapshot) + s.Bus.AddHandlerCtx(s.GetDashboardSnapshot) + s.Bus.AddHandlerCtx(s.DeleteDashboardSnapshot) + s.Bus.AddHandlerCtx(s.SearchDashboardSnapshots) + s.Bus.AddHandlerCtx(s.DeleteExpiredSnapshots) return s } -func (s *Service) CreateDashboardSnapshot(cmd *models.CreateDashboardSnapshotCommand) error { +func (s *Service) CreateDashboardSnapshot(ctx context.Context, cmd *models.CreateDashboardSnapshotCommand) error { marshalledData, err := cmd.Dashboard.Encode() if err != nil { return err } - encryptedDashboard, err := s.EncryptionService.Encrypt(marshalledData, setting.SecretKey) + encryptedDashboard, err := s.EncryptionService.Encrypt(ctx, marshalledData, setting.SecretKey) if err != nil { return err } @@ -47,14 +49,14 @@ func (s *Service) CreateDashboardSnapshot(cmd *models.CreateDashboardSnapshotCom return s.SQLStore.CreateDashboardSnapshot(cmd) } -func (s *Service) GetDashboardSnapshot(query *models.GetDashboardSnapshotQuery) error { +func (s *Service) GetDashboardSnapshot(ctx context.Context, query *models.GetDashboardSnapshotQuery) error { err := s.SQLStore.GetDashboardSnapshot(query) if err != nil { return err } if query.Result.DashboardEncrypted != nil { - decryptedDashboard, err := s.EncryptionService.Decrypt(query.Result.DashboardEncrypted, setting.SecretKey) + decryptedDashboard, err := s.EncryptionService.Decrypt(ctx, query.Result.DashboardEncrypted, setting.SecretKey) if err != nil { return err } @@ -70,14 +72,14 @@ func (s *Service) GetDashboardSnapshot(query *models.GetDashboardSnapshotQuery) return err } -func (s *Service) DeleteDashboardSnapshot(cmd *models.DeleteDashboardSnapshotCommand) error { +func (s *Service) DeleteDashboardSnapshot(_ context.Context, cmd *models.DeleteDashboardSnapshotCommand) error { return s.SQLStore.DeleteDashboardSnapshot(cmd) } -func (s *Service) SearchDashboardSnapshots(query *models.GetDashboardSnapshotsQuery) error { +func (s *Service) SearchDashboardSnapshots(_ context.Context, query *models.GetDashboardSnapshotsQuery) error { return s.SQLStore.SearchDashboardSnapshots(query) } -func (s *Service) DeleteExpiredSnapshots(cmd *models.DeleteExpiredSnapshotsCommand) error { +func (s *Service) DeleteExpiredSnapshots(_ context.Context, cmd *models.DeleteExpiredSnapshotsCommand) error { return s.SQLStore.DeleteExpiredSnapshots(cmd) } diff --git a/pkg/services/dashboardsnapshots/dashboardsnapshots_test.go b/pkg/services/dashboardsnapshots/dashboardsnapshots_test.go index 9c8c9755b11..1187323fab5 100644 --- a/pkg/services/dashboardsnapshots/dashboardsnapshots_test.go +++ b/pkg/services/dashboardsnapshots/dashboardsnapshots_test.go @@ -1,6 +1,7 @@ package dashboardsnapshots import ( + "context" "testing" "github.com/grafana/grafana/pkg/components/simplejson" @@ -32,28 +33,32 @@ func TestDashboardSnapshotsService(t *testing.T) { require.NoError(t, err) t.Run("create dashboard snapshot should encrypt the dashboard", func(t *testing.T) { + ctx := context.Background() + cmd := models.CreateDashboardSnapshotCommand{ Key: dashboardKey, DeleteKey: dashboardKey, Dashboard: dashboard, } - err = s.CreateDashboardSnapshot(&cmd) + err = s.CreateDashboardSnapshot(ctx, &cmd) require.NoError(t, err) - decrypted, err := s.EncryptionService.Decrypt(cmd.Result.DashboardEncrypted, setting.SecretKey) + decrypted, err := s.EncryptionService.Decrypt(ctx, cmd.Result.DashboardEncrypted, setting.SecretKey) require.NoError(t, err) require.Equal(t, rawDashboard, decrypted) }) t.Run("get dashboard snapshot should return the dashboard decrypted", func(t *testing.T) { + ctx := context.Background() + query := models.GetDashboardSnapshotQuery{ Key: dashboardKey, DeleteKey: dashboardKey, } - err := s.GetDashboardSnapshot(&query) + err := s.GetDashboardSnapshot(ctx, &query) require.NoError(t, err) decrypted, err := query.Result.Dashboard.Encode() diff --git a/pkg/services/datasourceproxy/datasourceproxy.go b/pkg/services/datasourceproxy/datasourceproxy.go index be40693c3f1..15141b369fb 100644 --- a/pkg/services/datasourceproxy/datasourceproxy.go +++ b/pkg/services/datasourceproxy/datasourceproxy.go @@ -19,7 +19,7 @@ import ( func ProvideService(dataSourceCache datasources.CacheService, plugReqValidator models.PluginRequestValidator, pm plugins.Manager, cfg *setting.Cfg, httpClientProvider httpclient.Provider, - oauthTokenService *oauthtoken.Service) *DataSourceProxyService { + oauthTokenService *oauthtoken.Service, dsService *datasources.Service) *DataSourceProxyService { return &DataSourceProxyService{ DataSourceCache: dataSourceCache, PluginRequestValidator: plugReqValidator, @@ -27,6 +27,7 @@ func ProvideService(dataSourceCache datasources.CacheService, plugReqValidator m Cfg: cfg, HTTPClientProvider: httpClientProvider, OAuthTokenService: oauthTokenService, + DataSourcesService: dsService, } } @@ -37,6 +38,7 @@ type DataSourceProxyService struct { Cfg *setting.Cfg HTTPClientProvider httpclient.Provider OAuthTokenService *oauthtoken.Service + DataSourcesService *datasources.Service } func (p *DataSourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) { @@ -73,8 +75,10 @@ func (p *DataSourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqConte return } - proxyPath := getProxyPath(c) - proxy, err := pluginproxy.NewDataSourceProxy(ds, plugin, c, proxyPath, p.Cfg, p.HTTPClientProvider, p.OAuthTokenService) + proxy, err := pluginproxy.NewDataSourceProxy( + ds, plugin, c, getProxyPath(c), p.Cfg, p.HTTPClientProvider, p.OAuthTokenService, p.DataSourcesService, + ) + if err != nil { if errors.Is(err, datasource.URLValidationError{}) { c.JsonApiErr(http.StatusBadRequest, fmt.Sprintf("Invalid data source URL: %q", ds.Url), err) diff --git a/pkg/services/datasources/cache.go b/pkg/services/datasources/cache.go index 9268cd44d8d..5e6cc73a8f6 100644 --- a/pkg/services/datasources/cache.go +++ b/pkg/services/datasources/cache.go @@ -43,11 +43,15 @@ func (dc *CacheServiceImpl) GetDatasource( } plog.Debug("Querying for data source via SQL store", "id", datasourceID, "orgId", user.OrgId) - ds, err := dc.SQLStore.GetDataSource("", datasourceID, "", user.OrgId) + + query := &models.GetDataSourceQuery{Id: datasourceID, OrgId: user.OrgId} + err := dc.SQLStore.GetDataSource(query) if err != nil { return nil, err } + ds := query.Result + if ds.Uid != "" { dc.CacheService.Set(uidKey(ds.OrgId, ds.Uid), ds, time.Second*5) } @@ -78,11 +82,14 @@ func (dc *CacheServiceImpl) GetDatasourceByUID( } plog.Debug("Querying for data source via SQL store", "uid", datasourceUID, "orgId", user.OrgId) - ds, err := dc.SQLStore.GetDataSource(datasourceUID, 0, "", user.OrgId) + query := &models.GetDataSourceQuery{Uid: datasourceUID, OrgId: user.OrgId} + err := dc.SQLStore.GetDataSource(query) if err != nil { return nil, err } + ds := query.Result + dc.CacheService.Set(uidCacheKey, ds, time.Second*5) dc.CacheService.Set(idKey(ds.Id), ds, time.Second*5) return ds, nil diff --git a/pkg/models/datasource_cache.go b/pkg/services/datasources/service.go similarity index 51% rename from pkg/models/datasource_cache.go rename to pkg/services/datasources/service.go index 3a793787dc1..67cdfd793a7 100644 --- a/pkg/models/datasource_cache.go +++ b/pkg/services/datasources/service.go @@ -1,6 +1,7 @@ -package models +package datasources import ( + "context" "crypto/tls" "fmt" "net/http" @@ -9,29 +10,23 @@ import ( "time" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/httpclient" + "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" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials" ) -func (ds *DataSource) getTimeout() time.Duration { - timeout := 0 - if ds.JsonData != nil { - timeout = ds.JsonData.Get("timeout").MustInt() - if timeout <= 0 { - if timeoutStr := ds.JsonData.Get("timeout").MustString(); timeoutStr != "" { - if t, err := strconv.Atoi(timeoutStr); err == nil { - timeout = t - } - } - } - } - if timeout <= 0 { - return sdkhttpclient.DefaultTimeoutOptions.Timeout - } +type Service struct { + Bus bus.Bus + SQLStore *sqlstore.SQLStore + EncryptionService encryption.Service - return time.Duration(timeout) * time.Second + ptc proxyTransportCache + dsDecryptionCache secureJSONDecryptionCache } type proxyTransportCache struct { @@ -44,31 +39,102 @@ type cachedRoundTripper struct { roundTripper http.RoundTripper } -var ptc = proxyTransportCache{ - cache: make(map[int64]cachedRoundTripper), +type secureJSONDecryptionCache struct { + cache map[int64]cachedDecryptedJSON + sync.Mutex } -func (ds *DataSource) GetHTTPClient(provider httpclient.Provider) (*http.Client, error) { - transport, err := ds.GetHTTPTransport(provider) +type cachedDecryptedJSON struct { + updated time.Time + json map[string]string +} + +func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService encryption.Service) *Service { + s := &Service{ + Bus: bus, + SQLStore: store, + EncryptionService: encryptionService, + ptc: proxyTransportCache{ + cache: make(map[int64]cachedRoundTripper), + }, + dsDecryptionCache: secureJSONDecryptionCache{ + cache: make(map[int64]cachedDecryptedJSON), + }, + } + + s.Bus.AddHandler(s.GetDataSources) + s.Bus.AddHandler(s.GetDataSourcesByType) + s.Bus.AddHandler(s.GetDataSource) + s.Bus.AddHandlerCtx(s.AddDataSource) + s.Bus.AddHandler(s.DeleteDataSource) + s.Bus.AddHandlerCtx(s.UpdateDataSource) + s.Bus.AddHandler(s.GetDefaultDataSource) + + return s +} + +func (s *Service) GetDataSource(query *models.GetDataSourceQuery) error { + return s.SQLStore.GetDataSource(query) +} + +func (s *Service) GetDataSources(query *models.GetDataSourcesQuery) error { + return s.SQLStore.GetDataSources(query) +} + +func (s *Service) GetDataSourcesByType(query *models.GetDataSourcesByTypeQuery) error { + return s.SQLStore.GetDataSourcesByType(query) +} + +func (s *Service) AddDataSource(ctx context.Context, cmd *models.AddDataSourceCommand) error { + var err error + cmd.EncryptedSecureJsonData, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureJsonData, setting.SecretKey) + if err != nil { + return err + } + + return s.SQLStore.AddDataSource(cmd) +} + +func (s *Service) DeleteDataSource(cmd *models.DeleteDataSourceCommand) error { + return s.SQLStore.DeleteDataSource(cmd) +} + +func (s *Service) UpdateDataSource(ctx context.Context, cmd *models.UpdateDataSourceCommand) error { + var err error + cmd.EncryptedSecureJsonData, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureJsonData, setting.SecretKey) + if err != nil { + return err + } + + return s.SQLStore.UpdateDataSource(cmd) +} + +func (s *Service) GetDefaultDataSource(query *models.GetDefaultDataSourceQuery) error { + return s.SQLStore.GetDefaultDataSource(query) +} + +func (s *Service) GetHTTPClient(ds *models.DataSource, provider httpclient.Provider) (*http.Client, error) { + transport, err := s.GetHTTPTransport(ds, provider) if err != nil { return nil, err } return &http.Client{ - Timeout: ds.getTimeout(), + Timeout: s.getTimeout(ds), Transport: transport, }, nil } -func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddlewares ...sdkhttpclient.Middleware) (http.RoundTripper, error) { - ptc.Lock() - defer ptc.Unlock() +func (s *Service) GetHTTPTransport(ds *models.DataSource, provider httpclient.Provider, + customMiddlewares ...sdkhttpclient.Middleware) (http.RoundTripper, error) { + s.ptc.Lock() + defer s.ptc.Unlock() - if t, present := ptc.cache[ds.Id]; present && ds.Updated.Equal(t.updated) { + if t, present := s.ptc.cache[ds.Id]; present && ds.Updated.Equal(t.updated) { return t.roundTripper, nil } - opts, err := ds.HTTPClientOptions() + opts, err := s.httpClientOptions(ds) if err != nil { return nil, err } @@ -80,7 +146,7 @@ func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddl return nil, err } - ptc.cache[ds.Id] = cachedRoundTripper{ + s.ptc.cache[ds.Id] = cachedRoundTripper{ roundTripper: rt, updated: ds.Updated, } @@ -88,10 +154,60 @@ func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddl return rt, nil } -func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) { - tlsOptions := ds.TLSOptions() +func (s *Service) GetTLSConfig(ds *models.DataSource, httpClientProvider httpclient.Provider) (*tls.Config, error) { + opts, err := s.httpClientOptions(ds) + if err != nil { + return nil, err + } + return httpClientProvider.GetTLSConfig(*opts) +} + +func (s *Service) DecryptedValues(ds *models.DataSource) map[string]string { + s.dsDecryptionCache.Lock() + defer s.dsDecryptionCache.Unlock() + + if item, present := s.dsDecryptionCache.cache[ds.Id]; present && ds.Updated.Equal(item.updated) { + return item.json + } + + json, err := s.EncryptionService.DecryptJsonData(context.Background(), ds.SecureJsonData, setting.SecretKey) + if err != nil { + return map[string]string{} + } + + s.dsDecryptionCache.cache[ds.Id] = cachedDecryptedJSON{ + updated: ds.Updated, + json: json, + } + + return json +} + +func (s *Service) DecryptedValue(ds *models.DataSource, key string) (string, bool) { + value, exists := s.DecryptedValues(ds)[key] + return value, exists +} + +func (s *Service) DecryptedBasicAuthPassword(ds *models.DataSource) string { + if value, ok := s.DecryptedValue(ds, "basicAuthPassword"); ok { + return value + } + + return ds.BasicAuthPassword +} + +func (s *Service) DecryptedPassword(ds *models.DataSource) string { + if value, ok := s.DecryptedValue(ds, "password"); ok { + return value + } + + return ds.Password +} + +func (s *Service) httpClientOptions(ds *models.DataSource) (*sdkhttpclient.Options, error) { + tlsOptions := s.dsTLSOptions(ds) timeouts := &sdkhttpclient.TimeoutOptions{ - Timeout: ds.getTimeout(), + Timeout: s.getTimeout(ds), DialTimeout: sdkhttpclient.DefaultTimeoutOptions.DialTimeout, KeepAlive: sdkhttpclient.DefaultTimeoutOptions.KeepAlive, TLSHandshakeTimeout: sdkhttpclient.DefaultTimeoutOptions.TLSHandshakeTimeout, @@ -103,7 +219,7 @@ func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) { } opts := &sdkhttpclient.Options{ Timeouts: timeouts, - Headers: getCustomHeaders(ds.JsonData, ds.DecryptedValues()), + Headers: s.getCustomHeaders(ds.JsonData, s.DecryptedValues(ds)), Labels: map[string]string{ "datasource_name": ds.Name, "datasource_uid": ds.Uid, @@ -118,17 +234,17 @@ func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) { if ds.BasicAuth { opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{ User: ds.BasicAuthUser, - Password: ds.DecryptedBasicAuthPassword(), + Password: s.DecryptedBasicAuthPassword(ds), } } else if ds.User != "" { opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{ User: ds.User, - Password: ds.DecryptedPassword(), + Password: s.DecryptedPassword(ds), } } if ds.JsonData != nil && ds.JsonData.Get("azureAuth").MustBool() { - credentials, err := azcredentials.FromDatasourceData(ds.JsonData.MustMap(), ds.DecryptedValues()) + credentials, err := azcredentials.FromDatasourceData(ds.JsonData.MustMap(), s.DecryptedValues(ds)) if err != nil { err = fmt.Errorf("invalid Azure credentials: %s", err) return nil, err @@ -150,11 +266,11 @@ func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) { Profile: ds.JsonData.Get("sigV4Profile").MustString(), } - if val, exists := ds.DecryptedValue("sigV4AccessKey"); exists { + if val, exists := s.DecryptedValue(ds, "sigV4AccessKey"); exists { opts.SigV4.AccessKey = val } - if val, exists := ds.DecryptedValue("sigV4SecretKey"); exists { + if val, exists := s.DecryptedValue(ds, "sigV4SecretKey"); exists { opts.SigV4.SecretKey = val } } @@ -162,7 +278,7 @@ func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) { return opts, nil } -func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions { +func (s *Service) dsTLSOptions(ds *models.DataSource) sdkhttpclient.TLSOptions { var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool var serverName string @@ -180,16 +296,16 @@ func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions { if tlsClientAuth || tlsAuthWithCACert { if tlsAuthWithCACert { - if val, exists := ds.DecryptedValue("tlsCACert"); exists && len(val) > 0 { + if val, exists := s.DecryptedValue(ds, "tlsCACert"); exists && len(val) > 0 { opts.CACertificate = val } } if tlsClientAuth { - if val, exists := ds.DecryptedValue("tlsClientCert"); exists && len(val) > 0 { + if val, exists := s.DecryptedValue(ds, "tlsClientCert"); exists && len(val) > 0 { opts.ClientCertificate = val } - if val, exists := ds.DecryptedValue("tlsClientKey"); exists && len(val) > 0 { + if val, exists := s.DecryptedValue(ds, "tlsClientKey"); exists && len(val) > 0 { opts.ClientKey = val } } @@ -198,17 +314,28 @@ func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions { return opts } -func (ds *DataSource) GetTLSConfig(httpClientProvider httpclient.Provider) (*tls.Config, error) { - opts, err := ds.HTTPClientOptions() - if err != nil { - return nil, err +func (s *Service) getTimeout(ds *models.DataSource) time.Duration { + timeout := 0 + if ds.JsonData != nil { + timeout = ds.JsonData.Get("timeout").MustInt() + if timeout <= 0 { + if timeoutStr := ds.JsonData.Get("timeout").MustString(); timeoutStr != "" { + if t, err := strconv.Atoi(timeoutStr); err == nil { + timeout = t + } + } + } } - return httpClientProvider.GetTLSConfig(*opts) + if timeout <= 0 { + return sdkhttpclient.DefaultTimeoutOptions.Timeout + } + + return time.Duration(timeout) * time.Second } // getCustomHeaders returns a map with all the to be set headers // The map key represents the HeaderName and the value represents this header's value -func getCustomHeaders(jsonData *simplejson.Json, decryptedValues map[string]string) map[string]string { +func (s *Service) getCustomHeaders(jsonData *simplejson.Json, decryptedValues map[string]string) map[string]string { headers := make(map[string]string) if jsonData == nil { return headers @@ -234,57 +361,11 @@ func getCustomHeaders(jsonData *simplejson.Json, decryptedValues map[string]stri return headers } -type cachedDecryptedJSON struct { - updated time.Time - json map[string]string -} - -type secureJSONDecryptionCache struct { - cache map[int64]cachedDecryptedJSON - sync.Mutex -} - -var dsDecryptionCache = secureJSONDecryptionCache{ - cache: make(map[int64]cachedDecryptedJSON), -} - -// DecryptedValues returns cached decrypted values from secureJsonData. -func (ds *DataSource) DecryptedValues() map[string]string { - dsDecryptionCache.Lock() - defer dsDecryptionCache.Unlock() - - if item, present := dsDecryptionCache.cache[ds.Id]; present && ds.Updated.Equal(item.updated) { - return item.json - } - - json := ds.SecureJsonData.Decrypt() - dsDecryptionCache.cache[ds.Id] = cachedDecryptedJSON{ - updated: ds.Updated, - json: json, - } - - return json -} - -// DecryptedValue returns cached decrypted value from cached secureJsonData. -func (ds *DataSource) DecryptedValue(key string) (string, bool) { - value, exists := ds.DecryptedValues()[key] - return value, exists -} - -// ClearDSDecryptionCache clears the datasource decryption cache. -func ClearDSDecryptionCache() { - dsDecryptionCache.Lock() - defer dsDecryptionCache.Unlock() - - dsDecryptionCache.cache = make(map[int64]cachedDecryptedJSON) -} - func awsServiceNamespace(dsType string) string { switch dsType { - case DS_ES, DS_ES_OPEN_DISTRO: + case models.DS_ES, models.DS_ES_OPEN_DISTRO: return "es" - case DS_PROMETHEUS: + case models.DS_PROMETHEUS: return "aps" default: panic(fmt.Sprintf("Unsupported datasource %q", dsType)) diff --git a/pkg/models/datasource_cache_test.go b/pkg/services/datasources/service_test.go similarity index 69% rename from pkg/models/datasource_cache_test.go rename to pkg/services/datasources/service_test.go index 149b50bddf4..a6e2bd77c3b 100644 --- a/pkg/models/datasource_cache_test.go +++ b/pkg/services/datasources/service_test.go @@ -1,26 +1,69 @@ -package models +package datasources import ( + "context" "io/ioutil" - "log" "net/http" "net/http/httptest" "testing" "time" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" - "github.com/grafana/grafana/pkg/components/securejsondata" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/httpclient" + "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/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials" - "github.com/grafana/grafana/pkg/util" "github.com/stretchr/testify/assert" "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 = "datasources_service_test" + t.Cleanup(func() { + setting.SecretKey = origSecret + }) + + var ds *models.DataSource + + t.Run("create datasource should encrypt the secure json data", func(t *testing.T) { + ctx := context.Background() + + sjd := map[string]string{"password": "12345"} + cmd := models.AddDataSourceCommand{SecureJsonData: sjd} + + err := s.AddDataSource(ctx, &cmd) + require.NoError(t, err) + + ds = cmd.Result + decrypted, err := s.EncryptionService.DecryptJsonData(ctx, ds.SecureJsonData, setting.SecretKey) + require.NoError(t, err) + require.Equal(t, sjd, decrypted) + }) + + t.Run("update datasource should encrypt the secure json data", func(t *testing.T) { + ctx := context.Background() + sjd := map[string]string{"password": "678910"} + cmd := models.UpdateDataSourceCommand{Id: ds.Id, OrgId: ds.OrgId, SecureJsonData: sjd} + err := s.UpdateDataSource(ctx, &cmd) + require.NoError(t, err) + + decrypted, err := s.EncryptionService.DecryptJsonData(ctx, cmd.Result.SecureJsonData, setting.SecretKey) + require.NoError(t, err) + require.Equal(t, sjd, decrypted) + }) +} + //nolint:goconst -func TestDataSource_GetHttpTransport(t *testing.T) { +func TestService_GetHttpTransport(t *testing.T) { t.Run("Should use cached proxy", func(t *testing.T) { var configuredTransport *http.Transport provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{ @@ -29,19 +72,20 @@ func TestDataSource_GetHttpTransport(t *testing.T) { }, }) - clearDSProxyCache(t) - ds := DataSource{ + ds := models.DataSource{ Id: 1, Url: "http://k8s:8001", Type: "Kubernetes", } - rt1, err := ds.GetHTTPTransport(provider) + dsService := ProvideService(bus.New(), nil, ossencryption.ProvideService()) + + rt1, err := dsService.GetHTTPTransport(&ds, provider) require.NoError(t, err) require.NotNil(t, rt1) tr1 := configuredTransport - rt2, err := ds.GetHTTPTransport(provider) + rt2, err := dsService.GetHTTPTransport(&ds, provider) require.NoError(t, err) require.NotNil(t, rt2) tr2 := configuredTransport @@ -60,15 +104,19 @@ func TestDataSource_GetHttpTransport(t *testing.T) { configuredTransport = transport }, }) - clearDSProxyCache(t) + setting.SecretKey = "password" json := simplejson.New() json.Set("tlsAuthWithCACert", true) - tlsCaCert, err := util.Encrypt([]byte(caCert), "password") + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + tlsCaCert, err := encryptionService.Encrypt(context.Background(), []byte(caCert), "password") require.NoError(t, err) - ds := DataSource{ + + ds := models.DataSource{ Id: 1, Url: "http://k8s:8001", Type: "Kubernetes", @@ -76,7 +124,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) { Updated: time.Now().Add(-2 * time.Minute), } - rt1, err := ds.GetHTTPTransport(provider) + rt1, err := dsService.GetHTTPTransport(&ds, provider) require.NotNil(t, rt1) require.NoError(t, err) @@ -90,7 +138,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) { ds.SecureJsonData = map[string][]byte{} ds.Updated = time.Now() - rt2, err := ds.GetHTTPTransport(provider) + rt2, err := dsService.GetHTTPTransport(&ds, provider) require.NoError(t, err) require.NotNil(t, rt2) tr2 := configuredTransport @@ -106,18 +154,22 @@ func TestDataSource_GetHttpTransport(t *testing.T) { configuredTransport = transport }, }) - clearDSProxyCache(t) + setting.SecretKey = "password" json := simplejson.New() json.Set("tlsAuth", true) - tlsClientCert, err := util.Encrypt([]byte(clientCert), "password") - require.NoError(t, err) - tlsClientKey, err := util.Encrypt([]byte(clientKey), "password") + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + tlsClientCert, err := encryptionService.Encrypt(context.Background(), []byte(clientCert), "password") require.NoError(t, err) - ds := DataSource{ + tlsClientKey, err := encryptionService.Encrypt(context.Background(), []byte(clientKey), "password") + require.NoError(t, err) + + ds := models.DataSource{ Id: 1, Url: "http://k8s:8001", Type: "Kubernetes", @@ -128,7 +180,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) { }, } - rt, err := ds.GetHTTPTransport(provider) + rt, err := dsService.GetHTTPTransport(&ds, provider) require.NoError(t, err) require.NotNil(t, rt) tr := configuredTransport @@ -144,18 +196,20 @@ func TestDataSource_GetHttpTransport(t *testing.T) { configuredTransport = transport }, }) - clearDSProxyCache(t) - ClearDSDecryptionCache() + setting.SecretKey = "password" json := simplejson.New() json.Set("tlsAuthWithCACert", true) json.Set("serverName", "server-name") - tlsCaCert, err := util.Encrypt([]byte(caCert), "password") + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + tlsCaCert, err := encryptionService.Encrypt(context.Background(), []byte(caCert), "password") require.NoError(t, err) - ds := DataSource{ + ds := models.DataSource{ Id: 1, Url: "http://k8s:8001", Type: "Kubernetes", @@ -165,7 +219,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) { }, } - rt, err := ds.GetHTTPTransport(provider) + rt, err := dsService.GetHTTPTransport(&ds, provider) require.NoError(t, err) require.NotNil(t, rt) tr := configuredTransport @@ -182,24 +236,26 @@ func TestDataSource_GetHttpTransport(t *testing.T) { configuredTransport = transport }, }) - clearDSProxyCache(t) json := simplejson.New() json.Set("tlsSkipVerify", true) - ds := DataSource{ + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + ds := models.DataSource{ Id: 1, Url: "http://k8s:8001", Type: "Kubernetes", JsonData: json, } - rt1, err := ds.GetHTTPTransport(provider) + rt1, err := dsService.GetHTTPTransport(&ds, provider) require.NoError(t, err) require.NotNil(t, rt1) tr1 := configuredTransport - rt2, err := ds.GetHTTPTransport(provider) + rt2, err := dsService.GetHTTPTransport(&ds, provider) require.NoError(t, err) require.NotNil(t, rt2) tr2 := configuredTransport @@ -210,18 +266,18 @@ func TestDataSource_GetHttpTransport(t *testing.T) { t.Run("Should set custom headers if configured in JsonData", func(t *testing.T) { provider := httpclient.NewProvider() - clearDSProxyCache(t) - ClearDSDecryptionCache() json := simplejson.NewFromAny(map[string]interface{}{ "httpHeaderName1": "Authorization", }) - encryptedData, err := util.Encrypt([]byte(`Bearer xf5yhfkpsnmgo`), setting.SecretKey) - if err != nil { - log.Fatal(err.Error()) - } - ds := DataSource{ + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + encryptedData, err := encryptionService.Encrypt(context.Background(), []byte(`Bearer xf5yhfkpsnmgo`), setting.SecretKey) + require.NoError(t, err) + + ds := models.DataSource{ Id: 1, Url: "http://k8s:8001", Type: "Kubernetes", @@ -229,7 +285,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) { SecureJsonData: map[string][]byte{"httpHeaderValue1": encryptedData}, } - headers := getCustomHeaders(json, map[string]string{"httpHeaderValue1": "Bearer xf5yhfkpsnmgo"}) + headers := dsService.getCustomHeaders(json, map[string]string{"httpHeaderValue1": "Bearer xf5yhfkpsnmgo"}) require.Equal(t, "Bearer xf5yhfkpsnmgo", headers["Authorization"]) // 1. Start HTTP test server which checks the request headers @@ -249,7 +305,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) { // 2. Get HTTP transport from datasource which uses the test server as backend ds.Url = backend.URL - rt, err := ds.GetHTTPTransport(provider) + rt, err := dsService.GetHTTPTransport(&ds, provider) require.NoError(t, err) require.NotNil(t, rt) @@ -269,19 +325,22 @@ func TestDataSource_GetHttpTransport(t *testing.T) { t.Run("Should use request timeout if configured in JsonData", func(t *testing.T) { provider := httpclient.NewProvider() - clearDSProxyCache(t) json := simplejson.NewFromAny(map[string]interface{}{ "timeout": 19, }) - ds := DataSource{ + + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + ds := models.DataSource{ Id: 1, Url: "http://k8s:8001", Type: "Kubernetes", JsonData: json, } - client, err := ds.GetHTTPClient(provider) + client, err := dsService.GetHTTPClient(&ds, provider) require.NoError(t, err) require.NotNil(t, client) require.Equal(t, 19*time.Second, client.Timeout) @@ -294,7 +353,6 @@ func TestDataSource_GetHttpTransport(t *testing.T) { configuredOpts = opts }, }) - clearDSProxyCache(t) origSigV4Enabled := setting.SigV4AuthEnabled setting.SigV4AuthEnabled = true @@ -305,12 +363,15 @@ func TestDataSource_GetHttpTransport(t *testing.T) { json, err := simplejson.NewJson([]byte(`{ "sigV4Auth": true }`)) require.NoError(t, err) - ds := DataSource{ - Type: DS_ES, + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + ds := models.DataSource{ + Type: models.DS_ES, JsonData: json, } - _, err = ds.GetHTTPTransport(provider) + _, err = dsService.GetHTTPTransport(&ds, provider) require.NoError(t, err) require.NotNil(t, configuredOpts) require.NotNil(t, configuredOpts.SigV4) @@ -318,7 +379,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) { }) } -func TestDataSource_getTimeout(t *testing.T) { +func TestService_getTimeout(t *testing.T) { originalTimeout := sdkhttpclient.DefaultTimeoutOptions.Timeout sdkhttpclient.DefaultTimeoutOptions.Timeout = 60 * time.Second t.Cleanup(func() { @@ -336,76 +397,100 @@ func TestDataSource_getTimeout(t *testing.T) { {jsonData: simplejson.NewFromAny(map[string]interface{}{"timeout": "2"}), expectedTimeout: 2 * time.Second}, } + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + for _, tc := range testCases { - ds := &DataSource{ + ds := &models.DataSource{ JsonData: tc.jsonData, } - assert.Equal(t, tc.expectedTimeout, ds.getTimeout()) + assert.Equal(t, tc.expectedTimeout, dsService.getTimeout(ds)) } } -func TestDataSource_DecryptedValue(t *testing.T) { +func TestService_DecryptedValue(t *testing.T) { t.Run("When datasource hasn't been updated, encrypted JSON should be fetched from cache", func(t *testing.T) { - ClearDSDecryptionCache() + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) - ds := DataSource{ - Id: 1, - Type: DS_INFLUXDB_08, - JsonData: simplejson.New(), - User: "user", - SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{ + encryptedJsonData, err := encryptionService.EncryptJsonData( + context.Background(), + map[string]string{ "password": "password", - }), + }, setting.SecretKey) + require.NoError(t, err) + + ds := models.DataSource{ + Id: 1, + Type: models.DS_INFLUXDB_08, + JsonData: simplejson.New(), + User: "user", + SecureJsonData: encryptedJsonData, } // Populate cache - password, ok := ds.DecryptedValue("password") + password, ok := dsService.DecryptedValue(&ds, "password") require.True(t, ok) require.Equal(t, "password", password) - ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ - "password": "", - }) + encryptedJsonData, err = encryptionService.EncryptJsonData( + context.Background(), + map[string]string{ + "password": "", + }, setting.SecretKey) + require.NoError(t, err) - password, ok = ds.DecryptedValue("password") + ds.SecureJsonData = encryptedJsonData + + password, ok = dsService.DecryptedValue(&ds, "password") require.True(t, ok) require.Equal(t, "password", password) }) t.Run("When datasource is updated, encrypted JSON should not be fetched from cache", func(t *testing.T) { - ClearDSDecryptionCache() + encryptionService := ossencryption.ProvideService() - ds := DataSource{ - Id: 1, - Type: DS_INFLUXDB_08, - JsonData: simplejson.New(), - User: "user", - SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{ + encryptedJsonData, err := encryptionService.EncryptJsonData( + context.Background(), + map[string]string{ "password": "password", - }), + }, setting.SecretKey) + require.NoError(t, err) + + ds := models.DataSource{ + Id: 1, + Type: models.DS_INFLUXDB_08, + JsonData: simplejson.New(), + User: "user", + SecureJsonData: encryptedJsonData, } + dsService := ProvideService(bus.New(), nil, encryptionService) + // Populate cache - password, ok := ds.DecryptedValue("password") + password, ok := dsService.DecryptedValue(&ds, "password") require.True(t, ok) require.Equal(t, "password", password) - ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ - "password": "", - }) + ds.SecureJsonData, err = encryptionService.EncryptJsonData( + context.Background(), + map[string]string{ + "password": "", + }, setting.SecretKey) ds.Updated = time.Now() + require.NoError(t, err) - password, ok = ds.DecryptedValue("password") + password, ok = dsService.DecryptedValue(&ds, "password") require.True(t, ok) require.Empty(t, password) }) } -func TestDataSource_HTTPClientOptions(t *testing.T) { +func TestService_HTTPClientOptions(t *testing.T) { emptyJsonData := simplejson.New() emptySecureJsonData := map[string][]byte{} - ds := DataSource{ + ds := models.DataSource{ Id: 1, Url: "https://api.example.com", Type: "prometheus", @@ -415,7 +500,10 @@ func TestDataSource_HTTPClientOptions(t *testing.T) { t.Run("should be disabled if not enabled in JsonData", func(t *testing.T) { t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData }) - opts, err := ds.HTTPClientOptions() + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + opts, err := dsService.httpClientOptions(&ds) require.NoError(t, err) assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"]) @@ -429,7 +517,10 @@ func TestDataSource_HTTPClientOptions(t *testing.T) { "azureAuth": true, }) - opts, err := ds.HTTPClientOptions() + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + opts, err := dsService.httpClientOptions(&ds) require.NoError(t, err) assert.Equal(t, true, opts.CustomOptions["_azureAuth"]) @@ -446,7 +537,10 @@ func TestDataSource_HTTPClientOptions(t *testing.T) { }, }) - opts, err := ds.HTTPClientOptions() + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + opts, err := dsService.httpClientOptions(&ds) require.NoError(t, err) assert.Equal(t, true, opts.CustomOptions["_azureAuth"]) @@ -467,7 +561,10 @@ func TestDataSource_HTTPClientOptions(t *testing.T) { }, }) - opts, err := ds.HTTPClientOptions() + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + opts, err := dsService.httpClientOptions(&ds) require.NoError(t, err) assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"]) @@ -482,7 +579,10 @@ func TestDataSource_HTTPClientOptions(t *testing.T) { "azureCredentials": "invalid", }) - _, err := ds.HTTPClientOptions() + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + _, err := dsService.httpClientOptions(&ds) assert.Error(t, err) }) @@ -493,7 +593,10 @@ func TestDataSource_HTTPClientOptions(t *testing.T) { "azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5", }) - opts, err := ds.HTTPClientOptions() + encryptionService := ossencryption.ProvideService() + dsService := ProvideService(bus.New(), nil, encryptionService) + + opts, err := dsService.httpClientOptions(&ds) require.NoError(t, err) require.Contains(t, opts.CustomOptions, "azureEndpointResourceId") @@ -504,15 +607,6 @@ func TestDataSource_HTTPClientOptions(t *testing.T) { }) } -func clearDSProxyCache(t *testing.T) { - t.Helper() - - ptc.Lock() - defer ptc.Unlock() - - ptc.cache = make(map[int64]cachedRoundTripper) -} - const caCert string = `-----BEGIN CERTIFICATE----- MIIDATCCAemgAwIBAgIJAMQ5hC3CPDTeMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV BAMMDGNhLWs4cy1zdGhsbTAeFw0xNjEwMjcwODQyMjdaFw00NDAzMTQwODQyMjda diff --git a/pkg/services/encryption/encryption.go b/pkg/services/encryption/encryption.go index 8e60c781374..31cef0392ed 100644 --- a/pkg/services/encryption/encryption.go +++ b/pkg/services/encryption/encryption.go @@ -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 } diff --git a/pkg/services/encryption/ossencryption/ossencryption.go b/pkg/services/encryption/ossencryption/ossencryption.go index c10c68afe62..4cf351f8fc7 100644 --- a/pkg/services/encryption/ossencryption/ossencryption.go +++ b/pkg/services/encryption/ossencryption/ossencryption.go @@ -1,6 +1,7 @@ package ossencryption import ( + "context" "crypto/aes" "crypto/cipher" "crypto/rand" @@ -21,7 +22,7 @@ func ProvideService() *Service { const saltLength = 8 -func (s *Service) Decrypt(payload []byte, secret string) ([]byte, error) { +func (s *Service) Decrypt(_ context.Context, payload []byte, secret string) ([]byte, error) { if len(payload) < saltLength { return nil, fmt.Errorf("unable to compute salt") } @@ -52,7 +53,7 @@ func (s *Service) Decrypt(payload []byte, secret string) ([]byte, error) { return payloadDst, nil } -func (s *Service) Encrypt(payload []byte, secret string) ([]byte, error) { +func (s *Service) Encrypt(_ context.Context, payload []byte, secret string) ([]byte, error) { salt, err := util.GetRandomString(saltLength) if err != nil { return nil, err @@ -82,6 +83,45 @@ func (s *Service) Encrypt(payload []byte, secret string) ([]byte, error) { return ciphertext, nil } +func (s *Service) EncryptJsonData(ctx context.Context, kv map[string]string, secret string) (map[string][]byte, error) { + encrypted := make(map[string][]byte) + for key, value := range kv { + encryptedData, err := s.Encrypt(ctx, []byte(value), secret) + if err != nil { + return nil, err + } + + encrypted[key] = encryptedData + } + return encrypted, nil +} + +func (s *Service) DecryptJsonData(ctx context.Context, sjd map[string][]byte, secret string) (map[string]string, error) { + decrypted := make(map[string]string) + for key, data := range sjd { + decryptedData, err := s.Decrypt(ctx, data, secret) + if err != nil { + return nil, err + } + + decrypted[key] = string(decryptedData) + } + return decrypted, nil +} + +func (s *Service) GetDecryptedValue(ctx context.Context, sjd map[string][]byte, key, fallback, secret string) string { + if value, ok := sjd[key]; ok { + decryptedData, err := s.Decrypt(ctx, value, secret) + if err != nil { + return fallback + } + + return string(decryptedData) + } + + return fallback +} + // Key needs to be 32bytes func encryptionKeyToBytes(secret, salt string) ([]byte, error) { return pbkdf2.Key([]byte(secret), []byte(salt), 10000, 32, sha256.New), nil diff --git a/pkg/services/encryption/ossencryption/ossencryption_test.go b/pkg/services/encryption/ossencryption/ossencryption_test.go index de98d2f09f9..ad61f4ab0f2 100644 --- a/pkg/services/encryption/ossencryption/ossencryption_test.go +++ b/pkg/services/encryption/ossencryption/ossencryption_test.go @@ -1,6 +1,7 @@ package ossencryption import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -21,17 +22,19 @@ func TestEncryption(t *testing.T) { }) t.Run("decrypting basic payload", func(t *testing.T) { - encrypted, err := svc.Encrypt([]byte("grafana"), "1234") + ctx := context.Background() + + encrypted, err := svc.Encrypt(ctx, []byte("grafana"), "1234") require.NoError(t, err) - decrypted, err := svc.Decrypt(encrypted, "1234") + decrypted, err := svc.Decrypt(ctx, encrypted, "1234") require.NoError(t, err) assert.Equal(t, []byte("grafana"), decrypted) }) t.Run("decrypting empty payload should return error", func(t *testing.T) { - _, err := svc.Decrypt([]byte(""), "1234") + _, err := svc.Decrypt(context.Background(), []byte(""), "1234") require.Error(t, err) assert.Equal(t, "unable to compute salt", err.Error()) diff --git a/pkg/services/login/authinfoservice/database.go b/pkg/services/login/authinfoservice/database.go index 157fa387053..a2a95a8ae57 100644 --- a/pkg/services/login/authinfoservice/database.go +++ b/pkg/services/login/authinfoservice/database.go @@ -169,7 +169,7 @@ func (s *Implementation) decodeAndDecrypt(str string) (string, error) { if err != nil { return "", err } - decrypted, err := s.EncryptionService.Decrypt(decoded, setting.SecretKey) + decrypted, err := s.EncryptionService.Decrypt(context.Background(), decoded, setting.SecretKey) if err != nil { return "", err } @@ -179,7 +179,7 @@ func (s *Implementation) decodeAndDecrypt(str string) (string, error) { // encryptAndEncode will encrypt a string with grafana's secretKey, and // then encode it with the standard bas64 encoder func (s *Implementation) encryptAndEncode(str string) (string, error) { - encrypted, err := s.EncryptionService.Encrypt([]byte(str), setting.SecretKey) + encrypted, err := s.EncryptionService.Encrypt(context.Background(), []byte(str), setting.SecretKey) if err != nil { return "", err } diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index 730d83c22c4..b0b046ac78f 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/encryption" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/notifier" @@ -63,6 +64,7 @@ type API struct { DataProxy *datasourceproxy.DataSourceProxyService MultiOrgAlertmanager *notifier.MultiOrgAlertmanager StateManager *state.Manager + EncryptionService encryption.Service } // RegisterAPIEndpoints registers API handlers @@ -76,7 +78,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) { api.RegisterAlertmanagerApiEndpoints(NewForkedAM( api.DatasourceCache, NewLotexAM(proxy, logger), - AlertmanagerSrv{store: api.AlertingStore, mam: api.MultiOrgAlertmanager, log: logger}, + AlertmanagerSrv{store: api.AlertingStore, mam: api.MultiOrgAlertmanager, enc: api.EncryptionService, log: logger}, ), m) // Register endpoints for proxying to Prometheus-compatible backends. api.RegisterPrometheusApiEndpoints(NewForkedProm( diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index d48c0215490..374eb58ec3f 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -2,6 +2,7 @@ package api import ( "context" + "encoding/base64" "errors" "fmt" "net/http" @@ -12,10 +13,12 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/ngalert/store" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" "gopkg.in/macaron.v1" ) @@ -27,6 +30,7 @@ const ( type AlertmanagerSrv struct { mam *notifier.MultiOrgAlertmanager + enc encryption.Service store store.AlertingStore log log.Logger } @@ -76,7 +80,7 @@ func (srv AlertmanagerSrv) loadSecureSettings(orgId int64, receivers []*apimodel for key := range cgmr.SecureSettings { _, ok := gr.SecureSettings[key] if !ok { - decryptedValue, err := cgmr.GetDecryptedSecret(key) + decryptedValue, err := srv.getDecryptedSecret(cgmr, key) if err != nil { return fmt.Errorf("failed to decrypt stored secure setting: %s: %w", key, err) } @@ -93,6 +97,25 @@ func (srv AlertmanagerSrv) loadSecureSettings(orgId int64, receivers []*apimodel return nil } +func (srv AlertmanagerSrv) getDecryptedSecret(r *apimodels.PostableGrafanaReceiver, key string) (string, error) { + storedValue, ok := r.SecureSettings[key] + if !ok { + return "", nil + } + + decodeValue, err := base64.StdEncoding.DecodeString(storedValue) + if err != nil { + return "", err + } + + decryptedValue, err := srv.enc.Decrypt(context.Background(), decodeValue, setting.SecretKey) + if err != nil { + return "", err + } + + return string(decryptedValue), nil +} + func (srv AlertmanagerSrv) RouteGetAMStatus(c *models.ReqContext) response.Response { am, errResp := srv.AlertmanagerFor(c.OrgId) if errResp != nil { @@ -194,7 +217,7 @@ func (srv AlertmanagerSrv) RouteGetAlertingConfig(c *models.ReqContext) response for _, pr := range recv.PostableGrafanaReceivers.GrafanaManagedReceivers { secureFields := make(map[string]bool, len(pr.SecureSettings)) for k := range pr.SecureSettings { - decryptedValue, err := pr.GetDecryptedSecret(k) + decryptedValue, err := srv.getDecryptedSecret(pr, k) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to decrypt stored secure setting: %s", k) } @@ -333,7 +356,7 @@ func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body ap return ErrResp(http.StatusInternalServerError, err, "") } - if err := body.ProcessConfig(); err != nil { + if err := body.ProcessConfig(srv.enc.Encrypt); err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration") } @@ -367,7 +390,7 @@ func (srv AlertmanagerSrv) RoutePostTestReceivers(c *models.ReqContext, body api return ErrResp(http.StatusInternalServerError, err, "") } - if err := body.ProcessConfig(); err != nil { + if err := body.ProcessConfig(srv.enc.Encrypt); err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration") } diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index 3e2e3c49a7d..1b539438951 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -1,6 +1,7 @@ package definitions import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -9,16 +10,15 @@ import ( "time" "github.com/go-openapi/strfmt" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" "github.com/pkg/errors" amv2 "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/common/model" "gopkg.in/yaml.v3" - - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" ) // swagger:route POST /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RoutePostAlertingConfig @@ -141,8 +141,8 @@ type TestReceiversConfigParams struct { Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"` } -func (c *TestReceiversConfigParams) ProcessConfig() error { - return processReceiverConfigs(c.Receivers) +func (c *TestReceiversConfigParams) ProcessConfig(encrypt EncryptFn) error { + return processReceiverConfigs(c.Receivers, encrypt) } // swagger:model @@ -412,8 +412,8 @@ func (c *PostableUserConfig) GetGrafanaReceiverMap() map[string]*PostableGrafana } // ProcessConfig parses grafana receivers, encrypts secrets and assigns UUIDs (if they are missing) -func (c *PostableUserConfig) ProcessConfig() error { - return processReceiverConfigs(c.AlertmanagerConfig.Receivers) +func (c *PostableUserConfig) ProcessConfig(encrypt EncryptFn) error { + return processReceiverConfigs(c.AlertmanagerConfig.Receivers, encrypt) } // MarshalYAML implements yaml.Marshaller. @@ -870,22 +870,6 @@ type PostableGrafanaReceiver struct { SecureSettings map[string]string `json:"secureSettings"` } -func (r *PostableGrafanaReceiver) GetDecryptedSecret(key string) (string, error) { - storedValue, ok := r.SecureSettings[key] - if !ok { - return "", nil - } - decodeValue, err := base64.StdEncoding.DecodeString(storedValue) - if err != nil { - return "", err - } - decryptedValue, err := util.Decrypt(decodeValue, setting.SecretKey) - if err != nil { - return "", err - } - return string(decryptedValue), nil -} - type ReceiverType int const ( @@ -1061,7 +1045,9 @@ type PostableGrafanaReceivers struct { GrafanaManagedReceivers []*PostableGrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"` } -func processReceiverConfigs(c []*PostableApiReceiver) error { +type EncryptFn func(ctx context.Context, payload []byte, secret string) ([]byte, error) + +func processReceiverConfigs(c []*PostableApiReceiver, encrypt EncryptFn) error { seenUIDs := make(map[string]struct{}) // encrypt secure settings for storing them in DB for _, r := range c { @@ -1069,7 +1055,7 @@ func processReceiverConfigs(c []*PostableApiReceiver) error { case GrafanaReceiverType: for _, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers { for k, v := range gr.SecureSettings { - encryptedData, err := util.Encrypt([]byte(v), setting.SecretKey) + encryptedData, err := encrypt(context.Background(), []byte(v), setting.SecretKey) if err != nil { return fmt.Errorf("failed to encrypt secure settings: %w", err) } diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index dc8e146fbde..ea0f1e34915 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -5,11 +5,13 @@ import ( "net/url" "time" + "github.com/benbjohnson/clock" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/ngalert/api" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/metrics" @@ -21,8 +23,6 @@ import ( "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb" - - "github.com/benbjohnson/clock" "golang.org/x/sync/errgroup" ) @@ -39,18 +39,19 @@ const ( func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, routeRegister routing.RouteRegister, sqlStore *sqlstore.SQLStore, kvStore kvstore.KVStore, dataService *tsdb.Service, dataProxy *datasourceproxy.DataSourceProxyService, - quotaService *quota.QuotaService, m *metrics.NGAlert) (*AlertNG, error) { + quotaService *quota.QuotaService, encryptionService encryption.Service, m *metrics.NGAlert) (*AlertNG, error) { ng := &AlertNG{ - Cfg: cfg, - DataSourceCache: dataSourceCache, - RouteRegister: routeRegister, - SQLStore: sqlStore, - KVStore: kvStore, - DataService: dataService, - DataProxy: dataProxy, - QuotaService: quotaService, - Metrics: m, - Log: log.New("ngalert"), + Cfg: cfg, + DataSourceCache: dataSourceCache, + RouteRegister: routeRegister, + SQLStore: sqlStore, + KVStore: kvStore, + DataService: dataService, + DataProxy: dataProxy, + QuotaService: quotaService, + EncryptionService: encryptionService, + Metrics: m, + Log: log.New("ngalert"), } if ng.IsDisabled() { @@ -66,18 +67,19 @@ func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, // AlertNG is the service for evaluating the condition of an alert definition. type AlertNG struct { - Cfg *setting.Cfg - DataSourceCache datasources.CacheService - RouteRegister routing.RouteRegister - SQLStore *sqlstore.SQLStore - KVStore kvstore.KVStore - DataService *tsdb.Service - DataProxy *datasourceproxy.DataSourceProxyService - QuotaService *quota.QuotaService - Metrics *metrics.NGAlert - Log log.Logger - schedule schedule.ScheduleService - stateManager *state.Manager + Cfg *setting.Cfg + DataSourceCache datasources.CacheService + RouteRegister routing.RouteRegister + SQLStore *sqlstore.SQLStore + KVStore kvstore.KVStore + DataService *tsdb.Service + DataProxy *datasourceproxy.DataSourceProxyService + QuotaService *quota.QuotaService + EncryptionService encryption.Service + Metrics *metrics.NGAlert + Log log.Logger + schedule schedule.ScheduleService + stateManager *state.Manager // Alerting notification services MultiOrgAlertmanager *notifier.MultiOrgAlertmanager @@ -99,8 +101,9 @@ func (ng *AlertNG) init() error { Logger: ng.Log, } + decryptFn := ng.EncryptionService.GetDecryptedValue multiOrgMetrics := ng.Metrics.GetMultiOrgAlertmanagerMetrics() - ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, store, store, ng.KVStore, multiOrgMetrics, log.New("ngalert.multiorg.alertmanager")) + ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, store, store, ng.KVStore, decryptFn, multiOrgMetrics, log.New("ngalert.multiorg.alertmanager")) if err != nil { return err } @@ -146,6 +149,7 @@ func (ng *AlertNG) init() error { Schedule: ng.schedule, DataProxy: ng.DataProxy, QuotaService: ng.QuotaService, + EncryptionService: ng.EncryptionService, InstanceStore: store, RuleStore: store, AlertingStore: store, diff --git a/pkg/services/ngalert/notifier/alertmanager.go b/pkg/services/ngalert/notifier/alertmanager.go index f11b85502ca..c223849f48c 100644 --- a/pkg/services/ngalert/notifier/alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager.go @@ -31,7 +31,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" @@ -118,9 +117,12 @@ type Alertmanager struct { config *apimodels.PostableUserConfig configHash [16]byte orgID int64 + + decryptFn channels.GetDecryptedValueFn } -func newAlertmanager(orgID int64, cfg *setting.Cfg, store store.AlertingStore, kvStore kvstore.KVStore, peer ClusterPeer, m *metrics.Alertmanager) (*Alertmanager, error) { +func newAlertmanager(orgID int64, cfg *setting.Cfg, store store.AlertingStore, kvStore kvstore.KVStore, + peer ClusterPeer, decryptFn channels.GetDecryptedValueFn, m *metrics.Alertmanager) (*Alertmanager, error) { am := &Alertmanager{ Settings: cfg, stopc: make(chan struct{}), @@ -133,6 +135,7 @@ func newAlertmanager(orgID int64, cfg *setting.Cfg, store store.AlertingStore, k peerTimeout: cfg.UnifiedAlerting.HAPeerTimeout, Metrics: m, orgID: orgID, + decryptFn: decryptFn, } am.gokitLogger = gokit_log.NewLogfmtLogger(logging.NewWrapper(am.logger)) @@ -472,7 +475,7 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaReceiver, tmpl *template.Template) (NotificationChannel, error) { // secure settings are already encrypted at this point - secureSettings := securejsondata.SecureJsonData(make(map[string][]byte, len(r.SecureSettings))) + secureSettings := make(map[string][]byte, len(r.SecureSettings)) for k, v := range r.SecureSettings { d, err := base64.StdEncoding.DecodeString(v) @@ -501,13 +504,13 @@ func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaRec case "email": n, err = channels.NewEmailNotifier(cfg, tmpl) // Email notifier already has a default template. case "pagerduty": - n, err = channels.NewPagerdutyNotifier(cfg, tmpl) + n, err = channels.NewPagerdutyNotifier(cfg, tmpl, am.decryptFn) case "pushover": - n, err = channels.NewPushoverNotifier(cfg, tmpl) + n, err = channels.NewPushoverNotifier(cfg, tmpl, am.decryptFn) case "slack": - n, err = channels.NewSlackNotifier(cfg, tmpl) + n, err = channels.NewSlackNotifier(cfg, tmpl, am.decryptFn) case "telegram": - n, err = channels.NewTelegramNotifier(cfg, tmpl) + n, err = channels.NewTelegramNotifier(cfg, tmpl, am.decryptFn) case "victorops": n, err = channels.NewVictoropsNotifier(cfg, tmpl) case "teams": @@ -517,21 +520,21 @@ func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaRec case "kafka": n, err = channels.NewKafkaNotifier(cfg, tmpl) case "webhook": - n, err = channels.NewWebHookNotifier(cfg, tmpl) + n, err = channels.NewWebHookNotifier(cfg, tmpl, am.decryptFn) case "sensugo": - n, err = channels.NewSensuGoNotifier(cfg, tmpl) + n, err = channels.NewSensuGoNotifier(cfg, tmpl, am.decryptFn) case "discord": n, err = channels.NewDiscordNotifier(cfg, tmpl) case "googlechat": n, err = channels.NewGoogleChatNotifier(cfg, tmpl) case "LINE": - n, err = channels.NewLineNotifier(cfg, tmpl) + n, err = channels.NewLineNotifier(cfg, tmpl, am.decryptFn) case "threema": - n, err = channels.NewThreemaNotifier(cfg, tmpl) + n, err = channels.NewThreemaNotifier(cfg, tmpl, am.decryptFn) case "opsgenie": - n, err = channels.NewOpsgenieNotifier(cfg, tmpl) + n, err = channels.NewOpsgenieNotifier(cfg, tmpl, am.decryptFn) case "prometheus-alertmanager": - n, err = channels.NewAlertmanagerNotifier(cfg, tmpl) + n, err = channels.NewAlertmanagerNotifier(cfg, tmpl, am.decryptFn) default: return nil, InvalidReceiverError{ Receiver: r, diff --git a/pkg/services/ngalert/notifier/alertmanager_test.go b/pkg/services/ngalert/notifier/alertmanager_test.go index 6ff14bf9d74..232c16238e6 100644 --- a/pkg/services/ngalert/notifier/alertmanager_test.go +++ b/pkg/services/ngalert/notifier/alertmanager_test.go @@ -9,23 +9,22 @@ import ( "testing" "time" - "github.com/grafana/grafana/pkg/infra/log" - gokit_log "github.com/go-kit/kit/log" "github.com/go-openapi/strfmt" - "github.com/prometheus/alertmanager/api/v2/models" - "github.com/prometheus/alertmanager/provider/mem" - "github.com/prometheus/alertmanager/types" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/model" - "github.com/stretchr/testify/require" - + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/logging" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/alertmanager/api/v2/models" + "github.com/prometheus/alertmanager/provider/mem" + "github.com/prometheus/alertmanager/types" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/model" + "github.com/stretchr/testify/require" ) func setupAMTest(t *testing.T) *Alertmanager { @@ -48,7 +47,8 @@ func setupAMTest(t *testing.T) *Alertmanager { } kvStore := newFakeKVStore(t) - am, err := newAlertmanager(1, cfg, s, kvStore, &NilPeer{}, m) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + am, err := newAlertmanager(1, cfg, s, kvStore, &NilPeer{}, decryptFn, m) require.NoError(t, err) return am } diff --git a/pkg/services/ngalert/notifier/channels/alertmanager.go b/pkg/services/ngalert/notifier/channels/alertmanager.go index d772e5d34d1..d5d46de6015 100644 --- a/pkg/services/ngalert/notifier/channels/alertmanager.go +++ b/pkg/services/ngalert/notifier/channels/alertmanager.go @@ -10,12 +10,17 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" + "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" ) +// GetDecryptedValueFn is a function that returns the decrypted value of +// the given key. If the key is not present, then it returns the fallback value. +type GetDecryptedValueFn func(ctx context.Context, sjd map[string][]byte, key string, fallback string, secret string) string + // NewAlertmanagerNotifier returns a new Alertmanager notifier. -func NewAlertmanagerNotifier(model *NotificationChannelConfig, t *template.Template) (*AlertmanagerNotifier, error) { +func NewAlertmanagerNotifier(model *NotificationChannelConfig, _ *template.Template, fn GetDecryptedValueFn) (*AlertmanagerNotifier, error) { if model.Settings == nil { return nil, receiverInitError{Reason: "no settings supplied"} } @@ -41,7 +46,7 @@ func NewAlertmanagerNotifier(model *NotificationChannelConfig, t *template.Templ urls = append(urls, u) } basicAuthUser := model.Settings.Get("basicAuthUser").MustString() - basicAuthPassword := model.DecryptedValue("basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString()) + basicAuthPassword := fn(context.Background(), model.SecureSettings, "basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString(), setting.SecretKey) return &AlertmanagerNotifier{ NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ diff --git a/pkg/services/ngalert/notifier/channels/alertmanager_test.go b/pkg/services/ngalert/notifier/channels/alertmanager_test.go index 0766c794450..1a05808d1b7 100644 --- a/pkg/services/ngalert/notifier/channels/alertmanager_test.go +++ b/pkg/services/ngalert/notifier/channels/alertmanager_test.go @@ -6,6 +6,8 @@ import ( "net/url" "testing" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -77,7 +79,8 @@ func TestAlertmanagerNotifier(t *testing.T) { Settings: settingsJSON, } - sn, err := NewAlertmanagerNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + sn, err := NewAlertmanagerNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Equal(t, c.expInitError, err.Error()) return diff --git a/pkg/services/ngalert/notifier/channels/line.go b/pkg/services/ngalert/notifier/channels/line.go index 509d9a3c8bf..a9268747561 100644 --- a/pkg/services/ngalert/notifier/channels/line.go +++ b/pkg/services/ngalert/notifier/channels/line.go @@ -6,13 +6,13 @@ import ( "net/url" "path" - "github.com/prometheus/alertmanager/template" - "github.com/prometheus/alertmanager/types" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" ) var ( @@ -20,8 +20,8 @@ var ( ) // NewLineNotifier is the constructor for the LINE notifier -func NewLineNotifier(model *NotificationChannelConfig, t *template.Template) (*LineNotifier, error) { - token := model.DecryptedValue("token", model.Settings.Get("token").MustString()) +func NewLineNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*LineNotifier, error) { + token := fn(context.Background(), model.SecureSettings, "token", model.Settings.Get("token").MustString(), setting.SecretKey) if token == "" { return nil, receiverInitError{Cfg: *model, Reason: "could not find token in settings"} } diff --git a/pkg/services/ngalert/notifier/channels/line_test.go b/pkg/services/ngalert/notifier/channels/line_test.go index c0c3b4bf45d..3915183db36 100644 --- a/pkg/services/ngalert/notifier/channels/line_test.go +++ b/pkg/services/ngalert/notifier/channels/line_test.go @@ -5,6 +5,8 @@ import ( "net/url" "testing" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -88,7 +90,8 @@ func TestLineNotifier(t *testing.T) { Settings: settingsJSON, } - pn, err := NewLineNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + pn, err := NewLineNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) diff --git a/pkg/services/ngalert/notifier/channels/opsgenie.go b/pkg/services/ngalert/notifier/channels/opsgenie.go index d6f25a47da0..64ee51be067 100644 --- a/pkg/services/ngalert/notifier/channels/opsgenie.go +++ b/pkg/services/ngalert/notifier/channels/opsgenie.go @@ -7,16 +7,16 @@ import ( "net/http" "sort" - "github.com/prometheus/alertmanager/notify" - "github.com/prometheus/alertmanager/template" - "github.com/prometheus/alertmanager/types" - "github.com/prometheus/common/model" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" + "github.com/prometheus/common/model" ) const ( @@ -43,10 +43,10 @@ type OpsgenieNotifier struct { } // NewOpsgenieNotifier is the constructor for the Opsgenie notifier -func NewOpsgenieNotifier(model *NotificationChannelConfig, t *template.Template) (*OpsgenieNotifier, error) { +func NewOpsgenieNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*OpsgenieNotifier, error) { autoClose := model.Settings.Get("autoClose").MustBool(true) overridePriority := model.Settings.Get("overridePriority").MustBool(true) - apiKey := model.DecryptedValue("apiKey", model.Settings.Get("apiKey").MustString()) + apiKey := fn(context.Background(), model.SecureSettings, "apiKey", model.Settings.Get("apiKey").MustString(), setting.SecretKey) apiURL := model.Settings.Get("apiUrl").MustString() if apiKey == "" { return nil, receiverInitError{Cfg: *model, Reason: "could not find api key property in settings"} diff --git a/pkg/services/ngalert/notifier/channels/opsgenie_test.go b/pkg/services/ngalert/notifier/channels/opsgenie_test.go index 038a6c404c9..51ecdd0195e 100644 --- a/pkg/services/ngalert/notifier/channels/opsgenie_test.go +++ b/pkg/services/ngalert/notifier/channels/opsgenie_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -168,7 +170,8 @@ func TestOpsgenieNotifier(t *testing.T) { Settings: settingsJSON, } - pn, err := NewOpsgenieNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + pn, err := NewOpsgenieNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) diff --git a/pkg/services/ngalert/notifier/channels/pagerduty.go b/pkg/services/ngalert/notifier/channels/pagerduty.go index 82871ab166c..c970cccb1c5 100644 --- a/pkg/services/ngalert/notifier/channels/pagerduty.go +++ b/pkg/services/ngalert/notifier/channels/pagerduty.go @@ -6,15 +6,15 @@ import ( "fmt" "os" - "github.com/prometheus/alertmanager/notify" - "github.com/prometheus/alertmanager/template" - "github.com/prometheus/alertmanager/types" - "github.com/prometheus/common/model" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" + "github.com/prometheus/common/model" ) const ( @@ -42,12 +42,12 @@ type PagerdutyNotifier struct { } // NewPagerdutyNotifier is the constructor for the PagerDuty notifier -func NewPagerdutyNotifier(model *NotificationChannelConfig, t *template.Template) (*PagerdutyNotifier, error) { +func NewPagerdutyNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*PagerdutyNotifier, error) { if model.Settings == nil { return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"} } - key := model.DecryptedValue("integrationKey", model.Settings.Get("integrationKey").MustString()) + key := fn(context.Background(), model.SecureSettings, "integrationKey", model.Settings.Get("integrationKey").MustString(), setting.SecretKey) if key == "" { return nil, receiverInitError{Cfg: *model, Reason: "could not find integration key property in settings"} } diff --git a/pkg/services/ngalert/notifier/channels/pagerduty_test.go b/pkg/services/ngalert/notifier/channels/pagerduty_test.go index fcb1563ed54..4562ff7b1b7 100644 --- a/pkg/services/ngalert/notifier/channels/pagerduty_test.go +++ b/pkg/services/ngalert/notifier/channels/pagerduty_test.go @@ -7,6 +7,8 @@ import ( "os" "testing" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -134,7 +136,8 @@ func TestPagerdutyNotifier(t *testing.T) { Settings: settingsJSON, } - pn, err := NewPagerdutyNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + pn, err := NewPagerdutyNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) diff --git a/pkg/services/ngalert/notifier/channels/pushover.go b/pkg/services/ngalert/notifier/channels/pushover.go index 4eefd078663..9f7155ebc3f 100644 --- a/pkg/services/ngalert/notifier/channels/pushover.go +++ b/pkg/services/ngalert/notifier/channels/pushover.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" + "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -40,13 +41,13 @@ type PushoverNotifier struct { } // NewSlackNotifier is the constructor for the Slack notifier -func NewPushoverNotifier(model *NotificationChannelConfig, t *template.Template) (*PushoverNotifier, error) { +func NewPushoverNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*PushoverNotifier, error) { if model.Settings == nil { return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"} } - userKey := model.DecryptedValue("userKey", model.Settings.Get("userKey").MustString()) - APIToken := model.DecryptedValue("apiToken", model.Settings.Get("apiToken").MustString()) + userKey := fn(context.Background(), model.SecureSettings, "userKey", model.Settings.Get("userKey").MustString(), setting.SecretKey) + APIToken := fn(context.Background(), model.SecureSettings, "apiToken", model.Settings.Get("apiToken").MustString(), setting.SecretKey) device := model.Settings.Get("device").MustString() alertingPriority, err := strconv.Atoi(model.Settings.Get("priority").MustString("0")) // default Normal if err != nil { diff --git a/pkg/services/ngalert/notifier/channels/pushover_test.go b/pkg/services/ngalert/notifier/channels/pushover_test.go index 55cf4c097b8..0e8acc354fe 100644 --- a/pkg/services/ngalert/notifier/channels/pushover_test.go +++ b/pkg/services/ngalert/notifier/channels/pushover_test.go @@ -11,6 +11,8 @@ import ( "strings" "testing" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" @@ -141,7 +143,8 @@ func TestPushoverNotifier(t *testing.T) { Settings: settingsJSON, } - pn, err := NewPushoverNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + pn, err := NewPushoverNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) diff --git a/pkg/services/ngalert/notifier/channels/sensugo.go b/pkg/services/ngalert/notifier/channels/sensugo.go index 07a92b3a17e..8eb7a620811 100644 --- a/pkg/services/ngalert/notifier/channels/sensugo.go +++ b/pkg/services/ngalert/notifier/channels/sensugo.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" + "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -31,7 +32,7 @@ type SensuGoNotifier struct { } // NewSensuGoNotifier is the constructor for the SensuGo notifier -func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template) (*SensuGoNotifier, error) { +func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*SensuGoNotifier, error) { if model.Settings == nil { return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"} } @@ -41,7 +42,7 @@ func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template) return nil, receiverInitError{Cfg: *model, Reason: "could not find URL property in settings"} } - apikey := model.DecryptedValue("apikey", model.Settings.Get("apikey").MustString()) + apikey := fn(context.Background(), model.SecureSettings, "apikey", model.Settings.Get("apikey").MustString(), setting.SecretKey) if apikey == "" { return nil, receiverInitError{Cfg: *model, Reason: "could not find the API key property in settings"} } diff --git a/pkg/services/ngalert/notifier/channels/sensugo_test.go b/pkg/services/ngalert/notifier/channels/sensugo_test.go index c4c22c8cd25..f4d97683eb1 100644 --- a/pkg/services/ngalert/notifier/channels/sensugo_test.go +++ b/pkg/services/ngalert/notifier/channels/sensugo_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -139,7 +141,8 @@ func TestSensuGoNotifier(t *testing.T) { Settings: settingsJSON, } - sn, err := NewSensuGoNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + sn, err := NewSensuGoNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) diff --git a/pkg/services/ngalert/notifier/channels/slack.go b/pkg/services/ngalert/notifier/channels/slack.go index 6ddda2509d4..d209b1ffd95 100644 --- a/pkg/services/ngalert/notifier/channels/slack.go +++ b/pkg/services/ngalert/notifier/channels/slack.go @@ -14,14 +14,13 @@ import ( "strings" "time" - "github.com/prometheus/alertmanager/config" - "github.com/prometheus/alertmanager/template" - "github.com/prometheus/alertmanager/types" - "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" ) // SlackNotifier is responsible for sending @@ -49,12 +48,12 @@ var reRecipient *regexp.Regexp = regexp.MustCompile("^((@[a-z0-9][a-zA-Z0-9._-]* var SlackAPIEndpoint = "https://slack.com/api/chat.postMessage" // NewSlackNotifier is the constructor for the Slack notifier -func NewSlackNotifier(model *NotificationChannelConfig, t *template.Template) (*SlackNotifier, error) { +func NewSlackNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*SlackNotifier, error) { if model.Settings == nil { return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"} } - slackURL := model.DecryptedValue("url", model.Settings.Get("url").MustString()) + slackURL := fn(context.Background(), model.SecureSettings, "url", model.Settings.Get("url").MustString(), setting.SecretKey) if slackURL == "" { slackURL = SlackAPIEndpoint } @@ -99,7 +98,7 @@ func NewSlackNotifier(model *NotificationChannelConfig, t *template.Template) (* } } - token := model.DecryptedValue("token", model.Settings.Get("token").MustString()) + token := fn(context.Background(), model.SecureSettings, "token", model.Settings.Get("token").MustString(), setting.SecretKey) if token == "" && apiURL.String() == SlackAPIEndpoint { return nil, receiverInitError{Cfg: *model, Reason: "token must be specified when using the Slack chat API", diff --git a/pkg/services/ngalert/notifier/channels/slack_test.go b/pkg/services/ngalert/notifier/channels/slack_test.go index ae89326adc2..d35ec92af7e 100644 --- a/pkg/services/ngalert/notifier/channels/slack_test.go +++ b/pkg/services/ngalert/notifier/channels/slack_test.go @@ -8,6 +8,8 @@ import ( "net/url" "testing" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -169,7 +171,8 @@ func TestSlackNotifier(t *testing.T) { Settings: settingsJSON, } - pn, err := NewSlackNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + pn, err := NewSlackNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) diff --git a/pkg/services/ngalert/notifier/channels/telegram.go b/pkg/services/ngalert/notifier/channels/telegram.go index ec396a4efaa..e7e772dfbb5 100644 --- a/pkg/services/ngalert/notifier/channels/telegram.go +++ b/pkg/services/ngalert/notifier/channels/telegram.go @@ -6,13 +6,13 @@ import ( "fmt" "mime/multipart" - "github.com/prometheus/alertmanager/template" - "github.com/prometheus/alertmanager/types" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" ) var ( @@ -31,12 +31,12 @@ type TelegramNotifier struct { } // NewTelegramNotifier is the constructor for the Telegram notifier -func NewTelegramNotifier(model *NotificationChannelConfig, t *template.Template) (*TelegramNotifier, error) { +func NewTelegramNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*TelegramNotifier, error) { if model.Settings == nil { return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"} } - botToken := model.DecryptedValue("bottoken", model.Settings.Get("bottoken").MustString()) + botToken := fn(context.Background(), model.SecureSettings, "bottoken", model.Settings.Get("bottoken").MustString(), setting.SecretKey) chatID := model.Settings.Get("chatid").MustString() message := model.Settings.Get("message").MustString(`{{ template "default.message" . }}`) diff --git a/pkg/services/ngalert/notifier/channels/telegram_test.go b/pkg/services/ngalert/notifier/channels/telegram_test.go index a4b7ce6d53a..a5799abcdf5 100644 --- a/pkg/services/ngalert/notifier/channels/telegram_test.go +++ b/pkg/services/ngalert/notifier/channels/telegram_test.go @@ -5,6 +5,8 @@ import ( "net/url" "testing" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -94,7 +96,8 @@ func TestTelegramNotifier(t *testing.T) { Settings: settingsJSON, } - pn, err := NewTelegramNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + pn, err := NewTelegramNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) diff --git a/pkg/services/ngalert/notifier/channels/threema.go b/pkg/services/ngalert/notifier/channels/threema.go index ae67502da3f..53c8568ad0d 100644 --- a/pkg/services/ngalert/notifier/channels/threema.go +++ b/pkg/services/ngalert/notifier/channels/threema.go @@ -7,14 +7,14 @@ import ( "path" "strings" - "github.com/prometheus/alertmanager/template" - "github.com/prometheus/alertmanager/types" - "github.com/prometheus/common/model" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" + "github.com/prometheus/common/model" ) var ( @@ -33,14 +33,14 @@ type ThreemaNotifier struct { } // NewThreemaNotifier is the constructor for the Threema notifier -func NewThreemaNotifier(model *NotificationChannelConfig, t *template.Template) (*ThreemaNotifier, error) { +func NewThreemaNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*ThreemaNotifier, error) { if model.Settings == nil { return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"} } gatewayID := model.Settings.Get("gateway_id").MustString() recipientID := model.Settings.Get("recipient_id").MustString() - apiSecret := model.DecryptedValue("api_secret", model.Settings.Get("api_secret").MustString()) + apiSecret := fn(context.Background(), model.SecureSettings, "api_secret", model.Settings.Get("api_secret").MustString(), setting.SecretKey) // Validation if gatewayID == "" { diff --git a/pkg/services/ngalert/notifier/channels/threema_test.go b/pkg/services/ngalert/notifier/channels/threema_test.go index 9a9b20dd59b..d2d84f1bf76 100644 --- a/pkg/services/ngalert/notifier/channels/threema_test.go +++ b/pkg/services/ngalert/notifier/channels/threema_test.go @@ -5,6 +5,8 @@ import ( "net/url" "testing" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" @@ -106,7 +108,8 @@ func TestThreemaNotifier(t *testing.T) { Settings: settingsJSON, } - pn, err := NewThreemaNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + pn, err := NewThreemaNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) diff --git a/pkg/services/ngalert/notifier/channels/utils.go b/pkg/services/ngalert/notifier/channels/utils.go index c3c827d7295..7a18ffcb6d2 100644 --- a/pkg/services/ngalert/notifier/channels/utils.go +++ b/pkg/services/ngalert/notifier/channels/utils.go @@ -16,7 +16,6 @@ import ( "github.com/grafana/grafana/pkg/util" "github.com/prometheus/common/model" - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/simplejson" ) @@ -56,20 +55,12 @@ func getAlertStatusColor(status model.AlertStatus) string { } type NotificationChannelConfig struct { - UID string `json:"uid"` - Name string `json:"name"` - Type string `json:"type"` - DisableResolveMessage bool `json:"disableResolveMessage"` - Settings *simplejson.Json `json:"settings"` - SecureSettings securejsondata.SecureJsonData `json:"secureSettings"` -} - -// DecryptedValue returns decrypted value from secureSettings -func (an *NotificationChannelConfig) DecryptedValue(field string, fallback string) string { - if value, ok := an.SecureSettings.DecryptedValue(field); ok { - return value - } - return fallback + UID string `json:"uid"` + Name string `json:"name"` + Type string `json:"type"` + DisableResolveMessage bool `json:"disableResolveMessage"` + Settings *simplejson.Json `json:"settings"` + SecureSettings map[string][]byte `json:"secureSettings"` } type httpCfg struct { diff --git a/pkg/services/ngalert/notifier/channels/webhook.go b/pkg/services/ngalert/notifier/channels/webhook.go index 13c0d1367f1..4d864b4d3a6 100644 --- a/pkg/services/ngalert/notifier/channels/webhook.go +++ b/pkg/services/ngalert/notifier/channels/webhook.go @@ -4,15 +4,15 @@ import ( "context" "encoding/json" - "github.com/prometheus/alertmanager/notify" - "github.com/prometheus/alertmanager/template" - "github.com/prometheus/alertmanager/types" - "github.com/prometheus/common/model" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" + "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/alertmanager/notify" + "github.com/prometheus/alertmanager/template" + "github.com/prometheus/alertmanager/types" + "github.com/prometheus/common/model" ) // WebhookNotifier is responsible for sending @@ -30,7 +30,7 @@ type WebhookNotifier struct { // NewWebHookNotifier is the constructor for // the WebHook notifier. -func NewWebHookNotifier(model *NotificationChannelConfig, t *template.Template) (*WebhookNotifier, error) { +func NewWebHookNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*WebhookNotifier, error) { if model.Settings == nil { return nil, receiverInitError{Cfg: *model, Reason: "could not find settings property"} } @@ -48,7 +48,7 @@ func NewWebHookNotifier(model *NotificationChannelConfig, t *template.Template) }), URL: url, User: model.Settings.Get("username").MustString(), - Password: model.DecryptedValue("password", model.Settings.Get("password").MustString()), + Password: fn(context.Background(), model.SecureSettings, "password", model.Settings.Get("password").MustString(), setting.SecretKey), HTTPMethod: model.Settings.Get("httpMethod").MustString("POST"), MaxAlerts: model.Settings.Get("maxAlerts").MustInt(0), log: log.New("alerting.notifier.webhook"), diff --git a/pkg/services/ngalert/notifier/channels/webhook_test.go b/pkg/services/ngalert/notifier/channels/webhook_test.go index dcef1a7a729..a6d8c8046c8 100644 --- a/pkg/services/ngalert/notifier/channels/webhook_test.go +++ b/pkg/services/ngalert/notifier/channels/webhook_test.go @@ -6,6 +6,8 @@ import ( "net/url" "testing" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" @@ -183,7 +185,8 @@ func TestWebhookNotifier(t *testing.T) { Settings: settingsJSON, } - pn, err := NewWebHookNotifier(m, tmpl) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + pn, err := NewWebHookNotifier(m, tmpl, decryptFn) if c.expInitError != "" { require.Error(t, err) require.Equal(t, c.expInitError, err.Error()) diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index 1c5ee178636..4f2aa506c71 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -6,18 +6,18 @@ import ( "sync" "time" - "github.com/grafana/grafana/pkg/services/ngalert/logging" + "github.com/grafana/grafana/pkg/services/ngalert/notifier/channels" gokit_log "github.com/go-kit/kit/log" - "github.com/prometheus/alertmanager/cluster" - "github.com/prometheus/client_golang/prometheus" - "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/ngalert/logging" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/alertmanager/cluster" + "github.com/prometheus/client_golang/prometheus" ) var ( @@ -40,10 +40,14 @@ type MultiOrgAlertmanager struct { orgStore store.OrgStore kvStore kvstore.KVStore + decryptFn channels.GetDecryptedValueFn + metrics *metrics.MultiOrgAlertmanager } -func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore store.AlertingStore, orgStore store.OrgStore, kvStore kvstore.KVStore, m *metrics.MultiOrgAlertmanager, l log.Logger) (*MultiOrgAlertmanager, error) { +func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore store.AlertingStore, orgStore store.OrgStore, + kvStore kvstore.KVStore, decryptFn channels.GetDecryptedValueFn, m *metrics.MultiOrgAlertmanager, l log.Logger, +) (*MultiOrgAlertmanager, error) { moa := &MultiOrgAlertmanager{ logger: l, settings: cfg, @@ -51,6 +55,7 @@ func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore store.AlertingStore, configStore: configStore, orgStore: orgStore, kvStore: kvStore, + decryptFn: decryptFn, metrics: m, } @@ -162,7 +167,7 @@ func (moa *MultiOrgAlertmanager) SyncAlertmanagersForOrgs(ctx context.Context, o // To export them, we need to translate the metrics from each individual registry and, // then aggregate them on the main registry. m := metrics.NewAlertmanagerMetrics(moa.metrics.GetOrCreateOrgRegistry(orgID)) - am, err := newAlertmanager(orgID, moa.settings, moa.configStore, moa.kvStore, moa.peer, m) + am, err := newAlertmanager(orgID, moa.settings, moa.configStore, moa.kvStore, moa.peer, moa.decryptFn, m) if err != nil { moa.logger.Error("unable to create Alertmanager for org", "org", orgID, "err", err) } diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go b/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go index 3eadb9054f6..d2b1331b9aa 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go @@ -9,10 +9,10 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/setting" - "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/require" @@ -29,6 +29,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) { tmpDir, err := ioutil.TempDir("", "test") require.NoError(t, err) kvStore := newFakeKVStore(t) + decryptFn := ossencryption.ProvideService().GetDecryptedValue reg := prometheus.NewPedanticRegistry() m := metrics.NewNGAlert(reg) cfg := &setting.Cfg{ @@ -39,7 +40,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) { DisabledOrgs: map[int64]struct{}{5: {}}, }, // do not poll in tests. } - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, m.GetMultiOrgAlertmanagerMetrics(), log.New("testlogger")) + mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), log.New("testlogger")) require.NoError(t, err) ctx := context.Background() @@ -108,9 +109,10 @@ func TestMultiOrgAlertmanager_AlertmanagerFor(t *testing.T) { UnifiedAlerting: setting.UnifiedAlertingSettings{AlertmanagerConfigPollInterval: 3 * time.Minute, DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration()}, // do not poll in tests. } kvStore := newFakeKVStore(t) + decryptFn := ossencryption.ProvideService().GetDecryptedValue reg := prometheus.NewPedanticRegistry() m := metrics.NewNGAlert(reg) - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, m.GetMultiOrgAlertmanagerMetrics(), log.New("testlogger")) + mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), log.New("testlogger")) require.NoError(t, err) ctx := context.Background() diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index ac3552eba60..cbb0c8bd6ec 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -9,7 +9,9 @@ import ( "testing" "time" + "github.com/benbjohnson/clock" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/metrics" @@ -18,8 +20,6 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/state" "github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/setting" - - "github.com/benbjohnson/clock" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" @@ -232,8 +232,10 @@ func setupScheduler(t *testing.T, rs store.RuleStore, is store.InstanceStore, ac mockedClock := clock.NewMock() logger := log.New("ngalert schedule test") m := metrics.NewNGAlert(prometheus.NewPedanticRegistry()) - moa, err := notifier.NewMultiOrgAlertmanager(&setting.Cfg{}, ¬ifier.FakeConfigStore{}, ¬ifier.FakeOrgStore{}, ¬ifier.FakeKVStore{}, nil, log.New("testlogger")) + decryptFn := ossencryption.ProvideService().GetDecryptedValue + moa, err := notifier.NewMultiOrgAlertmanager(&setting.Cfg{}, ¬ifier.FakeConfigStore{}, ¬ifier.FakeOrgStore{}, ¬ifier.FakeKVStore{}, decryptFn, nil, log.New("testlogger")) require.NoError(t, err) + schedCfg := SchedulerCfg{ C: mockedClock, BaseInterval: time.Second, diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index c5622b2d43d..6c06e5dea33 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -9,18 +9,16 @@ import ( "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/grafana/grafana/pkg/services/ngalert" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/metrics" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/model" - "github.com/grafana/grafana/pkg/services/ngalert/models" - - "github.com/grafana/grafana/pkg/services/ngalert" "github.com/grafana/grafana/pkg/services/ngalert/store" - "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/model" "github.com/stretchr/testify/require" ) @@ -34,7 +32,10 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, * cfg.UnifiedAlerting.Enabled = true m := metrics.NewNGAlert(prometheus.NewRegistry()) - ng, err := ngalert.ProvideService(cfg, nil, routing.NewRouteRegister(), sqlstore.InitTestDB(t), nil, nil, nil, nil, m) + ng, err := ngalert.ProvideService( + cfg, nil, routing.NewRouteRegister(), sqlstore.InitTestDB(t), + nil, nil, nil, nil, ossencryption.ProvideService(), m, + ) require.NoError(t, err) return ng, &store.DBstore{ SQLStore: ng.SQLStore, diff --git a/pkg/services/pluginsettings/service.go b/pkg/services/pluginsettings/service.go new file mode 100644 index 00000000000..02113dad0f6 --- /dev/null +++ b/pkg/services/pluginsettings/service.go @@ -0,0 +1,91 @@ +package pluginsettings + +import ( + "context" + "sync" + "time" + + "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" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/setting" +) + +type Service struct { + Bus bus.Bus + SQLStore *sqlstore.SQLStore + EncryptionService encryption.Service + + logger log.Logger + pluginSettingDecryptionCache secureJSONDecryptionCache +} + +type cachedDecryptedJSON struct { + updated time.Time + json map[string]string +} + +type secureJSONDecryptionCache struct { + cache map[int64]cachedDecryptedJSON + sync.Mutex +} + +func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService encryption.Service) *Service { + s := &Service{ + Bus: bus, + SQLStore: store, + EncryptionService: encryptionService, + logger: log.New("pluginsettings"), + pluginSettingDecryptionCache: secureJSONDecryptionCache{ + cache: make(map[int64]cachedDecryptedJSON), + }, + } + + s.Bus.AddHandler(s.GetPluginSettingById) + s.Bus.AddHandlerCtx(s.UpdatePluginSetting) + s.Bus.AddHandler(s.UpdatePluginSettingVersion) + + return s +} + +func (s *Service) GetPluginSettingById(query *models.GetPluginSettingByIdQuery) error { + return s.SQLStore.GetPluginSettingById(query) +} + +func (s *Service) UpdatePluginSetting(ctx context.Context, cmd *models.UpdatePluginSettingCmd) error { + var err error + cmd.EncryptedSecureJsonData, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureJsonData, setting.SecretKey) + if err != nil { + return err + } + + return s.SQLStore.UpdatePluginSetting(cmd) +} + +func (s *Service) UpdatePluginSettingVersion(cmd *models.UpdatePluginSettingVersionCmd) error { + return s.SQLStore.UpdatePluginSettingVersion(cmd) +} + +func (s *Service) DecryptedValues(ps *models.PluginSetting) map[string]string { + s.pluginSettingDecryptionCache.Lock() + defer s.pluginSettingDecryptionCache.Unlock() + + if item, present := s.pluginSettingDecryptionCache.cache[ps.Id]; present && ps.Updated.Equal(item.updated) { + return item.json + } + + json, err := s.EncryptionService.DecryptJsonData(context.Background(), ps.SecureJsonData, setting.SecretKey) + if err != nil { + s.logger.Error("Failed to decrypt secure json data", "error", err) + return map[string]string{} + } + + s.pluginSettingDecryptionCache.cache[ps.Id] = cachedDecryptedJSON{ + updated: ps.Updated, + json: json, + } + + return json +} diff --git a/pkg/services/pluginsettings/service_test.go b/pkg/services/pluginsettings/service_test.go new file mode 100644 index 00000000000..594318e677f --- /dev/null +++ b/pkg/services/pluginsettings/service_test.go @@ -0,0 +1,92 @@ +package pluginsettings + +import ( + "context" + "testing" + "time" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" + "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/require" +) + +func TestService_DecryptedValuesCache(t *testing.T) { + t.Run("When plugin settings hasn't been updated, encrypted JSON should be fetched from cache", func(t *testing.T) { + ctx := context.Background() + + encryptionService := ossencryption.ProvideService() + psService := ProvideService(bus.New(), nil, encryptionService) + + encryptedJsonData, err := encryptionService.EncryptJsonData( + ctx, + map[string]string{ + "password": "password", + }, setting.SecretKey) + require.NoError(t, err) + + ps := models.PluginSetting{ + Id: 1, + JsonData: map[string]interface{}{}, + SecureJsonData: encryptedJsonData, + } + + // Populate cache + password, ok := psService.DecryptedValues(&ps)["password"] + require.Equal(t, "password", password) + require.True(t, ok) + + encryptedJsonData, err = encryptionService.EncryptJsonData( + ctx, + map[string]string{ + "password": "", + }, setting.SecretKey) + require.NoError(t, err) + + ps.SecureJsonData = encryptedJsonData + + password, ok = psService.DecryptedValues(&ps)["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) { + ctx := context.Background() + + encryptionService := ossencryption.ProvideService() + psService := ProvideService(bus.New(), nil, encryptionService) + + encryptedJsonData, err := encryptionService.EncryptJsonData( + ctx, + map[string]string{ + "password": "password", + }, setting.SecretKey) + require.NoError(t, err) + + ps := models.PluginSetting{ + Id: 1, + JsonData: map[string]interface{}{}, + SecureJsonData: encryptedJsonData, + } + + // Populate cache + password, ok := psService.DecryptedValues(&ps)["password"] + require.Equal(t, "password", password) + require.True(t, ok) + + encryptedJsonData, err = encryptionService.EncryptJsonData( + ctx, + map[string]string{ + "password": "", + }, setting.SecretKey) + require.NoError(t, err) + + ps.SecureJsonData = encryptedJsonData + ps.Updated = time.Now() + + password, ok = psService.DecryptedValues(&ps)["password"] + require.Empty(t, password) + require.True(t, ok) + }) +} diff --git a/pkg/services/provisioning/notifiers/alert_notifications.go b/pkg/services/provisioning/notifiers/alert_notifications.go index d3a3b34fcd5..153f10ce5a9 100644 --- a/pkg/services/provisioning/notifiers/alert_notifications.go +++ b/pkg/services/provisioning/notifiers/alert_notifications.go @@ -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, + }, } } diff --git a/pkg/services/provisioning/notifiers/config_reader.go b/pkg/services/provisioning/notifiers/config_reader.go index 17886adba36..5095401269d 100644 --- a/pkg/services/provisioning/notifiers/config_reader.go +++ b/pkg/services/provisioning/notifiers/config_reader.go @@ -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 diff --git a/pkg/services/provisioning/notifiers/config_reader_test.go b/pkg/services/provisioning/notifiers/config_reader_test.go index 423dc5a25c8..018babb6db8 100644 --- a/pkg/services/provisioning/notifiers/config_reader_test.go +++ b/pkg/services/provisioning/notifiers/config_reader_test.go @@ -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(¬ificationsQuery) + err = sqlStore.GetAllAlertNotifications(¬ificationsQuery) 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(¬ificationsQuery) + err = sqlStore.GetAllAlertNotifications(¬ificationsQuery) 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(¬ificationsQuery) + err = sqlStore.GetAllAlertNotifications(¬ificationsQuery) 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(¬ificationsQuery) + err = sqlStore.GetAllAlertNotifications(¬ificationsQuery) 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(¬ificationsQuery) + err = sqlStore.GetAllAlertNotifications(¬ificationsQuery) 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(¬ificationsQuery) + err = sqlStore.GetAllAlertNotifications(¬ificationsQuery) 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(¬ificationsQuery) + err = sqlStore.GetAllAlertNotifications(¬ificationsQuery) 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(¬ificationsQuery) + err = sqlStore.GetAllAlertNotifications(¬ificationsQuery) 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) + }) +} diff --git a/pkg/services/provisioning/provisioning.go b/pkg/services/provisioning/provisioning.go index 70df46d0797..80c729982f2 100644 --- a/pkg/services/provisioning/provisioning.go +++ b/pkg/services/provisioning/provisioning.go @@ -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) } diff --git a/pkg/services/secrets/grafana_provider.go b/pkg/services/secrets/grafana_provider.go index 5c536e116b0..f0b99403caf 100644 --- a/pkg/services/secrets/grafana_provider.go +++ b/pkg/services/secrets/grafana_provider.go @@ -1,6 +1,8 @@ package secrets import ( + "context" + "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/setting" ) @@ -17,12 +19,12 @@ func newGrafanaProvider(settings setting.Provider, encryption encryption.Service } } -func (p grafanaProvider) Encrypt(blob []byte) ([]byte, error) { +func (p grafanaProvider) Encrypt(ctx context.Context, blob []byte) ([]byte, error) { key := p.settings.KeyValue("security", "secret_key").Value() - return p.encryption.Encrypt(blob, key) + return p.encryption.Encrypt(ctx, blob, key) } -func (p grafanaProvider) Decrypt(blob []byte) ([]byte, error) { +func (p grafanaProvider) Decrypt(ctx context.Context, blob []byte) ([]byte, error) { key := p.settings.KeyValue("security", "secret_key").Value() - return p.encryption.Decrypt(blob, key) + return p.encryption.Decrypt(ctx, blob, key) } diff --git a/pkg/services/secrets/secrets.go b/pkg/services/secrets/secrets.go index 7d239847c2c..36116db0fe0 100644 --- a/pkg/services/secrets/secrets.go +++ b/pkg/services/secrets/secrets.go @@ -55,8 +55,8 @@ type dataKeyCacheItem struct { } type Provider interface { - Encrypt(blob []byte) ([]byte, error) - Decrypt(blob []byte) ([]byte, error) + Encrypt(ctx context.Context, blob []byte) ([]byte, error) + Decrypt(ctx context.Context, blob []byte) ([]byte, error) } var b64 = base64.RawStdEncoding @@ -95,7 +95,7 @@ func (s *SecretsService) Encrypt(ctx context.Context, payload []byte, opt Encryp } } - encrypted, err := s.enc.Encrypt(payload, string(dataKey)) + encrypted, err := s.enc.Encrypt(ctx, payload, string(dataKey)) if err != nil { return nil, err } @@ -142,7 +142,7 @@ func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, e } } - return s.enc.Decrypt(payload, string(dataKey)) + return s.enc.Decrypt(ctx, payload, string(dataKey)) } func (s *SecretsService) EncryptJsonData(ctx context.Context, kv map[string]string, opt EncryptionOptions) (map[string][]byte, error) { @@ -206,7 +206,7 @@ func (s *SecretsService) newDataKey(ctx context.Context, name string, scope stri } // 2. Encrypt it - encrypted, err := provider.Encrypt(dataKey) + encrypted, err := provider.Encrypt(ctx, dataKey) if err != nil { return nil, err } @@ -254,7 +254,7 @@ func (s *SecretsService) dataKey(ctx context.Context, name string) ([]byte, erro return nil, fmt.Errorf("could not find encryption provider '%s'", dataKey.Provider) } - decrypted, err := provider.Decrypt(dataKey.EncryptedData) + decrypted, err := provider.Decrypt(ctx, dataKey.EncryptedData) if err != nil { return nil, err } diff --git a/pkg/services/secrets/secrets_test.go b/pkg/services/secrets/secrets_test.go index 8fa44e4d7e5..56589116703 100644 --- a/pkg/services/secrets/secrets_test.go +++ b/pkg/services/secrets/secrets_test.go @@ -108,6 +108,7 @@ func TestSecretsService_DataKeys(t *testing.T) { Provider: "test", EncryptedData: []byte{0x62, 0xAF, 0xA1, 0x1A}, } + err := svc.CreateDataKey(ctx, k) require.Error(t, err) diff --git a/pkg/services/sqlstore/alert_notification.go b/pkg/services/sqlstore/alert_notification.go index 1f37efca75f..aa995d4ec40 100644 --- a/pkg/services/sqlstore/alert_notification.go +++ b/pkg/services/sqlstore/alert_notification.go @@ -9,28 +9,11 @@ import ( "time" "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/util" ) -func init() { - bus.AddHandler("sql", GetAlertNotifications) - bus.AddHandler("sql", CreateAlertNotificationCommand) - bus.AddHandler("sql", UpdateAlertNotification) - bus.AddHandler("sql", DeleteAlertNotification) - bus.AddHandler("sql", GetAllAlertNotifications) - bus.AddHandlerCtx("sql", GetOrCreateAlertNotificationState) - bus.AddHandlerCtx("sql", SetAlertNotificationStateToCompleteCommand) - bus.AddHandlerCtx("sql", SetAlertNotificationStateToPendingCommand) - - bus.AddHandler("sql", GetAlertNotificationsWithUid) - bus.AddHandler("sql", UpdateAlertNotificationWithUid) - bus.AddHandler("sql", DeleteAlertNotificationWithUid) - bus.AddHandler("sql", GetAlertNotificationsWithUidToSend) -} - -func DeleteAlertNotification(cmd *models.DeleteAlertNotificationCommand) error { +func (ss *SQLStore) DeleteAlertNotification(cmd *models.DeleteAlertNotificationCommand) error { return inTransaction(func(sess *DBSession) error { sql := "DELETE FROM alert_notification WHERE alert_notification.org_id = ? AND alert_notification.id = ?" res, err := sess.Exec(sql, cmd.OrgId, cmd.Id) @@ -54,7 +37,7 @@ func DeleteAlertNotification(cmd *models.DeleteAlertNotificationCommand) error { }) } -func DeleteAlertNotificationWithUid(cmd *models.DeleteAlertNotificationWithUidCommand) error { +func (ss *SQLStore) DeleteAlertNotificationWithUid(cmd *models.DeleteAlertNotificationWithUidCommand) error { existingNotification := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid} if err := getAlertNotificationWithUidInternal(existingNotification, newSession(context.Background())); err != nil { return err @@ -76,7 +59,7 @@ func DeleteAlertNotificationWithUid(cmd *models.DeleteAlertNotificationWithUidCo return nil } -func GetAlertNotifications(query *models.GetAlertNotificationsQuery) error { +func (ss *SQLStore) GetAlertNotifications(query *models.GetAlertNotificationsQuery) error { return getAlertNotificationInternal(query, newSession(context.Background())) } @@ -106,11 +89,11 @@ func newAlertNotificationUidCacheKey(orgID, notificationId int64) string { return fmt.Sprintf("notification-uid-by-org-%d-and-id-%d", orgID, notificationId) } -func GetAlertNotificationsWithUid(query *models.GetAlertNotificationsWithUidQuery) error { +func (ss *SQLStore) GetAlertNotificationsWithUid(query *models.GetAlertNotificationsWithUidQuery) error { return getAlertNotificationWithUidInternal(query, newSession(context.Background())) } -func GetAllAlertNotifications(query *models.GetAllAlertNotificationsQuery) error { +func (ss *SQLStore) GetAllAlertNotifications(query *models.GetAllAlertNotificationsQuery) error { results := make([]*models.AlertNotification, 0) if err := x.Where("org_id = ?", query.OrgId).Asc("name").Find(&results); err != nil { return err @@ -120,7 +103,7 @@ func GetAllAlertNotifications(query *models.GetAllAlertNotificationsQuery) error return nil } -func GetAlertNotificationsWithUidToSend(query *models.GetAlertNotificationsWithUidToSendQuery) error { +func (ss *SQLStore) GetAlertNotificationsWithUidToSend(query *models.GetAlertNotificationsWithUidToSendQuery) error { var sql bytes.Buffer params := make([]interface{}, 0) @@ -281,7 +264,7 @@ func getAlertNotificationWithUidInternal(query *models.GetAlertNotificationsWith return nil } -func CreateAlertNotificationCommand(cmd *models.CreateAlertNotificationCommand) error { +func (ss *SQLStore) CreateAlertNotificationCommand(cmd *models.CreateAlertNotificationCommand) error { return inTransaction(func(sess *DBSession) error { if cmd.Uid == "" { uid, uidGenerationErr := generateNewAlertNotificationUid(sess, cmd.OrgId) @@ -337,7 +320,7 @@ func CreateAlertNotificationCommand(cmd *models.CreateAlertNotificationCommand) Name: cmd.Name, Type: cmd.Type, Settings: cmd.Settings, - SecureSettings: securejsondata.GetEncryptedJsonData(cmd.SecureSettings), + SecureSettings: cmd.EncryptedSecureSettings, SendReminder: cmd.SendReminder, DisableResolveMessage: cmd.DisableResolveMessage, Frequency: frequency, @@ -371,7 +354,7 @@ func generateNewAlertNotificationUid(sess *DBSession, orgId int64) (string, erro return "", models.ErrAlertNotificationFailedGenerateUniqueUid } -func UpdateAlertNotification(cmd *models.UpdateAlertNotificationCommand) error { +func (ss *SQLStore) UpdateAlertNotification(cmd *models.UpdateAlertNotificationCommand) error { return inTransaction(func(sess *DBSession) (err error) { current := models.AlertNotification{} @@ -402,7 +385,7 @@ func UpdateAlertNotification(cmd *models.UpdateAlertNotificationCommand) error { current.Updated = time.Now() current.Settings = cmd.Settings - current.SecureSettings = securejsondata.GetEncryptedJsonData(cmd.SecureSettings) + current.SecureSettings = cmd.EncryptedSecureSettings current.Name = cmd.Name current.Type = cmd.Type current.IsDefault = cmd.IsDefault @@ -439,7 +422,7 @@ func UpdateAlertNotification(cmd *models.UpdateAlertNotificationCommand) error { }) } -func UpdateAlertNotificationWithUid(cmd *models.UpdateAlertNotificationWithUidCommand) error { +func (ss *SQLStore) UpdateAlertNotificationWithUid(cmd *models.UpdateAlertNotificationWithUidCommand) error { getAlertNotificationWithUidQuery := &models.GetAlertNotificationsWithUidQuery{OrgId: cmd.OrgId, Uid: cmd.Uid} if err := getAlertNotificationWithUidInternal(getAlertNotificationWithUidQuery, newSession(context.Background())); err != nil { @@ -480,7 +463,7 @@ func UpdateAlertNotificationWithUid(cmd *models.UpdateAlertNotificationWithUidCo return nil } -func SetAlertNotificationStateToCompleteCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToCompleteCommand) error { +func (ss *SQLStore) SetAlertNotificationStateToCompleteCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToCompleteCommand) error { return inTransactionCtx(ctx, func(sess *DBSession) error { version := cmd.Version var current models.AlertNotificationState @@ -509,7 +492,7 @@ func SetAlertNotificationStateToCompleteCommand(ctx context.Context, cmd *models }) } -func SetAlertNotificationStateToPendingCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error { +func (ss *SQLStore) SetAlertNotificationStateToPendingCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error { return withDbSession(ctx, x, func(sess *DBSession) error { newVersion := cmd.Version + 1 sql := `UPDATE alert_notification_state SET @@ -545,7 +528,7 @@ func SetAlertNotificationStateToPendingCommand(ctx context.Context, cmd *models. }) } -func GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error { +func (ss *SQLStore) GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error { return inTransactionCtx(ctx, func(sess *DBSession) error { nj := &models.AlertNotificationState{} diff --git a/pkg/services/sqlstore/alert_notification_test.go b/pkg/services/sqlstore/alert_notification_test.go index 6042930ea27..18ba9df943d 100644 --- a/pkg/services/sqlstore/alert_notification_test.go +++ b/pkg/services/sqlstore/alert_notification_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" . "github.com/smartystreets/goconvey/convey" @@ -17,7 +18,12 @@ import ( func TestAlertNotificationSQLAccess(t *testing.T) { Convey("Testing Alert notification sql access", t, func() { - InitTestDB(t) + sqlStore := InitTestDB(t) + + // Set up bus handlers + bus.AddHandler("deleteAlertNotification", func(cmd *models.DeleteAlertNotificationCommand) error { + return sqlStore.DeleteAlertNotification(cmd) + }) Convey("Alert notification state", func() { var alertID int64 = 7 @@ -29,7 +35,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Convey("Get no existing state should create a new state", func() { query := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} - err := GetOrCreateAlertNotificationState(context.Background(), query) + err := sqlStore.GetOrCreateAlertNotificationState(context.Background(), query) So(err, ShouldBeNil) So(query.Result, ShouldNotBeNil) So(query.Result.State, ShouldEqual, "unknown") @@ -38,7 +44,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Convey("Get existing state should not create a new state", func() { query2 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} - err := GetOrCreateAlertNotificationState(context.Background(), query2) + err := sqlStore.GetOrCreateAlertNotificationState(context.Background(), query2) So(err, ShouldBeNil) So(query2.Result, ShouldNotBeNil) So(query2.Result.Id, ShouldEqual, query.Result.Id) @@ -54,12 +60,12 @@ func TestAlertNotificationSQLAccess(t *testing.T) { AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion, } - err := SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) + err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) So(err, ShouldBeNil) So(cmd.ResultVersion, ShouldEqual, 1) query2 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} - err = GetOrCreateAlertNotificationState(context.Background(), query2) + err = sqlStore.GetOrCreateAlertNotificationState(context.Background(), query2) So(err, ShouldBeNil) So(query2.Result.Version, ShouldEqual, 1) So(query2.Result.State, ShouldEqual, models.AlertNotificationStatePending) @@ -71,11 +77,11 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Id: s.Id, Version: cmd.ResultVersion, } - err := SetAlertNotificationStateToCompleteCommand(context.Background(), &setStateCmd) + err := sqlStore.SetAlertNotificationStateToCompleteCommand(context.Background(), &setStateCmd) So(err, ShouldBeNil) query3 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} - err = GetOrCreateAlertNotificationState(context.Background(), query3) + err = sqlStore.GetOrCreateAlertNotificationState(context.Background(), query3) So(err, ShouldBeNil) So(query3.Result.Version, ShouldEqual, 2) So(query3.Result.State, ShouldEqual, models.AlertNotificationStateCompleted) @@ -89,11 +95,11 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Id: s.Id, Version: unknownVersion, } - err := SetAlertNotificationStateToCompleteCommand(context.Background(), &cmd) + err := sqlStore.SetAlertNotificationStateToCompleteCommand(context.Background(), &cmd) So(err, ShouldBeNil) query3 := &models.GetOrCreateNotificationStateQuery{AlertId: alertID, OrgId: orgID, NotifierId: notifierID} - err = GetOrCreateAlertNotificationState(context.Background(), query3) + err = sqlStore.GetOrCreateAlertNotificationState(context.Background(), query3) So(err, ShouldBeNil) So(query3.Result.Version, ShouldEqual, unknownVersion+1) So(query3.Result.State, ShouldEqual, models.AlertNotificationStateCompleted) @@ -109,7 +115,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Version: s.Version, AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion, } - err := SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) + err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) So(err, ShouldEqual, models.ErrAlertNotificationStateVersionConflict) }) @@ -120,7 +126,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Version: s.Version, AlertRuleStateUpdatedVersion: 1000, } - err := SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) + err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) So(err, ShouldBeNil) So(cmd.ResultVersion, ShouldEqual, 1) @@ -134,7 +140,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Version: s.Version, AlertRuleStateUpdatedVersion: s.AlertRuleStateUpdatedVersion, } - err := SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) + err := sqlStore.SetAlertNotificationStateToPendingCommand(context.Background(), &cmd) So(err, ShouldNotBeNil) }) }) @@ -150,7 +156,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Name: "email", } - err := GetAlertNotifications(cmd) + err := sqlStore.GetAlertNotifications(cmd) So(err, ShouldBeNil) So(cmd.Result, ShouldBeNil) }) @@ -165,14 +171,14 @@ func TestAlertNotificationSQLAccess(t *testing.T) { } Convey("and missing frequency", func() { - err := CreateAlertNotificationCommand(cmd) + err := sqlStore.CreateAlertNotificationCommand(cmd) So(err, ShouldEqual, models.ErrNotificationFrequencyNotFound) }) Convey("invalid frequency", func() { cmd.Frequency = "invalid duration" - err := CreateAlertNotificationCommand(cmd) + err := sqlStore.CreateAlertNotificationCommand(cmd) So(regexp.MustCompile(`^time: invalid duration "?invalid duration"?$`).MatchString( err.Error()), ShouldBeTrue) }) @@ -187,7 +193,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), } - err := CreateAlertNotificationCommand(cmd) + err := sqlStore.CreateAlertNotificationCommand(cmd) So(err, ShouldBeNil) updateCmd := &models.UpdateAlertNotificationCommand{ @@ -196,14 +202,14 @@ func TestAlertNotificationSQLAccess(t *testing.T) { } Convey("and missing frequency", func() { - err := UpdateAlertNotification(updateCmd) + err := sqlStore.UpdateAlertNotification(updateCmd) So(err, ShouldEqual, models.ErrNotificationFrequencyNotFound) }) Convey("invalid frequency", func() { updateCmd.Frequency = "invalid duration" - err := UpdateAlertNotification(updateCmd) + err := sqlStore.UpdateAlertNotification(updateCmd) So(err, ShouldNotBeNil) So(regexp.MustCompile(`^time: invalid duration "?invalid duration"?$`).MatchString( err.Error()), ShouldBeTrue) @@ -220,7 +226,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), } - err := CreateAlertNotificationCommand(cmd) + err := sqlStore.CreateAlertNotificationCommand(cmd) So(err, ShouldBeNil) So(cmd.Result.Id, ShouldNotEqual, 0) So(cmd.Result.OrgId, ShouldNotEqual, 0) @@ -230,7 +236,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { So(cmd.Result.Uid, ShouldNotBeEmpty) Convey("Cannot save Alert Notification with the same name", func() { - err = CreateAlertNotificationCommand(cmd) + err = sqlStore.CreateAlertNotificationCommand(cmd) So(err, ShouldNotBeNil) }) Convey("Cannot save Alert Notification with the same name and another uid", func() { @@ -243,7 +249,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Settings: cmd.Settings, Uid: "notifier1", } - err = CreateAlertNotificationCommand(anotherUidCmd) + err = sqlStore.CreateAlertNotificationCommand(anotherUidCmd) So(err, ShouldNotBeNil) }) Convey("Can save Alert Notification with another name and another uid", func() { @@ -256,7 +262,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Settings: cmd.Settings, Uid: "notifier2", } - err = CreateAlertNotificationCommand(anotherUidCmd) + err = sqlStore.CreateAlertNotificationCommand(anotherUidCmd) So(err, ShouldBeNil) }) @@ -271,7 +277,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), Id: cmd.Result.Id, } - err := UpdateAlertNotification(newCmd) + err := sqlStore.UpdateAlertNotification(newCmd) So(err, ShouldBeNil) So(newCmd.Result.Name, ShouldEqual, "NewName") So(newCmd.Result.Frequency, ShouldEqual, 60*time.Second) @@ -287,7 +293,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), Id: cmd.Result.Id, } - err := UpdateAlertNotification(newCmd) + err := sqlStore.UpdateAlertNotification(newCmd) So(err, ShouldBeNil) So(newCmd.Result.SendReminder, ShouldBeFalse) }) @@ -301,11 +307,11 @@ func TestAlertNotificationSQLAccess(t *testing.T) { otherOrg := models.CreateAlertNotificationCommand{Name: "default", Type: "email", OrgId: 2, SendReminder: true, Frequency: "10s", Settings: simplejson.New()} - So(CreateAlertNotificationCommand(&cmd1), ShouldBeNil) - So(CreateAlertNotificationCommand(&cmd2), ShouldBeNil) - So(CreateAlertNotificationCommand(&cmd3), ShouldBeNil) - So(CreateAlertNotificationCommand(&cmd4), ShouldBeNil) - So(CreateAlertNotificationCommand(&otherOrg), ShouldBeNil) + So(sqlStore.CreateAlertNotificationCommand(&cmd1), ShouldBeNil) + So(sqlStore.CreateAlertNotificationCommand(&cmd2), ShouldBeNil) + So(sqlStore.CreateAlertNotificationCommand(&cmd3), ShouldBeNil) + So(sqlStore.CreateAlertNotificationCommand(&cmd4), ShouldBeNil) + So(sqlStore.CreateAlertNotificationCommand(&otherOrg), ShouldBeNil) Convey("search", func() { query := &models.GetAlertNotificationsWithUidToSendQuery{ @@ -313,7 +319,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { OrgId: 1, } - err := GetAlertNotificationsWithUidToSend(query) + err := sqlStore.GetAlertNotificationsWithUidToSend(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 3) }) @@ -323,7 +329,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { OrgId: 1, } - err := GetAllAlertNotifications(query) + err := sqlStore.GetAllAlertNotifications(query) So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 4) So(query.Result[0].Name, ShouldEqual, cmd4.Name) @@ -337,7 +343,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { ss := InitTestDB(t) notification := &models.CreateAlertNotificationCommand{Uid: "aNotificationUid", OrgId: 1, Name: "aNotificationUid"} - err := CreateAlertNotificationCommand(notification) + err := sqlStore.CreateAlertNotificationCommand(notification) So(err, ShouldBeNil) byUidQuery := &models.GetAlertNotificationsWithUidQuery{ @@ -345,7 +351,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { OrgId: notification.OrgId, } - notificationByUidErr := GetAlertNotificationsWithUid(byUidQuery) + notificationByUidErr := sqlStore.GetAlertNotificationsWithUid(byUidQuery) So(notificationByUidErr, ShouldBeNil) Convey("Can cache notification Uid", func() { @@ -410,7 +416,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), Id: 1, } - err := UpdateAlertNotification(updateCmd) + err := sqlStore.UpdateAlertNotification(updateCmd) So(err, ShouldEqual, models.ErrAlertNotificationNotFound) Convey("using UID", func() { @@ -425,7 +431,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Uid: "uid", NewUid: "newUid", } - err := UpdateAlertNotificationWithUid(updateWithUidCmd) + err := sqlStore.UpdateAlertNotificationWithUid(updateWithUidCmd) So(err, ShouldEqual, models.ErrAlertNotificationNotFound) }) }) @@ -439,25 +445,26 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Settings: simplejson.New(), } - err := CreateAlertNotificationCommand(cmd) + err := sqlStore.CreateAlertNotificationCommand(cmd) So(err, ShouldBeNil) deleteCmd := &models.DeleteAlertNotificationCommand{ Id: cmd.Result.Id, OrgId: 1, } - err = DeleteAlertNotification(deleteCmd) + err = sqlStore.DeleteAlertNotification(deleteCmd) So(err, ShouldBeNil) Convey("using UID", func() { - err := CreateAlertNotificationCommand(cmd) + err := sqlStore.CreateAlertNotificationCommand(cmd) So(err, ShouldBeNil) deleteWithUidCmd := &models.DeleteAlertNotificationWithUidCommand{ Uid: cmd.Result.Uid, OrgId: 1, } - err = DeleteAlertNotificationWithUid(deleteWithUidCmd) + + err = sqlStore.DeleteAlertNotificationWithUid(deleteWithUidCmd) So(err, ShouldBeNil) So(deleteWithUidCmd.DeletedAlertNotificationId, ShouldEqual, cmd.Result.Id) }) @@ -468,7 +475,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Id: 1, OrgId: 1, } - err := DeleteAlertNotification(deleteCmd) + err := sqlStore.DeleteAlertNotification(deleteCmd) So(err, ShouldEqual, models.ErrAlertNotificationNotFound) Convey("using UID", func() { @@ -476,7 +483,7 @@ func TestAlertNotificationSQLAccess(t *testing.T) { Uid: "uid", OrgId: 1, } - err = DeleteAlertNotificationWithUid(deleteWithUidCmd) + err = sqlStore.DeleteAlertNotificationWithUid(deleteWithUidCmd) So(err, ShouldEqual, models.ErrAlertNotificationNotFound) }) }) diff --git a/pkg/services/sqlstore/dashboard_snapshot_test.go b/pkg/services/sqlstore/dashboard_snapshot_test.go index 0cc48773579..8abea341740 100644 --- a/pkg/services/sqlstore/dashboard_snapshot_test.go +++ b/pkg/services/sqlstore/dashboard_snapshot_test.go @@ -4,6 +4,7 @@ package sqlstore import ( + "context" "testing" "time" @@ -30,7 +31,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { rawDashboard, err := dashboard.Encode() require.NoError(t, err) - encryptedDashboard, err := ossencryption.ProvideService().Encrypt(rawDashboard, setting.SecretKey) + encryptedDashboard, err := ossencryption.ProvideService().Encrypt(context.Background(), rawDashboard, setting.SecretKey) require.NoError(t, err) cmd := models.CreateDashboardSnapshotCommand{ @@ -51,6 +52,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { assert.NotNil(t, query.Result) decryptedDashboard, err := ossencryption.ProvideService().Decrypt( + context.Background(), query.Result.DashboardEncrypted, setting.SecretKey, ) @@ -132,6 +134,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { t.Run("Should have encrypted dashboard data", func(t *testing.T) { decryptedDashboard, err := ossencryption.ProvideService().Decrypt( + context.Background(), cmd.Result.DashboardEncrypted, setting.SecretKey, ) diff --git a/pkg/services/sqlstore/datasource.go b/pkg/services/sqlstore/datasource.go index 6c70cb8c1c4..9b9372a846b 100644 --- a/pkg/services/sqlstore/datasource.go +++ b/pkg/services/sqlstore/datasource.go @@ -5,49 +5,17 @@ import ( "strings" "time" - "github.com/grafana/grafana/pkg/events" - "github.com/grafana/grafana/pkg/util/errutil" - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/models" - - "xorm.io/xorm" - - "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/components/securejsondata" + "github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/util/errutil" + "xorm.io/xorm" ) -func init() { - bus.AddHandler("sql", GetDataSources) - bus.AddHandler("sql", GetDataSourcesByType) - bus.AddHandler("sql", GetDataSource) - bus.AddHandler("sql", AddDataSource) - bus.AddHandler("sql", DeleteDataSource) - bus.AddHandler("sql", UpdateDataSource) - bus.AddHandler("sql", GetDefaultDataSource) -} - -// GetDataSource returns a datasource by org_id and either uid (preferred), id, or name. -// Zero values (0, or "") should be used for the parameters that will not be queried. -func (ss *SQLStore) GetDataSource(uid string, id int64, name string, orgID int64) (*models.DataSource, error) { - query := &models.GetDataSourceQuery{ - Id: id, - Uid: uid, - Name: name, - OrgId: orgID, - } - - if err := GetDataSource(query); err != nil { - return nil, err - } - - return query.Result, nil -} - // GetDataSource adds a datasource to the query model by querying by org_id as well as // either uid (preferred), id, or name and is added to the bus. -func GetDataSource(query *models.GetDataSourceQuery) error { +func (ss *SQLStore) GetDataSource(query *models.GetDataSourceQuery) error { metrics.MDBDataSourceQueryByID.Inc() if query.OrgId == 0 || (query.Id == 0 && len(query.Name) == 0 && len(query.Uid) == 0) { return models.ErrDataSourceIdentifierNotSet @@ -67,7 +35,7 @@ func GetDataSource(query *models.GetDataSourceQuery) error { return nil } -func GetDataSources(query *models.GetDataSourcesQuery) error { +func (ss *SQLStore) GetDataSources(query *models.GetDataSourcesQuery) error { var sess *xorm.Session if query.DataSourceLimit <= 0 { sess = x.Where("org_id=?", query.OrgId).Asc("name") @@ -80,7 +48,7 @@ func GetDataSources(query *models.GetDataSourcesQuery) error { } // GetDataSourcesByType returns all datasources for a given type or an error if the specified type is an empty string -func GetDataSourcesByType(query *models.GetDataSourcesByTypeQuery) error { +func (ss *SQLStore) GetDataSourcesByType(query *models.GetDataSourcesByTypeQuery) error { if query.Type == "" { return fmt.Errorf("datasource type cannot be empty") } @@ -90,7 +58,7 @@ func GetDataSourcesByType(query *models.GetDataSourcesByTypeQuery) error { } // GetDefaultDataSource is used to get the default datasource of organization -func GetDefaultDataSource(query *models.GetDefaultDataSourceQuery) error { +func (ss *SQLStore) GetDefaultDataSource(query *models.GetDefaultDataSourceQuery) error { datasource := models.DataSource{} exists, err := x.Where("org_id=? AND is_default=?", query.OrgId, true).Get(&datasource) @@ -103,26 +71,9 @@ func GetDefaultDataSource(query *models.GetDefaultDataSourceQuery) error { return err } -// DeleteDataSource deletes a datasource by org_id and either uid (preferred), id, or name. -// Zero values (0, or "") should be used for the parameters that will not be queried. -func (ss *SQLStore) DeleteDataSource(uid string, id int64, name string, orgID int64) (int64, error) { - cmd := &models.DeleteDataSourceCommand{ - ID: id, - UID: uid, - Name: name, - OrgID: orgID, - } - - if err := DeleteDataSource(cmd); err != nil { - return 0, err - } - - return cmd.DeletedDatasourcesCount, nil -} - // DeleteDataSource removes a datasource by org_id as well as either uid (preferred), id, or name // and is added to the bus. -func DeleteDataSource(cmd *models.DeleteDataSourceCommand) error { +func (ss *SQLStore) DeleteDataSource(cmd *models.DeleteDataSourceCommand) error { params := make([]interface{}, 0) makeQuery := func(sql string, p ...interface{}) { @@ -159,7 +110,7 @@ func DeleteDataSource(cmd *models.DeleteDataSourceCommand) error { }) } -func AddDataSource(cmd *models.AddDataSourceCommand) error { +func (ss *SQLStore) AddDataSource(cmd *models.AddDataSourceCommand) error { return inTransaction(func(sess *DBSession) error { existing := models.DataSource{OrgId: cmd.OrgId, Name: cmd.Name} has, _ := sess.Get(&existing) @@ -195,7 +146,7 @@ func AddDataSource(cmd *models.AddDataSourceCommand) error { BasicAuthPassword: cmd.BasicAuthPassword, WithCredentials: cmd.WithCredentials, JsonData: cmd.JsonData, - SecureJsonData: securejsondata.GetEncryptedJsonData(cmd.SecureJsonData), + SecureJsonData: cmd.EncryptedSecureJsonData, Created: time.Now(), Updated: time.Now(), Version: 1, @@ -237,7 +188,7 @@ func updateIsDefaultFlag(ds *models.DataSource, sess *DBSession) error { return nil } -func UpdateDataSource(cmd *models.UpdateDataSourceCommand) error { +func (ss *SQLStore) UpdateDataSource(cmd *models.UpdateDataSourceCommand) error { return inTransaction(func(sess *DBSession) error { if cmd.JsonData == nil { cmd.JsonData = simplejson.New() @@ -259,7 +210,7 @@ func UpdateDataSource(cmd *models.UpdateDataSourceCommand) error { BasicAuthPassword: cmd.BasicAuthPassword, WithCredentials: cmd.WithCredentials, JsonData: cmd.JsonData, - SecureJsonData: securejsondata.GetEncryptedJsonData(cmd.SecureJsonData), + SecureJsonData: cmd.EncryptedSecureJsonData, Updated: time.Now(), ReadOnly: cmd.ReadOnly, Version: cmd.Version + 1, diff --git a/pkg/services/sqlstore/datasource_test.go b/pkg/services/sqlstore/datasource_test.go index f1de7de2ccc..434eb4205d6 100644 --- a/pkg/services/sqlstore/datasource_test.go +++ b/pkg/services/sqlstore/datasource_test.go @@ -33,13 +33,13 @@ func TestDataAccess(t *testing.T) { Url: "http://test", } - initDatasource := func() *models.DataSource { + initDatasource := func(sqlStore *SQLStore) *models.DataSource { cmd := defaultAddDatasourceCommand - err := AddDataSource(&cmd) + err := sqlStore.AddDataSource(&cmd) require.NoError(t, err) query := models.GetDataSourcesQuery{OrgId: 10} - err = GetDataSources(&query) + err = sqlStore.GetDataSources(&query) require.NoError(t, err) require.Equal(t, 1, len(query.Result)) @@ -48,9 +48,9 @@ func TestDataAccess(t *testing.T) { t.Run("AddDataSource", func(t *testing.T) { t.Run("Can add datasource", func(t *testing.T) { - InitTestDB(t) + sqlStore := InitTestDB(t) - err := AddDataSource(&models.AddDataSourceCommand{ + err := sqlStore.AddDataSource(&models.AddDataSourceCommand{ OrgId: 10, Name: "laban", Type: models.DS_GRAPHITE, @@ -62,7 +62,7 @@ func TestDataAccess(t *testing.T) { require.NoError(t, err) query := models.GetDataSourcesQuery{OrgId: 10} - err = GetDataSources(&query) + err = sqlStore.GetDataSources(&query) require.NoError(t, err) require.Equal(t, 1, len(query.Result)) @@ -74,26 +74,26 @@ func TestDataAccess(t *testing.T) { }) t.Run("generates uid if not specified", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) require.NotEmpty(t, ds.Uid) }) t.Run("fails to insert ds with same uid", func(t *testing.T) { - InitTestDB(t) + sqlStore := InitTestDB(t) cmd1 := defaultAddDatasourceCommand cmd2 := defaultAddDatasourceCommand cmd1.Uid = "test" cmd2.Uid = "test" - err := AddDataSource(&cmd1) + err := sqlStore.AddDataSource(&cmd1) require.NoError(t, err) - err = AddDataSource(&cmd2) + err = sqlStore.AddDataSource(&cmd2) require.Error(t, err) require.IsType(t, models.ErrDataSourceUidExists, err) }) t.Run("fires an event when the datasource is added", func(t *testing.T) { - InitTestDB(t) + sqlStore := InitTestDB(t) var created *events.DataSourceCreated bus.AddEventListener(func(e *events.DataSourceCreated) error { @@ -101,7 +101,7 @@ func TestDataAccess(t *testing.T) { return nil }) - err := AddDataSource(&defaultAddDatasourceCommand) + err := sqlStore.AddDataSource(&defaultAddDatasourceCommand) require.NoError(t, err) require.Eventually(t, func() bool { @@ -109,7 +109,7 @@ func TestDataAccess(t *testing.T) { }, time.Second, time.Millisecond) query := models.GetDataSourcesQuery{OrgId: 10} - err = GetDataSources(&query) + err = sqlStore.GetDataSources(&query) require.NoError(t, err) require.Equal(t, 1, len(query.Result)) @@ -122,34 +122,34 @@ func TestDataAccess(t *testing.T) { t.Run("UpdateDataSource", func(t *testing.T) { t.Run("updates datasource with version", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) cmd := defaultUpdateDatasourceCommand cmd.Id = ds.Id cmd.Version = ds.Version - err := UpdateDataSource(&cmd) + err := sqlStore.UpdateDataSource(&cmd) require.NoError(t, err) }) t.Run("does not overwrite Uid if not specified", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) require.NotEmpty(t, ds.Uid) cmd := defaultUpdateDatasourceCommand cmd.Id = ds.Id - err := UpdateDataSource(&cmd) + err := sqlStore.UpdateDataSource(&cmd) require.NoError(t, err) query := models.GetDataSourceQuery{Id: ds.Id, OrgId: 10} - err = GetDataSource(&query) + err = sqlStore.GetDataSource(&query) require.NoError(t, err) require.Equal(t, ds.Uid, query.Result.Uid) }) t.Run("prevents update if version changed", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) cmd := models.UpdateDataSourceCommand{ Id: ds.Id, @@ -163,16 +163,16 @@ func TestDataAccess(t *testing.T) { // Make a copy as UpdateDataSource modifies it cmd2 := cmd - err := UpdateDataSource(&cmd) + err := sqlStore.UpdateDataSource(&cmd) require.NoError(t, err) - err = UpdateDataSource(&cmd2) + err = sqlStore.UpdateDataSource(&cmd2) require.Error(t, err) }) t.Run("updates ds without version specified", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) cmd := &models.UpdateDataSourceCommand{ Id: ds.Id, @@ -183,13 +183,13 @@ func TestDataAccess(t *testing.T) { Url: "http://test", } - err := UpdateDataSource(cmd) + err := sqlStore.UpdateDataSource(cmd) require.NoError(t, err) }) t.Run("updates ds without higher version", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) cmd := &models.UpdateDataSourceCommand{ Id: ds.Id, @@ -201,34 +201,34 @@ func TestDataAccess(t *testing.T) { Version: 90000, } - err := UpdateDataSource(cmd) + err := sqlStore.UpdateDataSource(cmd) require.NoError(t, err) }) }) t.Run("DeleteDataSourceById", func(t *testing.T) { t.Run("can delete datasource", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) - err := DeleteDataSource(&models.DeleteDataSourceCommand{ID: ds.Id, OrgID: ds.OrgId}) + err := sqlStore.DeleteDataSource(&models.DeleteDataSourceCommand{ID: ds.Id, OrgID: ds.OrgId}) require.NoError(t, err) query := models.GetDataSourcesQuery{OrgId: 10} - err = GetDataSources(&query) + err = sqlStore.GetDataSources(&query) require.NoError(t, err) require.Equal(t, 0, len(query.Result)) }) t.Run("Can not delete datasource with wrong orgId", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) - err := DeleteDataSource(&models.DeleteDataSourceCommand{ID: ds.Id, OrgID: 123123}) + err := sqlStore.DeleteDataSource(&models.DeleteDataSourceCommand{ID: ds.Id, OrgID: 123123}) require.NoError(t, err) query := models.GetDataSourcesQuery{OrgId: 10} - err = GetDataSources(&query) + err = sqlStore.GetDataSources(&query) require.NoError(t, err) require.Equal(t, 1, len(query.Result)) @@ -236,8 +236,8 @@ func TestDataAccess(t *testing.T) { }) t.Run("fires an event when the datasource is deleted", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) var deleted *events.DataSourceDeleted bus.AddEventListener(func(e *events.DataSourceDeleted) error { @@ -245,7 +245,7 @@ func TestDataAccess(t *testing.T) { return nil }) - err := DeleteDataSource(&models.DeleteDataSourceCommand{ID: ds.Id, UID: "nisse-uid", Name: "nisse", OrgID: 123123}) + err := sqlStore.DeleteDataSource(&models.DeleteDataSourceCommand{ID: ds.Id, UID: "nisse-uid", Name: "nisse", OrgID: 123123}) require.NoError(t, err) require.Eventually(t, func() bool { @@ -259,14 +259,14 @@ func TestDataAccess(t *testing.T) { }) t.Run("DeleteDataSourceByName", func(t *testing.T) { - InitTestDB(t) - ds := initDatasource() + sqlStore := InitTestDB(t) + ds := initDatasource(sqlStore) query := models.GetDataSourcesQuery{OrgId: 10} - err := DeleteDataSource(&models.DeleteDataSourceCommand{Name: ds.Name, OrgID: ds.OrgId}) + err := sqlStore.DeleteDataSource(&models.DeleteDataSourceCommand{Name: ds.Name, OrgID: ds.OrgId}) require.NoError(t, err) - err = GetDataSources(&query) + err = sqlStore.GetDataSources(&query) require.NoError(t, err) require.Equal(t, 0, len(query.Result)) @@ -274,10 +274,10 @@ func TestDataAccess(t *testing.T) { t.Run("GetDataSources", func(t *testing.T) { t.Run("Number of data sources returned limited to 6 per organization", func(t *testing.T) { - InitTestDB(t) + sqlStore := InitTestDB(t) datasourceLimit := 6 for i := 0; i < datasourceLimit+1; i++ { - err := AddDataSource(&models.AddDataSourceCommand{ + err := sqlStore.AddDataSource(&models.AddDataSourceCommand{ OrgId: 10, Name: "laban" + strconv.Itoa(i), Type: models.DS_GRAPHITE, @@ -290,17 +290,17 @@ func TestDataAccess(t *testing.T) { } query := models.GetDataSourcesQuery{OrgId: 10, DataSourceLimit: datasourceLimit} - err := GetDataSources(&query) + err := sqlStore.GetDataSources(&query) require.NoError(t, err) require.Equal(t, datasourceLimit, len(query.Result)) }) t.Run("No limit should be applied on the returned data sources if the limit is not set", func(t *testing.T) { - InitTestDB(t) + sqlStore := InitTestDB(t) numberOfDatasource := 5100 for i := 0; i < numberOfDatasource; i++ { - err := AddDataSource(&models.AddDataSourceCommand{ + err := sqlStore.AddDataSource(&models.AddDataSourceCommand{ OrgId: 10, Name: "laban" + strconv.Itoa(i), Type: models.DS_GRAPHITE, @@ -313,17 +313,17 @@ func TestDataAccess(t *testing.T) { } query := models.GetDataSourcesQuery{OrgId: 10} - err := GetDataSources(&query) + err := sqlStore.GetDataSources(&query) require.NoError(t, err) require.Equal(t, numberOfDatasource, len(query.Result)) }) t.Run("No limit should be applied on the returned data sources if the limit is negative", func(t *testing.T) { - InitTestDB(t) + sqlStore := InitTestDB(t) numberOfDatasource := 5100 for i := 0; i < numberOfDatasource; i++ { - err := AddDataSource(&models.AddDataSourceCommand{ + err := sqlStore.AddDataSource(&models.AddDataSourceCommand{ OrgId: 10, Name: "laban" + strconv.Itoa(i), Type: models.DS_GRAPHITE, @@ -336,7 +336,7 @@ func TestDataAccess(t *testing.T) { } query := models.GetDataSourcesQuery{OrgId: 10, DataSourceLimit: -1} - err := GetDataSources(&query) + err := sqlStore.GetDataSources(&query) require.NoError(t, err) require.Equal(t, numberOfDatasource, len(query.Result)) @@ -345,9 +345,9 @@ func TestDataAccess(t *testing.T) { t.Run("GetDataSourcesByType", func(t *testing.T) { t.Run("Only returns datasources of specified type", func(t *testing.T) { - InitTestDB(t) + sqlStore := InitTestDB(t) - err := AddDataSource(&models.AddDataSourceCommand{ + err := sqlStore.AddDataSource(&models.AddDataSourceCommand{ OrgId: 10, Name: "Elasticsearch", Type: models.DS_ES, @@ -358,7 +358,7 @@ func TestDataAccess(t *testing.T) { }) require.NoError(t, err) - err = AddDataSource(&models.AddDataSourceCommand{ + err = sqlStore.AddDataSource(&models.AddDataSourceCommand{ OrgId: 10, Name: "Graphite", Type: models.DS_GRAPHITE, @@ -371,16 +371,18 @@ func TestDataAccess(t *testing.T) { query := models.GetDataSourcesByTypeQuery{Type: models.DS_ES} - err = GetDataSourcesByType(&query) + err = sqlStore.GetDataSourcesByType(&query) require.NoError(t, err) require.Equal(t, 1, len(query.Result)) }) t.Run("Returns an error if no type specified", func(t *testing.T) { + sqlStore := InitTestDB(t) + query := models.GetDataSourcesByTypeQuery{} - err := GetDataSourcesByType(&query) + err := sqlStore.GetDataSourcesByType(&query) require.Error(t, err) }) @@ -391,6 +393,8 @@ func TestGetDefaultDataSource(t *testing.T) { InitTestDB(t) t.Run("should return error if there is no default datasource", func(t *testing.T) { + sqlStore := InitTestDB(t) + cmd := models.AddDataSourceCommand{ OrgId: 10, Name: "nisse", @@ -399,16 +403,18 @@ func TestGetDefaultDataSource(t *testing.T) { Url: "http://test", } - err := AddDataSource(&cmd) + err := sqlStore.AddDataSource(&cmd) require.NoError(t, err) query := models.GetDefaultDataSourceQuery{OrgId: 10} - err = GetDefaultDataSource(&query) + err = sqlStore.GetDefaultDataSource(&query) require.Error(t, err) assert.True(t, errors.Is(err, models.ErrDataSourceNotFound)) }) t.Run("should return default datasource if exists", func(t *testing.T) { + sqlStore := InitTestDB(t) + cmd := models.AddDataSourceCommand{ OrgId: 10, Name: "default datasource", @@ -418,18 +424,19 @@ func TestGetDefaultDataSource(t *testing.T) { IsDefault: true, } - err := AddDataSource(&cmd) + err := sqlStore.AddDataSource(&cmd) require.NoError(t, err) query := models.GetDefaultDataSourceQuery{OrgId: 10} - err = GetDefaultDataSource(&query) + err = sqlStore.GetDefaultDataSource(&query) require.NoError(t, err) assert.Equal(t, "default datasource", query.Result.Name) }) t.Run("should not return default datasource of other organisation", func(t *testing.T) { + sqlStore := InitTestDB(t) query := models.GetDefaultDataSourceQuery{OrgId: 1} - err := GetDefaultDataSource(&query) + err := sqlStore.GetDefaultDataSource(&query) require.Error(t, err) assert.True(t, errors.Is(err, models.ErrDataSourceNotFound)) }) diff --git a/pkg/services/sqlstore/migrations/ualert/channel.go b/pkg/services/sqlstore/migrations/ualert/channel.go index e75b3ec8df5..333fc3ff2df 100644 --- a/pkg/services/sqlstore/migrations/ualert/channel.go +++ b/pkg/services/sqlstore/migrations/ualert/channel.go @@ -8,23 +8,21 @@ import ( "sort" "strings" - "github.com/prometheus/alertmanager/pkg/labels" - - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/util" + "github.com/prometheus/alertmanager/pkg/labels" ) type notificationChannel struct { - ID int64 `xorm:"id"` - OrgID int64 `xorm:"org_id"` - Uid string `xorm:"uid"` - Name string `xorm:"name"` - Type string `xorm:"type"` - DisableResolveMessage bool `xorm:"disable_resolve_message"` - IsDefault bool `xorm:"is_default"` - Settings *simplejson.Json `xorm:"settings"` - SecureSettings securejsondata.SecureJsonData `xorm:"secure_settings"` + ID int64 `xorm:"id"` + OrgID int64 `xorm:"org_id"` + Uid string `xorm:"uid"` + Name string `xorm:"name"` + Type string `xorm:"type"` + DisableResolveMessage bool `xorm:"disable_resolve_message"` + IsDefault bool `xorm:"is_default"` + Settings *simplejson.Json `xorm:"settings"` + SecureSettings SecureJsonData `xorm:"secure_settings"` } // channelsPerOrg maps notification channels per organisation @@ -332,7 +330,7 @@ func (m *migration) generateChannelUID() (string, bool) { // Some settings were migrated from settings to secure settings in between. // See https://grafana.com/docs/grafana/latest/installation/upgrading/#ensure-encryption-of-existing-alert-notification-channel-secrets. // migrateSettingsToSecureSettings takes care of that. -func migrateSettingsToSecureSettings(chanType string, settings *simplejson.Json, secureSettings securejsondata.SecureJsonData) (*simplejson.Json, map[string]string, error) { +func migrateSettingsToSecureSettings(chanType string, settings *simplejson.Json, secureSettings SecureJsonData) (*simplejson.Json, map[string]string, error) { keys := []string{} switch chanType { case "slack": @@ -413,7 +411,7 @@ type amConfigsPerOrg = map[int64]*PostableUserConfig func (c *PostableUserConfig) EncryptSecureSettings() error { for _, r := range c.AlertmanagerConfig.Receivers { for _, gr := range r.GrafanaManagedReceivers { - encryptedData := securejsondata.GetEncryptedJsonData(gr.SecureSettings) + encryptedData := GetEncryptedJsonData(gr.SecureSettings) for k, v := range encryptedData { gr.SecureSettings[k] = base64.StdEncoding.EncodeToString(v) } diff --git a/pkg/components/securejsondata/securejsondata.go b/pkg/services/sqlstore/migrations/ualert/securejsondata.go similarity index 98% rename from pkg/components/securejsondata/securejsondata.go rename to pkg/services/sqlstore/migrations/ualert/securejsondata.go index 015290a6624..119a44f3d50 100644 --- a/pkg/components/securejsondata/securejsondata.go +++ b/pkg/services/sqlstore/migrations/ualert/securejsondata.go @@ -1,4 +1,4 @@ -package securejsondata +package ualert import ( "github.com/grafana/grafana/pkg/infra/log" diff --git a/pkg/services/sqlstore/plugin_setting.go b/pkg/services/sqlstore/plugin_setting.go index 21777ee9080..09998a48973 100644 --- a/pkg/services/sqlstore/plugin_setting.go +++ b/pkg/services/sqlstore/plugin_setting.go @@ -3,18 +3,9 @@ package sqlstore import ( "time" - "github.com/grafana/grafana/pkg/components/securejsondata" - - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/models" ) -func init() { - bus.AddHandler("sql", GetPluginSettingById) - bus.AddHandler("sql", UpdatePluginSetting) - bus.AddHandler("sql", UpdatePluginSettingVersion) -} - func (ss *SQLStore) GetPluginSettings(orgID int64) ([]*models.PluginSettingInfoDTO, error) { sql := `SELECT org_id, plugin_id, enabled, pinned, plugin_version FROM plugin_setting ` @@ -33,7 +24,7 @@ func (ss *SQLStore) GetPluginSettings(orgID int64) ([]*models.PluginSettingInfoD return rslt, nil } -func GetPluginSettingById(query *models.GetPluginSettingByIdQuery) error { +func (ss *SQLStore) GetPluginSettingById(query *models.GetPluginSettingByIdQuery) error { pluginSetting := models.PluginSetting{OrgId: query.OrgId, PluginId: query.PluginId} has, err := x.Get(&pluginSetting) if err != nil { @@ -45,9 +36,7 @@ func GetPluginSettingById(query *models.GetPluginSettingByIdQuery) error { return nil } -func UpdatePluginSetting(cmd *models.UpdatePluginSettingCmd) error { - encryptedJsonData := securejsondata.GetEncryptedJsonData(cmd.SecureJsonData) - +func (ss *SQLStore) UpdatePluginSetting(cmd *models.UpdatePluginSettingCmd) error { return inTransaction(func(sess *DBSession) error { var pluginSetting models.PluginSetting @@ -65,7 +54,7 @@ func UpdatePluginSetting(cmd *models.UpdatePluginSettingCmd) error { Pinned: cmd.Pinned, JsonData: cmd.JsonData, PluginVersion: cmd.PluginVersion, - SecureJsonData: encryptedJsonData, + SecureJsonData: cmd.EncryptedSecureJsonData, Created: time.Now(), Updated: time.Now(), } @@ -81,7 +70,7 @@ func UpdatePluginSetting(cmd *models.UpdatePluginSettingCmd) error { return err } - for key, encryptedData := range encryptedJsonData { + for key, encryptedData := range cmd.EncryptedSecureJsonData { pluginSetting.SecureJsonData[key] = encryptedData } @@ -105,7 +94,7 @@ func UpdatePluginSetting(cmd *models.UpdatePluginSettingCmd) error { }) } -func UpdatePluginSettingVersion(cmd *models.UpdatePluginSettingVersionCmd) error { +func (ss *SQLStore) UpdatePluginSettingVersion(cmd *models.UpdatePluginSettingVersionCmd) error { return inTransaction(func(sess *DBSession) error { _, err := sess.Exec("UPDATE plugin_setting SET plugin_version=? WHERE org_id=? AND plugin_id=?", cmd.PluginVersion, cmd.OrgId, cmd.PluginId) return err diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go index 2874525c236..1a454db205f 100644 --- a/pkg/tests/api/alerting/api_notification_channel_test.go +++ b/pkg/tests/api/alerting/api_notification_channel_test.go @@ -1727,7 +1727,7 @@ var expEmailNotifications = []*models.SendEmailCommandSync{ "Message": "", "Status": "firing", "Alerts": channels.ExtendedAlerts{ - { + channels.ExtendedAlert{ Status: "firing", Labels: template.KV{"alertname": "EmailAlert"}, Annotations: template.KV{}, diff --git a/pkg/tsdb/cloudmonitoring/cloudmonitoring.go b/pkg/tsdb/cloudmonitoring/cloudmonitoring.go index d6f00c73f0c..f79b1270548 100644 --- a/pkg/tsdb/cloudmonitoring/cloudmonitoring.go +++ b/pkg/tsdb/cloudmonitoring/cloudmonitoring.go @@ -16,7 +16,7 @@ import ( "strings" "time" - "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/api/pluginproxy" @@ -24,6 +24,8 @@ import ( "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/setting" "golang.org/x/oauth2/google" ) @@ -66,11 +68,13 @@ const ( perSeriesAlignerDefault string = "ALIGN_MEAN" ) -func ProvideService(cfg *setting.Cfg, pluginManager plugins.Manager, httpClientProvider httpclient.Provider) *Service { +func ProvideService(cfg *setting.Cfg, pluginManager plugins.Manager, httpClientProvider httpclient.Provider, + dsService *datasources.Service) *Service { return &Service{ PluginManager: pluginManager, HTTPClientProvider: httpClientProvider, Cfg: cfg, + dsService: dsService, } } @@ -78,29 +82,32 @@ type Service struct { PluginManager plugins.Manager HTTPClientProvider httpclient.Provider Cfg *setting.Cfg + dsService *datasources.Service } // Executor executes queries for the CloudMonitoring datasource. type Executor struct { - httpClient *http.Client - dsInfo *models.DataSource - pluginManager plugins.Manager - cfg *setting.Cfg + httpClient *http.Client + dsInfo *models.DataSource + pluginManager plugins.Manager + encryptionService encryption.Service + cfg *setting.Cfg } // NewExecutor returns an Executor. //nolint: staticcheck // plugins.DataPlugin deprecated func (s *Service) NewExecutor(dsInfo *models.DataSource) (plugins.DataPlugin, error) { - httpClient, err := dsInfo.GetHTTPClient(s.HTTPClientProvider) + httpClient, err := s.dsService.GetHTTPClient(dsInfo, s.HTTPClientProvider) if err != nil { return nil, err } return &Executor{ - httpClient: httpClient, - dsInfo: dsInfo, - pluginManager: s.PluginManager, - cfg: s.Cfg, + httpClient: httpClient, + dsInfo: dsInfo, + pluginManager: s.PluginManager, + encryptionService: s.dsService.EncryptionService, + cfg: s.Cfg, }, nil } @@ -548,7 +555,7 @@ func (e *Executor) createRequest(ctx context.Context, dsInfo *models.DataSource, } } - pluginproxy.ApplyRoute(ctx, req, proxyPass, cloudMonitoringRoute, dsInfo, e.cfg) + pluginproxy.ApplyRoute(ctx, req, proxyPass, cloudMonitoringRoute, dsInfo, e.cfg, e.encryptionService) return req, nil } diff --git a/pkg/tsdb/data_plugin_adapter.go b/pkg/tsdb/data_plugin_adapter.go index 0dd7913735f..ea67719f3b8 100644 --- a/pkg/tsdb/data_plugin_adapter.go +++ b/pkg/tsdb/data_plugin_adapter.go @@ -9,17 +9,33 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/adapters" + "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/oauthtoken" ) // nolint:staticcheck // plugins.DataQuery deprecated -func dataPluginQueryAdapter(pluginID string, handler backend.QueryDataHandler, oAuthService oauthtoken.OAuthTokenService) plugins.DataPluginFunc { - return plugins.DataPluginFunc(func(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (plugins.DataResponse, error) { - instanceSettings, err := modelToInstanceSettings(ds) +func dataPluginQueryAdapter(pluginID string, handler backend.QueryDataHandler, oAuthService oauthtoken.OAuthTokenService, + dsService *datasources.Service) plugins.DataPluginFunc { + return func(ctx context.Context, ds *models.DataSource, query plugins.DataQuery) (plugins.DataResponse, error) { + jsonDataBytes, err := ds.JsonData.MarshalJSON() if err != nil { return plugins.DataResponse{}, err } + instanceSettings := &backend.DataSourceInstanceSettings{ + ID: ds.Id, + Name: ds.Name, + URL: ds.Url, + Database: ds.Database, + User: ds.User, + BasicAuthEnabled: ds.BasicAuth, + BasicAuthUser: ds.BasicAuthUser, + JSONData: jsonDataBytes, + DecryptedSecureJSONData: dsService.DecryptedValues(ds), + Updated: ds.Updated, + UID: ds.Uid, + } + if query.Headers == nil { query.Headers = make(map[string]string) } @@ -90,26 +106,5 @@ func dataPluginQueryAdapter(pluginID string, handler backend.QueryDataHandler, o } return tR, nil - }) -} - -func modelToInstanceSettings(ds *models.DataSource) (*backend.DataSourceInstanceSettings, error) { - jsonDataBytes, err := ds.JsonData.MarshalJSON() - if err != nil { - return nil, err } - - return &backend.DataSourceInstanceSettings{ - ID: ds.Id, - Name: ds.Name, - URL: ds.Url, - Database: ds.Database, - User: ds.User, - BasicAuthEnabled: ds.BasicAuth, - BasicAuthUser: ds.BasicAuthUser, - JSONData: jsonDataBytes, - DecryptedSecureJSONData: ds.DecryptedValues(), - Updated: ds.Updated, - UID: ds.Uid, - }, nil } diff --git a/pkg/tsdb/grafanads/grafana.go b/pkg/tsdb/grafanads/grafana.go index 4c0a866e46d..0e2e74a2e0d 100644 --- a/pkg/tsdb/grafanads/grafana.go +++ b/pkg/tsdb/grafanads/grafana.go @@ -8,7 +8,6 @@ import ( "path/filepath" "strings" - "github.com/grafana/grafana/pkg/components/securejsondata" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" @@ -86,7 +85,7 @@ func DataSourceModel(orgId int64) *models.DataSource { Type: "grafana", OrgId: orgId, JsonData: simplejson.New(), - SecureJsonData: make(securejsondata.SecureJsonData), + SecureJsonData: make(map[string][]byte), } } diff --git a/pkg/tsdb/mssql/mssql.go b/pkg/tsdb/mssql/mssql.go index ead5ff3ed71..f769d32c5e4 100644 --- a/pkg/tsdb/mssql/mssql.go +++ b/pkg/tsdb/mssql/mssql.go @@ -10,19 +10,18 @@ import ( "strconv" "strings" + mssql "github.com/denisenkom/go-mssqldb" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" - - mssql "github.com/denisenkom/go-mssqldb" - "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/util" ) var logger = log.New("tsdb.mssql") diff --git a/pkg/tsdb/mysql/mysql.go b/pkg/tsdb/mysql/mysql.go index 531a97f6a9f..5b065c18a52 100644 --- a/pkg/tsdb/mysql/mysql.go +++ b/pkg/tsdb/mysql/mysql.go @@ -12,18 +12,17 @@ import ( "time" "github.com/VividCortex/mysqlerr" + "github.com/go-sql-driver/mysql" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/datasource" "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" "github.com/grafana/grafana/pkg/infra/httpclient" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/setting" - - "github.com/go-sql-driver/mysql" - "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/tsdb/sqleng" ) diff --git a/pkg/tsdb/postgres/postgres.go b/pkg/tsdb/postgres/postgres.go index 16af7c15ad8..f700710244e 100644 --- a/pkg/tsdb/postgres/postgres.go +++ b/pkg/tsdb/postgres/postgres.go @@ -13,13 +13,12 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util/errutil" - - "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/tsdb/sqleng" + "github.com/grafana/grafana/pkg/util/errutil" ) var logger = log.New("tsdb.postgres") diff --git a/pkg/tsdb/service.go b/pkg/tsdb/service.go index cdabac43b3c..64fca330ba5 100644 --- a/pkg/tsdb/service.go +++ b/pkg/tsdb/service.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" + "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/cloudmonitoring" @@ -19,9 +20,10 @@ func NewService( pluginManager plugins.Manager, backendPluginManager backendplugin.Manager, oauthTokenService *oauthtoken.Service, + dataSourcesService *datasources.Service, cloudMonitoringService *cloudmonitoring.Service, ) *Service { - s := newService(cfg, pluginManager, backendPluginManager, oauthTokenService) + s := newService(cfg, pluginManager, backendPluginManager, oauthTokenService, dataSourcesService) // register backend data sources using legacy plugin // contracts/non-SDK contracts @@ -31,14 +33,15 @@ func NewService( } func newService(cfg *setting.Cfg, manager plugins.Manager, backendPluginManager backendplugin.Manager, - oauthTokenService oauthtoken.OAuthTokenService) *Service { + oauthTokenService oauthtoken.OAuthTokenService, dataSourcesService *datasources.Service) *Service { return &Service{ Cfg: cfg, PluginManager: manager, BackendPluginManager: backendPluginManager, // nolint:staticcheck // plugins.DataPlugin deprecated - registry: map[string]func(*models.DataSource) (plugins.DataPlugin, error){}, - OAuthTokenService: oauthTokenService, + registry: map[string]func(*models.DataSource) (plugins.DataPlugin, error){}, + OAuthTokenService: oauthTokenService, + DataSourcesService: dataSourcesService, } } @@ -48,6 +51,7 @@ type Service struct { PluginManager plugins.Manager BackendPluginManager backendplugin.Manager OAuthTokenService oauthtoken.OAuthTokenService + DataSourcesService *datasources.Service //nolint: staticcheck // plugins.DataPlugin deprecated registry map[string]func(*models.DataSource) (plugins.DataPlugin, error) } @@ -65,7 +69,8 @@ func (s *Service) HandleRequest(ctx context.Context, ds *models.DataSource, quer return plugin.DataQuery(ctx, ds, query) } - return dataPluginQueryAdapter(ds.Type, s.BackendPluginManager, s.OAuthTokenService).DataQuery(ctx, ds, query) + return dataPluginQueryAdapter(ds.Type, s.BackendPluginManager, s.OAuthTokenService, s.DataSourcesService). + DataQuery(ctx, ds, query) } // RegisterQueryHandler registers a query handler factory. diff --git a/pkg/tsdb/service_test.go b/pkg/tsdb/service_test.go index b8adf85f04f..5c725257839 100644 --- a/pkg/tsdb/service_test.go +++ b/pkg/tsdb/service_test.go @@ -5,11 +5,14 @@ import ( "testing" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/manager" + "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/encryption/ossencryption" "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/require" "golang.org/x/oauth2" @@ -142,8 +145,9 @@ func createService() (*Service, *fakeExecutor, *fakeBackendPM) { manager := &manager.PluginManager{ BackendPluginManager: fakeBackendPM, } + dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService()) - s := newService(setting.NewCfg(), manager, fakeBackendPM, &fakeOAuthTokenService{}) + s := newService(setting.NewCfg(), manager, fakeBackendPM, &fakeOAuthTokenService{}, dsService) e := &fakeExecutor{ //nolint: staticcheck // plugins.DataPlugin deprecated results: make(map[string]plugins.DataQueryResult), diff --git a/pkg/util/encryption.go b/pkg/util/encryption.go index f60b89fd7ab..a494e93039f 100644 --- a/pkg/util/encryption.go +++ b/pkg/util/encryption.go @@ -17,7 +17,7 @@ const saltLength = 8 // Decrypt decrypts a payload with a given secret. // Deprecated. Do not use it. // Use encryption.Service instead. -var Decrypt = func(payload []byte, secret string) ([]byte, error) { +func Decrypt(payload []byte, secret string) ([]byte, error) { if len(payload) < saltLength { return nil, fmt.Errorf("unable to compute salt") } @@ -51,7 +51,7 @@ var Decrypt = func(payload []byte, secret string) ([]byte, error) { // Encrypt encrypts a payload with a given secret. // Deprecated. Do not use it. // Use encryption.Service instead. -var Encrypt = func(payload []byte, secret string) ([]byte, error) { +func Encrypt(payload []byte, secret string) ([]byte, error) { salt, err := GetRandomString(saltLength) if err != nil { return nil, err