Encryption: Refactor securejsondata.SecureJsonData to stop relying on global functions (#38865)

* Encryption: Add support to encrypt/decrypt sjd

* Add datasources.Service as a proxy to datasources db operations

* Encrypt ds.SecureJsonData before calling SQLStore

* Move ds cache code into ds service

* Fix tlsmanager tests

* Fix pluginproxy tests

* Remove some securejsondata.GetEncryptedJsonData usages

* Add pluginsettings.Service as a proxy for plugin settings db operations

* Add AlertNotificationService as a proxy for alert notification db operations

* Remove some securejsondata.GetEncryptedJsonData usages

* Remove more securejsondata.GetEncryptedJsonData usages

* Fix lint errors

* Minor fixes

* Remove encryption global functions usages from ngalert

* Fix lint errors

* Minor fixes

* Minor fixes

* Remove securejsondata.DecryptedValue usage

* Refactor the refactor

* Remove securejsondata.DecryptedValue usage

* Move securejsondata to migrations package

* Move securejsondata to migrations package

* Minor fix

* Fix integration test

* Fix integration tests

* Undo undesired changes

* Fix tests

* Add context.Context into encryption methods

* Fix tests

* Fix tests

* Fix tests

* Trigger CI

* Fix test

* Add names to params of encryption service interface

* Remove bus from CacheServiceImpl

* Add logging

* Add keys to logger

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>

* Add missing key to logger

Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>

* Undo changes in markdown files

* Fix formatting

* Add context to secrets service

* Rename decryptSecureJsonData to decryptSecureJsonDataFn

* Name args in GetDecryptedValueFn

* Add template back to NewAlertmanagerNotifier

* Copy GetDecryptedValueFn to ngalert

* Add logging to pluginsettings

* Fix pluginsettings test

Co-authored-by: Tania B <yalyna.ts@gmail.com>
Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
pull/40153/head
Joan López de la Franca Beltran 4 years ago committed by GitHub
parent da813877fb
commit 722c414fef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      pkg/api/alerting.go
  2. 4
      pkg/api/api.go
  3. 2
      pkg/api/app_routes.go
  4. 41
      pkg/api/datasources.go
  5. 9
      pkg/api/frontendsettings.go
  6. 5
      pkg/api/http_server.go
  7. 4
      pkg/api/login.go
  8. 6
      pkg/api/login_test.go
  9. 11
      pkg/api/pluginproxy/ds_auth_provider.go
  10. 57
      pkg/api/pluginproxy/ds_proxy.go
  11. 123
      pkg/api/pluginproxy/ds_proxy_test.go
  12. 11
      pkg/api/pluginproxy/pluginproxy.go
  13. 69
      pkg/api/pluginproxy/pluginproxy_test.go
  14. 29
      pkg/cmd/grafana-cli/commands/datamigrations/encrypt_datasource_passwords_test.go
  15. 43
      pkg/models/alert_notifications.go
  16. 65
      pkg/models/datasource.go
  17. 23
      pkg/models/plugin_setting_cache.go
  18. 70
      pkg/models/plugin_setting_cache_test.go
  19. 13
      pkg/models/plugin_settings.go
  20. 5
      pkg/plugins/adapters/adapters.go
  21. 45
      pkg/plugins/plugincontext/plugincontext.go
  22. 3
      pkg/server/backgroundsvcs/background_services.go
  23. 5
      pkg/server/wire.go
  24. 6
      pkg/services/alerting/engine.go
  25. 4
      pkg/services/alerting/engine_integration_test.go
  26. 3
      pkg/services/alerting/engine_test.go
  27. 8
      pkg/services/alerting/extractor_test.go
  28. 16
      pkg/services/alerting/notifier.go
  29. 4
      pkg/services/alerting/notifier_test.go
  30. 5
      pkg/services/alerting/notifiers/alertmanager.go
  31. 13
      pkg/services/alerting/notifiers/alertmanager_test.go
  32. 6
      pkg/services/alerting/notifiers/base_test.go
  33. 2
      pkg/services/alerting/notifiers/dingding.go
  34. 8
      pkg/services/alerting/notifiers/dingding_test.go
  35. 2
      pkg/services/alerting/notifiers/discord.go
  36. 5
      pkg/services/alerting/notifiers/discord_test.go
  37. 2
      pkg/services/alerting/notifiers/email.go
  38. 7
      pkg/services/alerting/notifiers/email_test.go
  39. 2
      pkg/services/alerting/notifiers/googlechat.go
  40. 5
      pkg/services/alerting/notifiers/googlechat_test.go
  41. 2
      pkg/services/alerting/notifiers/hipchat.go
  42. 7
      pkg/services/alerting/notifiers/hipchat_test.go
  43. 2
      pkg/services/alerting/notifiers/kafka.go
  44. 5
      pkg/services/alerting/notifiers/kafka_test.go
  45. 6
      pkg/services/alerting/notifiers/line.go
  46. 5
      pkg/services/alerting/notifiers/line_test.go
  47. 6
      pkg/services/alerting/notifiers/opsgenie.go
  48. 16
      pkg/services/alerting/notifiers/opsgenie_test.go
  49. 6
      pkg/services/alerting/notifiers/pagerduty.go
  50. 24
      pkg/services/alerting/notifiers/pagerduty_test.go
  51. 9
      pkg/services/alerting/notifiers/pushover.go
  52. 11
      pkg/services/alerting/notifiers/pushover_test.go
  53. 6
      pkg/services/alerting/notifiers/sensu.go
  54. 5
      pkg/services/alerting/notifiers/sensu_test.go
  55. 6
      pkg/services/alerting/notifiers/sensugo.go
  56. 10
      pkg/services/alerting/notifiers/sensugo_test.go
  57. 6
      pkg/services/alerting/notifiers/slack.go
  58. 32
      pkg/services/alerting/notifiers/slack_test.go
  59. 2
      pkg/services/alerting/notifiers/teams.go
  60. 7
      pkg/services/alerting/notifiers/teams_test.go
  61. 6
      pkg/services/alerting/notifiers/telegram.go
  62. 8
      pkg/services/alerting/notifiers/telegram_test.go
  63. 6
      pkg/services/alerting/notifiers/threema.go
  64. 11
      pkg/services/alerting/notifiers/threema_test.go
  65. 2
      pkg/services/alerting/notifiers/victorops.go
  66. 12
      pkg/services/alerting/notifiers/victorops_test.go
  67. 6
      pkg/services/alerting/notifiers/webhook.go
  68. 5
      pkg/services/alerting/notifiers/webhook_test.go
  69. 4
      pkg/services/alerting/result_handler.go
  70. 6
      pkg/services/alerting/rule_test.go
  71. 102
      pkg/services/alerting/service.go
  72. 56
      pkg/services/alerting/service_test.go
  73. 26
      pkg/services/alerting/test_notification.go
  74. 26
      pkg/services/dashboardsnapshots/dashboardsnapshots.go
  75. 11
      pkg/services/dashboardsnapshots/dashboardsnapshots_test.go
  76. 10
      pkg/services/datasourceproxy/datasourceproxy.go
  77. 11
      pkg/services/datasources/cache.go
  78. 271
      pkg/services/datasources/service.go
  79. 276
      pkg/services/datasources/service_test.go
  80. 11
      pkg/services/encryption/encryption.go
  81. 44
      pkg/services/encryption/ossencryption/ossencryption.go
  82. 9
      pkg/services/encryption/ossencryption/ossencryption_test.go
  83. 4
      pkg/services/login/authinfoservice/database.go
  84. 4
      pkg/services/ngalert/api/api.go
  85. 31
      pkg/services/ngalert/api/api_alertmanager.go
  86. 38
      pkg/services/ngalert/api/tooling/definitions/alertmanager.go
  87. 56
      pkg/services/ngalert/ngalert.go
  88. 29
      pkg/services/ngalert/notifier/alertmanager.go
  89. 20
      pkg/services/ngalert/notifier/alertmanager_test.go
  90. 9
      pkg/services/ngalert/notifier/channels/alertmanager.go
  91. 5
      pkg/services/ngalert/notifier/channels/alertmanager_test.go
  92. 10
      pkg/services/ngalert/notifier/channels/line.go
  93. 5
      pkg/services/ngalert/notifier/channels/line_test.go
  94. 14
      pkg/services/ngalert/notifier/channels/opsgenie.go
  95. 5
      pkg/services/ngalert/notifier/channels/opsgenie_test.go
  96. 14
      pkg/services/ngalert/notifier/channels/pagerduty.go
  97. 5
      pkg/services/ngalert/notifier/channels/pagerduty_test.go
  98. 7
      pkg/services/ngalert/notifier/channels/pushover.go
  99. 5
      pkg/services/ngalert/notifier/channels/pushover_test.go
  100. 5
      pkg/services/ngalert/notifier/channels/sensugo.go
  101. Some files were not shown because too many files have changed in this diff Show More

@ -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

@ -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)

