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 16:33:50 +02:00
committed by GitHub
parent da813877fb
commit 722c414fef
141 changed files with 1968 additions and 1197 deletions

View File

@@ -1,6 +1,7 @@
package api
import (
"context"
"errors"
"fmt"
"strconv"
@@ -13,6 +14,7 @@ import (
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
macaron "gopkg.in/macaron.v1"
)
@@ -287,10 +289,10 @@ func CreateAlertNotification(c *models.ReqContext, cmd models.CreateAlertNotific
return response.JSON(200, dtos.NewAlertNotification(cmd.Result))
}
func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) response.Response {
func (hs *HTTPServer) UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotificationCommand) response.Response {
cmd.OrgId = c.OrgId
err := fillWithSecureSettingsData(&cmd)
err := hs.fillWithSecureSettingsData(c.Req.Context(), &cmd)
if err != nil {
return response.Error(500, "Failed to update alert notification", err)
}
@@ -314,11 +316,11 @@ func UpdateAlertNotification(c *models.ReqContext, cmd models.UpdateAlertNotific
return response.JSON(200, dtos.NewAlertNotification(query.Result))
}
func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) response.Response {
func (hs *HTTPServer) UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNotificationWithUidCommand) response.Response {
cmd.OrgId = c.OrgId
cmd.Uid = macaron.Params(c.Req)[":uid"]
err := fillWithSecureSettingsDataByUID(&cmd)
err := hs.fillWithSecureSettingsDataByUID(c.Req.Context(), &cmd)
if err != nil {
return response.Error(500, "Failed to update alert notification", err)
}
@@ -342,7 +344,7 @@ func UpdateAlertNotificationByUID(c *models.ReqContext, cmd models.UpdateAlertNo
return response.JSON(200, dtos.NewAlertNotification(query.Result))
}
func fillWithSecureSettingsData(cmd *models.UpdateAlertNotificationCommand) error {
func (hs *HTTPServer) fillWithSecureSettingsData(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error {
if len(cmd.SecureSettings) == 0 {
return nil
}
@@ -352,11 +354,15 @@ func fillWithSecureSettingsData(cmd *models.UpdateAlertNotificationCommand) erro
Id: cmd.Id,
}
if err := bus.Dispatch(query); err != nil {
if err := bus.DispatchCtx(ctx, query); err != nil {
return err
}
secureSettings, err := hs.EncryptionService.DecryptJsonData(ctx, query.Result.SecureSettings, setting.SecretKey)
if err != nil {
return err
}
secureSettings := query.Result.SecureSettings.Decrypt()
for k, v := range secureSettings {
if _, ok := cmd.SecureSettings[k]; !ok {
cmd.SecureSettings[k] = v
@@ -366,7 +372,7 @@ func fillWithSecureSettingsData(cmd *models.UpdateAlertNotificationCommand) erro
return nil
}
func fillWithSecureSettingsDataByUID(cmd *models.UpdateAlertNotificationWithUidCommand) error {
func (hs *HTTPServer) fillWithSecureSettingsDataByUID(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error {
if len(cmd.SecureSettings) == 0 {
return nil
}
@@ -376,11 +382,15 @@ func fillWithSecureSettingsDataByUID(cmd *models.UpdateAlertNotificationWithUidC
Uid: cmd.Uid,
}
if err := bus.Dispatch(query); err != nil {
if err := bus.DispatchCtx(ctx, query); err != nil {
return err
}
secureSettings, err := hs.EncryptionService.DecryptJsonData(ctx, query.Result.SecureSettings, setting.SecretKey)
if err != nil {
return err
}
secureSettings := query.Result.SecureSettings.Decrypt()
for k, v := range secureSettings {
if _, ok := cmd.SecureSettings[k]; !ok {
cmd.SecureSettings[k] = v

View File

@@ -394,11 +394,11 @@ func (hs *HTTPServer) registerRoutes() {
alertNotifications.Get("/", routing.Wrap(GetAlertNotifications))
alertNotifications.Post("/test", bind(dtos.NotificationTestCommand{}), routing.Wrap(NotificationTest))
alertNotifications.Post("/", bind(models.CreateAlertNotificationCommand{}), routing.Wrap(CreateAlertNotification))
alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), routing.Wrap(UpdateAlertNotification))
alertNotifications.Put("/:notificationId", bind(models.UpdateAlertNotificationCommand{}), routing.Wrap(hs.UpdateAlertNotification))
alertNotifications.Get("/:notificationId", routing.Wrap(GetAlertNotificationByID))
alertNotifications.Delete("/:notificationId", routing.Wrap(DeleteAlertNotification))
alertNotifications.Get("/uid/:uid", routing.Wrap(GetAlertNotificationByUID))
alertNotifications.Put("/uid/:uid", bind(models.UpdateAlertNotificationWithUidCommand{}), routing.Wrap(UpdateAlertNotificationByUID))
alertNotifications.Put("/uid/:uid", bind(models.UpdateAlertNotificationWithUidCommand{}), routing.Wrap(hs.UpdateAlertNotificationByUID))
alertNotifications.Delete("/uid/:uid", routing.Wrap(DeleteAlertNotificationByUID))
}, reqEditorRole)

View File

@@ -60,7 +60,7 @@ func AppPluginRoute(route *plugins.AppPluginRoute, appID string, hs *HTTPServer)
return func(c *models.ReqContext) {
path := macaron.Params(c.Req)["*"]
proxy := pluginproxy.NewApiPluginProxy(c, path, route, appID, hs.Cfg)
proxy := pluginproxy.NewApiPluginProxy(c, path, route, appID, hs.Cfg, hs.EncryptionService)
proxy.Transport = pluginProxyTransport
proxy.ServeHTTP(c.Resp, c.Req)
}

View File

@@ -1,11 +1,13 @@
package api
import (
"context"
"encoding/json"
"errors"
"fmt"
"sort"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/datasource"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
@@ -13,10 +15,9 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/adapters"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
macaron "gopkg.in/macaron.v1"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"gopkg.in/macaron.v1"
)
var datasourcesLogger = log.New("datasources")
@@ -92,7 +93,7 @@ func (hs *HTTPServer) DeleteDataSourceById(c *models.ReqContext) response.Respon
return response.Error(400, "Missing valid datasource id", nil)
}
ds, err := getRawDataSourceById(id, c.OrgId)
ds, err := getRawDataSourceById(c.Req.Context(), id, c.OrgId)
if err != nil {
if errors.Is(err, models.ErrDataSourceNotFound) {
return response.Error(404, "Data source not found", nil)
@@ -106,7 +107,7 @@ func (hs *HTTPServer) DeleteDataSourceById(c *models.ReqContext) response.Respon
cmd := &models.DeleteDataSourceCommand{ID: id, OrgID: c.OrgId}
err = bus.Dispatch(cmd)
err = bus.DispatchCtx(c.Req.Context(), cmd)
if err != nil {
return response.Error(500, "Failed to delete datasource", err)
}
@@ -240,7 +241,7 @@ func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext, cmd models.UpdateDa
return resp
}
err := fillWithSecureJSONData(&cmd)
err := hs.fillWithSecureJSONData(c.Req.Context(), &cmd)
if err != nil {
return response.Error(500, "Failed to update datasource", err)
}
@@ -277,12 +278,12 @@ func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext, cmd models.UpdateDa
})
}
func fillWithSecureJSONData(cmd *models.UpdateDataSourceCommand) error {
func (hs *HTTPServer) fillWithSecureJSONData(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
if len(cmd.SecureJsonData) == 0 {
return nil
}
ds, err := getRawDataSourceById(cmd.Id, cmd.OrgId)
ds, err := getRawDataSourceById(ctx, cmd.Id, cmd.OrgId)
if err != nil {
return err
}
@@ -291,7 +292,11 @@ func fillWithSecureJSONData(cmd *models.UpdateDataSourceCommand) error {
return models.ErrDatasourceIsReadOnly
}
secureJSONData := ds.SecureJsonData.Decrypt()
secureJSONData, err := hs.EncryptionService.DecryptJsonData(ctx, ds.SecureJsonData, setting.SecretKey)
if err != nil {
return err
}
for k, v := range secureJSONData {
if _, ok := cmd.SecureJsonData[k]; !ok {
cmd.SecureJsonData[k] = v
@@ -301,13 +306,13 @@ func fillWithSecureJSONData(cmd *models.UpdateDataSourceCommand) error {
return nil
}
func getRawDataSourceById(id int64, orgID int64) (*models.DataSource, error) {
func getRawDataSourceById(ctx context.Context, id int64, orgID int64) (*models.DataSource, error) {
query := models.GetDataSourceQuery{
Id: id,
OrgId: orgID,
}
if err := bus.Dispatch(&query); err != nil {
if err := bus.DispatchCtx(ctx, &query); err != nil {
return nil, err
}
@@ -381,7 +386,7 @@ func (hs *HTTPServer) CallDatasourceResource(c *models.ReqContext) {
return
}
dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds)
dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds, hs.decryptSecureJsonDataFn())
if err != nil {
c.JsonApiErr(500, "Unable to process datasource instance model", err)
}
@@ -445,7 +450,7 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo
return response.Error(500, "Unable to find datasource plugin", err)
}
dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds)
dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds, hs.decryptSecureJsonDataFn())
if err != nil {
return response.Error(500, "Unable to get datasource model", err)
}
@@ -483,3 +488,13 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo
return response.JSON(200, payload)
}
func (hs *HTTPServer) decryptSecureJsonDataFn() func(map[string][]byte) map[string]string {
return func(m map[string][]byte) map[string]string {
decryptedJsonData, err := hs.EncryptionService.DecryptJsonData(context.Background(), m, setting.SecretKey)
if err != nil {
hs.log.Error("Failed to decrypt secure json data", "error", err)
}
return decryptedJsonData
}
}

View File

@@ -77,7 +77,10 @@ func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins *plu
if ds.Access == models.DS_ACCESS_DIRECT {
if ds.BasicAuth {
dsMap["basicAuth"] = util.GetBasicAuthHeader(ds.BasicAuthUser, ds.DecryptedBasicAuthPassword())
dsMap["basicAuth"] = util.GetBasicAuthHeader(
ds.BasicAuthUser,
hs.DataSourcesService.DecryptedBasicAuthPassword(ds),
)
}
if ds.WithCredentials {
dsMap["withCredentials"] = ds.WithCredentials
@@ -85,13 +88,13 @@ func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins *plu
if ds.Type == models.DS_INFLUXDB_08 {
dsMap["username"] = ds.User
dsMap["password"] = ds.DecryptedPassword()
dsMap["password"] = hs.DataSourcesService.DecryptedPassword(ds)
dsMap["url"] = url + "/db/" + ds.Database
}
if ds.Type == models.DS_INFLUXDB {
dsMap["username"] = ds.User
dsMap["password"] = ds.DecryptedPassword()
dsMap["password"] = hs.DataSourcesService.DecryptedPassword(ds)
dsMap["url"] = url
}
}

View File

@@ -106,6 +106,7 @@ type HTTPServer struct {
OAuthTokenService oauthtoken.OAuthTokenService
Listener net.Listener
EncryptionService encryption.Service
DataSourcesService *datasources.Service
cleanUpService *cleanup.CleanUpService
tracingService *tracing.TracingService
internalMetricsSvc *metrics.InternalMetricsService
@@ -133,7 +134,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
notificationService *notifications.NotificationService, tracingService *tracing.TracingService,
internalMetricsSvc *metrics.InternalMetricsService, quotaService *quota.QuotaService,
socialService social.Service, oauthTokenService oauthtoken.OAuthTokenService,
encryptionService encryption.Service, searchUsersService searchusers.Service) (*HTTPServer, error) {
encryptionService encryption.Service, searchUsersService searchusers.Service,
dataSourcesService *datasources.Service) (*HTTPServer, error) {
macaron.Env = cfg.Env
m := macaron.New()
@@ -180,6 +182,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
SocialService: socialService,
OAuthTokenService: oauthTokenService,
EncryptionService: encryptionService,
DataSourcesService: dataSourcesService,
searchUsersService: searchUsersService,
}
if hs.Listener != nil {

View File

@@ -315,12 +315,12 @@ func (hs *HTTPServer) tryGetEncryptedCookie(ctx *models.ReqContext, cookieName s
return "", false
}
decryptedError, err := hs.EncryptionService.Decrypt(decoded, setting.SecretKey)
decryptedError, err := hs.EncryptionService.Decrypt(ctx.Req.Context(), decoded, setting.SecretKey)
return string(decryptedError), err == nil
}
func (hs *HTTPServer) trySetEncryptedCookie(ctx *models.ReqContext, cookieName string, value string, maxAge int) error {
encryptedError, err := hs.EncryptionService.Encrypt([]byte(value), setting.SecretKey)
encryptedError, err := hs.EncryptionService.Encrypt(ctx.Req.Context(), []byte(value), setting.SecretKey)
if err != nil {
return err
}

View File

@@ -2,6 +2,7 @@ package api
import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
@@ -12,8 +13,6 @@ import (
"strings"
"testing"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
@@ -24,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/hooks"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/setting"
@@ -125,7 +125,7 @@ func TestLoginErrorCookieAPIEndpoint(t *testing.T) {
setting.OAuthAutoLogin = true
oauthError := errors.New("User not a member of one of the required organizations")
encryptedError, err := hs.EncryptionService.Encrypt([]byte(oauthError.Error()), setting.SecretKey)
encryptedError, err := hs.EncryptionService.Encrypt(context.Background(), []byte(oauthError.Error()), setting.SecretKey)
require.NoError(t, err)
expCookiePath := "/"
if len(setting.AppSubUrl) > 0 {

View File

@@ -9,18 +9,25 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
// ApplyRoute should use the plugin route data to set auth headers and custom headers.
func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route *plugins.AppPluginRoute,
ds *models.DataSource, cfg *setting.Cfg) {
ds *models.DataSource, cfg *setting.Cfg, encryptionService encryption.Service) {
proxyPath = strings.TrimPrefix(proxyPath, route.Path)
secureJsonData, err := encryptionService.DecryptJsonData(ctx, ds.SecureJsonData, setting.SecretKey)
if err != nil {
logger.Error("Error interpolating proxy url", "error", err)
return
}
data := templateData{
JsonData: ds.JsonData.Interface().(map[string]interface{}),
SecureJsonData: ds.SecureJsonData.Decrypt(),
SecureJsonData: secureJsonData,
}
if len(route.URL) > 0 {

View File

@@ -18,6 +18,7 @@ import (
glog "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/oauthtoken"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -31,15 +32,16 @@ var (
)
type DataSourceProxy struct {
ds *models.DataSource
ctx *models.ReqContext
targetUrl *url.URL
proxyPath string
route *plugins.AppPluginRoute
plugin *plugins.DataSourcePlugin
cfg *setting.Cfg
clientProvider httpclient.Provider
oAuthTokenService oauthtoken.OAuthTokenService
ds *models.DataSource
ctx *models.ReqContext
targetUrl *url.URL
proxyPath string
route *plugins.AppPluginRoute
plugin *plugins.DataSourcePlugin
cfg *setting.Cfg
clientProvider httpclient.Provider
oAuthTokenService oauthtoken.OAuthTokenService
dataSourcesService *datasources.Service
}
type handleResponseTransport struct {
@@ -72,21 +74,23 @@ func (lw *logWrapper) Write(p []byte) (n int, err error) {
// NewDataSourceProxy creates a new Datasource proxy
func NewDataSourceProxy(ds *models.DataSource, plugin *plugins.DataSourcePlugin, ctx *models.ReqContext,
proxyPath string, cfg *setting.Cfg, clientProvider httpclient.Provider, oAuthTokenService oauthtoken.OAuthTokenService) (*DataSourceProxy, error) {
proxyPath string, cfg *setting.Cfg, clientProvider httpclient.Provider,
oAuthTokenService oauthtoken.OAuthTokenService, dsService *datasources.Service) (*DataSourceProxy, error) {
targetURL, err := datasource.ValidateURL(ds.Type, ds.Url)
if err != nil {
return nil, err
}
return &DataSourceProxy{
ds: ds,
plugin: plugin,
ctx: ctx,
proxyPath: proxyPath,
targetUrl: targetURL,
cfg: cfg,
clientProvider: clientProvider,
oAuthTokenService: oAuthTokenService,
ds: ds,
plugin: plugin,
ctx: ctx,
proxyPath: proxyPath,
targetUrl: targetURL,
cfg: cfg,
clientProvider: clientProvider,
oAuthTokenService: oAuthTokenService,
dataSourcesService: dsService,
}, nil
}
@@ -106,7 +110,7 @@ func (proxy *DataSourceProxy) HandleRequest() {
proxyErrorLogger := logger.New("userId", proxy.ctx.UserId, "orgId", proxy.ctx.OrgId, "uname", proxy.ctx.Login,
"path", proxy.ctx.Req.URL.Path, "remote_addr", proxy.ctx.RemoteAddr(), "referer", proxy.ctx.Req.Referer())
transport, err := proxy.ds.GetHTTPTransport(proxy.clientProvider)
transport, err := proxy.dataSourcesService.GetHTTPTransport(proxy.ds, proxy.clientProvider)
if err != nil {
proxy.ctx.JsonApiErr(400, "Unable to load TLS certificate", err)
return
@@ -186,13 +190,16 @@ func (proxy *DataSourceProxy) director(req *http.Request) {
case models.DS_INFLUXDB_08:
req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath)
reqQueryVals.Add("u", proxy.ds.User)
reqQueryVals.Add("p", proxy.ds.DecryptedPassword())
reqQueryVals.Add("p", proxy.dataSourcesService.DecryptedPassword(proxy.ds))
req.URL.RawQuery = reqQueryVals.Encode()
case models.DS_INFLUXDB:
req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
req.URL.RawQuery = reqQueryVals.Encode()
if !proxy.ds.BasicAuth {
req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.DecryptedPassword()))
req.Header.Set(
"Authorization",
util.GetBasicAuthHeader(proxy.ds.User, proxy.dataSourcesService.DecryptedPassword(proxy.ds)),
)
}
default:
req.URL.RawPath = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
@@ -208,7 +215,7 @@ func (proxy *DataSourceProxy) director(req *http.Request) {
if proxy.ds.BasicAuth {
req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser,
proxy.ds.DecryptedBasicAuthPassword()))
proxy.dataSourcesService.DecryptedBasicAuthPassword(proxy.ds)))
}
dsAuth := req.Header.Get("X-DS-Authorization")
@@ -236,7 +243,11 @@ func (proxy *DataSourceProxy) director(req *http.Request) {
req.Header.Del("Referer")
if proxy.route != nil {
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, proxy.cfg)
ApplyRoute(
proxy.ctx.Req.Context(), req, proxy.proxyPath,
proxy.route, proxy.ds, proxy.cfg,
proxy.dataSourcesService.EncryptionService,
)
}
if proxy.oAuthTokenService.IsOAuthPassThruEnabled(proxy.ds) {

View File

@@ -14,18 +14,18 @@ import (
"github.com/grafana/grafana/pkg/api/datasource"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/httpclient"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/services/oauthtoken"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
macaron "gopkg.in/macaron.v1"
"gopkg.in/macaron.v1"
)
func TestDataSourceProxy_routeRule(t *testing.T) {
@@ -85,7 +85,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
})
setting.SecretKey = "password" //nolint:goconst
key, err := util.Encrypt([]byte("123"), "password")
key, err := ossencryption.ProvideService().Encrypt(context.Background(), []byte("123"), "password")
require.NoError(t, err)
ds := &models.DataSource{
@@ -113,10 +113,11 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("When matching route path", func(t *testing.T) {
ctx, req := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
proxy.route = plugin.Routes[0]
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg, ossencryption.ProvideService())
assert.Equal(t, "https://www.google.com/some/method", req.URL.String())
assert.Equal(t, "my secret 123", req.Header.Get("x-header"))
@@ -124,10 +125,11 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("When matching route path and has dynamic url", func(t *testing.T) {
ctx, req := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/common/some/method", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/common/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
proxy.route = plugin.Routes[3]
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg, ossencryption.ProvideService())
assert.Equal(t, "https://dynamic.grafana.com/some/method?apiKey=123", req.URL.String())
assert.Equal(t, "my secret 123", req.Header.Get("x-header"))
@@ -135,20 +137,22 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("When matching route path with no url", func(t *testing.T) {
ctx, req := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
proxy.route = plugin.Routes[4]
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg, ossencryption.ProvideService())
assert.Equal(t, "http://localhost/asd", req.URL.String())
})
t.Run("When matching route path and has dynamic body", func(t *testing.T) {
ctx, req := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/body", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/body", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
proxy.route = plugin.Routes[5]
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds, cfg, ossencryption.ProvideService())
content, err := ioutil.ReadAll(req.Body)
require.NoError(t, err)
@@ -158,7 +162,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("Validating request", func(t *testing.T) {
t.Run("plugin route with valid role", func(t *testing.T) {
ctx, _ := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
err = proxy.validateRequest()
require.NoError(t, err)
@@ -166,7 +171,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("plugin route with admin role and user is editor", func(t *testing.T) {
ctx, _ := setUp()
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
err = proxy.validateRequest()
require.Error(t, err)
@@ -175,7 +181,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
t.Run("plugin route with admin role and user is admin", func(t *testing.T) {
ctx, _ := setUp()
ctx.SignedInUser.OrgRole = models.ROLE_ADMIN
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/admin", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
err = proxy.validateRequest()
require.NoError(t, err)
@@ -221,7 +228,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
})
setting.SecretKey = "password"
key, err := util.Encrypt([]byte("123"), "password")
key, err := ossencryption.ProvideService().Encrypt(context.Background(), []byte("123"), "password")
require.NoError(t, err)
ds := &models.DataSource{
@@ -255,9 +262,10 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
cfg := &setting.Cfg{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds, cfg)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds, cfg, ossencryption.ProvideService())
authorizationHeaderCall1 = req.Header.Get("Authorization")
assert.Equal(t, "https://api.nr1.io/some/path", req.URL.String())
@@ -270,9 +278,10 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
req, err := http.NewRequest("GET", "http://localhost/asd", nil)
require.NoError(t, err)
client = newFakeHTTPClient(t, json2)
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken2", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken2", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[1], proxy.ds, cfg)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[1], proxy.ds, cfg, ossencryption.ProvideService())
authorizationHeaderCall2 = req.Header.Get("Authorization")
@@ -286,9 +295,10 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
require.NoError(t, err)
client = newFakeHTTPClient(t, []byte{})
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "pathwithtoken1", cfg, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds, cfg)
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, plugin.Routes[0], proxy.ds, cfg, ossencryption.ProvideService())
authorizationHeaderCall3 := req.Header.Get("Authorization")
assert.Equal(t, "https://api.nr1.io/some/path", req.URL.String())
@@ -306,7 +316,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
ds := &models.DataSource{Url: "htttp://graphite:8080", Type: models.DS_GRAPHITE}
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{BuildVersion: "5.3.0"}, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{BuildVersion: "5.3.0"}, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
require.NoError(t, err)
@@ -331,7 +342,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
}
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
@@ -354,7 +366,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
}
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
requestURL, err := url.Parse("http://grafana.com/sub")
@@ -381,7 +394,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
}
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
requestURL, err := url.Parse("http://grafana.com/sub")
@@ -402,7 +416,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
Url: "http://host/root/",
}
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
req.Header.Set("Origin", "grafana.com")
@@ -457,7 +472,8 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
},
oAuthEnabled: true,
}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &mockAuthToken)
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/", &setting.Cfg{}, httpClientProvider, &mockAuthToken, dsService)
require.NoError(t, err)
req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
require.NoError(t, err)
@@ -522,7 +538,6 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
createAuthTest(t, models.DS_ES, authTypeBasic, authCheckHeader, true),
}
for _, test := range tests {
models.ClearDSDecryptionCache()
runDatasourceAuthTest(t, test)
}
})
@@ -584,7 +599,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
t.Run("When response header Set-Cookie is not set should remove proxied Set-Cookie header", func(t *testing.T) {
ctx, ds := setUp(t)
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
proxy.HandleRequest()
@@ -599,7 +615,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
"Set-Cookie": "important_cookie=important_value",
},
})
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
proxy.HandleRequest()
@@ -618,7 +635,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
t.Log("Wrote 401 response")
},
})
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/render", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
proxy.HandleRequest()
@@ -640,7 +658,8 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
})
ctx.Req = httptest.NewRequest("GET", "/api/datasources/proxy/1/path/%2Ftest%2Ftest%2F?query=%2Ftest%2Ftest%2F", nil)
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "/path/%2Ftest%2Ftest%2F", &setting.Cfg{}, httpClientProvider, &oauthtoken.Service{}, dsService)
require.NoError(t, err)
proxy.HandleRequest()
@@ -662,7 +681,8 @@ func TestNewDataSourceProxy_InvalidURL(t *testing.T) {
}
cfg := setting.Cfg{}
plugin := plugins.DataSourcePlugin{}
_, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
_, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService)
require.Error(t, err)
assert.True(t, strings.HasPrefix(err.Error(), `validation of data source URL "://host/root" failed`))
}
@@ -679,7 +699,8 @@ func TestNewDataSourceProxy_ProtocolLessURL(t *testing.T) {
cfg := setting.Cfg{}
plugin := plugins.DataSourcePlugin{}
_, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
_, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService)
require.NoError(t, err)
}
@@ -717,7 +738,8 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) {
Url: tc.url,
}
p, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
p, err := NewDataSourceProxy(&ds, &plugin, &ctx, "api/method", &cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService)
if tc.err == nil {
require.NoError(t, err)
assert.Equal(t, &url.URL{
@@ -741,7 +763,8 @@ func getDatasourceProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *sett
Url: "http://host/root/",
}
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
require.NoError(t, err)
@@ -794,6 +817,8 @@ const (
)
func createAuthTest(t *testing.T, dsType string, authType string, authCheck string, useSecureJsonData bool) *testCase {
ctx := context.Background()
// Basic user:password
base64AuthHeader := "Basic dXNlcjpwYXNzd29yZA=="
@@ -805,13 +830,16 @@ func createAuthTest(t *testing.T, dsType string, authType string, authCheck stri
},
}
var message string
var err error
if authType == authTypePassword {
message = fmt.Sprintf("%v should add username and password", dsType)
test.datasource.User = "user"
if useSecureJsonData {
test.datasource.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{
"password": "password",
})
test.datasource.SecureJsonData, err = ossencryption.ProvideService().EncryptJsonData(
ctx,
map[string]string{
"password": "password",
}, setting.SecretKey)
} else {
test.datasource.Password = "password"
}
@@ -820,13 +848,16 @@ func createAuthTest(t *testing.T, dsType string, authType string, authCheck stri
test.datasource.BasicAuth = true
test.datasource.BasicAuthUser = "user"
if useSecureJsonData {
test.datasource.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{
"basicAuthPassword": "password",
})
test.datasource.SecureJsonData, err = ossencryption.ProvideService().EncryptJsonData(
ctx,
map[string]string{
"basicAuthPassword": "password",
}, setting.SecretKey)
} else {
test.datasource.BasicAuthPassword = "password"
}
}
require.NoError(t, err)
if useSecureJsonData {
message += " from securejsondata"
@@ -852,7 +883,8 @@ func createAuthTest(t *testing.T, dsType string, authType string, authCheck stri
func runDatasourceAuthTest(t *testing.T, test *testCase) {
plugin := &plugins.DataSourcePlugin{}
ctx := &models.ReqContext{}
proxy, err := NewDataSourceProxy(test.datasource, plugin, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(test.datasource, plugin, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService)
require.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
@@ -891,7 +923,8 @@ func Test_PathCheck(t *testing.T) {
return ctx, req
}
ctx, _ := setUp()
proxy, err := NewDataSourceProxy(&models.DataSource{}, plugin, ctx, "b", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{})
dsService := datasources.ProvideService(bus.New(), nil, ossencryption.ProvideService())
proxy, err := NewDataSourceProxy(&models.DataSource{}, plugin, ctx, "b", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService)
require.NoError(t, err)
require.Nil(t, proxy.validateRequest())

View File

@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/proxyutil"
@@ -21,7 +22,7 @@ type templateData struct {
// NewApiPluginProxy create a plugin proxy
func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.AppPluginRoute,
appID string, cfg *setting.Cfg) *httputil.ReverseProxy {
appID string, cfg *setting.Cfg, encryptionService encryption.Service) *httputil.ReverseProxy {
director := func(req *http.Request) {
query := models.GetPluginSettingByIdQuery{OrgId: ctx.OrgId, PluginId: appID}
if err := bus.Dispatch(&query); err != nil {
@@ -29,9 +30,15 @@ func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.
return
}
secureJsonData, err := encryptionService.DecryptJsonData(ctx.Req.Context(), query.Result.SecureJsonData, setting.SecretKey)
if err != nil {
ctx.JsonApiErr(500, "Failed to decrypt plugin settings", err)
return
}
data := templateData{
JsonData: query.Result.JsonData,
SecureJsonData: query.Result.SecureJsonData.Decrypt(),
SecureJsonData: secureJsonData,
}
interpolatedURL, err := interpolateString(route.URL, data)

View File

@@ -1,18 +1,19 @@
package pluginproxy
import (
"context"
"io/ioutil"
"net/http"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/encryption/ossencryption"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/macaron.v1"
)
func TestPluginProxy(t *testing.T) {
@@ -25,8 +26,8 @@ func TestPluginProxy(t *testing.T) {
setting.SecretKey = "password"
bus.AddHandler("test", func(query *models.GetPluginSettingByIdQuery) error {
key, err := util.Encrypt([]byte("123"), "password")
bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetPluginSettingByIdQuery) error {
key, err := ossencryption.ProvideService().Encrypt(ctx, []byte("123"), "password")
if err != nil {
return err
}
@@ -39,12 +40,18 @@ func TestPluginProxy(t *testing.T) {
return nil
})
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{
Login: "test_user",
},
Context: &macaron.Context{
Req: httpReq,
},
},
&setting.Cfg{SendUserHeader: true},
route,
@@ -54,12 +61,18 @@ func TestPluginProxy(t *testing.T) {
})
t.Run("When SendUserHeader config is enabled", func(t *testing.T) {
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{
Login: "test_user",
},
Context: &macaron.Context{
Req: httpReq,
},
},
&setting.Cfg{SendUserHeader: true},
nil,
@@ -70,12 +83,18 @@ func TestPluginProxy(t *testing.T) {
})
t.Run("When SendUserHeader config is disabled", func(t *testing.T) {
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{
Login: "test_user",
},
Context: &macaron.Context{
Req: httpReq,
},
},
&setting.Cfg{SendUserHeader: false},
nil,
@@ -85,10 +104,16 @@ func TestPluginProxy(t *testing.T) {
})
t.Run("When SendUserHeader config is enabled but user is anonymous", func(t *testing.T) {
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{IsAnonymous: true},
Context: &macaron.Context{
Req: httpReq,
},
},
&setting.Cfg{SendUserHeader: true},
nil,
@@ -104,7 +129,7 @@ func TestPluginProxy(t *testing.T) {
Method: "GET",
}
bus.AddHandler("test", func(query *models.GetPluginSettingByIdQuery) error {
bus.AddHandlerCtx("test", func(_ context.Context, query *models.GetPluginSettingByIdQuery) error {
query.Result = &models.PluginSetting{
JsonData: map[string]interface{}{
"dynamicUrl": "https://dynamic.grafana.com",
@@ -113,12 +138,18 @@ func TestPluginProxy(t *testing.T) {
return nil
})
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{
Login: "test_user",
},
Context: &macaron.Context{
Req: httpReq,
},
},
&setting.Cfg{SendUserHeader: true},
route,
@@ -138,12 +169,18 @@ func TestPluginProxy(t *testing.T) {
return nil
})
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{
Login: "test_user",
},
Context: &macaron.Context{
Req: httpReq,
},
},
&setting.Cfg{SendUserHeader: true},
route,
@@ -158,22 +195,38 @@ func TestPluginProxy(t *testing.T) {
Body: []byte(`{ "url": "{{.JsonData.dynamicUrl}}", "secret": "{{.SecureJsonData.key}}" }`),
}
bus.AddHandler("test", func(query *models.GetPluginSettingByIdQuery) error {
bus.AddHandlerCtx("test", func(ctx context.Context, query *models.GetPluginSettingByIdQuery) error {
encryptedJsonData, err := ossencryption.ProvideService().EncryptJsonData(
ctx,
map[string]string{"key": "123"},
setting.SecretKey,
)
if err != nil {
return err
}
query.Result = &models.PluginSetting{
JsonData: map[string]interface{}{
"dynamicUrl": "https://dynamic.grafana.com",
},
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{"key": "123"}),
SecureJsonData: encryptedJsonData,
}
return nil
})
httpReq, err := http.NewRequest(http.MethodGet, "", nil)
require.NoError(t, err)
req := getPluginProxiedRequest(
t,
&models.ReqContext{
SignedInUser: &models.SignedInUser{
Login: "test_user",
},
Context: &macaron.Context{
Req: httpReq,
},
},
&setting.Cfg{SendUserHeader: true},
route,
@@ -194,7 +247,7 @@ func getPluginProxiedRequest(t *testing.T, ctx *models.ReqContext, cfg *setting.
ReqRole: models.ROLE_EDITOR,
}
}
proxy := NewApiPluginProxy(ctx, "", route, "", cfg)
proxy := NewApiPluginProxy(ctx, "", route, "", cfg, ossencryption.ProvideService())
req, err := http.NewRequest(http.MethodGet, "/api/plugin-proxy/grafana-simple-app/api/v4/alerts", nil)
require.NoError(t, err)