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

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