@ -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)
}

@ -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
}
}

@ -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
}
}

@ -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 {

@ -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
}

@ -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 {

@ -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 {

@ -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) {

@ -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())

@ -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)

@ -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)

@ -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
}

@ -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
}

@ -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
}

@ -1,23 +0,0 @@
package models
var pluginSettingDecryptionCache = secureJSONDecryptionCache{
cache: make(map[int64]cachedDecryptedJSON),
}
// DecryptedValues returns cached decrypted values from secureJsonData.
func (ps *PluginSetting) DecryptedValues() map[string]string {
pluginSettingDecryptionCache.Lock()
defer pluginSettingDecryptionCache.Unlock()
if item, present := pluginSettingDecryptionCache.cache[ps.Id]; present && ps.Updated.Equal(item.updated) {
return item.json
}
json := ps.SecureJsonData.Decrypt()
pluginSettingDecryptionCache.cache[ps.Id] = cachedDecryptedJSON{
updated: ps.Updated,
json: json,
}
return json
}

@ -1,70 +0,0 @@
package models
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/securejsondata"
)
// clearPluginSettingDecryptionCache clears the datasource decryption cache.
func clearPluginSettingDecryptionCache() {
pluginSettingDecryptionCache.Lock()
defer pluginSettingDecryptionCache.Unlock()
pluginSettingDecryptionCache.cache = make(map[int64]cachedDecryptedJSON)
}
func TestPluginSettingDecryptionCache(t *testing.T) {
t.Run("When plugin settings hasn't been updated, encrypted JSON should be fetched from cache", func(t *testing.T) {
clearPluginSettingDecryptionCache()
ps := PluginSetting{
Id: 1,
JsonData: map[string]interface{}{},
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{
"password": "password",
}),
}
// Populate cache
password, ok := ps.DecryptedValues()["password"]
require.Equal(t, "password", password)
require.True(t, ok)
ps.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{
"password": "",
})
require.Equal(t, "password", password)
require.True(t, ok)
})
t.Run("When plugin settings is updated, encrypted JSON should not be fetched from cache", func(t *testing.T) {
clearPluginSettingDecryptionCache()
ps := PluginSetting{
Id: 1,
JsonData: map[string]interface{}{},
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{
"password": "password",
}),
}
// Populate cache
password, ok := ps.DecryptedValues()["password"]
require.Equal(t, "password", password)
require.True(t, ok)
ps.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{
"password": "",
})
ps.Updated = time.Now()
password, ok = ps.DecryptedValues()["password"]
require.Empty(t, password)
require.True(t, ok)
})
}

