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

* Encryption: Add support to encrypt/decrypt sjd

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

* Encrypt ds.SecureJsonData before calling SQLStore

* Move ds cache code into ds service

* Fix tlsmanager tests

* Fix pluginproxy tests

* Remove some securejsondata.GetEncryptedJsonData usages

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

* Add AlertNotificationService as a proxy for alert notification db operations

* Remove some securejsondata.GetEncryptedJsonData usages

* Remove more securejsondata.GetEncryptedJsonData usages

* Fix lint errors

* Minor fixes

* Remove encryption global functions usages from ngalert

* Fix lint errors

* Minor fixes

* Minor fixes

* Remove securejsondata.DecryptedValue usage

* Refactor the refactor

* Remove securejsondata.DecryptedValue usage

* Move securejsondata to migrations package

* Move securejsondata to migrations package

* Minor fix

* Fix integration test

* Fix integration tests

* Undo undesired changes

* Fix tests

* Add context.Context into encryption methods

* Fix tests

* Fix tests

* Fix tests

* Trigger CI

* Fix test

* Add names to params of encryption service interface

* Remove bus from CacheServiceImpl

* Add logging

* Add keys to logger

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

* Add missing key to logger

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

* Undo changes in markdown files

* Fix formatting

* Add context to secrets service

* Rename decryptSecureJsonData to decryptSecureJsonDataFn

* Name args in GetDecryptedValueFn

* Add template back to NewAlertmanagerNotifier

* Copy GetDecryptedValueFn to ngalert

* Add logging to pluginsettings

* Fix pluginsettings test

