Plugins: Modify interface for plugin validations to allow taking PDC into account (#96089)
* Request interceptor: Do not block PDC * Apply change after feedback received * Add test * Check if secure socks proxy configured for the instance * Apply suggestions from code review * Add dedicated service for datasource request URL validation (#99179) --------- Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
This commit is contained in:
co-authored by
Will Browne
parent
33a53d170b
commit
d192a44469
@@ -24,35 +24,35 @@ import (
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func ProvideService(dataSourceCache datasources.CacheService, plugReqValidator validations.PluginRequestValidator,
|
||||
func ProvideService(dataSourceCache datasources.CacheService, datasourceReqValidator validations.DataSourceRequestValidator,
|
||||
pluginStore pluginstore.Store, cfg *setting.Cfg, httpClientProvider httpclient.Provider,
|
||||
oauthTokenService *oauthtoken.Service, dsService datasources.DataSourceService,
|
||||
tracer tracing.Tracer, secretsService secrets.Service, features featuremgmt.FeatureToggles) *DataSourceProxyService {
|
||||
return &DataSourceProxyService{
|
||||
DataSourceCache: dataSourceCache,
|
||||
PluginRequestValidator: plugReqValidator,
|
||||
pluginStore: pluginStore,
|
||||
Cfg: cfg,
|
||||
HTTPClientProvider: httpClientProvider,
|
||||
OAuthTokenService: oauthTokenService,
|
||||
DataSourcesService: dsService,
|
||||
tracer: tracer,
|
||||
secretsService: secretsService,
|
||||
features: features,
|
||||
DataSourceCache: dataSourceCache,
|
||||
DataSourceRequestValidator: datasourceReqValidator,
|
||||
pluginStore: pluginStore,
|
||||
Cfg: cfg,
|
||||
HTTPClientProvider: httpClientProvider,
|
||||
OAuthTokenService: oauthTokenService,
|
||||
DataSourcesService: dsService,
|
||||
tracer: tracer,
|
||||
secretsService: secretsService,
|
||||
features: features,
|
||||
}
|
||||
}
|
||||
|
||||
type DataSourceProxyService struct {
|
||||
DataSourceCache datasources.CacheService
|
||||
PluginRequestValidator validations.PluginRequestValidator
|
||||
pluginStore pluginstore.Store
|
||||
Cfg *setting.Cfg
|
||||
HTTPClientProvider httpclient.Provider
|
||||
OAuthTokenService *oauthtoken.Service
|
||||
DataSourcesService datasources.DataSourceService
|
||||
tracer tracing.Tracer
|
||||
secretsService secrets.Service
|
||||
features featuremgmt.FeatureToggles
|
||||
DataSourceCache datasources.CacheService
|
||||
DataSourceRequestValidator validations.DataSourceRequestValidator
|
||||
pluginStore pluginstore.Store
|
||||
Cfg *setting.Cfg
|
||||
HTTPClientProvider httpclient.Provider
|
||||
OAuthTokenService *oauthtoken.Service
|
||||
DataSourcesService datasources.DataSourceService
|
||||
tracer tracing.Tracer
|
||||
secretsService secrets.Service
|
||||
features featuremgmt.FeatureToggles
|
||||
}
|
||||
|
||||
func (p *DataSourceProxyService) ProxyDataSourceRequest(c *contextmodel.ReqContext) {
|
||||
@@ -108,7 +108,7 @@ func toAPIError(c *contextmodel.ReqContext, err error) {
|
||||
}
|
||||
|
||||
func (p *DataSourceProxyService) proxyDatasourceRequest(c *contextmodel.ReqContext, ds *datasources.DataSource) {
|
||||
err := p.PluginRequestValidator.Validate(ds.URL, c.Req)
|
||||
err := p.DataSourceRequestValidator.Validate(ds, c.Req)
|
||||
if err != nil {
|
||||
c.JsonApiErr(http.StatusForbidden, "Access denied", err)
|
||||
return
|
||||
|
||||
@@ -94,8 +94,8 @@ func TestDatasourceProxy_proxyDatasourceRequest(t *testing.T) {
|
||||
}}
|
||||
|
||||
p := DataSourceProxyService{
|
||||
PluginRequestValidator: &fakePluginRequestValidator{},
|
||||
pluginStore: pluginStore,
|
||||
DataSourceRequestValidator: &fakeDataSourceRequestValidator{},
|
||||
pluginStore: pluginStore,
|
||||
}
|
||||
|
||||
responseRecorder := httptest.NewRecorder()
|
||||
@@ -129,8 +129,8 @@ func TestDatasourceProxy_proxyDatasourceRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type fakePluginRequestValidator struct{}
|
||||
type fakeDataSourceRequestValidator struct{}
|
||||
|
||||
func (rv *fakePluginRequestValidator) Validate(_ string, _ *http.Request) error {
|
||||
func (rv *fakeDataSourceRequestValidator) Validate(_ *datasources.DataSource, _ *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -70,6 +70,16 @@ type DataSource struct {
|
||||
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Updated time.Time `json:"updated,omitempty"`
|
||||
|
||||
isSecureSocksDSProxyEnabled *bool `xorm:"-"`
|
||||
}
|
||||
|
||||
func (ds *DataSource) IsSecureSocksDSProxyEnabled() bool {
|
||||
if ds.isSecureSocksDSProxyEnabled == nil {
|
||||
enabled := ds.JsonData != nil && ds.JsonData.Get("enableSecureSocksProxy").MustBool(false)
|
||||
ds.isSecureSocksDSProxyEnabled = &enabled
|
||||
}
|
||||
return *ds.isSecureSocksDSProxyEnabled
|
||||
}
|
||||
|
||||
type TeamHTTPHeadersJSONData struct {
|
||||
|
||||
@@ -102,3 +102,58 @@ func TestTeamHTTPHeaders(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSecureSocksDSProxyEnabled(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
ds *DataSource
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
desc: "Empty json",
|
||||
ds: &DataSource{
|
||||
JsonData: simplejson.New(),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
desc: "Json with enableSecureSocksProxy",
|
||||
ds: &DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||
"enableSecureSocksProxy": true,
|
||||
}),
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
desc: "Json with string enableSecureSocksProxy",
|
||||
ds: &DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||
"enableSecureSocksProxy": "true",
|
||||
}),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
desc: "Json with enableSecureSocksProxy false",
|
||||
ds: &DataSource{
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||
"enableSecureSocksProxy": false,
|
||||
}),
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
desc: "Json with no json data",
|
||||
ds: &DataSource{},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
actual := tc.ds.IsSecureSocksDSProxyEnabled()
|
||||
assert.Equal(t, tc.want, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,7 +745,7 @@ func (s *Service) httpClientOptions(ctx context.Context, ds *datasources.DataSou
|
||||
}
|
||||
}
|
||||
|
||||
if ds.JsonData != nil && ds.JsonData.Get("enableSecureSocksProxy").MustBool(false) {
|
||||
if ds.IsSecureSocksDSProxyEnabled() {
|
||||
proxyOpts := &sdkproxy.Options{
|
||||
Enabled: true,
|
||||
Auth: &sdkproxy.AuthOptions{
|
||||
|
||||
@@ -152,18 +152,18 @@ func buildQueryDataService(t *testing.T, cs datasources.CacheService, fpc *fakeP
|
||||
setting.NewCfg(),
|
||||
cs,
|
||||
nil,
|
||||
&fakePluginRequestValidator{},
|
||||
&fakeDataSourceRequestValidator{},
|
||||
fpc,
|
||||
pCtxProvider,
|
||||
)
|
||||
}
|
||||
|
||||
// copied from pkg/api/metrics_test.go
|
||||
type fakePluginRequestValidator struct {
|
||||
type fakeDataSourceRequestValidator struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (rv *fakePluginRequestValidator) Validate(dsURL string, req *http.Request) error {
|
||||
func (rv *fakeDataSourceRequestValidator) Validate(ds *datasources.DataSource, req *http.Request) error {
|
||||
return rv.err
|
||||
}
|
||||
|
||||
|
||||
+18
-18
@@ -42,19 +42,19 @@ func ProvideService(
|
||||
cfg *setting.Cfg,
|
||||
dataSourceCache datasources.CacheService,
|
||||
expressionService *expr.Service,
|
||||
pluginRequestValidator validations.PluginRequestValidator,
|
||||
dataSourceRequestValidator validations.DataSourceRequestValidator,
|
||||
pluginClient plugins.Client,
|
||||
pCtxProvider *plugincontext.Provider,
|
||||
) *ServiceImpl {
|
||||
g := &ServiceImpl{
|
||||
cfg: cfg,
|
||||
dataSourceCache: dataSourceCache,
|
||||
expressionService: expressionService,
|
||||
pluginRequestValidator: pluginRequestValidator,
|
||||
pluginClient: pluginClient,
|
||||
pCtxProvider: pCtxProvider,
|
||||
log: log.New("query_data"),
|
||||
concurrentQueryLimit: cfg.SectionWithEnvOverrides("query").Key("concurrent_query_limit").MustInt(runtime.NumCPU()),
|
||||
cfg: cfg,
|
||||
dataSourceCache: dataSourceCache,
|
||||
expressionService: expressionService,
|
||||
dataSourceRequestValidator: dataSourceRequestValidator,
|
||||
pluginClient: pluginClient,
|
||||
pCtxProvider: pCtxProvider,
|
||||
log: log.New("query_data"),
|
||||
concurrentQueryLimit: cfg.SectionWithEnvOverrides("query").Key("concurrent_query_limit").MustInt(runtime.NumCPU()),
|
||||
}
|
||||
g.log.Info("Query Service initialization")
|
||||
return g
|
||||
@@ -70,14 +70,14 @@ type Service interface {
|
||||
var _ Service = (*ServiceImpl)(nil)
|
||||
|
||||
type ServiceImpl struct {
|
||||
cfg *setting.Cfg
|
||||
dataSourceCache datasources.CacheService
|
||||
expressionService *expr.Service
|
||||
pluginRequestValidator validations.PluginRequestValidator
|
||||
pluginClient plugins.Client
|
||||
pCtxProvider *plugincontext.Provider
|
||||
log log.Logger
|
||||
concurrentQueryLimit int
|
||||
cfg *setting.Cfg
|
||||
dataSourceCache datasources.CacheService
|
||||
expressionService *expr.Service
|
||||
dataSourceRequestValidator validations.DataSourceRequestValidator
|
||||
pluginClient plugins.Client
|
||||
pCtxProvider *plugincontext.Provider
|
||||
log log.Logger
|
||||
concurrentQueryLimit int
|
||||
}
|
||||
|
||||
// Run ServiceImpl.
|
||||
@@ -244,7 +244,7 @@ func (s *ServiceImpl) handleExpressions(ctx context.Context, user identity.Reque
|
||||
func (s *ServiceImpl) handleQuerySingleDatasource(ctx context.Context, user identity.Requester, parsedReq *parsedRequest) (*backend.QueryDataResponse, error) {
|
||||
queries := parsedReq.getFlattenedQueries()
|
||||
ds := queries[0].datasource
|
||||
if err := s.pluginRequestValidator.Validate(ds.URL, nil); err != nil {
|
||||
if err := s.dataSourceRequestValidator.Validate(ds, nil); err != nil {
|
||||
return nil, datasources.ErrDataSourceAccessDenied
|
||||
}
|
||||
|
||||
|
||||
@@ -462,7 +462,7 @@ func setup(t *testing.T) *testContext {
|
||||
t.Helper()
|
||||
pc := &fakePluginClient{}
|
||||
dc := &fakeDataSourceCache{cache: dss}
|
||||
rv := &fakePluginRequestValidator{}
|
||||
rv := &fakeDataSourceRequestValidator{}
|
||||
|
||||
sqlStore, cfg := db.InitTestDBWithCfg(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
@@ -497,7 +497,7 @@ func setup(t *testing.T) *testContext {
|
||||
type testContext struct {
|
||||
pluginContext *fakePluginClient
|
||||
secretStore secretskvs.SecretsKVStore
|
||||
pluginRequestValidator *fakePluginRequestValidator
|
||||
pluginRequestValidator *fakeDataSourceRequestValidator
|
||||
queryService *ServiceImpl // implementation belonging to this package
|
||||
signedInUser *user.SignedInUser
|
||||
}
|
||||
@@ -518,11 +518,11 @@ func metricRequestWithQueries(t *testing.T, rawQueries ...string) dtos.MetricReq
|
||||
}
|
||||
}
|
||||
|
||||
type fakePluginRequestValidator struct {
|
||||
type fakeDataSourceRequestValidator struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (rv *fakePluginRequestValidator) Validate(dsURL string, req *http.Request) error {
|
||||
func (rv *fakeDataSourceRequestValidator) Validate(ds *datasources.DataSource, req *http.Request) error {
|
||||
return rv.err
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,26 @@ package validations
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
)
|
||||
|
||||
type OSSPluginRequestValidator struct{}
|
||||
type OSSDataSourceRequestValidator struct{}
|
||||
|
||||
func (*OSSPluginRequestValidator) Validate(string, *http.Request) error {
|
||||
func (*OSSDataSourceRequestValidator) Validate(*datasources.DataSource, *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProvideValidator() *OSSPluginRequestValidator {
|
||||
return &OSSPluginRequestValidator{}
|
||||
func ProvideValidator() *OSSDataSourceRequestValidator {
|
||||
return &OSSDataSourceRequestValidator{}
|
||||
}
|
||||
|
||||
type OSSDataSourceRequestURLValidator struct{}
|
||||
|
||||
func (*OSSDataSourceRequestURLValidator) Validate(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProvideURLValidator() *OSSDataSourceRequestURLValidator {
|
||||
return &OSSDataSourceRequestURLValidator{}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,18 @@ package validations
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
)
|
||||
|
||||
type PluginRequestValidator interface {
|
||||
type DataSourceRequestValidator interface {
|
||||
// Validate performs a request validation based
|
||||
// on the data source URL and some of the request
|
||||
// attributes (headers, cookies, etc).
|
||||
Validate(dsURL string, req *http.Request) error
|
||||
Validate(ds *datasources.DataSource, req *http.Request) error
|
||||
}
|
||||
|
||||
type DataSourceRequestURLValidator interface {
|
||||
// Validate performs a request validation based on the data source URL
|
||||
Validate(dsURL string) error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user