@ -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

@ -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
}

@ -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
}
}

@ -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,

@ -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(

@ -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()

@ -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{}

@ -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

@ -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")

@ -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 &notificationService{
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)

@ -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 {

@ -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),

@ -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)

@ -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) {

@ -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"}

@ -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)

@ -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()

@ -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)

@ -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)

@ -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)

@ -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"}

@ -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)

@ -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]

@ -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)

@ -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"}

@ -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)

@ -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"}
}

@ -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)

@ -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"}

@ -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)

@ -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"}

@ -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)

@ -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 {

@ -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)

@ -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

@ -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)

@ -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"}

@ -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)

@ -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",

@ -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)

@ -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"}

@ -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)

@ -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()

@ -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)

@ -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 == "" {

@ -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)

@ -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 == "" {

@ -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)

@ -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),

@ -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)

@ -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),
}
}

@ -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) {

@ -0,0 +1,102 @@
package alerting
import (
"context"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
type AlertNotificationService struct {
Bus bus.Bus
SQLStore *sqlstore.SQLStore
EncryptionService encryption.Service
}
func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService encryption.Service,
) *AlertNotificationService {
s := &AlertNotificationService{
Bus: bus,
SQLStore: store,
EncryptionService: encryptionService,
}
s.Bus.AddHandler(s.GetAlertNotifications)
s.Bus.AddHandlerCtx(s.CreateAlertNotificationCommand)
s.Bus.AddHandlerCtx(s.UpdateAlertNotification)
s.Bus.AddHandler(s.DeleteAlertNotification)
s.Bus.AddHandler(s.GetAllAlertNotifications)
s.Bus.AddHandlerCtx(s.GetOrCreateAlertNotificationState)
s.Bus.AddHandlerCtx(s.SetAlertNotificationStateToCompleteCommand)
s.Bus.AddHandlerCtx(s.SetAlertNotificationStateToPendingCommand)
s.Bus.AddHandler(s.GetAlertNotificationsWithUid)
s.Bus.AddHandler(s.UpdateAlertNotificationWithUid)
s.Bus.AddHandler(s.DeleteAlertNotificationWithUid)
s.Bus.AddHandler(s.GetAlertNotificationsWithUidToSend)
s.Bus.AddHandlerCtx(s.HandleNotificationTestCommand)
return s
}
func (s *AlertNotificationService) GetAlertNotifications(query *models.GetAlertNotificationsQuery) error {
return s.SQLStore.GetAlertNotifications(query)
}
func (s *AlertNotificationService) CreateAlertNotificationCommand(ctx context.Context, cmd *models.CreateAlertNotificationCommand) error {
var err error
cmd.EncryptedSecureSettings, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureSettings, setting.SecretKey)
if err != nil {
return err
}
return s.SQLStore.CreateAlertNotificationCommand(cmd)
}
func (s *AlertNotificationService) UpdateAlertNotification(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error {
var err error
cmd.EncryptedSecureSettings, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureSettings, setting.SecretKey)
if err != nil {
return err
}
return s.SQLStore.UpdateAlertNotification(cmd)
}
func (s *AlertNotificationService) DeleteAlertNotification(cmd *models.DeleteAlertNotificationCommand) error {
return s.SQLStore.DeleteAlertNotification(cmd)
}
func (s *AlertNotificationService) GetAllAlertNotifications(query *models.GetAllAlertNotificationsQuery) error {
return s.SQLStore.GetAllAlertNotifications(query)
}
func (s *AlertNotificationService) GetOrCreateAlertNotificationState(ctx context.Context, cmd *models.GetOrCreateNotificationStateQuery) error {
return s.SQLStore.GetOrCreateAlertNotificationState(ctx, cmd)
}
func (s *AlertNotificationService) SetAlertNotificationStateToCompleteCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToCompleteCommand) error {
return s.SQLStore.SetAlertNotificationStateToCompleteCommand(ctx, cmd)
}
func (s *AlertNotificationService) SetAlertNotificationStateToPendingCommand(ctx context.Context, cmd *models.SetAlertNotificationStateToPendingCommand) error {
return s.SQLStore.SetAlertNotificationStateToPendingCommand(ctx, cmd)
}
func (s *AlertNotificationService) GetAlertNotificationsWithUid(query *models.GetAlertNotificationsWithUidQuery) error {
return s.SQLStore.GetAlertNotificationsWithUid(query)
}
func (s *AlertNotificationService) UpdateAlertNotificationWithUid(cmd *models.UpdateAlertNotificationWithUidCommand) error {
return s.SQLStore.UpdateAlertNotificationWithUid(cmd)
}
func (s *AlertNotificationService) DeleteAlertNotificationWithUid(cmd *models.DeleteAlertNotificationWithUidCommand) error {
return s.SQLStore.DeleteAlertNotificationWithUid(cmd)
}
func (s *AlertNotificationService) GetAlertNotificationsWithUidToSend(query *models.GetAlertNotificationsWithUidToSendQuery) error {
return s.SQLStore.GetAlertNotificationsWithUidToSend(query)
}