Co-authored-by: Tania B <yalyna.ts@gmail.com>
Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com>
This commit is contained in:
Joan López de la Franca Beltran
2021-10-07 17:33:50 +03:00
committed by GitHub
co-authored by Emil Tullstedt Tania B
parent da813877fb
commit 722c414fef
141 changed files with 1968 additions and 1197 deletions
+20 -10
View File
@@ -1,6 +1,7 @@
package api package api
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
@@ -13,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
macaron "gopkg.in/macaron.v1" 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)) 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 cmd.OrgId = c.OrgId
err := fillWithSecureSettingsData(&cmd) err := hs.fillWithSecureSettingsData(c.Req.Context(), &cmd)
if err != nil { if err != nil {
return response.Error(500, "Failed to update alert notification", err) 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)) 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.OrgId = c.OrgId
cmd.Uid = macaron.Params(c.Req)[":uid"] cmd.Uid = macaron.Params(c.Req)[":uid"]
err := fillWithSecureSettingsDataByUID(&cmd) err := hs.fillWithSecureSettingsDataByUID(c.Req.Context(), &cmd)
if err != nil { if err != nil {
return response.Error(500, "Failed to update alert notification", err) 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)) 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 { if len(cmd.SecureSettings) == 0 {
return nil return nil
} }
@@ -352,11 +354,15 @@ func fillWithSecureSettingsData(cmd *models.UpdateAlertNotificationCommand) erro
Id: cmd.Id, 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 return err
} }
secureSettings := query.Result.SecureSettings.Decrypt()
for k, v := range secureSettings { for k, v := range secureSettings {
if _, ok := cmd.SecureSettings[k]; !ok { if _, ok := cmd.SecureSettings[k]; !ok {
cmd.SecureSettings[k] = v cmd.SecureSettings[k] = v
@@ -366,7 +372,7 @@ func fillWithSecureSettingsData(cmd *models.UpdateAlertNotificationCommand) erro
return nil return nil
} }
func fillWithSecureSettingsDataByUID(cmd *models.UpdateAlertNotificationWithUidCommand) error { func (hs *HTTPServer) fillWithSecureSettingsDataByUID(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error {
if len(cmd.SecureSettings) == 0 { if len(cmd.SecureSettings) == 0 {
return nil return nil
} }
@@ -376,11 +382,15 @@ func fillWithSecureSettingsDataByUID(cmd *models.UpdateAlertNotificationWithUidC
Uid: cmd.Uid, 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 return err
} }
secureSettings := query.Result.SecureSettings.Decrypt()
for k, v := range secureSettings { for k, v := range secureSettings {
if _, ok := cmd.SecureSettings[k]; !ok { if _, ok := cmd.SecureSettings[k]; !ok {
cmd.SecureSettings[k] = v cmd.SecureSettings[k] = v
+2 -2
View File
@@ -394,11 +394,11 @@ func (hs *HTTPServer) registerRoutes() {
alertNotifications.Get("/", routing.Wrap(GetAlertNotifications)) alertNotifications.Get("/", routing.Wrap(GetAlertNotifications))
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), routing.Wrap(NotificationTest)) alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), routing.Wrap(NotificationTest))
alertNotifications.Post("/", bind(models.CreateAlertNotificationCommand{}), routing.Wrap(CreateAlertNotification)) 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.Get("/:notificationId", routing.Wrap(GetAlertNotificationByID))
alertNotifications.Delete("/:notificationId", routing.Wrap(DeleteAlertNotification)) alertNotifications.Delete("/:notificationId", routing.Wrap(DeleteAlertNotification))
alertNotifications.Get("/uid/:uid", routing.Wrap(GetAlertNotificationByUID)) 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)) alertNotifications.Delete("/uid/:uid", routing.Wrap(DeleteAlertNotificationByUID))
}, reqEditorRole) }, reqEditorRole)
+1 -1
View File
@@ -60,7 +60,7 @@ func AppPluginRoute(route *plugins.AppPluginRoute, appID string, hs *HTTPServer)
return func(c *models.ReqContext) { return func(c *models.ReqContext) {
path := macaron.Params(c.Req)["*"] 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.Transport = pluginProxyTransport
proxy.ServeHTTP(c.Resp, c.Req) proxy.ServeHTTP(c.Resp, c.Req)
} }
+28 -13
View File
@@ -1,11 +1,13 @@
package api package api
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"sort" "sort"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/api/datasource"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
@@ -13,10 +15,9 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/adapters" "github.com/grafana/grafana/pkg/plugins/adapters"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
macaron "gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
"github.com/grafana/grafana-plugin-sdk-go/backend"
) )
var datasourcesLogger = log.New("datasources") 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) 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 err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) { if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil) 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} cmd := &models.DeleteDataSourceCommand{ID: id, OrgID: c.OrgId}
err = bus.Dispatch(cmd) err = bus.DispatchCtx(c.Req.Context(), cmd)
if err != nil { if err != nil {
return response.Error(500, "Failed to delete datasource", err) 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 return resp
} }
err := fillWithSecureJSONData(&cmd) err := hs.fillWithSecureJSONData(c.Req.Context(), &cmd)
if err != nil { if err != nil {
return response.Error(500, "Failed to update datasource", err) 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 { if len(cmd.SecureJsonData) == 0 {
return nil return nil
} }
ds, err := getRawDataSourceById(cmd.Id, cmd.OrgId) ds, err := getRawDataSourceById(ctx, cmd.Id, cmd.OrgId)
if err != nil { if err != nil {
return err return err
} }
@@ -291,7 +292,11 @@ func fillWithSecureJSONData(cmd *models.UpdateDataSourceCommand) error {
return models.ErrDatasourceIsReadOnly 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 { for k, v := range secureJSONData {
if _, ok := cmd.SecureJsonData[k]; !ok { if _, ok := cmd.SecureJsonData[k]; !ok {
cmd.SecureJsonData[k] = v cmd.SecureJsonData[k] = v
@@ -301,13 +306,13 @@ func fillWithSecureJSONData(cmd *models.UpdateDataSourceCommand) error {
return nil 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{ query := models.GetDataSourceQuery{
Id: id, Id: id,
OrgId: orgID, OrgId: orgID,
} }
if err := bus.Dispatch(&query); err != nil { if err := bus.DispatchCtx(ctx, &query); err != nil {
return nil, err return nil, err
} }
@@ -381,7 +386,7 @@ func (hs *HTTPServer) CallDatasourceResource(c *models.ReqContext) {
return return
} }
dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds) dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds, hs.decryptSecureJsonDataFn())
if err != nil { if err != nil {
c.JsonApiErr(500, "Unable to process datasource instance model", err) 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) 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 { if err != nil {
return response.Error(500, "Unable to get datasource model", err) 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) 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
}
}
+6 -3
View File
@@ -77,7 +77,10 @@ func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins *plu
if ds.Access == models.DS_ACCESS_DIRECT { if ds.Access == models.DS_ACCESS_DIRECT {
if ds.BasicAuth { if ds.BasicAuth {
dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.DecryptedBasicAuthPassword()) dsMap["basicAuth"] = util.GetBasicAuthHeader(
ds.BasicAuthUser,
hs.DataSourcesService.DecryptedBasicAuthPassword(ds),
)
} }
if ds.WithCredentials { if ds.WithCredentials {
dsMap["withCredentials"] = 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 { if ds.Type == models.DS_INFLUXDB_08 {
dsMap["username"] = ds.User dsMap["username"] = ds.User
dsMap["password"] = ds.DecryptedPassword() dsMap["password"] = hs.DataSourcesService.DecryptedPassword(ds)
dsMap["url"] = url + "/db/" + ds.Database dsMap["url"] = url + "/db/" + ds.Database
} }
if ds.Type == models.DS_INFLUXDB { if ds.Type == models.DS_INFLUXDB {
dsMap["username"] = ds.User dsMap["username"] = ds.User
dsMap["password"] = ds.DecryptedPassword() dsMap["password"] = hs.DataSourcesService.DecryptedPassword(ds)
dsMap["url"] = url dsMap["url"] = url
} }
} }
+4 -1
View File
@@ -106,6 +106,7 @@ type HTTPServer struct {
OAuthTokenService oauthtoken.OAuthTokenService OAuthTokenService oauthtoken.OAuthTokenService
Listener net.Listener Listener net.Listener
EncryptionService encryption.Service EncryptionService encryption.Service
DataSourcesService *datasources.Service
cleanUpService *cleanup.CleanUpService cleanUpService *cleanup.CleanUpService
tracingService *tracing.TracingService tracingService *tracing.TracingService
internalMetricsSvc *metrics.InternalMetricsService internalMetricsSvc *metrics.InternalMetricsService
@@ -133,7 +134,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
notificationService *notifications.NotificationService, tracingService *tracing.TracingService, notificationService *notifications.NotificationService, tracingService *tracing.TracingService,
internalMetricsSvc *metrics.InternalMetricsService, quotaService *quota.QuotaService, internalMetricsSvc *metrics.InternalMetricsService, quotaService *quota.QuotaService,
socialService social.Service, oauthTokenService oauthtoken.OAuthTokenService, 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 macaron.Env = cfg.Env
m := macaron.New() m := macaron.New()
@@ -180,6 +182,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
SocialService: socialService, SocialService: socialService,
OAuthTokenService: oauthTokenService, OAuthTokenService: oauthTokenService,
EncryptionService: encryptionService, EncryptionService: encryptionService,
DataSourcesService: dataSourcesService,
searchUsersService: searchUsersService, searchUsersService: searchUsersService,
} }
if hs.Listener != nil { if hs.Listener != nil {
+2 -2
View File
@@ -315,12 +315,12 @@ func (hs *HTTPServer) tryGetEncryptedCookie(ctx *models.ReqContext, cookieName s
return "", false 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 return string(decryptedError), err == nil
} }
func (hs *HTTPServer) trySetEncryptedCookie(ctx *models.ReqContext, cookieName string, value string, maxAge int) error { 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 { if err != nil {
return err return err
} }
+3 -3
View File
@@ -2,6 +2,7 @@ package api
import ( import (
"bytes" "bytes"
"context"
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
@@ -12,8 +13,6 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
@@ -24,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "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/hooks"
"github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@@ -125,7 +125,7 @@ func TestLoginErrorCookieAPIEndpoint(t *testing.T) {
setting.OAuthAutoLogin = true setting.OAuthAutoLogin = true
oauthError := errors.New("User not a member of one of the required organizations") 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) require.NoError(t, err)
expCookiePath := "/" expCookiePath := "/"
if len(setting.AppSubUrl) > 0 { if len(setting.AppSubUrl) > 0 {
+9 -2
View File
@@ -9,18 +9,25 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
// ApplyRoute should use the plugin route data to set auth headers and custom headers. // 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, 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) 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{ data := templateData{
JsonData: ds.JsonData.Interface().(map[string]interface{}), JsonData: ds.JsonData.Interface().(map[string]interface{}),
SecureJsonData: ds.SecureJsonData.Decrypt(), SecureJsonData: secureJsonData,
} }
if len(route.URL) > 0 { if len(route.URL) > 0 {
+34 -23
View File
@@ -18,6 +18,7 @@ import (
glog "github.com/grafana/grafana/pkg/infra/log" glog "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "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/services/oauthtoken"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@@ -31,15 +32,16 @@ var (
) )
type DataSourceProxy struct { type DataSourceProxy struct {
ds *models.DataSource ds *models.DataSource
ctx *models.ReqContext ctx *models.ReqContext
targetUrl *url.URL targetUrl *url.URL
proxyPath string proxyPath string
route *plugins.AppPluginRoute route *plugins.AppPluginRoute
plugin *plugins.DataSourcePlugin plugin *plugins.DataSourcePlugin
cfg *setting.Cfg cfg *setting.Cfg
clientProvider httpclient.Provider clientProvider httpclient.Provider
oAuthTokenService oauthtoken.OAuthTokenService oAuthTokenService oauthtoken.OAuthTokenService
dataSourcesService *datasources.Service
} }
type handleResponseTransport struct { type handleResponseTransport struct {
@@ -72,21 +74,23 @@ func (lw *logWrapper) Write(p []byte) (n int, err error) {
// NewDataSourceProxy creates a new Datasource proxy // NewDataSourceProxy creates a new Datasource proxy
func NewDataSourceProxy(ds *models.DataSource, plugin *plugins.DataSourcePlugin, ctx *models.ReqContext, 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) targetURL, err := datasource.ValidateURL(ds.Type, ds.Url)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &DataSourceProxy{ return &DataSourceProxy{
ds: ds, ds: ds,
plugin: plugin, plugin: plugin,
ctx: ctx, ctx: ctx,
proxyPath: proxyPath, proxyPath: proxyPath,
targetUrl: targetURL, targetUrl: targetURL,
cfg: cfg, cfg: cfg,
clientProvider: clientProvider, clientProvider: clientProvider,
oAuthTokenService: oAuthTokenService, oAuthTokenService: oAuthTokenService,
dataSourcesService: dsService,
}, nil }, nil
} }
@@ -106,7 +110,7 @@ func (proxy *DataSourceProxy) HandleRequest() {
proxyErrorLogger := logger.New("userId", proxy.ctx.UserId, "orgId", proxy.ctx.OrgId, "uname", proxy.ctx.Login, 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()) "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 { if err != nil {
proxy.ctx.JsonApiErr(400, "Unable to load TLS certificate", err) proxy.ctx.JsonApiErr(400, "Unable to load TLS certificate", err)
return return
@@ -186,13 +190,16 @@ func (proxy *DataSourceProxy) director(req *http.Request) {
case models.DS_INFLUXDB_08: case models.DS_INFLUXDB_08:
req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath) req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath)
reqQueryVals.Add("u", proxy.ds.User) 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() req.URL.RawQuery = reqQueryVals.Encode()
case models.DS_INFLUXDB: case models.DS_INFLUXDB:
req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath) req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
req.URL.RawQuery = reqQueryVals.Encode() req.URL.RawQuery = reqQueryVals.Encode()
if !proxy.ds.BasicAuth { 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: default:
req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath) 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 { if proxy.ds.BasicAuth {
req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser, req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser,
proxy.ds.DecryptedBasicAuthPassword())) proxy.dataSourcesService.DecryptedBasicAuthPassword(proxy.ds)))
} }
dsAuth := req.Header.Get("X-DS-Authorization") dsAuth := req.Header.Get("X-DS-Authorization")
@@ -236,7 +243,11 @@ func (proxy *DataSourceProxy) director(req *http.Request) {
req.Header.Del("Referer") req.Header.Del("Referer")
if proxy.route != nil { 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) { if proxy.oAuthTokenService.IsOAuthPassThruEnabled(proxy.ds) {
+78 -45
View File
@@ -14,18 +14,18 @@ import (
"github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/api/datasource"
"github.com/grafana/grafana/pkg/bus" "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/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "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/services/oauthtoken"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/oauth2" "golang.org/x/oauth2"
macaron "gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
) )
func TestDataSourceProxy_routeRule(t *testing.T) { func TestDataSourceProxy_routeRule(t *testing.T) {
@@ -85,7 +85,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
}) })
setting.SecretKey = "password" //nolint:goconst 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) require.NoError(t, err)
ds := &models.DataSource{ ds := &models.DataSource{
@@ -113,10 +113,11 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("When matching route path", func(t *testing.T) { t.Run("When matching route path", func(t *testing.T) {
ctx, req := setUp() 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) require.NoError(t, err)
proxy.route = plugin.Routes[0] 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, "https://www.google.com/some/method", req.URL.String())
assert.Equal(t, "my secret 123", req.Header.Get("x-header")) 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) { t.Run("When matching route path and has dynamic url", func(t *testing.T) {
ctx, req := setUp() 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) require.NoError(t, err)
proxy.route = plugin.Routes[3] 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, "https://dynamic.grafana.com/some/method?apiKey=123", req.URL.String())
assert.Equal(t, "my secret 123", req.Header.Get("x-header")) 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) { t.Run("When matching route path with no url", func(t *testing.T) {
ctx, req := setUp() 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) require.NoError(t, err)
proxy.route = plugin.Routes[4] 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()) assert.Equal(t, "http://localhost/asd", req.URL.String())
}) })
t.Run("When matching route path and has dynamic body", func(t *testing.T) { t.Run("When matching route path and has dynamic body", func(t *testing.T) {
ctx, req := setUp() 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) require.NoError(t, err)
proxy.route = plugin.Routes[5] 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) content, err := ioutil.ReadAll(req.Body)
require.NoError(t, err) require.NoError(t, err)
@@ -158,7 +162,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("Validating request", func(t *testing.T) { t.Run("Validating request", func(t *testing.T) {
t.Run("plugin route with valid role", func(t *testing.T) { t.Run("plugin route with valid role", func(t *testing.T) {
ctx, _ := setUp() 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) require.NoError(t, err)
err = proxy.validateRequest() err = proxy.validateRequest()
require.NoError(t, err) 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) { t.Run("plugin route with admin role and user is editor", func(t *testing.T) {
ctx, _ := setUp() 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) require.NoError(t, err)
err = proxy.validateRequest() err = proxy.validateRequest()
require.Error(t, err) 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) { t.Run("plugin route with admin role and user is admin", func(t *testing.T) {
ctx, _ := setUp() ctx, _ := setUp()
ctx.SignedInUser.OrgRole = models.ROLE_ADMIN 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) require.NoError(t, err)
err = proxy.validateRequest() err = proxy.validateRequest()
require.NoError(t, err) require.NoError(t, err)
@@ -221,7 +228,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
}) })
setting.SecretKey = "password" setting.SecretKey = "password"
key, err := util.Encrypt([]byte("123"), "password") key, err := ossencryption.ProvideService().Encrypt(context.Background(), []byte("123"), "password")
require.NoError(t, err) require.NoError(t, err)
ds := &models.DataSource{ ds := &models.DataSource{
@@ -255,9 +262,10 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
cfg := &setting.Cfg{} 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) 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") authorizationHeaderCall1 = req.Header.Get("Authorization")
assert.Equal(t, "https://api.nr1.io/some/path", req.URL.String()) 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) req, err := http.NewRequest("GET", "http://localhost/asd", nil)
require.NoError(t, err) require.NoError(t, err)
client = newFakeHTTPClient(t, json2) 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) 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") authorizationHeaderCall2 = req.Header.Get("Authorization")
@@ -286,9 +295,10 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
client = newFakeHTTPClient(t, []byte{}) 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) 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") authorizationHeaderCall3 := req.Header.Get("Authorization")
assert.Equal(t, "https://api.nr1.io/some/path", req.URL.String()) 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} ds := &models.DataSource{Url: "htttp://graphite:8080", Type: models.DS_GRAPHITE}
ctx := &models.ReqContext{} 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) require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
require.NoError(t, err) require.NoError(t, err)
@@ -331,7 +342,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
} }
ctx := &models.ReqContext{} 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) require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
@@ -354,7 +366,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
} }
ctx := &models.ReqContext{} 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) require.NoError(t, err)
requestURL, err := url.Parse("http://grafana.com/sub") requestURL, err := url.Parse("http://grafana.com/sub")
@@ -381,7 +394,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
} }
ctx := &models.ReqContext{} 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) require.NoError(t, err)
requestURL, err := url.Parse("http://grafana.com/sub") requestURL, err := url.Parse("http://grafana.com/sub")
@@ -402,7 +416,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
Url: "http://host/root/", Url: "http://host/root/",
} }
ctx := &models.ReqContext{} 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) require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
req.Header.Set("Origin", "grafana.com") req.Header.Set("Origin", "grafana.com")
@@ -457,7 +472,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
}, },
oAuthEnabled: true, 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) require.NoError(t, err)
req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
require.NoError(t, err) require.NoError(t, err)
@@ -522,7 +538,6 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
createAuthTest(t, models.DS_ES, authTypeBasic, authCheckHeader, true), createAuthTest(t, models.DS_ES, authTypeBasic, authCheckHeader, true),
} }
for _, test := range tests { for _, test := range tests {
models.ClearDSDecryptionCache()
runDatasourceAuthTest(t, test) 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) { t.Run("When response header Set-Cookie is not set should remove proxied Set-Cookie header", func(t *testing.T) {
ctx, ds := setUp(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) require.NoError(t, err)
proxy.HandleRequest() proxy.HandleRequest()
@@ -599,7 +615,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
"Set-Cookie": "important_cookie=important_value", "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) require.NoError(t, err)
proxy.HandleRequest() proxy.HandleRequest()
@@ -618,7 +635,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
t.Log("Wrote 401 response") 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) require.NoError(t, err)
proxy.HandleRequest() 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) 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) require.NoError(t, err)
proxy.HandleRequest() proxy.HandleRequest()
@@ -662,7 +681,8 @@ func TestNewDataSourceProxy_InvalidURL(t *testing.T) {
} }
cfg := setting.Cfg{} cfg := setting.Cfg{}
plugin := plugins.DataSourcePlugin{} 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) require.Error(t, err)
assert.True(t, strings.HasPrefix(err.Error(), `validation of data source URL "://host/root" failed`)) 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{} cfg := setting.Cfg{}
plugin := plugins.DataSourcePlugin{} 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) require.NoError(t, err)
} }
@@ -717,7 +738,8 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) {
Url: tc.url, 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 { if tc.err == nil {
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, &url.URL{ assert.Equal(t, &url.URL{
@@ -741,7 +763,8 @@ func getDatasourceProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *sett
Url: "http://host/root/", 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) require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
require.NoError(t, err) require.NoError(t, err)
@@ -794,6 +817,8 @@ const (
) )
func createAuthTest(t *testing.T, dsType string, authType string, authCheck string, useSecureJsonData bool) *testCase { func createAuthTest(t *testing.T, dsType string, authType string, authCheck string, useSecureJsonData bool) *testCase {
ctx := context.Background()
// Basic user:password // Basic user:password
base64AuthHeader := "Basic dXNlcjpwYXNzd29yZA==" base64AuthHeader := "Basic dXNlcjpwYXNzd29yZA=="
@@ -805,13 +830,16 @@ func createAuthTest(t *testing.T, dsType string, authType string, authCheck stri
}, },
} }
var message string var message string
var err error
if authType == authTypePassword { if authType == authTypePassword {
message = fmt.Sprintf("%v should add username and password", dsType) message = fmt.Sprintf("%v should add username and password", dsType)
test.datasource.User = "user" test.datasource.User = "user"
if useSecureJsonData { if useSecureJsonData {
test.datasource.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ test.datasource.SecureJsonData, err = ossencryption.ProvideService().EncryptJsonData(
"password": "password", ctx,
}) map[string]string{
"password": "password",
}, setting.SecretKey)
} else { } else {
test.datasource.Password = "password" 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.BasicAuth = true
test.datasource.BasicAuthUser = "user" test.datasource.BasicAuthUser = "user"
if useSecureJsonData { if useSecureJsonData {
test.datasource.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ test.datasource.SecureJsonData, err = ossencryption.ProvideService().EncryptJsonData(
"basicAuthPassword": "password", ctx,
}) map[string]string{
"basicAuthPassword": "password",
}, setting.SecretKey)
} else { } else {
test.datasource.BasicAuthPassword = "password" test.datasource.BasicAuthPassword = "password"
} }
} }
require.NoError(t, err)
if useSecureJsonData { if useSecureJsonData {
message += " from securejsondata" 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) { func runDatasourceAuthTest(t *testing.T, test *testCase) {
plugin := &plugins.DataSourcePlugin{} plugin := &plugins.DataSourcePlugin{}
ctx := &models.ReqContext{} 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) require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil) req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
@@ -891,7 +923,8 @@ func Test_PathCheck(t *testing.T) {
return ctx, req return ctx, req
} }
ctx, _ := setUp() 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.NoError(t, err)
require.Nil(t, proxy.validateRequest()) require.Nil(t, proxy.validateRequest())
+9 -2
View File
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/proxyutil" "github.com/grafana/grafana/pkg/util/proxyutil"
@@ -21,7 +22,7 @@ type templateData struct {
// NewApiPluginProxy create a plugin proxy // NewApiPluginProxy create a plugin proxy
func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.AppPluginRoute, 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) { director := func(req *http.Request) {
query := models.GetPluginSettingByIdQuery{OrgId: ctx.OrgId, PluginId: appID} query := models.GetPluginSettingByIdQuery{OrgId: ctx.OrgId, PluginId: appID}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
@@ -29,9 +30,15 @@ func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.
return 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{ data := templateData{
JsonData: query.Result.JsonData, JsonData: query.Result.JsonData,
SecureJsonData: query.Result.SecureJsonData.Decrypt(), SecureJsonData: secureJsonData,
} }
interpolatedURL, err := interpolateString(route.URL, data) interpolatedURL, err := interpolateString(route.URL, data)
+61 -8
View File
@@ -1,18 +1,19 @@
package pluginproxy package pluginproxy
import ( import (
"context"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"testing" "testing"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "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/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"gopkg.in/macaron.v1"
) )
func TestPluginProxy(t *testing.T) { func TestPluginProxy(t *testing.T) {
@@ -25,8 +26,8 @@ func TestPluginProxy(t *testing.T) {
setting.SecretKey = "password" setting.SecretKey = "password"
bus.AddHandler("test", func(query *models.GetPluginSettingByIdQuery) error { bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetPluginSettingByIdQuery) error {
key, err := util.Encrypt([]byte("123"), "password") key, err := ossencryption.ProvideService().Encrypt(ctx, []byte("123"), "password")
if err != nil { if err != nil {
return err return err
} }
@@ -39,12 +40,18 @@ func TestPluginProxy(t *testing.T) {
return nil return nil
}) })
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest( req := getPluginProxiedRequest(
t, t,
&models.ReqContext{ &models.ReqContext{
SignedInUser: &models.SignedInUser{ SignedInUser: &models.SignedInUser{
Login: "test_user", Login: "test_user",
}, },
Context: &macaron.Context{
Req: httpReq,
},
}, },
&setting.Cfg{SendUserHeader: true}, &setting.Cfg{SendUserHeader: true},
route, route,
@@ -54,12 +61,18 @@ func TestPluginProxy(t *testing.T) {
}) })
t.Run("When SendUserHeader config is enabled", func(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( req := getPluginProxiedRequest(
t, t,
&models.ReqContext{ &models.ReqContext{
SignedInUser: &models.SignedInUser{ SignedInUser: &models.SignedInUser{
Login: "test_user", Login: "test_user",
}, },
Context: &macaron.Context{
Req: httpReq,
},
}, },
&setting.Cfg{SendUserHeader: true}, &setting.Cfg{SendUserHeader: true},
nil, nil,
@@ -70,12 +83,18 @@ func TestPluginProxy(t *testing.T) {
}) })
t.Run("When SendUserHeader config is disabled", func(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( req := getPluginProxiedRequest(
t, t,
&models.ReqContext{ &models.ReqContext{
SignedInUser: &models.SignedInUser{ SignedInUser: &models.SignedInUser{
Login: "test_user", Login: "test_user",
}, },
Context: &macaron.Context{
Req: httpReq,
},
}, },
&setting.Cfg{SendUserHeader: false}, &setting.Cfg{SendUserHeader: false},
nil, 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) { 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( req := getPluginProxiedRequest(
t, t,
&models.ReqContext{ &models.ReqContext{
SignedInUser: &models.SignedInUser{IsAnonymous: true}, SignedInUser: &models.SignedInUser{IsAnonymous: true},
Context: &macaron.Context{
Req: httpReq,
},
}, },
&setting.Cfg{SendUserHeader: true}, &setting.Cfg{SendUserHeader: true},
nil, nil,
@@ -104,7 +129,7 @@ func TestPluginProxy(t *testing.T) {
Method: "GET", Method: "GET",
} }
bus.AddHandler("test", func(query *models.GetPluginSettingByIdQuery) error { bus.AddHandlerCtx("test", func(_ context.Context, query *models.GetPluginSettingByIdQuery) error {
query.Result = &models.PluginSetting{ query.Result = &models.PluginSetting{
JsonData: map[string]interface{}{ JsonData: map[string]interface{}{
"dynamicUrl": "https://dynamic.grafana.com", "dynamicUrl": "https://dynamic.grafana.com",
@@ -113,12 +138,18 @@ func TestPluginProxy(t *testing.T) {
return nil return nil
}) })
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest( req := getPluginProxiedRequest(
t, t,
&models.ReqContext{ &models.ReqContext{
SignedInUser: &models.SignedInUser{ SignedInUser: &models.SignedInUser{
Login: "test_user", Login: "test_user",
}, },
Context: &macaron.Context{
Req: httpReq,
},
}, },
&setting.Cfg{SendUserHeader: true}, &setting.Cfg{SendUserHeader: true},
route, route,
@@ -138,12 +169,18 @@ func TestPluginProxy(t *testing.T) {
return nil return nil
}) })
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest( req := getPluginProxiedRequest(
t, t,
&models.ReqContext{ &models.ReqContext{
SignedInUser: &models.SignedInUser{ SignedInUser: &models.SignedInUser{
Login: "test_user", Login: "test_user",
}, },
Context: &macaron.Context{
Req: httpReq,
},
}, },
&setting.Cfg{SendUserHeader: true}, &setting.Cfg{SendUserHeader: true},
route, route,
@@ -158,22 +195,38 @@ func TestPluginProxy(t *testing.T) {
Body: []byte(`{ "url": "{{.JsonData.dynamicUrl}}", "secret": "{{.SecureJsonData.key}}" }`), 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{ query.Result = &models.PluginSetting{
JsonData: map[string]interface{}{ JsonData: map[string]interface{}{
"dynamicUrl": "https://dynamic.grafana.com", "dynamicUrl": "https://dynamic.grafana.com",
}, },
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{"key": "123"}), SecureJsonData: encryptedJsonData,
} }
return nil return nil
}) })
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest( req := getPluginProxiedRequest(
t, t,
&models.ReqContext{ &models.ReqContext{
SignedInUser: &models.SignedInUser{ SignedInUser: &models.SignedInUser{
Login: "test_user", Login: "test_user",
}, },
Context: &macaron.Context{
Req: httpReq,
},
}, },
&setting.Cfg{SendUserHeader: true}, &setting.Cfg{SendUserHeader: true},
route, route,
@@ -194,7 +247,7 @@ func getPluginProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *setting.
ReqRole: models.ROLE_EDITOR, 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) req, err := http.NewRequest(http.MethodGet, "/api/plugin-proxy/grafana-simple-app/api/v4/alerts", nil)
require.NoError(t, err) require.NoError(t, err)
@@ -6,9 +6,10 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/commands/commandstest" "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/models"
"github.com/grafana/grafana/pkg/services/sqlstore" "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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -30,12 +31,14 @@ func TestPasswordMigrationCommand(t *testing.T) {
for _, ds := range datasources { for _, ds := range datasources {
ds.Created = time.Now() ds.Created = time.Now()
ds.Updated = time.Now() ds.Updated = time.Now()
if ds.Name == "elasticsearch" { if ds.Name == "elasticsearch" {
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ key, err := util.Encrypt([]byte("value"), setting.SecretKey)
"key": "value", require.NoError(t, err)
})
ds.SecureJsonData = map[string][]byte{"key": key}
} else { } 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) assert.Equal(t, len(dss), 4)
for _, ds := range dss { for _, ds := range dss {
sj := ds.SecureJsonData.Decrypt() sj, err := DecryptSecureJsonData(ds)
require.NoError(t, err)
if ds.Name == "influxdb" { if ds.Name == "influxdb" {
assert.Equal(t, ds.Password, "") 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
}
+19 -24
View File
@@ -4,7 +4,6 @@ import (
"errors" "errors"
"time" "time"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
) )
@@ -27,19 +26,19 @@ var (
) )
type AlertNotification struct { type AlertNotification struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Uid string `json:"-"` Uid string `json:"-"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
SendReminder bool `json:"sendReminder"` SendReminder bool `json:"sendReminder"`
DisableResolveMessage bool `json:"disableResolveMessage"` DisableResolveMessage bool `json:"disableResolveMessage"`
Frequency time.Duration `json:"frequency"` Frequency time.Duration `json:"frequency"`
IsDefault bool `json:"isDefault"` IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"` Settings *simplejson.Json `json:"settings"`
SecureSettings securejsondata.SecureJsonData `json:"secureSettings"` SecureSettings map[string][]byte `json:"secureSettings"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
Updated time.Time `json:"updated"` Updated time.Time `json:"updated"`
} }
type CreateAlertNotificationCommand struct { type CreateAlertNotificationCommand struct {
@@ -53,7 +52,9 @@ type CreateAlertNotificationCommand struct {
Settings *simplejson.Json `json:"settings"` Settings *simplejson.Json `json:"settings"`
SecureSettings map[string]string `json:"secureSettings"` SecureSettings map[string]string `json:"secureSettings"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
EncryptedSecureSettings map[string][]byte `json:"-"`
Result *AlertNotification Result *AlertNotification
} }
@@ -69,7 +70,9 @@ type UpdateAlertNotificationCommand struct {
Settings *simplejson.Json `json:"settings" binding:"Required"` Settings *simplejson.Json `json:"settings" binding:"Required"`
SecureSettings map[string]string `json:"secureSettings"` SecureSettings map[string]string `json:"secureSettings"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
EncryptedSecureSettings map[string][]byte `json:"-"`
Result *AlertNotification Result *AlertNotification
} }
@@ -166,11 +169,3 @@ type GetOrCreateNotificationStateQuery struct {
Result *AlertNotificationState 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
}
+23 -42
View File
@@ -4,7 +4,6 @@ import (
"errors" "errors"
"time" "time"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
) )
@@ -38,47 +37,27 @@ type DataSource struct {
OrgId int64 `json:"orgId"` OrgId int64 `json:"orgId"`
Version int `json:"version"` Version int `json:"version"`
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
Access DsAccess `json:"access"` Access DsAccess `json:"access"`
Url string `json:"url"` Url string `json:"url"`
Password string `json:"password"` Password string `json:"password"`
User string `json:"user"` User string `json:"user"`
Database string `json:"database"` Database string `json:"database"`
BasicAuth bool `json:"basicAuth"` BasicAuth bool `json:"basicAuth"`
BasicAuthUser string `json:"basicAuthUser"` BasicAuthUser string `json:"basicAuthUser"`
BasicAuthPassword string `json:"basicAuthPassword"` BasicAuthPassword string `json:"basicAuthPassword"`
WithCredentials bool `json:"withCredentials"` WithCredentials bool `json:"withCredentials"`
IsDefault bool `json:"isDefault"` IsDefault bool `json:"isDefault"`
JsonData *simplejson.Json `json:"jsonData"` JsonData *simplejson.Json `json:"jsonData"`
SecureJsonData securejsondata.SecureJsonData `json:"secureJsonData"` SecureJsonData map[string][]byte `json:"secureJsonData"`
ReadOnly bool `json:"readOnly"` ReadOnly bool `json:"readOnly"`
Uid string `json:"uid"` Uid string `json:"uid"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
Updated time.Time `json:"updated"` 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 // COMMANDS
@@ -100,8 +79,9 @@ type AddDataSourceCommand struct {
SecureJsonData map[string]string `json:"secureJsonData"` SecureJsonData map[string]string `json:"secureJsonData"`
Uid string `json:"uid"` Uid string `json:"uid"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
ReadOnly bool `json:"-"` ReadOnly bool `json:"-"`
EncryptedSecureJsonData map[string][]byte `json:"-"`
Result *DataSource Result *DataSource
} }
@@ -125,9 +105,10 @@ type UpdateDataSourceCommand struct {
Version int `json:"version"` Version int `json:"version"`
Uid string `json:"uid"` Uid string `json:"uid"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
Id int64 `json:"-"` Id int64 `json:"-"`
ReadOnly bool `json:"-"` ReadOnly bool `json:"-"`
EncryptedSecureJsonData map[string][]byte `json:"-"`
Result *DataSource Result *DataSource
} }
-23
View File
@@ -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
}
-70
View File
@@ -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)
})
}
+4 -9
View File
@@ -3,8 +3,6 @@ package models
import ( import (
"errors" "errors"
"time" "time"
"github.com/grafana/grafana/pkg/components/securejsondata"
) )
var ( var (
@@ -18,7 +16,7 @@ type PluginSetting struct {
Enabled bool Enabled bool
Pinned bool Pinned bool
JsonData map[string]interface{} JsonData map[string]interface{}
SecureJsonData securejsondata.SecureJsonData SecureJsonData map[string][]byte
PluginVersion string PluginVersion string
Created time.Time Created time.Time
@@ -36,8 +34,9 @@ type UpdatePluginSettingCmd struct {
SecureJsonData map[string]string `json:"secureJsonData"` SecureJsonData map[string]string `json:"secureJsonData"`
PluginVersion string `json:"version"` PluginVersion string `json:"version"`
PluginId string `json:"-"` PluginId string `json:"-"`
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
EncryptedSecureJsonData map[string][]byte `json:"-"`
} }
// specific command, will only update version // specific command, will only update version
@@ -47,10 +46,6 @@ type UpdatePluginSettingVersionCmd struct {
OrgId int64 `json:"-"` OrgId int64 `json:"-"`
} }
func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() securejsondata.SecureJsonData {
return securejsondata.GetEncryptedJsonData(cmd.SecureJsonData)
}
// --------------------- // ---------------------
// QUERIES // QUERIES
+3 -2
View File
@@ -7,7 +7,8 @@ import (
) )
// ModelToInstanceSettings converts a models.DataSource to a backend.DataSourceInstanceSettings. // 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() jsonDataBytes, err := ds.JsonData.MarshalJSON()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -23,7 +24,7 @@ func ModelToInstanceSettings(ds *models.DataSource) (*backend.DataSourceInstance
BasicAuthEnabled: ds.BasicAuth, BasicAuthEnabled: ds.BasicAuth,
BasicAuthUser: ds.BasicAuthUser, BasicAuthUser: ds.BasicAuthUser,
JSONData: jsonDataBytes, JSONData: jsonDataBytes,
DecryptedSecureJSONData: ds.DecryptedValues(), DecryptedSecureJSONData: decryptFn(ds.SecureJsonData),
Updated: ds.Updated, Updated: ds.Updated,
}, nil }, nil
} }
+33 -12
View File
@@ -1,36 +1,47 @@
package plugincontext package plugincontext
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"time" "time"
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/localcache" "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/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/adapters" "github.com/grafana/grafana/pkg/plugins/adapters"
"github.com/grafana/grafana/pkg/services/datasources" "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" "github.com/grafana/grafana/pkg/util/errutil"
) )
func ProvideService(bus bus.Bus, cacheService *localcache.CacheService, pluginManager plugins.Manager, 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{ return &Provider{
Bus: bus, Bus: bus,
CacheService: cacheService, CacheService: cacheService,
PluginManager: pluginManager, PluginManager: pluginManager,
DataSourceCache: dataSourceCache, DataSourceCache: dataSourceCache,
EncryptionService: encryptionService,
PluginSettingsService: pluginSettingsService,
logger: log.New("plugincontext"),
} }
} }
type Provider struct { type Provider struct {
Bus bus.Bus Bus bus.Bus
CacheService *localcache.CacheService CacheService *localcache.CacheService
PluginManager plugins.Manager PluginManager plugins.Manager
DataSourceCache datasources.CacheService 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 // 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 { if err != nil {
return pc, false, errutil.Wrap("Failed to unmarshal plugin json data", err) return pc, false, errutil.Wrap("Failed to unmarshal plugin json data", err)
} }
decryptedSecureJSONData = ps.DecryptedValues() decryptedSecureJSONData = p.PluginSettingsService.DecryptedValues(ps)
updated = ps.Updated updated = ps.Updated
} }
@@ -79,7 +90,7 @@ func (p *Provider) Get(pluginID string, datasourceUID string, user *models.Signe
if err != nil { if err != nil {
return pc, false, errutil.Wrap("Failed to get datasource", err) 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 { if err != nil {
return pc, false, errutil.Wrap("Failed to convert datasource", err) 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) p.CacheService.Set(cacheKey, query.Result, pluginSettingsCacheTTL)
return query.Result, nil 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/live/pushhttp"
"github.com/grafana/grafana/pkg/services/ngalert" "github.com/grafana/grafana/pkg/services/ngalert"
"github.com/grafana/grafana/pkg/services/notifications" "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/provisioning"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/secrets"
@@ -49,7 +50,7 @@ func ProvideBackgroundServiceRegistry(
_ *influxdb.Service, _ *loki.Service, _ *opentsdb.Service, _ *prometheus.Service, _ *tempo.Service, _ *influxdb.Service, _ *loki.Service, _ *opentsdb.Service, _ *prometheus.Service, _ *tempo.Service,
_ *testdatasource.TestDataPlugin, _ *plugindashboards.Service, _ *dashboardsnapshots.Service, _ secrets.SecretsService, _ *testdatasource.TestDataPlugin, _ *plugindashboards.Service, _ *dashboardsnapshots.Service, _ secrets.SecretsService,
_ *postgres.Service, _ *mysql.Service, _ *mssql.Service, _ *grafanads.Service, _ *postgres.Service, _ *mysql.Service, _ *mssql.Service, _ *grafanads.Service,
_ *pluginsettings.Service, _ *alerting.AlertNotificationService,
) *BackgroundServiceRegistry { ) *BackgroundServiceRegistry {
return NewBackgroundServiceRegistry( return NewBackgroundServiceRegistry(
httpServer, httpServer,
+5
View File
@@ -33,6 +33,7 @@ import (
"github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/contexthandler"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/dashboardsnapshots"
"github.com/grafana/grafana/pkg/services/datasourceproxy" "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/hooks"
"github.com/grafana/grafana/pkg/services/libraryelements" "github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/librarypanels" "github.com/grafana/grafana/pkg/services/librarypanels"
@@ -45,6 +46,7 @@ import (
ngmetrics "github.com/grafana/grafana/pkg/services/ngalert/metrics" ngmetrics "github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/notifications" "github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/services/oauthtoken" "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/quota"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/schemaloader" "github.com/grafana/grafana/pkg/services/schemaloader"
@@ -146,6 +148,9 @@ var wireBasicSet = wire.NewSet(
secrets.ProvideSecretsService, secrets.ProvideSecretsService,
grafanads.ProvideService, grafanads.ProvideService,
dashboardsnapshots.ProvideService, dashboardsnapshots.ProvideService,
datasources.ProvideService,
pluginsettings.ProvideService,
alerting.ProvideService,
) )
var wireSet = wire.NewSet( var wireSet = wire.NewSet(
+4 -2
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "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/services/rendering"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go"
@@ -47,7 +48,8 @@ func (e *AlertEngine) IsDisabled() bool {
// ProvideAlertEngine returns a new AlertEngine. // ProvideAlertEngine returns a new AlertEngine.
func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidator models.PluginRequestValidator, 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{ e := &AlertEngine{
Cfg: cfg, Cfg: cfg,
RenderService: renderer, RenderService: renderer,
@@ -62,7 +64,7 @@ func ProvideAlertEngine(renderer rendering.Service, bus bus.Bus, requestValidato
e.evalHandler = NewEvalHandler(e.DataService) e.evalHandler = NewEvalHandler(e.DataService)
e.ruleReader = newRuleReader() e.ruleReader = newRuleReader()
e.log = log.New("alerting.engine") e.log = log.New("alerting.engine")
e.resultHandler = newResultHandler(e.RenderService) e.resultHandler = newResultHandler(e.RenderService, encryptionService.GetDecryptedValue)
e.registerUsageMetrics() e.registerUsageMetrics()
@@ -13,7 +13,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -21,7 +21,7 @@ import (
func TestEngineTimeouts(t *testing.T) { func TestEngineTimeouts(t *testing.T) {
Convey("Alerting engine timeout tests", t, func() { Convey("Alerting engine timeout tests", t, func() {
usMock := &usagestats.UsageStatsMock{T: t} 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.AlertingNotificationTimeout = 30 * time.Second
setting.AlertingMaxAttempts = 3 setting.AlertingMaxAttempts = 3
engine.resultHandler = &FakeResultHandler{} engine.resultHandler = &FakeResultHandler{}
+2 -1
View File
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -44,7 +45,7 @@ func TestEngineProcessJob(t *testing.T) {
Convey("Alerting engine job processing", t, func() { Convey("Alerting engine job processing", t, func() {
bus := bus.New() bus := bus.New()
usMock := &usagestats.UsageStatsMock{T: t} 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.AlertingEvaluationTimeout = 30 * time.Second
setting.AlertingNotificationTimeout = 30 * time.Second setting.AlertingNotificationTimeout = 30 * time.Second
setting.AlertingMaxAttempts = 3 setting.AlertingMaxAttempts = 3
+5 -3
View File
@@ -181,12 +181,14 @@ func TestAlertRuleExtraction(t *testing.T) {
}) })
t.Run("Alert notifications are in DB", func(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"} firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"}
err = sqlstore.CreateAlertNotificationCommand(&firstNotification) err = sqlStore.CreateAlertNotificationCommand(&firstNotification)
require.Nil(t, err) require.Nil(t, err)
secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"} secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"}
err = sqlstore.CreateAlertNotificationCommand(&secondNotification) err = sqlStore.CreateAlertNotificationCommand(&secondNotification)
require.Nil(t, err) require.Nil(t, err)
json, err := ioutil.ReadFile("./testdata/influxdb-alert.json") json, err := ioutil.ReadFile("./testdata/influxdb-alert.json")
+11 -5
View File
@@ -83,16 +83,18 @@ type ShowWhen struct {
Is string `json:"is"` Is string `json:"is"`
} }
func newNotificationService(renderService rendering.Service) *notificationService { func newNotificationService(renderService rendering.Service, decryptFn GetDecryptedValueFn) *notificationService {
return &notificationService{ return &notificationService{
log: log.New("alerting.notifier"), log: log.New("alerting.notifier"),
renderService: renderService, renderService: renderService,
decryptFn: decryptFn,
} }
} }
type notificationService struct { type notificationService struct {
log log.Logger log log.Logger
renderService rendering.Service renderService rendering.Service
decryptFn GetDecryptedValueFn
} }
func (n *notificationService) SendIfNeeded(evalCtx *EvalContext) error { func (n *notificationService) SendIfNeeded(evalCtx *EvalContext) error {
@@ -250,7 +252,7 @@ func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids [
var result notifierStateSlice var result notifierStateSlice
for _, notification := range query.Result { for _, notification := range query.Result {
not, err := InitNotifier(notification) not, err := InitNotifier(notification, n.decryptFn)
if err != nil { if err != nil {
n.log.Error("Could not create notifier", "notifier", notification.Uid, "error", err) n.log.Error("Could not create notifier", "notifier", notification.Uid, "error", err)
continue continue
@@ -280,17 +282,21 @@ func (n *notificationService) getNeededNotifiers(orgID int64, notificationUids [
} }
// InitNotifier instantiate a new notifier based on the model. // 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] notifierPlugin, found := notifierFactories[model.Type]
if !found { if !found {
return nil, fmt.Errorf("unsupported notification type %q", model.Type) 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. // 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) var notifierFactories = make(map[string]*NotifierPlugin)
+2 -2
View File
@@ -263,7 +263,7 @@ func notificationServiceScenario(t *testing.T, name string, evalCtx *EvalContext
}, },
} }
scenarioCtx.notificationService = newNotificationService(renderService) scenarioCtx.notificationService = newNotificationService(renderService, nil)
fn(scenarioCtx) fn(scenarioCtx)
}) })
} }
@@ -279,7 +279,7 @@ type testNotifier struct {
Frequency time.Duration Frequency time.Duration
} }
func newTestNotifier(model *models.AlertNotification) (Notifier, error) { func newTestNotifier(model *models.AlertNotification, _ GetDecryptedValueFn) (Notifier, error) {
uploadImage := true uploadImage := true
value, exist := model.Settings.CheckGet("uploadImage") value, exist := model.Settings.CheckGet("uploadImage")
if exist { if exist {
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
) )
func init() { func init() {
@@ -49,7 +50,7 @@ func init() {
} }
// NewAlertmanagerNotifier returns a new Alertmanager notifier // 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() urlString := model.Settings.Get("url").MustString()
if urlString == "" { if urlString == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} 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() 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{ return &AlertmanagerNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
@@ -4,15 +4,14 @@ import (
"context" "context"
"testing" "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/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "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/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
) )
func TestReplaceIllegalCharswithUnderscore(t *testing.T) { func TestReplaceIllegalCharswithUnderscore(t *testing.T) {
@@ -93,7 +92,7 @@ func TestAlertmanagerNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewAlertmanagerNotifier(model) _, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -107,7 +106,7 @@ func TestAlertmanagerNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewAlertmanagerNotifier(model) not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
alertmanagerNotifier := not.(*AlertmanagerNotifier) alertmanagerNotifier := not.(*AlertmanagerNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -126,7 +125,7 @@ func TestAlertmanagerNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewAlertmanagerNotifier(model) not, err := NewAlertmanagerNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
alertmanagerNotifier := not.(*AlertmanagerNotifier) alertmanagerNotifier := not.(*AlertmanagerNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+2 -4
View File
@@ -5,14 +5,12 @@ import (
"testing" "testing"
"time" "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/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/validations"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
) )
func TestShouldSendAlertNotification(t *testing.T) { func TestShouldSendAlertNotification(t *testing.T) {
+1 -1
View File
@@ -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() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
@@ -4,11 +4,11 @@ import (
"context" "context"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "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/smartystreets/goconvey/convey"
) )
@@ -24,7 +24,7 @@ func TestDingDingNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := newDingDingNotifier(model) _, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("settings should trigger incident", func() { Convey("settings should trigger incident", func() {
@@ -37,7 +37,7 @@ func TestDingDingNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := newDingDingNotifier(model) not, err := newDingDingNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
notifier := not.(*DingDingNotifier) notifier := not.(*DingDingNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+1 -1
View File
@@ -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() avatar := model.Settings.Get("avatar_url").MustString()
content := model.Settings.Get("content").MustString() content := model.Settings.Get("content").MustString()
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -21,7 +22,7 @@ func TestDiscordNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := newDiscordNotifier(model) _, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -40,7 +41,7 @@ func TestDiscordNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := newDiscordNotifier(model) not, err := newDiscordNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
discordNotifier := not.(*DiscordNotifier) discordNotifier := not.(*DiscordNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+1 -1
View File
@@ -48,7 +48,7 @@ type EmailNotifier struct {
// NewEmailNotifier is the constructor function // NewEmailNotifier is the constructor function
// for the EmailNotifier. // 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() addressesString := model.Settings.Get("addresses").MustString()
singleEmail := model.Settings.Get("singleEmail").MustBool(false) singleEmail := model.Settings.Get("singleEmail").MustBool(false)
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -21,7 +22,7 @@ func TestEmailNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewEmailNotifier(model) _, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -38,7 +39,7 @@ func TestEmailNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewEmailNotifier(model) not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
emailNotifier := not.(*EmailNotifier) emailNotifier := not.(*EmailNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -62,7 +63,7 @@ func TestEmailNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewEmailNotifier(model) not, err := NewEmailNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
emailNotifier := not.(*EmailNotifier) emailNotifier := not.(*EmailNotifier)
So(err, ShouldBeNil) 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() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} 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/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -21,7 +22,7 @@ func TestGoogleChatNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := newGoogleChatNotifier(model) _, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -38,7 +39,7 @@ func TestGoogleChatNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := newGoogleChatNotifier(model) not, err := newGoogleChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
webhookNotifier := not.(*GoogleChatNotifier) webhookNotifier := not.(*GoogleChatNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+1 -1
View File
@@ -53,7 +53,7 @@ const (
// NewHipChatNotifier is the constructor functions // NewHipChatNotifier is the constructor functions
// for the HipChatNotifier // 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() url := model.Settings.Get("url").MustString()
if strings.HasSuffix(url, "/") { if strings.HasSuffix(url, "/") {
url = url[:len(url)-1] url = url[:len(url)-1]
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -22,7 +23,7 @@ func TestHipChatNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewHipChatNotifier(model) _, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -38,7 +39,7 @@ func TestHipChatNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewHipChatNotifier(model) not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
hipchatNotifier := not.(*HipChatNotifier) hipchatNotifier := not.(*HipChatNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -64,7 +65,7 @@ func TestHipChatNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewHipChatNotifier(model) not, err := NewHipChatNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
hipchatNotifier := not.(*HipChatNotifier) hipchatNotifier := not.(*HipChatNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+1 -1
View File
@@ -41,7 +41,7 @@ func init() {
} }
// NewKafkaNotifier is the constructor function for the Kafka notifier. // 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() endpoint := model.Settings.Get("kafkaRestProxy").MustString()
if endpoint == "" { if endpoint == "" {
return nil, alerting.ValidationError{Reason: "Could not find kafka rest proxy endpoint property in settings"} 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/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -21,7 +22,7 @@ func TestKafkaNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewKafkaNotifier(model) _, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -39,7 +40,7 @@ func TestKafkaNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewKafkaNotifier(model) not, err := NewKafkaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
kafkaNotifier := not.(*KafkaNotifier) kafkaNotifier := not.(*KafkaNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers package notifiers
import ( import (
"context"
"fmt" "fmt"
"net/url" "net/url"
@@ -8,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
) )
func init() { func init() {
@@ -35,8 +37,8 @@ const (
) )
// NewLINENotifier is the constructor for the LINE notifier // NewLINENotifier is the constructor for the LINE notifier
func NewLINENotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewLINENotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
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 == "" { if token == "" {
return nil, alerting.ValidationError{Reason: "Could not find token in settings"} return nil, alerting.ValidationError{Reason: "Could not find token in settings"}
} }
+3 -2
View File
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -20,7 +21,7 @@ func TestLineNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewLINENotifier(model) _, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("settings should trigger incident", func() { Convey("settings should trigger incident", func() {
@@ -35,7 +36,7 @@ func TestLineNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewLINENotifier(model) not, err := NewLINENotifier(model, ossencryption.ProvideService().GetDecryptedValue)
lineNotifier := not.(*LineNotifier) lineNotifier := not.(*LineNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers package notifiers
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
@@ -9,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
) )
const ( const (
@@ -82,10 +84,10 @@ const (
) )
// NewOpsGenieNotifier is the constructor for OpsGenie. // 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) autoClose := model.Settings.Get("autoClose").MustBool(true)
overridePriority := model.Settings.Get("overridePriority").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() apiURL := model.Settings.Get("apiUrl").MustString()
if apiKey == "" { if apiKey == "" {
return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"} return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"}
@@ -4,12 +4,12 @@ import (
"context" "context"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "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/smartystreets/goconvey/convey"
) )
@@ -26,7 +26,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewOpsGenieNotifier(model) _, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -43,7 +43,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewOpsGenieNotifier(model) not, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
opsgenieNotifier := not.(*OpsGenieNotifier) opsgenieNotifier := not.(*OpsGenieNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -67,7 +67,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewOpsGenieNotifier(model) _, err := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(err, ShouldHaveSameTypeAs, alerting.ValidationError{}) So(err, ShouldHaveSameTypeAs, alerting.ValidationError{})
So(err.Error(), ShouldEndWith, "Invalid value for sendTagsAs: \"not_a_valid_value\"") So(err.Error(), ShouldEndWith, "Invalid value for sendTagsAs: \"not_a_valid_value\"")
@@ -90,7 +90,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
notifier, notifierErr := NewOpsGenieNotifier(model) // unhandled error notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
opsgenieNotifier := notifier.(*OpsGenieNotifier) opsgenieNotifier := notifier.(*OpsGenieNotifier)
@@ -140,7 +140,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
notifier, notifierErr := NewOpsGenieNotifier(model) // unhandled error notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
opsgenieNotifier := notifier.(*OpsGenieNotifier) opsgenieNotifier := notifier.(*OpsGenieNotifier)
@@ -190,7 +190,7 @@ func TestOpsGenieNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
notifier, notifierErr := NewOpsGenieNotifier(model) // unhandled error notifier, notifierErr := NewOpsGenieNotifier(model, ossencryption.ProvideService().GetDecryptedValue) // unhandled error
opsgenieNotifier := notifier.(*OpsGenieNotifier) opsgenieNotifier := notifier.(*OpsGenieNotifier)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers package notifiers
import ( import (
"context"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@@ -11,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
) )
func init() { func init() {
@@ -74,10 +76,10 @@ var (
) )
// NewPagerdutyNotifier is the constructor for the PagerDuty notifier // 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") severity := model.Settings.Get("severity").MustString("critical")
autoResolve := model.Settings.Get("autoResolve").MustBool(false) 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) messageInDetails := model.Settings.Get("messageInDetails").MustBool(false)
if key == "" { if key == "" {
return nil, alerting.ValidationError{Reason: "Could not find integration key property in settings"} return nil, alerting.ValidationError{Reason: "Could not find integration key property in settings"}
@@ -5,13 +5,13 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "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/smartystreets/goconvey/convey"
) )
@@ -40,7 +40,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err = NewPagerdutyNotifier(model) _, err = NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -56,7 +56,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPagerdutyNotifier(model) not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
pagerdutyNotifier := not.(*PagerdutyNotifier) pagerdutyNotifier := not.(*PagerdutyNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -79,7 +79,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPagerdutyNotifier(model) not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
pagerdutyNotifier := not.(*PagerdutyNotifier) pagerdutyNotifier := not.(*PagerdutyNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -106,7 +106,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPagerdutyNotifier(model) not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
pagerdutyNotifier := not.(*PagerdutyNotifier) pagerdutyNotifier := not.(*PagerdutyNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -131,7 +131,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPagerdutyNotifier(model) not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil) So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier) pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -188,7 +188,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPagerdutyNotifier(model) not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil) So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier) pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -245,7 +245,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPagerdutyNotifier(model) not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil) So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier) pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -315,7 +315,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPagerdutyNotifier(model) not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil) So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier) pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -395,7 +395,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPagerdutyNotifier(model) not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil) So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier) pagerdutyNotifier := not.(*PagerdutyNotifier)
@@ -474,7 +474,7 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPagerdutyNotifier(model) not, err := NewPagerdutyNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil) So(err, ShouldBeNil)
pagerdutyNotifier := not.(*PagerdutyNotifier) pagerdutyNotifier := not.(*PagerdutyNotifier)
+6 -3
View File
@@ -2,12 +2,15 @@ package notifiers
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"mime/multipart" "mime/multipart"
"os" "os"
"strconv" "strconv"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@@ -191,9 +194,9 @@ func init() {
} }
// NewPushoverNotifier is the constructor for the Pushover Notifier // NewPushoverNotifier is the constructor for the Pushover Notifier
func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewPushoverNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
userKey := model.DecryptedValue("userKey", model.Settings.Get("userKey").MustString()) userKey := fn(context.Background(), model.SecureSettings, "userKey", model.Settings.Get("userKey").MustString(), setting.SecretKey)
APIToken := model.DecryptedValue("apiToken", model.Settings.Get("apiToken").MustString()) APIToken := fn(context.Background(), model.SecureSettings, "apiToken", model.Settings.Get("apiToken").MustString(), setting.SecretKey)
device := model.Settings.Get("device").MustString() device := model.Settings.Get("device").MustString()
alertingPriority, err := strconv.Atoi(model.Settings.Get("priority").MustString("0")) // default Normal alertingPriority, err := strconv.Atoi(model.Settings.Get("priority").MustString("0")) // default Normal
if err != nil { if err != nil {
@@ -5,12 +5,11 @@ import (
"strings" "strings"
"testing" "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/components/simplejson"
"github.com/grafana/grafana/pkg/models" "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/smartystreets/goconvey/convey"
) )
@@ -27,7 +26,7 @@ func TestPushoverNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewPushoverNotifier(model) _, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -49,7 +48,7 @@ func TestPushoverNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewPushoverNotifier(model) not, err := NewPushoverNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
pushoverNotifier := not.(*PushoverNotifier) pushoverNotifier := not.(*PushoverNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers package notifiers
import ( import (
"context"
"strconv" "strconv"
"strings" "strings"
@@ -9,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
) )
func init() { func init() {
@@ -59,7 +61,7 @@ func init() {
} }
// NewSensuNotifier is the constructor for the Sensu Notifier. // 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() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} 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, URL: url,
User: model.Settings.Get("username").MustString(), User: model.Settings.Get("username").MustString(),
Source: model.Settings.Get("source").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(), Handler: model.Settings.Get("handler").MustString(),
log: log.New("alerting.notifier.sensu"), log: log.New("alerting.notifier.sensu"),
}, nil }, nil
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -21,7 +22,7 @@ func TestSensuNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewSensuNotifier(model) _, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -40,7 +41,7 @@ func TestSensuNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewSensuNotifier(model) not, err := NewSensuNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
sensuNotifier := not.(*SensuNotifier) sensuNotifier := not.(*SensuNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers package notifiers
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@@ -11,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
) )
func init() { func init() {
@@ -70,9 +72,9 @@ func init() {
} }
// NewSensuGoNotifier is the constructor for the Sensu Go Notifier. // 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() 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 == "" { if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find URL property in settings"} return nil, alerting.ValidationError{Reason: "Could not find URL property in settings"}
@@ -3,11 +3,11 @@ package notifiers
import ( import (
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "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) { func TestSensuGoNotifier(t *testing.T) {
@@ -21,7 +21,7 @@ func TestSensuGoNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err = NewSensuGoNotifier(model) _, err = NewSensuGoNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.Error(t, err) require.Error(t, err)
json = ` json = `
@@ -42,7 +42,7 @@ func TestSensuGoNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewSensuGoNotifier(model) not, err := NewSensuGoNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err) require.NoError(t, err)
sensuGoNotifier := not.(*SensuGoNotifier) sensuGoNotifier := not.(*SensuGoNotifier)
+3 -3
View File
@@ -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" const slackAPIEndpoint = "https://slack.com/api/chat.postMessage"
// NewSlackNotifier is the constructor for the Slack notifier. // NewSlackNotifier is the constructor for the Slack notifier.
func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewSlackNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) {
urlStr := model.DecryptedValue("url", model.Settings.Get("url").MustString()) urlStr := fn(context.Background(), model.SecureSettings, "url", model.Settings.Get("url").MustString(), setting.SecretKey)
if urlStr == "" { if urlStr == "" {
urlStr = slackAPIEndpoint urlStr = slackAPIEndpoint
} }
@@ -150,7 +150,7 @@ func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error
mentionUsersStr := model.Settings.Get("mentionUsers").MustString() mentionUsersStr := model.Settings.Get("mentionUsers").MustString()
mentionGroupsStr := model.Settings.Get("mentionGroups").MustString() mentionGroupsStr := model.Settings.Get("mentionGroups").MustString()
mentionChannel := model.Settings.Get("mentionChannel").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 { if token == "" && apiURL.String() == slackAPIEndpoint {
return nil, alerting.ValidationError{ return nil, alerting.ValidationError{
Reason: "token must be specified when using the Slack chat API", Reason: "token must be specified when using the Slack chat API",
+20 -12
View File
@@ -1,11 +1,13 @@
package notifiers package notifiers
import ( import (
"context"
"testing" "testing"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -22,7 +24,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON, 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") 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, Settings: settingsJSON,
} }
not, err := NewSlackNotifier(model) not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err) require.NoError(t, err)
slackNotifier := not.(*SlackNotifier) slackNotifier := not.(*SlackNotifier)
assert.Equal(t, "ops", slackNotifier.Name) assert.Equal(t, "ops", slackNotifier.Name)
@@ -78,7 +80,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewSlackNotifier(model) not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err) require.NoError(t, err)
slackNotifier := not.(*SlackNotifier) slackNotifier := not.(*SlackNotifier)
assert.Equal(t, "ops", slackNotifier.Name) assert.Equal(t, "ops", slackNotifier.Name)
@@ -110,9 +112,15 @@ func TestSlackNotifier(t *testing.T) {
settingsJSON, err := simplejson.NewJson([]byte(json)) settingsJSON, err := simplejson.NewJson([]byte(json))
require.NoError(t, err) 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{ model := &models.AlertNotification{
Name: "ops", Name: "ops",
Type: "slack", Type: "slack",
@@ -120,7 +128,7 @@ func TestSlackNotifier(t *testing.T) {
SecureSettings: securedSettingsJSON, SecureSettings: securedSettingsJSON,
} }
not, err := NewSlackNotifier(model) not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err) require.NoError(t, err)
slackNotifier := not.(*SlackNotifier) slackNotifier := not.(*SlackNotifier)
assert.Equal(t, "ops", slackNotifier.Name) assert.Equal(t, "ops", slackNotifier.Name)
@@ -151,7 +159,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err = NewSlackNotifier(model) _, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"#open tsdb\"") assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"#open tsdb\"")
}) })
@@ -170,7 +178,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err = NewSlackNotifier(model) _, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"@user name\"") assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"@user name\"")
}) })
@@ -189,7 +197,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err = NewSlackNotifier(model) _, err = NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"@User\"") assert.EqualError(t, err, "alert validation error: recipient on invalid format: \"@User\"")
}) })
@@ -208,7 +216,7 @@ func TestSlackNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewSlackNotifier(model) not, err := NewSlackNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err) require.NoError(t, err)
slackNotifier := not.(*SlackNotifier) slackNotifier := not.(*SlackNotifier)
assert.Equal(t, "1ABCDE", slackNotifier.recipient) assert.Equal(t, "1ABCDE", slackNotifier.recipient)
+1 -1
View File
@@ -30,7 +30,7 @@ func init() {
} }
// NewTeamsNotifier is the constructor for Teams notifier. // 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() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} 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/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -21,7 +22,7 @@ func TestTeamsNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewTeamsNotifier(model) _, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -38,7 +39,7 @@ func TestTeamsNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewTeamsNotifier(model) not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
teamsNotifier := not.(*TeamsNotifier) teamsNotifier := not.(*TeamsNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -60,7 +61,7 @@ func TestTeamsNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewTeamsNotifier(model) not, err := NewTeamsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
teamsNotifier := not.(*TeamsNotifier) teamsNotifier := not.(*TeamsNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+4 -2
View File
@@ -2,6 +2,7 @@ package notifiers
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"mime/multipart" "mime/multipart"
@@ -11,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
) )
const ( const (
@@ -61,12 +63,12 @@ type TelegramNotifier struct {
} }
// NewTelegramNotifier is the constructor for the Telegram notifier // 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 { if model.Settings == nil {
return nil, alerting.ValidationError{Reason: "No Settings Supplied"} 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() chatID := model.Settings.Get("chatid").MustString()
uploadImage := model.Settings.Get("uploadImage").MustBool() uploadImage := model.Settings.Get("uploadImage").MustBool()
@@ -4,11 +4,11 @@ import (
"context" "context"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "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/smartystreets/goconvey/convey"
) )
@@ -25,7 +25,7 @@ func TestTelegramNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewTelegramNotifier(model) _, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -43,7 +43,7 @@ func TestTelegramNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewTelegramNotifier(model) not, err := NewTelegramNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
telegramNotifier := not.(*TelegramNotifier) telegramNotifier := not.(*TelegramNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
+4 -2
View File
@@ -1,6 +1,7 @@
package notifiers package notifiers
import ( import (
"context"
"fmt" "fmt"
"net/url" "net/url"
"strings" "strings"
@@ -9,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
) )
var ( var (
@@ -69,14 +71,14 @@ type ThreemaNotifier struct {
} }
// NewThreemaNotifier is the constructor for the Threema notifier // 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 { if model.Settings == nil {
return nil, alerting.ValidationError{Reason: "No Settings Supplied"} return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
} }
gatewayID := model.Settings.Get("gateway_id").MustString() gatewayID := model.Settings.Get("gateway_id").MustString()
recipientID := model.Settings.Get("recipient_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 // Validation
if gatewayID == "" { if gatewayID == "" {
@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@@ -23,7 +24,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewThreemaNotifier(model) _, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -42,7 +43,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewThreemaNotifier(model) not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil) So(err, ShouldBeNil)
threemaNotifier := not.(*ThreemaNotifier) threemaNotifier := not.(*ThreemaNotifier)
@@ -69,7 +70,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewThreemaNotifier(model) not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(not, ShouldBeNil) So(not, ShouldBeNil)
var valErr alerting.ValidationError var valErr alerting.ValidationError
So(errors.As(err, &valErr), ShouldBeTrue) So(errors.As(err, &valErr), ShouldBeTrue)
@@ -91,7 +92,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewThreemaNotifier(model) not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(not, ShouldBeNil) So(not, ShouldBeNil)
var valErr alerting.ValidationError var valErr alerting.ValidationError
So(errors.As(err, &valErr), ShouldBeTrue) So(errors.As(err, &valErr), ShouldBeTrue)
@@ -113,7 +114,7 @@ func TestThreemaNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewThreemaNotifier(model) not, err := NewThreemaNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(not, ShouldBeNil) So(not, ShouldBeNil)
var valErr alerting.ValidationError var valErr alerting.ValidationError
So(errors.As(err, &valErr), ShouldBeTrue) So(errors.As(err, &valErr), ShouldBeTrue)
+1 -1
View File
@@ -47,7 +47,7 @@ func init() {
// NewVictoropsNotifier creates an instance of VictoropsNotifier that // NewVictoropsNotifier creates an instance of VictoropsNotifier that
// handles posting notifications to Victorops REST API // 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) autoResolve := model.Settings.Get("autoResolve").MustBool(true)
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
@@ -4,12 +4,12 @@ import (
"context" "context"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/validations"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "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/smartystreets/goconvey/convey"
) )
@@ -35,7 +35,7 @@ func TestVictoropsNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewVictoropsNotifier(model) _, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -52,7 +52,7 @@ func TestVictoropsNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewVictoropsNotifier(model) not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
victoropsNotifier := not.(*VictoropsNotifier) victoropsNotifier := not.(*VictoropsNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@@ -76,7 +76,7 @@ func TestVictoropsNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewVictoropsNotifier(model) not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil) So(err, ShouldBeNil)
victoropsNotifier := not.(*VictoropsNotifier) victoropsNotifier := not.(*VictoropsNotifier)
@@ -124,7 +124,7 @@ func TestVictoropsNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewVictoropsNotifier(model) not, err := NewVictoropsNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
So(err, ShouldBeNil) So(err, ShouldBeNil)
victoropsNotifier := not.(*VictoropsNotifier) victoropsNotifier := not.(*VictoropsNotifier)
+4 -2
View File
@@ -1,12 +1,14 @@
package notifiers package notifiers
import ( import (
"context"
"encoding/json" "encoding/json"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/setting"
) )
func init() { func init() {
@@ -58,13 +60,13 @@ func init() {
// NewWebHookNotifier is the constructor for // NewWebHookNotifier is the constructor for
// the WebHook notifier. // 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() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} 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{ return &WebhookNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -21,7 +22,7 @@ func TestWebhookNotifier_parsingFromSettings(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err = NewWebHookNotifier(model) _, err = NewWebHookNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.Error(t, err) require.Error(t, err)
}) })
@@ -36,7 +37,7 @@ func TestWebhookNotifier_parsingFromSettings(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewWebHookNotifier(model) not, err := NewWebHookNotifier(model, ossencryption.ProvideService().GetDecryptedValue)
require.NoError(t, err) require.NoError(t, err)
webhookNotifier := not.(*WebhookNotifier) webhookNotifier := not.(*WebhookNotifier)
+2 -2
View File
@@ -24,10 +24,10 @@ type defaultResultHandler struct {
log log.Logger log log.Logger
} }
func newResultHandler(renderService rendering.Service) *defaultResultHandler { func newResultHandler(renderService rendering.Service, decryptFn GetDecryptedValueFn) *defaultResultHandler {
return &defaultResultHandler{ return &defaultResultHandler{
log: log.New("alerting.resultHandler"), log: log.New("alerting.resultHandler"),
notifier: newNotificationService(renderService), notifier: newNotificationService(renderService, decryptFn),
} }
} }
+3 -3
View File
@@ -83,16 +83,16 @@ func TestAlertRuleForParsing(t *testing.T) {
} }
func TestAlertRuleModel(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) { RegisterCondition("test", func(model *simplejson.Json, index int) (Condition, error) {
return &FakeCondition{}, nil return &FakeCondition{}, nil
}) })
firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"} firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"}
err := sqlstore.CreateAlertNotificationCommand(&firstNotification) err := sqlStore.CreateAlertNotificationCommand(&firstNotification)
require.Nil(t, err) require.Nil(t, err)
secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"} secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"}
err = sqlstore.CreateAlertNotificationCommand(&secondNotification) err = sqlStore.CreateAlertNotificationCommand(&secondNotification)
require.Nil(t, err) require.Nil(t, err)
t.Run("Testing alert rule with notification id and uid", func(t *testing.T) { t.Run("Testing alert rule with notification id and uid", func(t *testing.T) {
+102
View File
@@ -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)
}
+56
View File
@@ -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)
})
}
+14 -12
View File
@@ -6,13 +6,12 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
) )
// NotificationTestCommand initiates an test // NotificationTestCommand initiates an test
@@ -31,12 +30,8 @@ var (
logger = log.New("alerting.testnotification") logger = log.New("alerting.testnotification")
) )
func init() { func (s *AlertNotificationService) HandleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCommand) error {
bus.AddHandlerCtx("alerting", handleNotificationTestCommand) notifier := newNotificationService(nil, nil)
}
func handleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCommand) error {
notifier := newNotificationService(nil)
model := &models.AlertNotification{ model := &models.AlertNotification{
Name: cmd.Name, Name: cmd.Name,
@@ -56,7 +51,11 @@ func handleNotificationTestCommand(ctx context.Context, cmd *NotificationTestCom
} }
if query.Result.SecureSettings != nil { 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 secureSettingsMap[k] = v
} }
model.SecureSettings = securejsondata.GetEncryptedJsonData(secureSettingsMap) var err error
model.SecureSettings, err = s.EncryptionService.EncryptJsonData(ctx, secureSettingsMap, setting.SecretKey)
notifiers, err := InitNotifier(model) if err != nil {
return err
}
notifiers, err := InitNotifier(model, s.EncryptionService.GetDecryptedValue)
if err != nil { if err != nil {
logger.Error("Failed to create notifier", "error", err.Error()) logger.Error("Failed to create notifier", "error", err.Error())
return err return err
@@ -1,6 +1,8 @@
package dashboardsnapshots package dashboardsnapshots
import ( import (
"context"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@@ -22,22 +24,22 @@ func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService enc
EncryptionService: encryptionService, EncryptionService: encryptionService,
} }
s.Bus.AddHandler(s.CreateDashboardSnapshot) s.Bus.AddHandlerCtx(s.CreateDashboardSnapshot)
s.Bus.AddHandler(s.GetDashboardSnapshot) s.Bus.AddHandlerCtx(s.GetDashboardSnapshot)
s.Bus.AddHandler(s.DeleteDashboardSnapshot) s.Bus.AddHandlerCtx(s.DeleteDashboardSnapshot)
s.Bus.AddHandler(s.SearchDashboardSnapshots) s.Bus.AddHandlerCtx(s.SearchDashboardSnapshots)
s.Bus.AddHandler(s.DeleteExpiredSnapshots) s.Bus.AddHandlerCtx(s.DeleteExpiredSnapshots)
return s 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() marshalledData, err := cmd.Dashboard.Encode()
if err != nil { if err != nil {
return err return err
} }
encryptedDashboard, err := s.EncryptionService.Encrypt(marshalledData, setting.SecretKey) encryptedDashboard, err := s.EncryptionService.Encrypt(ctx, marshalledData, setting.SecretKey)
if err != nil { if err != nil {
return err return err
} }
@@ -47,14 +49,14 @@ func (s *Service) CreateDashboardSnapshot(cmd *models.CreateDashboardSnapshotCom
return s.SQLStore.CreateDashboardSnapshot(cmd) 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) err := s.SQLStore.GetDashboardSnapshot(query)
if err != nil { if err != nil {
return err return err
} }
if query.Result.DashboardEncrypted != nil { 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 { if err != nil {
return err return err
} }
@@ -70,14 +72,14 @@ func (s *Service) GetDashboardSnapshot(query *models.GetDashboardSnapshotQuery)
return err 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) 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) 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) return s.SQLStore.DeleteExpiredSnapshots(cmd)
} }
@@ -1,6 +1,7 @@
package dashboardsnapshots package dashboardsnapshots
import ( import (
"context"
"testing" "testing"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
@@ -32,28 +33,32 @@ func TestDashboardSnapshotsService(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("create dashboard snapshot should encrypt the dashboard", func(t *testing.T) { t.Run("create dashboard snapshot should encrypt the dashboard", func(t *testing.T) {
ctx := context.Background()
cmd := models.CreateDashboardSnapshotCommand{ cmd := models.CreateDashboardSnapshotCommand{
Key: dashboardKey, Key: dashboardKey,
DeleteKey: dashboardKey, DeleteKey: dashboardKey,
Dashboard: dashboard, Dashboard: dashboard,
} }
err = s.CreateDashboardSnapshot(&cmd) err = s.CreateDashboardSnapshot(ctx, &cmd)
require.NoError(t, err) 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.NoError(t, err)
require.Equal(t, rawDashboard, decrypted) require.Equal(t, rawDashboard, decrypted)
}) })
t.Run("get dashboard snapshot should return the dashboard decrypted", func(t *testing.T) { t.Run("get dashboard snapshot should return the dashboard decrypted", func(t *testing.T) {
ctx := context.Background()
query := models.GetDashboardSnapshotQuery{ query := models.GetDashboardSnapshotQuery{
Key: dashboardKey, Key: dashboardKey,
DeleteKey: dashboardKey, DeleteKey: dashboardKey,
} }
err := s.GetDashboardSnapshot(&query) err := s.GetDashboardSnapshot(ctx, &query)
require.NoError(t, err) require.NoError(t, err)
decrypted, err := query.Result.Dashboard.Encode() decrypted, err := query.Result.Dashboard.Encode()
@@ -19,7 +19,7 @@ import (
func ProvideService(dataSourceCache datasources.CacheService, plugReqValidator models.PluginRequestValidator, func ProvideService(dataSourceCache datasources.CacheService, plugReqValidator models.PluginRequestValidator,
pm plugins.Manager, cfg *setting.Cfg, httpClientProvider httpclient.Provider, pm plugins.Manager, cfg *setting.Cfg, httpClientProvider httpclient.Provider,
oauthTokenService *oauthtoken.Service) *DataSourceProxyService { oauthTokenService *oauthtoken.Service, dsService *datasources.Service) *DataSourceProxyService {
return &DataSourceProxyService{ return &DataSourceProxyService{
DataSourceCache: dataSourceCache, DataSourceCache: dataSourceCache,
PluginRequestValidator: plugReqValidator, PluginRequestValidator: plugReqValidator,
@@ -27,6 +27,7 @@ func ProvideService(dataSourceCache datasources.CacheService, plugReqValidator m
Cfg: cfg, Cfg: cfg,
HTTPClientProvider: httpClientProvider, HTTPClientProvider: httpClientProvider,
OAuthTokenService: oauthTokenService, OAuthTokenService: oauthTokenService,
DataSourcesService: dsService,
} }
} }
@@ -37,6 +38,7 @@ type DataSourceProxyService struct {
Cfg *setting.Cfg Cfg *setting.Cfg
HTTPClientProvider httpclient.Provider HTTPClientProvider httpclient.Provider
OAuthTokenService *oauthtoken.Service OAuthTokenService *oauthtoken.Service
DataSourcesService *datasources.Service
} }
func (p *DataSourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) { func (p *DataSourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) {
@@ -73,8 +75,10 @@ func (p *DataSourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqConte
return return
} }
proxyPath := getProxyPath(c) proxy, err := pluginproxy.NewDataSourceProxy(
proxy, err := pluginproxy.NewDataSourceProxy(ds, plugin, c, proxyPath, p.Cfg, p.HTTPClientProvider, p.OAuthTokenService) ds, plugin, c, getProxyPath(c), p.Cfg, p.HTTPClientProvider, p.OAuthTokenService, p.DataSourcesService,
)
if err != nil { if err != nil {
if errors.Is(err, datasource.URLValidationError{}) { if errors.Is(err, datasource.URLValidationError{}) {
c.JsonApiErr(http.StatusBadRequest, fmt.Sprintf("Invalid data source URL: %q", ds.Url), err) c.JsonApiErr(http.StatusBadRequest, fmt.Sprintf("Invalid data source URL: %q", ds.Url), err)
+9 -2
View File
@@ -43,11 +43,15 @@ func (dc *CacheServiceImpl) GetDatasource(
} }
plog.Debug("Querying for data source via SQL store", "id", datasourceID, "orgId", user.OrgId) 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 { if err != nil {
return nil, err return nil, err
} }
ds := query.Result
if ds.Uid != "" { if ds.Uid != "" {
dc.CacheService.Set(uidKey(ds.OrgId, ds.Uid), ds, time.Second*5) 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) 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 { if err != nil {
return nil, err return nil, err
} }
ds := query.Result
dc.CacheService.Set(uidCacheKey, ds, time.Second*5) dc.CacheService.Set(uidCacheKey, ds, time.Second*5)
dc.CacheService.Set(idKey(ds.Id), ds, time.Second*5) dc.CacheService.Set(idKey(ds.Id), ds, time.Second*5)
return ds, nil return ds, nil
@@ -1,6 +1,7 @@
package models package datasources
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net/http" "net/http"
@@ -9,29 +10,23 @@ import (
"time" "time"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" 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/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient" "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/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
) )
func (ds *DataSource) getTimeout() time.Duration { type Service struct {
timeout := 0 Bus bus.Bus
if ds.JsonData != nil { SQLStore *sqlstore.SQLStore
timeout = ds.JsonData.Get("timeout").MustInt() EncryptionService encryption.Service
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
}
return time.Duration(timeout) * time.Second ptc proxyTransportCache
dsDecryptionCache secureJSONDecryptionCache
} }
type proxyTransportCache struct { type proxyTransportCache struct {
@@ -44,31 +39,102 @@ type cachedRoundTripper struct {
roundTripper http.RoundTripper roundTripper http.RoundTripper
} }
var ptc = proxyTransportCache{ type secureJSONDecryptionCache struct {
cache: make(map[int64]cachedRoundTripper), cache map[int64]cachedDecryptedJSON
sync.Mutex
} }
func (ds *DataSource) GetHTTPClient(provider httpclient.Provider) (*http.Client, error) { type cachedDecryptedJSON struct {
transport, err := ds.GetHTTPTransport(provider) updated time.Time
json map[string]string
}
func ProvideService(bus bus.Bus, store *sqlstore.SQLStore, encryptionService encryption.Service) *Service {
s := &Service{
Bus: bus,
SQLStore: store,
EncryptionService: encryptionService,
ptc: proxyTransportCache{
cache: make(map[int64]cachedRoundTripper),
},
dsDecryptionCache: secureJSONDecryptionCache{
cache: make(map[int64]cachedDecryptedJSON),
},
}
s.Bus.AddHandler(s.GetDataSources)
s.Bus.AddHandler(s.GetDataSourcesByType)
s.Bus.AddHandler(s.GetDataSource)
s.Bus.AddHandlerCtx(s.AddDataSource)
s.Bus.AddHandler(s.DeleteDataSource)
s.Bus.AddHandlerCtx(s.UpdateDataSource)
s.Bus.AddHandler(s.GetDefaultDataSource)
return s
}
func (s *Service) GetDataSource(query *models.GetDataSourceQuery) error {
return s.SQLStore.GetDataSource(query)
}
func (s *Service) GetDataSources(query *models.GetDataSourcesQuery) error {
return s.SQLStore.GetDataSources(query)
}
func (s *Service) GetDataSourcesByType(query *models.GetDataSourcesByTypeQuery) error {
return s.SQLStore.GetDataSourcesByType(query)
}
func (s *Service) AddDataSource(ctx context.Context, cmd *models.AddDataSourceCommand) error {
var err error
cmd.EncryptedSecureJsonData, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureJsonData, setting.SecretKey)
if err != nil {
return err
}
return s.SQLStore.AddDataSource(cmd)
}
func (s *Service) DeleteDataSource(cmd *models.DeleteDataSourceCommand) error {
return s.SQLStore.DeleteDataSource(cmd)
}
func (s *Service) UpdateDataSource(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
var err error
cmd.EncryptedSecureJsonData, err = s.EncryptionService.EncryptJsonData(ctx, cmd.SecureJsonData, setting.SecretKey)
if err != nil {
return err
}
return s.SQLStore.UpdateDataSource(cmd)
}
func (s *Service) GetDefaultDataSource(query *models.GetDefaultDataSourceQuery) error {
return s.SQLStore.GetDefaultDataSource(query)
}
func (s *Service) GetHTTPClient(ds *models.DataSource, provider httpclient.Provider) (*http.Client, error) {
transport, err := s.GetHTTPTransport(ds, provider)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &http.Client{ return &http.Client{
Timeout: ds.getTimeout(), Timeout: s.getTimeout(ds),
Transport: transport, Transport: transport,
}, nil }, nil
} }
func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddlewares ...sdkhttpclient.Middleware) (http.RoundTripper, error) { func (s *Service) GetHTTPTransport(ds *models.DataSource, provider httpclient.Provider,
ptc.Lock() customMiddlewares ...sdkhttpclient.Middleware) (http.RoundTripper, error) {
defer ptc.Unlock() 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 return t.roundTripper, nil
} }
opts, err := ds.HTTPClientOptions() opts, err := s.httpClientOptions(ds)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -80,7 +146,7 @@ func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddl
return nil, err return nil, err
} }
ptc.cache[ds.Id] = cachedRoundTripper{ s.ptc.cache[ds.Id] = cachedRoundTripper{
roundTripper: rt, roundTripper: rt,
updated: ds.Updated, updated: ds.Updated,
} }
@@ -88,10 +154,60 @@ func (ds *DataSource) GetHTTPTransport(provider httpclient.Provider, customMiddl
return rt, nil return rt, nil
} }
func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) { func (s *Service) GetTLSConfig(ds *models.DataSource, httpClientProvider httpclient.Provider) (*tls.Config, error) {
tlsOptions := ds.TLSOptions() 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{ timeouts := &sdkhttpclient.TimeoutOptions{
Timeout: ds.getTimeout(), Timeout: s.getTimeout(ds),
DialTimeout: sdkhttpclient.DefaultTimeoutOptions.DialTimeout, DialTimeout: sdkhttpclient.DefaultTimeoutOptions.DialTimeout,
KeepAlive: sdkhttpclient.DefaultTimeoutOptions.KeepAlive, KeepAlive: sdkhttpclient.DefaultTimeoutOptions.KeepAlive,
TLSHandshakeTimeout: sdkhttpclient.DefaultTimeoutOptions.TLSHandshakeTimeout, TLSHandshakeTimeout: sdkhttpclient.DefaultTimeoutOptions.TLSHandshakeTimeout,
@@ -103,7 +219,7 @@ func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) {
} }
opts := &sdkhttpclient.Options{ opts := &sdkhttpclient.Options{
Timeouts: timeouts, Timeouts: timeouts,
Headers: getCustomHeaders(ds.JsonData, ds.DecryptedValues()), Headers: s.getCustomHeaders(ds.JsonData, s.DecryptedValues(ds)),
Labels: map[string]string{ Labels: map[string]string{
"datasource_name": ds.Name, "datasource_name": ds.Name,
"datasource_uid": ds.Uid, "datasource_uid": ds.Uid,
@@ -118,17 +234,17 @@ func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) {
if ds.BasicAuth { if ds.BasicAuth {
opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{ opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{
User: ds.BasicAuthUser, User: ds.BasicAuthUser,
Password: ds.DecryptedBasicAuthPassword(), Password: s.DecryptedBasicAuthPassword(ds),
} }
} else if ds.User != "" { } else if ds.User != "" {
opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{ opts.BasicAuth = &sdkhttpclient.BasicAuthOptions{
User: ds.User, User: ds.User,
Password: ds.DecryptedPassword(), Password: s.DecryptedPassword(ds),
} }
} }
if ds.JsonData != nil && ds.JsonData.Get("azureAuth").MustBool() { 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 { if err != nil {
err = fmt.Errorf("invalid Azure credentials: %s", err) err = fmt.Errorf("invalid Azure credentials: %s", err)
return nil, err return nil, err
@@ -150,11 +266,11 @@ func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) {
Profile: ds.JsonData.Get("sigV4Profile").MustString(), 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 opts.SigV4.AccessKey = val
} }
if val, exists := ds.DecryptedValue("sigV4SecretKey"); exists { if val, exists := s.DecryptedValue(ds, "sigV4SecretKey"); exists {
opts.SigV4.SecretKey = val opts.SigV4.SecretKey = val
} }
} }
@@ -162,7 +278,7 @@ func (ds *DataSource) HTTPClientOptions() (*sdkhttpclient.Options, error) {
return opts, nil return opts, nil
} }
func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions { func (s *Service) dsTLSOptions(ds *models.DataSource) sdkhttpclient.TLSOptions {
var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool var tlsSkipVerify, tlsClientAuth, tlsAuthWithCACert bool
var serverName string var serverName string
@@ -180,16 +296,16 @@ func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions {
if tlsClientAuth || tlsAuthWithCACert { if tlsClientAuth || tlsAuthWithCACert {
if 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 opts.CACertificate = val
} }
} }
if tlsClientAuth { 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 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 opts.ClientKey = val
} }
} }
@@ -198,17 +314,28 @@ func (ds *DataSource) TLSOptions() sdkhttpclient.TLSOptions {
return opts return opts
} }
func (ds *DataSource) GetTLSConfig(httpClientProvider httpclient.Provider) (*tls.Config, error) { func (s *Service) getTimeout(ds *models.DataSource) time.Duration {
opts, err := ds.HTTPClientOptions() timeout := 0
if err != nil { if ds.JsonData != nil {
return nil, err 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 // 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 // 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) headers := make(map[string]string)
if jsonData == nil { if jsonData == nil {
return headers return headers
@@ -234,57 +361,11 @@ func getCustomHeaders(jsonData *simplejson.Json, decryptedValues map[string]stri
return headers 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 { func awsServiceNamespace(dsType string) string {
switch dsType { switch dsType {
case DS_ES, DS_ES_OPEN_DISTRO: case models.DS_ES, models.DS_ES_OPEN_DISTRO:
return "es" return "es"
case DS_PROMETHEUS: case models.DS_PROMETHEUS:
return "aps" return "aps"
default: default:
panic(fmt.Sprintf("Unsupported datasource %q", dsType)) panic(fmt.Sprintf("Unsupported datasource %q", dsType))
@@ -1,26 +1,69 @@
package models package datasources
import ( import (
"context"
"io/ioutil" "io/ioutil"
"log"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time" "time"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" 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/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient" "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/setting"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials" "github.com/grafana/grafana/pkg/tsdb/azuremonitor/azcredentials"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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 //nolint:goconst
func TestDataSource_GetHttpTransport(t *testing.T) { func TestService_GetHttpTransport(t *testing.T) {
t.Run("Should use cached proxy", func(t *testing.T) { t.Run("Should use cached proxy", func(t *testing.T) {
var configuredTransport *http.Transport var configuredTransport *http.Transport
provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{ provider := httpclient.NewProvider(sdkhttpclient.ProviderOptions{
@@ -29,19 +72,20 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
}, },
}) })
clearDSProxyCache(t) ds := models.DataSource{
ds := DataSource{
Id: 1, Id: 1,
Url: "http://k8s:8001", Url: "http://k8s:8001",
Type: "Kubernetes", 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.NoError(t, err)
require.NotNil(t, rt1) require.NotNil(t, rt1)
tr1 := configuredTransport tr1 := configuredTransport
rt2, err := ds.GetHTTPTransport(provider) rt2, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, rt2) require.NotNil(t, rt2)
tr2 := configuredTransport tr2 := configuredTransport
@@ -60,15 +104,19 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
configuredTransport = transport configuredTransport = transport
}, },
}) })
clearDSProxyCache(t)
setting.SecretKey = "password" setting.SecretKey = "password"
json := simplejson.New() json := simplejson.New()
json.Set("tlsAuthWithCACert", true) 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) require.NoError(t, err)
ds := DataSource{
ds := models.DataSource{
Id: 1, Id: 1,
Url: "http://k8s:8001", Url: "http://k8s:8001",
Type: "Kubernetes", Type: "Kubernetes",
@@ -76,7 +124,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
Updated: time.Now().Add(-2 * time.Minute), Updated: time.Now().Add(-2 * time.Minute),
} }
rt1, err := ds.GetHTTPTransport(provider) rt1, err := dsService.GetHTTPTransport(&ds, provider)
require.NotNil(t, rt1) require.NotNil(t, rt1)
require.NoError(t, err) require.NoError(t, err)
@@ -90,7 +138,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
ds.SecureJsonData = map[string][]byte{} ds.SecureJsonData = map[string][]byte{}
ds.Updated = time.Now() ds.Updated = time.Now()
rt2, err := ds.GetHTTPTransport(provider) rt2, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, rt2) require.NotNil(t, rt2)
tr2 := configuredTransport tr2 := configuredTransport
@@ -106,18 +154,22 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
configuredTransport = transport configuredTransport = transport
}, },
}) })
clearDSProxyCache(t)
setting.SecretKey = "password" setting.SecretKey = "password"
json := simplejson.New() json := simplejson.New()
json.Set("tlsAuth", true) json.Set("tlsAuth", true)
tlsClientCert, err := util.Encrypt([]byte(clientCert), "password") encryptionService := ossencryption.ProvideService()
require.NoError(t, err) dsService := ProvideService(bus.New(), nil, encryptionService)
tlsClientKey, err := util.Encrypt([]byte(clientKey), "password")
tlsClientCert, err := encryptionService.Encrypt(context.Background(), []byte(clientCert), "password")
require.NoError(t, err) require.NoError(t, err)
ds := DataSource{ tlsClientKey, err := encryptionService.Encrypt(context.Background(), []byte(clientKey), "password")
require.NoError(t, err)
ds := models.DataSource{
Id: 1, Id: 1,
Url: "http://k8s:8001", Url: "http://k8s:8001",
Type: "Kubernetes", 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.NoError(t, err)
require.NotNil(t, rt) require.NotNil(t, rt)
tr := configuredTransport tr := configuredTransport
@@ -144,18 +196,20 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
configuredTransport = transport configuredTransport = transport
}, },
}) })
clearDSProxyCache(t)
ClearDSDecryptionCache()
setting.SecretKey = "password" setting.SecretKey = "password"
json := simplejson.New() json := simplejson.New()
json.Set("tlsAuthWithCACert", true) json.Set("tlsAuthWithCACert", true)
json.Set("serverName", "server-name") 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) require.NoError(t, err)
ds := DataSource{ ds := models.DataSource{
Id: 1, Id: 1,
Url: "http://k8s:8001", Url: "http://k8s:8001",
Type: "Kubernetes", 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.NoError(t, err)
require.NotNil(t, rt) require.NotNil(t, rt)
tr := configuredTransport tr := configuredTransport
@@ -182,24 +236,26 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
configuredTransport = transport configuredTransport = transport
}, },
}) })
clearDSProxyCache(t)
json := simplejson.New() json := simplejson.New()
json.Set("tlsSkipVerify", true) json.Set("tlsSkipVerify", true)
ds := DataSource{ encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
ds := models.DataSource{
Id: 1, Id: 1,
Url: "http://k8s:8001", Url: "http://k8s:8001",
Type: "Kubernetes", Type: "Kubernetes",
JsonData: json, JsonData: json,
} }
rt1, err := ds.GetHTTPTransport(provider) rt1, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, rt1) require.NotNil(t, rt1)
tr1 := configuredTransport tr1 := configuredTransport
rt2, err := ds.GetHTTPTransport(provider) rt2, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, rt2) require.NotNil(t, rt2)
tr2 := configuredTransport 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) { t.Run("Should set custom headers if configured in JsonData", func(t *testing.T) {
provider := httpclient.NewProvider() provider := httpclient.NewProvider()
clearDSProxyCache(t)
ClearDSDecryptionCache()
json := simplejson.NewFromAny(map[string]interface{}{ json := simplejson.NewFromAny(map[string]interface{}{
"httpHeaderName1": "Authorization", "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, Id: 1,
Url: "http://k8s:8001", Url: "http://k8s:8001",
Type: "Kubernetes", Type: "Kubernetes",
@@ -229,7 +285,7 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
SecureJsonData: map[string][]byte{"httpHeaderValue1": encryptedData}, 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"]) require.Equal(t, "Bearer xf5yhfkpsnmgo", headers["Authorization"])
// 1. Start HTTP test server which checks the request headers // 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 // 2. Get HTTP transport from datasource which uses the test server as backend
ds.Url = backend.URL ds.Url = backend.URL
rt, err := ds.GetHTTPTransport(provider) rt, err := dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, rt) 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) { t.Run("Should use request timeout if configured in JsonData", func(t *testing.T) {
provider := httpclient.NewProvider() provider := httpclient.NewProvider()
clearDSProxyCache(t)
json := simplejson.NewFromAny(map[string]interface{}{ json := simplejson.NewFromAny(map[string]interface{}{
"timeout": 19, "timeout": 19,
}) })
ds := DataSource{
encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
ds := models.DataSource{
Id: 1, Id: 1,
Url: "http://k8s:8001", Url: "http://k8s:8001",
Type: "Kubernetes", Type: "Kubernetes",
JsonData: json, JsonData: json,
} }
client, err := ds.GetHTTPClient(provider) client, err := dsService.GetHTTPClient(&ds, provider)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, client) require.NotNil(t, client)
require.Equal(t, 19*time.Second, client.Timeout) require.Equal(t, 19*time.Second, client.Timeout)
@@ -294,7 +353,6 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
configuredOpts = opts configuredOpts = opts
}, },
}) })
clearDSProxyCache(t)
origSigV4Enabled := setting.SigV4AuthEnabled origSigV4Enabled := setting.SigV4AuthEnabled
setting.SigV4AuthEnabled = true setting.SigV4AuthEnabled = true
@@ -305,12 +363,15 @@ func TestDataSource_GetHttpTransport(t *testing.T) {
json, err := simplejson.NewJson([]byte(`{ "sigV4Auth": true }`)) json, err := simplejson.NewJson([]byte(`{ "sigV4Auth": true }`))
require.NoError(t, err) require.NoError(t, err)
ds := DataSource{ encryptionService := ossencryption.ProvideService()
Type: DS_ES, dsService := ProvideService(bus.New(), nil, encryptionService)
ds := models.DataSource{
Type: models.DS_ES,
JsonData: json, JsonData: json,
} }
_, err = ds.GetHTTPTransport(provider) _, err = dsService.GetHTTPTransport(&ds, provider)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, configuredOpts) require.NotNil(t, configuredOpts)
require.NotNil(t, configuredOpts.SigV4) 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 originalTimeout := sdkhttpclient.DefaultTimeoutOptions.Timeout
sdkhttpclient.DefaultTimeoutOptions.Timeout = 60 * time.Second sdkhttpclient.DefaultTimeoutOptions.Timeout = 60 * time.Second
t.Cleanup(func() { 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}, {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 { for _, tc := range testCases {
ds := &DataSource{ ds := &models.DataSource{
JsonData: tc.jsonData, 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) { 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{ encryptedJsonData, err := encryptionService.EncryptJsonData(
Id: 1, context.Background(),
Type: DS_INFLUXDB_08, map[string]string{
JsonData: simplejson.New(),
User: "user",
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{
"password": "password", "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 // Populate cache
password, ok := ds.DecryptedValue("password") password, ok := dsService.DecryptedValue(&ds, "password")
require.True(t, ok) require.True(t, ok)
require.Equal(t, "password", password) require.Equal(t, "password", password)
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ encryptedJsonData, err = encryptionService.EncryptJsonData(
"password": "", 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.True(t, ok)
require.Equal(t, "password", password) require.Equal(t, "password", password)
}) })
t.Run("When datasource is updated, encrypted JSON should not be fetched from cache", func(t *testing.T) { t.Run("When datasource is updated, encrypted JSON should not be fetched from cache", func(t *testing.T) {
ClearDSDecryptionCache() encryptionService := ossencryption.ProvideService()
ds := DataSource{ encryptedJsonData, err := encryptionService.EncryptJsonData(
Id: 1, context.Background(),
Type: DS_INFLUXDB_08, map[string]string{
JsonData: simplejson.New(),
User: "user",
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{
"password": "password", "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 // Populate cache
password, ok := ds.DecryptedValue("password") password, ok := dsService.DecryptedValue(&ds, "password")
require.True(t, ok) require.True(t, ok)
require.Equal(t, "password", password) require.Equal(t, "password", password)
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{ ds.SecureJsonData, err = encryptionService.EncryptJsonData(
"password": "", context.Background(),
}) map[string]string{
"password": "",
}, setting.SecretKey)
ds.Updated = time.Now() ds.Updated = time.Now()
require.NoError(t, err)
password, ok = ds.DecryptedValue("password") password, ok = dsService.DecryptedValue(&ds, "password")
require.True(t, ok) require.True(t, ok)
require.Empty(t, password) require.Empty(t, password)
}) })
} }
func TestDataSource_HTTPClientOptions(t *testing.T) { func TestService_HTTPClientOptions(t *testing.T) {
emptyJsonData := simplejson.New() emptyJsonData := simplejson.New()
emptySecureJsonData := map[string][]byte{} emptySecureJsonData := map[string][]byte{}
ds := DataSource{ ds := models.DataSource{
Id: 1, Id: 1,
Url: "https://api.example.com", Url: "https://api.example.com",
Type: "prometheus", 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.Run("should be disabled if not enabled in JsonData", func(t *testing.T) {
t.Cleanup(func() { ds.JsonData = emptyJsonData; ds.SecureJsonData = emptySecureJsonData }) 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) require.NoError(t, err)
assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"]) assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"])
@@ -429,7 +517,10 @@ func TestDataSource_HTTPClientOptions(t *testing.T) {
"azureAuth": true, "azureAuth": true,
}) })
opts, err := ds.HTTPClientOptions() encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
opts, err := dsService.httpClientOptions(&ds)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, true, opts.CustomOptions["_azureAuth"]) 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) require.NoError(t, err)
assert.Equal(t, true, opts.CustomOptions["_azureAuth"]) 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) require.NoError(t, err)
assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"]) assert.NotEqual(t, true, opts.CustomOptions["_azureAuth"])
@@ -482,7 +579,10 @@ func TestDataSource_HTTPClientOptions(t *testing.T) {
"azureCredentials": "invalid", "azureCredentials": "invalid",
}) })
_, err := ds.HTTPClientOptions() encryptionService := ossencryption.ProvideService()
dsService := ProvideService(bus.New(), nil, encryptionService)
_, err := dsService.httpClientOptions(&ds)
assert.Error(t, err) assert.Error(t, err)
}) })
@@ -493,7 +593,10 @@ func TestDataSource_HTTPClientOptions(t *testing.T) {
"azureEndpointResourceId": "https://api.example.com/abd5c4ce-ca73-41e9-9cb2-bed39aa2adb5", "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.NoError(t, err)
require.Contains(t, opts.CustomOptions, "azureEndpointResourceId") 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----- const caCert string = `-----BEGIN CERTIFICATE-----
MIIDATCCAemgAwIBAgIJAMQ5hC3CPDTeMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV MIIDATCCAemgAwIBAgIJAMQ5hC3CPDTeMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
BAMMDGNhLWs4cy1zdGhsbTAeFw0xNjEwMjcwODQyMjdaFw00NDAzMTQwODQyMjda BAMMDGNhLWs4cy1zdGhsbTAeFw0xNjEwMjcwODQyMjdaFw00NDAzMTQwODQyMjda
+9 -2
View File
@@ -1,6 +1,13 @@
package encryption package encryption
import "context"
type Service interface { type Service interface {
Encrypt([]byte, string) ([]byte, error) Encrypt(ctx context.Context, payload []byte, secret string) ([]byte, error)
Decrypt([]byte, 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 package ossencryption
import ( import (
"context"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
@@ -21,7 +22,7 @@ func ProvideService() *Service {
const saltLength = 8 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 { if len(payload) < saltLength {
return nil, fmt.Errorf("unable to compute salt") 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 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) salt, err := util.GetRandomString(saltLength)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -82,6 +83,45 @@ func (s *Service) Encrypt(payload []byte, secret string) ([]byte, error) {
return ciphertext, nil 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 // Key needs to be 32bytes
func encryptionKeyToBytes(secret, salt string) ([]byte, error) { func encryptionKeyToBytes(secret, salt string) ([]byte, error) {
return pbkdf2.Key([]byte(secret), []byte(salt), 10000, 32, sha256.New), nil return pbkdf2.Key([]byte(secret), []byte(salt), 10000, 32, sha256.New), nil
@@ -1,6 +1,7 @@
package ossencryption package ossencryption
import ( import (
"context"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -21,17 +22,19 @@ func TestEncryption(t *testing.T) {
}) })
t.Run("decrypting basic payload", func(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) require.NoError(t, err)
decrypted, err := svc.Decrypt(encrypted, "1234") decrypted, err := svc.Decrypt(ctx, encrypted, "1234")
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []byte("grafana"), decrypted) assert.Equal(t, []byte("grafana"), decrypted)
}) })
t.Run("decrypting empty payload should return error", func(t *testing.T) { 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) require.Error(t, err)
assert.Equal(t, "unable to compute salt", err.Error()) assert.Equal(t, "unable to compute salt", err.Error())
@@ -169,7 +169,7 @@ func (s *Implementation) decodeAndDecrypt(str string) (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
decrypted, err := s.EncryptionService.Decrypt(decoded, setting.SecretKey) decrypted, err := s.EncryptionService.Decrypt(context.Background(), decoded, setting.SecretKey)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -179,7 +179,7 @@ func (s *Implementation) decodeAndDecrypt(str string) (string, error) {
// encryptAndEncode will encrypt a string with grafana's secretKey, and // encryptAndEncode will encrypt a string with grafana's secretKey, and
// then encode it with the standard bas64 encoder // then encode it with the standard bas64 encoder
func (s *Implementation) encryptAndEncode(str string) (string, error) { 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 { if err != nil {
return "", err return "", err
} }
+3 -1
View File
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources" "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" 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/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/ngalert/notifier"
@@ -63,6 +64,7 @@ type API struct {
DataProxy *datasourceproxy.DataSourceProxyService DataProxy *datasourceproxy.DataSourceProxyService
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
StateManager *state.Manager StateManager *state.Manager
EncryptionService encryption.Service
} }
// RegisterAPIEndpoints registers API handlers // RegisterAPIEndpoints registers API handlers
@@ -76,7 +78,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
api.RegisterAlertmanagerApiEndpoints(NewForkedAM( api.RegisterAlertmanagerApiEndpoints(NewForkedAM(
api.DatasourceCache, api.DatasourceCache,
NewLotexAM(proxy, logger), 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) ), m)
// Register endpoints for proxying to Prometheus-compatible backends. // Register endpoints for proxying to Prometheus-compatible backends.
api.RegisterPrometheusApiEndpoints(NewForkedProm( api.RegisterPrometheusApiEndpoints(NewForkedProm(
+27 -4
View File
@@ -2,6 +2,7 @@ package api
import ( import (
"context" "context"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@@ -12,10 +13,12 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/encryption"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"gopkg.in/macaron.v1" "gopkg.in/macaron.v1"
) )
@@ -27,6 +30,7 @@ const (
type AlertmanagerSrv struct { type AlertmanagerSrv struct {
mam *notifier.MultiOrgAlertmanager mam *notifier.MultiOrgAlertmanager
enc encryption.Service
store store.AlertingStore store store.AlertingStore
log log.Logger log log.Logger
} }
@@ -76,7 +80,7 @@ func (srv AlertmanagerSrv) loadSecureSettings(orgId int64, receivers []*apimodel
for key := range cgmr.SecureSettings { for key := range cgmr.SecureSettings {
_, ok := gr.SecureSettings[key] _, ok := gr.SecureSettings[key]
if !ok { if !ok {
decryptedValue, err := cgmr.GetDecryptedSecret(key) decryptedValue, err := srv.getDecryptedSecret(cgmr, key)
if err != nil { if err != nil {
return fmt.Errorf("failed to decrypt stored secure setting: %s: %w", key, err) 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 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 { func (srv AlertmanagerSrv) RouteGetAMStatus(c *models.ReqContext) response.Response {
am, errResp := srv.AlertmanagerFor(c.OrgId) am, errResp := srv.AlertmanagerFor(c.OrgId)
if errResp != nil { if errResp != nil {
@@ -194,7 +217,7 @@ func (srv AlertmanagerSrv) RouteGetAlertingConfig(c *models.ReqContext) response
for _, pr := range recv.PostableGrafanaReceivers.GrafanaManagedReceivers { for _, pr := range recv.PostableGrafanaReceivers.GrafanaManagedReceivers {
secureFields := make(map[string]bool, len(pr.SecureSettings)) secureFields := make(map[string]bool, len(pr.SecureSettings))
for k := range pr.SecureSettings { for k := range pr.SecureSettings {
decryptedValue, err := pr.GetDecryptedSecret(k) decryptedValue, err := srv.getDecryptedSecret(pr, k)
if err != nil { if err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to decrypt stored secure setting: %s", k) 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, "") 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") 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, "") 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") return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration")
} }
@@ -1,6 +1,7 @@
package definitions package definitions
import ( import (
"context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
@@ -9,16 +10,15 @@ import (
"time" "time"
"github.com/go-openapi/strfmt" "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" "github.com/pkg/errors"
amv2 "github.com/prometheus/alertmanager/api/v2/models" amv2 "github.com/prometheus/alertmanager/api/v2/models"
"github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"gopkg.in/yaml.v3" "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 // 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"` Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
} }
func (c *TestReceiversConfigParams) ProcessConfig() error { func (c *TestReceiversConfigParams) ProcessConfig(encrypt EncryptFn) error {
return processReceiverConfigs(c.Receivers) return processReceiverConfigs(c.Receivers, encrypt)
} }
// swagger:model // 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) // ProcessConfig parses grafana receivers, encrypts secrets and assigns UUIDs (if they are missing)
func (c *PostableUserConfig) ProcessConfig() error { func (c *PostableUserConfig) ProcessConfig(encrypt EncryptFn) error {
return processReceiverConfigs(c.AlertmanagerConfig.Receivers) return processReceiverConfigs(c.AlertmanagerConfig.Receivers, encrypt)
} }
// MarshalYAML implements yaml.Marshaller. // MarshalYAML implements yaml.Marshaller.
@@ -870,22 +870,6 @@ type PostableGrafanaReceiver struct {
SecureSettings map[string]string `json:"secureSettings"` 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 type ReceiverType int
const ( const (
@@ -1061,7 +1045,9 @@ type PostableGrafanaReceivers struct {
GrafanaManagedReceivers []*PostableGrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"` 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{}) seenUIDs := make(map[string]struct{})
// encrypt secure settings for storing them in DB // encrypt secure settings for storing them in DB
for _, r := range c { for _, r := range c {
@@ -1069,7 +1055,7 @@ func processReceiverConfigs(c []*PostableApiReceiver) error {
case GrafanaReceiverType: case GrafanaReceiverType:
for _, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers { for _, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers {
for k, v := range gr.SecureSettings { 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 { if err != nil {
return fmt.Errorf("failed to encrypt secure settings: %w", err) return fmt.Errorf("failed to encrypt secure settings: %w", err)
} }
+30 -26
View File
@@ -5,11 +5,13 @@ import (
"net/url" "net/url"
"time" "time"
"github.com/benbjohnson/clock"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/datasourceproxy" "github.com/grafana/grafana/pkg/services/datasourceproxy"
"github.com/grafana/grafana/pkg/services/datasources" "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/api"
"github.com/grafana/grafana/pkg/services/ngalert/eval" "github.com/grafana/grafana/pkg/services/ngalert/eval"
"github.com/grafana/grafana/pkg/services/ngalert/metrics" "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/services/sqlstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb" "github.com/grafana/grafana/pkg/tsdb"
"github.com/benbjohnson/clock"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
@@ -39,18 +39,19 @@ const (
func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, routeRegister routing.RouteRegister, func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, routeRegister routing.RouteRegister,
sqlStore *sqlstore.SQLStore, kvStore kvstore.KVStore, dataService *tsdb.Service, dataProxy *datasourceproxy.DataSourceProxyService, 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{ ng := &AlertNG{
Cfg: cfg, Cfg: cfg,
DataSourceCache: dataSourceCache, DataSourceCache: dataSourceCache,
RouteRegister: routeRegister, RouteRegister: routeRegister,
SQLStore: sqlStore, SQLStore: sqlStore,
KVStore: kvStore, KVStore: kvStore,
DataService: dataService, DataService: dataService,
DataProxy: dataProxy, DataProxy: dataProxy,
QuotaService: quotaService, QuotaService: quotaService,
Metrics: m, EncryptionService: encryptionService,
Log: log.New("ngalert"), Metrics: m,
Log: log.New("ngalert"),
} }
if ng.IsDisabled() { 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. // AlertNG is the service for evaluating the condition of an alert definition.
type AlertNG struct { type AlertNG struct {
Cfg *setting.Cfg Cfg *setting.Cfg
DataSourceCache datasources.CacheService DataSourceCache datasources.CacheService
RouteRegister routing.RouteRegister RouteRegister routing.RouteRegister
SQLStore *sqlstore.SQLStore SQLStore *sqlstore.SQLStore
KVStore kvstore.KVStore KVStore kvstore.KVStore
DataService *tsdb.Service DataService *tsdb.Service
DataProxy *datasourceproxy.DataSourceProxyService DataProxy *datasourceproxy.DataSourceProxyService
QuotaService *quota.QuotaService QuotaService *quota.QuotaService
Metrics *metrics.NGAlert EncryptionService encryption.Service
Log log.Logger Metrics *metrics.NGAlert
schedule schedule.ScheduleService Log log.Logger
stateManager *state.Manager schedule schedule.ScheduleService
stateManager *state.Manager
// Alerting notification services // Alerting notification services
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
@@ -99,8 +101,9 @@ func (ng *AlertNG) init() error {
Logger: ng.Log, Logger: ng.Log,
} }
decryptFn := ng.EncryptionService.GetDecryptedValue
multiOrgMetrics := ng.Metrics.GetMultiOrgAlertmanagerMetrics() 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 { if err != nil {
return err return err
} }
@@ -146,6 +149,7 @@ func (ng *AlertNG) init() error {
Schedule: ng.schedule, Schedule: ng.schedule,
DataProxy: ng.DataProxy, DataProxy: ng.DataProxy,
QuotaService: ng.QuotaService, QuotaService: ng.QuotaService,
EncryptionService: ng.EncryptionService,
InstanceStore: store, InstanceStore: store,
RuleStore: store, RuleStore: store,
AlertingStore: store, AlertingStore: store,
+16 -13
View File
@@ -31,7 +31,6 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model" "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/kvstore"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
@@ -118,9 +117,12 @@ type Alertmanager struct {
config *apimodels.PostableUserConfig config *apimodels.PostableUserConfig
configHash [16]byte configHash [16]byte
orgID int64 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{ am := &Alertmanager{
Settings: cfg, Settings: cfg,
stopc: make(chan struct{}), stopc: make(chan struct{}),
@@ -133,6 +135,7 @@ func newAlertmanager(orgID int64, cfg *setting.Cfg, store store.AlertingStore, k
peerTimeout: cfg.UnifiedAlerting.HAPeerTimeout, peerTimeout: cfg.UnifiedAlerting.HAPeerTimeout,
Metrics: m, Metrics: m,
orgID: orgID, orgID: orgID,
decryptFn: decryptFn,
} }
am.gokitLogger = gokit_log.NewLogfmtLogger(logging.NewWrapper(am.logger)) 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) { func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaReceiver, tmpl *template.Template) (NotificationChannel, error) {
// secure settings are already encrypted at this point // 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 { for k, v := range r.SecureSettings {
d, err := base64.StdEncoding.DecodeString(v) d, err := base64.StdEncoding.DecodeString(v)
@@ -501,13 +504,13 @@ func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaRec
case "email": case "email":
n, err = channels.NewEmailNotifier(cfg, tmpl) // Email notifier already has a default template. n, err = channels.NewEmailNotifier(cfg, tmpl) // Email notifier already has a default template.
case "pagerduty": case "pagerduty":
n, err = channels.NewPagerdutyNotifier(cfg, tmpl) n, err = channels.NewPagerdutyNotifier(cfg, tmpl, am.decryptFn)
case "pushover": case "pushover":
n, err = channels.NewPushoverNotifier(cfg, tmpl) n, err = channels.NewPushoverNotifier(cfg, tmpl, am.decryptFn)
case "slack": case "slack":
n, err = channels.NewSlackNotifier(cfg, tmpl) n, err = channels.NewSlackNotifier(cfg, tmpl, am.decryptFn)
case "telegram": case "telegram":
n, err = channels.NewTelegramNotifier(cfg, tmpl) n, err = channels.NewTelegramNotifier(cfg, tmpl, am.decryptFn)
case "victorops": case "victorops":
n, err = channels.NewVictoropsNotifier(cfg, tmpl) n, err = channels.NewVictoropsNotifier(cfg, tmpl)
case "teams": case "teams":
@@ -517,21 +520,21 @@ func (am *Alertmanager) buildReceiverIntegration(r *apimodels.PostableGrafanaRec
case "kafka": case "kafka":
n, err = channels.NewKafkaNotifier(cfg, tmpl) n, err = channels.NewKafkaNotifier(cfg, tmpl)
case "webhook": case "webhook":
n, err = channels.NewWebHookNotifier(cfg, tmpl) n, err = channels.NewWebHookNotifier(cfg, tmpl, am.decryptFn)
case "sensugo": case "sensugo":
n, err = channels.NewSensuGoNotifier(cfg, tmpl) n, err = channels.NewSensuGoNotifier(cfg, tmpl, am.decryptFn)
case "discord": case "discord":
n, err = channels.NewDiscordNotifier(cfg, tmpl) n, err = channels.NewDiscordNotifier(cfg, tmpl)
case "googlechat": case "googlechat":
n, err = channels.NewGoogleChatNotifier(cfg, tmpl) n, err = channels.NewGoogleChatNotifier(cfg, tmpl)
case "LINE": case "LINE":
n, err = channels.NewLineNotifier(cfg, tmpl) n, err = channels.NewLineNotifier(cfg, tmpl, am.decryptFn)
case "threema": case "threema":
n, err = channels.NewThreemaNotifier(cfg, tmpl) n, err = channels.NewThreemaNotifier(cfg, tmpl, am.decryptFn)
case "opsgenie": case "opsgenie":
n, err = channels.NewOpsgenieNotifier(cfg, tmpl) n, err = channels.NewOpsgenieNotifier(cfg, tmpl, am.decryptFn)
case "prometheus-alertmanager": case "prometheus-alertmanager":
n, err = channels.NewAlertmanagerNotifier(cfg, tmpl) n, err = channels.NewAlertmanagerNotifier(cfg, tmpl, am.decryptFn)
default: default:
return nil, InvalidReceiverError{ return nil, InvalidReceiverError{
Receiver: r, Receiver: r,
@@ -9,23 +9,22 @@ import (
"testing" "testing"
"time" "time"
"github.com/grafana/grafana/pkg/infra/log"
gokit_log "github.com/go-kit/kit/log" gokit_log "github.com/go-kit/kit/log"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/prometheus/alertmanager/api/v2/models" "github.com/grafana/grafana/pkg/infra/log"
"github.com/prometheus/alertmanager/provider/mem" "github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/types"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" 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/logging"
"github.com/grafana/grafana/pkg/services/ngalert/metrics" "github.com/grafana/grafana/pkg/services/ngalert/metrics"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting" "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 { func setupAMTest(t *testing.T) *Alertmanager {
@@ -48,7 +47,8 @@ func setupAMTest(t *testing.T) *Alertmanager {
} }
kvStore := newFakeKVStore(t) 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) require.NoError(t, err)
return am return am
} }
@@ -10,12 +10,17 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" 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/template"
"github.com/prometheus/alertmanager/types" "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. // 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 { if model.Settings == nil {
return nil, receiverInitError{Reason: "no settings supplied"} return nil, receiverInitError{Reason: "no settings supplied"}
} }
@@ -41,7 +46,7 @@ func NewAlertmanagerNotifier(model *NotificationChannelConfig, t *template.Templ
urls = append(urls, u) urls = append(urls, u)
} }
basicAuthUser := model.Settings.Get("basicAuthUser").MustString() 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{ return &AlertmanagerNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{
@@ -6,6 +6,8 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@@ -77,7 +79,8 @@ func TestAlertmanagerNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
sn, err := NewAlertmanagerNotifier(m, tmpl) decryptFn := ossencryption.ProvideService().GetDecryptedValue
sn, err := NewAlertmanagerNotifier(m, tmpl, decryptFn)
if c.expInitError != "" { if c.expInitError != "" {
require.Equal(t, c.expInitError, err.Error()) require.Equal(t, c.expInitError, err.Error())
return return
@@ -6,13 +6,13 @@ import (
"net/url" "net/url"
"path" "path"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" 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 ( var (
@@ -20,8 +20,8 @@ var (
) )
// NewLineNotifier is the constructor for the LINE notifier // NewLineNotifier is the constructor for the LINE notifier
func NewLineNotifier(model *NotificationChannelConfig, t *template.Template) (*LineNotifier, error) { func NewLineNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*LineNotifier, error) {
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 == "" { if token == "" {
return nil, receiverInitError{Cfg: *model, Reason: "could not find token in settings"} return nil, receiverInitError{Cfg: *model, Reason: "could not find token in settings"}
} }
@@ -5,6 +5,8 @@ import (
"net/url" "net/url"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@@ -88,7 +90,8 @@ func TestLineNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
pn, err := NewLineNotifier(m, tmpl) decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewLineNotifier(m, tmpl, decryptFn)
if c.expInitError != "" { if c.expInitError != "" {
require.Error(t, err) require.Error(t, err)
require.Equal(t, c.expInitError, err.Error()) require.Equal(t, c.expInitError, err.Error())
@@ -7,16 +7,16 @@ import (
"net/http" "net/http"
"sort" "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/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" 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 ( const (
@@ -43,10 +43,10 @@ type OpsgenieNotifier struct {
} }
// NewOpsgenieNotifier is the constructor for the Opsgenie notifier // 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) autoClose := model.Settings.Get("autoClose").MustBool(true)
overridePriority := model.Settings.Get("overridePriority").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() apiURL := model.Settings.Get("apiUrl").MustString()
if apiKey == "" { if apiKey == "" {
return nil, receiverInitError{Cfg: *model, Reason: "could not find api key property in settings"} return nil, receiverInitError{Cfg: *model, Reason: "could not find api key property in settings"}
@@ -6,6 +6,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@@ -168,7 +170,8 @@ func TestOpsgenieNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
pn, err := NewOpsgenieNotifier(m, tmpl) decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewOpsgenieNotifier(m, tmpl, decryptFn)
if c.expInitError != "" { if c.expInitError != "" {
require.Error(t, err) require.Error(t, err)
require.Equal(t, c.expInitError, err.Error()) require.Equal(t, c.expInitError, err.Error())
@@ -6,15 +6,15 @@ import (
"fmt" "fmt"
"os" "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/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" 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 ( const (
@@ -42,12 +42,12 @@ type PagerdutyNotifier struct {
} }
// NewPagerdutyNotifier is the constructor for the PagerDuty notifier // 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 { if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"} 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 == "" { if key == "" {
return nil, receiverInitError{Cfg: *model, Reason: "could not find integration key property in settings"} return nil, receiverInitError{Cfg: *model, Reason: "could not find integration key property in settings"}
} }
@@ -7,6 +7,8 @@ import (
"os" "os"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@@ -134,7 +136,8 @@ func TestPagerdutyNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
pn, err := NewPagerdutyNotifier(m, tmpl) decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewPagerdutyNotifier(m, tmpl, decryptFn)
if c.expInitError != "" { if c.expInitError != "" {
require.Error(t, err) require.Error(t, err)
require.Equal(t, c.expInitError, err.Error()) require.Equal(t, c.expInitError, err.Error())
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" 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/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@@ -40,13 +41,13 @@ type PushoverNotifier struct {
} }
// NewSlackNotifier is the constructor for the Slack notifier // 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 { if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"} return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"}
} }
userKey := model.DecryptedValue("userKey", model.Settings.Get("userKey").MustString()) userKey := fn(context.Background(), model.SecureSettings, "userKey", model.Settings.Get("userKey").MustString(), setting.SecretKey)
APIToken := model.DecryptedValue("apiToken", model.Settings.Get("apiToken").MustString()) APIToken := fn(context.Background(), model.SecureSettings, "apiToken", model.Settings.Get("apiToken").MustString(), setting.SecretKey)
device := model.Settings.Get("device").MustString() device := model.Settings.Get("device").MustString()
alertingPriority, err := strconv.Atoi(model.Settings.Get("priority").MustString("0")) // default Normal alertingPriority, err := strconv.Atoi(model.Settings.Get("priority").MustString("0")) // default Normal
if err != nil { if err != nil {
@@ -11,6 +11,8 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@@ -141,7 +143,8 @@ func TestPushoverNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
pn, err := NewPushoverNotifier(m, tmpl) decryptFn := ossencryption.ProvideService().GetDecryptedValue
pn, err := NewPushoverNotifier(m, tmpl, decryptFn)
if c.expInitError != "" { if c.expInitError != "" {
require.Error(t, err) require.Error(t, err)
require.Equal(t, c.expInitError, err.Error()) require.Equal(t, c.expInitError, err.Error())
@@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" 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/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@@ -31,7 +32,7 @@ type SensuGoNotifier struct {
} }
// NewSensuGoNotifier is the constructor for the SensuGo notifier // 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 { if model.Settings == nil {
return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"} 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"} 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 == "" { if apikey == "" {
return nil, receiverInitError{Cfg: *model, Reason: "could not find the API key property in settings"} 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