@ -0,0 +1,56 @@
package alerting
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
sqlStore := sqlstore.InitTestDB(t)
s := ProvideService(bus.New(), sqlStore, ossencryption.ProvideService())
origSecret := setting.SecretKey
setting.SecretKey = "alert_notification_service_test"
t.Cleanup(func() {
setting.SecretKey = origSecret
})
var an *models.AlertNotification
t.Run("create alert notification should encrypt the secure json data", func(t *testing.T) {
ctx := context.Background()
ss := map[string]string{"password": "12345"}
cmd := models.CreateAlertNotificationCommand{SecureSettings: ss}
err := s.CreateAlertNotificationCommand(ctx, &cmd)
require.NoError(t, err)
an = cmd.Result
decrypted, err := s.EncryptionService.DecryptJsonData(ctx, an.SecureSettings, setting.SecretKey)
require.NoError(t, err)
require.Equal(t, ss, decrypted)
})
t.Run("update alert notification should encrypt the secure json data", func(t *testing.T) {
ctx := context.Background()
ss := map[string]string{"password": "678910"}
cmd := models.UpdateAlertNotificationCommand{Id: an.Id, Settings: simplejson.New(), SecureSettings: ss}
err := s.UpdateAlertNotification(ctx, &cmd)
require.NoError(t, err)
decrypted, err := s.EncryptionService.DecryptJsonData(ctx, cmd.Result.SecureSettings, setting.SecretKey)
require.NoError(t, err)
require.Equal(t, ss, decrypted)
})
}

@ -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

@ -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)
}

@ -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()

@ -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)

@ -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

@ -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
}
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 (ds *DataSource) GetHTTPClient(provider httpclient.Provider) (*http.Client, error) {
transport, err := ds.GetHTTPTransport(provider)
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))

@ -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")
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
tlsClientCert, err := encryptionService.Encrypt(context.Background(), []byte(clientCert), "password")
require.NoError(t, err)
tlsClientKey, err := util.Encrypt([]byte(clientKey), "password")
tlsClientKey, err := encryptionService.Encrypt(context.Background(), []byte(clientKey), "password")
require.NoError(t, err)
ds := DataSource{
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

@ -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
}

@ -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

@ -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())

@ -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
}

@ -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(

@ -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")
}

@ -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)
}

@ -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,

@ -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,

@ -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
}

@ -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{

@ -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

@ -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"}
}

@ -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())

@ -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"}

@ -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())

@ -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"}
}

@ -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())

@ -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 {

@ -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())

@ -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"}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save