From 6415d2802e1973f02e2a0ab9037045efc4b0ad5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Wed, 3 Feb 2021 20:47:45 +0100 Subject: [PATCH] Plugins: Requests validator (#30445) * Introduce PluginRequestValidator abstraction with a NoOp implementation * Update PluginRequestValidator abstraction to use the dsURL instead * Inject PluginRequestValidator into the HTTPServer and validate requests going through data source proxy * Inject PluginRequestValidator into the BackendPluginManager and validate requests going through it * Validate requests going through QueryMetrics & QueryMetricsV2 * Validate BackendPluginManager health requests * Fix backend plugins manager tests * Validate requests going through alerting service * Fix tests * fix tests * goimports Co-authored-by: Leonard Gram --- pkg/api/dataproxy.go | 17 ++++--- pkg/api/http_server.go | 45 ++++++++++--------- pkg/api/metrics.go | 33 +++++++++----- pkg/extensions/main.go | 2 + pkg/mocks/mock_gcsifaces/mocks.go | 5 ++- pkg/models/validations.go | 12 +++++ pkg/plugins/backendplugin/manager.go | 39 +++++++++++++--- pkg/plugins/backendplugin/manager_test.go | 12 ++++- .../pluginextensionv2/rendererv2.pb.go | 3 +- pkg/services/alerting/conditions/query.go | 5 +++ .../alerting/conditions/query_test.go | 5 ++- pkg/services/alerting/engine.go | 15 ++++--- pkg/services/alerting/eval_context.go | 19 ++++---- pkg/services/alerting/eval_context_test.go | 6 ++- pkg/services/alerting/eval_handler_test.go | 30 +++++++------ pkg/services/alerting/notifier_test.go | 8 ++-- .../alerting/notifiers/alertmanager_test.go | 4 +- pkg/services/alerting/notifiers/base_test.go | 4 +- .../alerting/notifiers/dingding_test.go | 4 +- .../alerting/notifiers/opsgenie_test.go | 4 +- .../alerting/notifiers/pagerduty_test.go | 12 ++--- .../alerting/notifiers/pushover_test.go | 6 ++- .../alerting/notifiers/telegram_test.go | 10 +++-- pkg/services/alerting/test_notification.go | 9 +++- pkg/services/alerting/test_rule.go | 2 +- pkg/services/validations/oss.go | 15 +++++++ 26 files changed, 224 insertions(+), 102 deletions(-) create mode 100644 pkg/models/validations.go create mode 100644 pkg/services/validations/oss.go diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 2d98f1a095e..49de42c5a01 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -3,6 +3,7 @@ package api import ( "errors" "fmt" + "net/http" "github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/api/pluginproxy" @@ -19,17 +20,23 @@ func (hs *HTTPServer) ProxyDataSourceRequest(c *models.ReqContext) { ds, err := hs.DatasourceCache.GetDatasource(dsID, c.SignedInUser, c.SkipCache) if err != nil { if errors.Is(err, models.ErrDataSourceAccessDenied) { - c.JsonApiErr(403, "Access denied to datasource", err) + c.JsonApiErr(http.StatusForbidden, "Access denied to datasource", err) return } - c.JsonApiErr(500, "Unable to load datasource meta data", err) + c.JsonApiErr(http.StatusInternalServerError, "Unable to load datasource meta data", err) + return + } + + err = hs.PluginRequestValidator.Validate(ds.Url, c.Req.Request) + if err != nil { + c.JsonApiErr(http.StatusForbidden, "Access denied", err) return } // find plugin plugin, ok := plugins.DataSources[ds.Type] if !ok { - c.JsonApiErr(500, "Unable to find datasource plugin", err) + c.JsonApiErr(http.StatusInternalServerError, "Unable to find datasource plugin", err) return } @@ -39,9 +46,9 @@ func (hs *HTTPServer) ProxyDataSourceRequest(c *models.ReqContext) { proxy, err := pluginproxy.NewDataSourceProxy(ds, plugin, c, proxyPath, hs.Cfg) if err != nil { if errors.Is(err, datasource.URLValidationError{}) { - c.JsonApiErr(400, fmt.Sprintf("Invalid data source URL: %q", ds.Url), err) + c.JsonApiErr(http.StatusBadRequest, fmt.Sprintf("Invalid data source URL: %q", ds.Url), err) } else { - c.JsonApiErr(500, "Failed creating data source proxy", err) + c.JsonApiErr(http.StatusInternalServerError, "Failed creating data source proxy", err) } return } diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index f2f0ab53c60..521989a2014 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -60,28 +60,29 @@ type HTTPServer struct { httpSrv *http.Server middlewares []macaron.Handler - RouteRegister routing.RouteRegister `inject:""` - Bus bus.Bus `inject:""` - RenderService rendering.Service `inject:""` - Cfg *setting.Cfg `inject:""` - HooksService *hooks.HooksService `inject:""` - CacheService *localcache.CacheService `inject:""` - DatasourceCache datasources.CacheService `inject:""` - AuthTokenService models.UserTokenService `inject:""` - QuotaService *quota.QuotaService `inject:""` - RemoteCacheService *remotecache.RemoteCache `inject:""` - ProvisioningService provisioning.ProvisioningService `inject:""` - Login *login.LoginService `inject:""` - License models.Licensing `inject:""` - BackendPluginManager backendplugin.Manager `inject:""` - PluginManager *plugins.PluginManager `inject:""` - SearchService *search.SearchService `inject:""` - ShortURLService *shorturls.ShortURLService `inject:""` - Live *live.GrafanaLive `inject:""` - ContextHandler *contexthandler.ContextHandler `inject:""` - SQLStore *sqlstore.SQLStore `inject:""` - LibraryPanelService *librarypanels.LibraryPanelService `inject:""` - Listener net.Listener + RouteRegister routing.RouteRegister `inject:""` + Bus bus.Bus `inject:""` + RenderService rendering.Service `inject:""` + Cfg *setting.Cfg `inject:""` + HooksService *hooks.HooksService `inject:""` + CacheService *localcache.CacheService `inject:""` + DatasourceCache datasources.CacheService `inject:""` + AuthTokenService models.UserTokenService `inject:""` + QuotaService *quota.QuotaService `inject:""` + RemoteCacheService *remotecache.RemoteCache `inject:""` + ProvisioningService provisioning.ProvisioningService `inject:""` + Login *login.LoginService `inject:""` + License models.Licensing `inject:""` + BackendPluginManager backendplugin.Manager `inject:""` + PluginRequestValidator models.PluginRequestValidator `inject:""` + PluginManager *plugins.PluginManager `inject:""` + SearchService *search.SearchService `inject:""` + ShortURLService *shorturls.ShortURLService `inject:""` + Live *live.GrafanaLive `inject:""` + ContextHandler *contexthandler.ContextHandler `inject:""` + SQLStore *sqlstore.SQLStore `inject:""` + LibraryPanelService *librarypanels.LibraryPanelService `inject:""` + Listener net.Listener } func (hs *HTTPServer) Init() error { diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index 8ad60076d28..e8032fbf6c1 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -3,6 +3,7 @@ package api import ( "context" "errors" + "net/http" "github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/models" @@ -19,7 +20,7 @@ import ( // POST /api/ds/query DataSource query w/ expressions func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricRequest) response.Response { if len(reqDTO.Queries) == 0 { - return response.Error(400, "No queries found in query", nil) + return response.Error(http.StatusBadRequest, "No queries found in query", nil) } request := &tsdb.TsdbQuery{ @@ -43,7 +44,7 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricReq datasourceID, err := query.Get("datasourceId").Int64() if err != nil { hs.log.Debug("Can't process query since it's missing data source ID") - return response.Error(400, "Query missing data source ID", nil) + return response.Error(http.StatusBadRequest, "Query missing data source ID", nil) } // For mixed datasource case, each data source is sent in a single request. @@ -66,17 +67,22 @@ func (hs *HTTPServer) QueryMetricsV2(c *models.ReqContext, reqDTO dtos.MetricReq }) } - resp, err := tsdb.HandleRequest(c.Req.Context(), ds, request) + err := hs.PluginRequestValidator.Validate(ds.Url, nil) if err != nil { - return response.Error(500, "Metric request error", err) + return response.Error(http.StatusForbidden, "Access denied", err) } - statusCode := 200 + resp, err := tsdb.HandleRequest(c.Req.Context(), ds, request) + if err != nil { + return response.Error(http.StatusInternalServerError, "Metric request error", err) + } + + statusCode := http.StatusOK for _, res := range resp.Results { if res.Error != nil { res.ErrorString = res.Error.Error() resp.Message = res.ErrorString - statusCode = 400 + statusCode = http.StatusBadRequest } } @@ -154,12 +160,12 @@ func (hs *HTTPServer) QueryMetrics(c *models.ReqContext, reqDto dtos.MetricReque timeRange := tsdb.NewTimeRange(reqDto.From, reqDto.To) if len(reqDto.Queries) == 0 { - return response.Error(400, "No queries found in query", nil) + return response.Error(http.StatusBadRequest, "No queries found in query", nil) } datasourceId, err := reqDto.Queries[0].Get("datasourceId").Int64() if err != nil { - return response.Error(400, "Query missing datasourceId", nil) + return response.Error(http.StatusBadRequest, "Query missing datasourceId", nil) } ds, err := hs.DatasourceCache.GetDatasource(datasourceId, c.SignedInUser, c.SkipCache) @@ -167,6 +173,11 @@ func (hs *HTTPServer) QueryMetrics(c *models.ReqContext, reqDto dtos.MetricReque return hs.handleGetDataSourceError(err, datasourceId) } + err = hs.PluginRequestValidator.Validate(ds.Url, nil) + if err != nil { + return response.Error(http.StatusForbidden, "Access denied", err) + } + request := &tsdb.TsdbQuery{ TimeRange: timeRange, Debug: reqDto.Debug, @@ -185,15 +196,15 @@ func (hs *HTTPServer) QueryMetrics(c *models.ReqContext, reqDto dtos.MetricReque resp, err := tsdb.HandleRequest(c.Req.Context(), ds, request) if err != nil { - return response.Error(500, "Metric request error", err) + return response.Error(http.StatusInternalServerError, "Metric request error", err) } - statusCode := 200 + statusCode := http.StatusOK for _, res := range resp.Results { if res.Error != nil { res.ErrorString = res.Error.Error() resp.Message = res.ErrorString - statusCode = 400 + statusCode = http.StatusBadRequest } } diff --git a/pkg/extensions/main.go b/pkg/extensions/main.go index 4fe31846135..24031ace2e9 100644 --- a/pkg/extensions/main.go +++ b/pkg/extensions/main.go @@ -10,6 +10,7 @@ import ( _ "github.com/gobwas/glob" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/licensing" + "github.com/grafana/grafana/pkg/services/validations" _ "github.com/grafana/loki/pkg/logproto" _ "github.com/grpc-ecosystem/go-grpc-middleware" _ "github.com/jung-kurt/gofpdf" @@ -26,6 +27,7 @@ import ( func init() { registry.RegisterService(&licensing.OSSLicensingService{}) + registry.RegisterService(&validations.OSSPluginRequestValidator{}) } var IsEnterprise bool = false diff --git a/pkg/mocks/mock_gcsifaces/mocks.go b/pkg/mocks/mock_gcsifaces/mocks.go index 6aa07721eac..68fda5383d1 100644 --- a/pkg/mocks/mock_gcsifaces/mocks.go +++ b/pkg/mocks/mock_gcsifaces/mocks.go @@ -5,13 +5,14 @@ package mock_gcsifaces import ( - storage "cloud.google.com/go/storage" context "context" + reflect "reflect" + + storage "cloud.google.com/go/storage" gomock "github.com/golang/mock/gomock" gcsifaces "github.com/grafana/grafana/pkg/ifaces/gcsifaces" google "golang.org/x/oauth2/google" jwt "golang.org/x/oauth2/jwt" - reflect "reflect" ) // MockStorageClient is a mock of StorageClient interface diff --git a/pkg/models/validations.go b/pkg/models/validations.go new file mode 100644 index 00000000000..389e5466f80 --- /dev/null +++ b/pkg/models/validations.go @@ -0,0 +1,12 @@ +package models + +import ( + "net/http" +) + +type PluginRequestValidator 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 +} diff --git a/pkg/plugins/backendplugin/manager.go b/pkg/plugins/backendplugin/manager.go index af93f719175..b5ecedba5da 100644 --- a/pkg/plugins/backendplugin/manager.go +++ b/pkg/plugins/backendplugin/manager.go @@ -51,12 +51,13 @@ type Manager interface { } type manager struct { - Cfg *setting.Cfg `inject:""` - License models.Licensing `inject:""` - pluginsMu sync.RWMutex - plugins map[string]Plugin - logger log.Logger - pluginSettings map[string]pluginSettings + Cfg *setting.Cfg `inject:""` + License models.Licensing `inject:""` + PluginRequestValidator models.PluginRequestValidator `inject:""` + pluginsMu sync.RWMutex + plugins map[string]Plugin + logger log.Logger + pluginSettings map[string]pluginSettings } func (m *manager) Init() error { @@ -195,6 +196,19 @@ func (m *manager) CollectMetrics(ctx context.Context, pluginID string) (*backend // CheckHealth checks the health of a registered backend plugin. func (m *manager) CheckHealth(ctx context.Context, pluginContext backend.PluginContext) (*backend.CheckHealthResult, error) { + var dsURL string + if pluginContext.DataSourceInstanceSettings != nil { + dsURL = pluginContext.DataSourceInstanceSettings.URL + } + + err := m.PluginRequestValidator.Validate(dsURL, nil) + if err != nil { + return &backend.CheckHealthResult{ + Status: http.StatusForbidden, + Message: "Access denied", + }, nil + } + m.pluginsMu.RLock() p, registered := m.plugins[pluginContext.PluginID] m.pluginsMu.RUnlock() @@ -204,7 +218,7 @@ func (m *manager) CheckHealth(ctx context.Context, pluginContext backend.PluginC } var resp *backend.CheckHealthResult - err := instrumentCheckHealthRequest(p.PluginID(), func() (innerErr error) { + err = instrumentCheckHealthRequest(p.PluginID(), func() (innerErr error) { resp, innerErr = p.CheckHealth(ctx, &backend.CheckHealthRequest{PluginContext: pluginContext}) return }) @@ -289,6 +303,17 @@ func (m *manager) callResourceInternal(w http.ResponseWriter, req *http.Request, // CallResource calls a plugin resource. func (m *manager) CallResource(pCtx backend.PluginContext, reqCtx *models.ReqContext, path string) { + var dsURL string + if pCtx.DataSourceInstanceSettings != nil { + dsURL = pCtx.DataSourceInstanceSettings.URL + } + + err := m.PluginRequestValidator.Validate(dsURL, reqCtx.Req.Request) + if err != nil { + reqCtx.JsonApiErr(http.StatusForbidden, "Access denied", err) + return + } + clonedReq := reqCtx.Req.Clone(reqCtx.Req.Context()) rawURL := path if clonedReq.URL.RawQuery != "" { diff --git a/pkg/plugins/backendplugin/manager_test.go b/pkg/plugins/backendplugin/manager_test.go index eae2e01a831..87aedd781a5 100644 --- a/pkg/plugins/backendplugin/manager_test.go +++ b/pkg/plugins/backendplugin/manager_test.go @@ -279,12 +279,14 @@ func newManagerScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *m t.Helper() cfg := setting.NewCfg() license := &testLicensingService{} + validator := &testPluginRequestValidator{} ctx := &managerScenarioCtx{ cfg: cfg, license: license, manager: &manager{ - Cfg: cfg, - License: license, + Cfg: cfg, + License: license, + PluginRequestValidator: validator, }, } @@ -418,3 +420,9 @@ func (t *testLicensingService) HasValidLicense() bool { func (t *testLicensingService) Environment() map[string]string { return map[string]string{"GF_ENTERPRISE_LICENSE_TEXT": t.tokenRaw} } + +type testPluginRequestValidator struct{} + +func (t *testPluginRequestValidator) Validate(string, *http.Request) error { + return nil +} diff --git a/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go b/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go index 7c90a2e0d7e..b0a32fd6ec9 100644 --- a/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go +++ b/pkg/plugins/backendplugin/pluginextensionv2/rendererv2.pb.go @@ -6,11 +6,12 @@ package pluginextensionv2 import ( context "context" fmt "fmt" + math "math" + proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" - math "math" ) // Reference imports to suppress errors if they are not otherwise used. diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index 120668d11e9..5062ee51d45 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -119,6 +119,11 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange * return nil, fmt.Errorf("could not find datasource: %w", err) } + err := context.RequestValidator.Validate(getDsInfo.Result.Url, nil) + if err != nil { + return nil, fmt.Errorf("access denied: %w", err) + } + req := c.getRequestForAlertRule(getDsInfo.Result, timeRange, context.IsDebug) result := make(tsdb.TimeSeriesSlice, 0) diff --git a/pkg/services/alerting/conditions/query_test.go b/pkg/services/alerting/conditions/query_test.go index 42396080177..7dbc64482a5 100644 --- a/pkg/services/alerting/conditions/query_test.go +++ b/pkg/services/alerting/conditions/query_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/grafana/grafana-plugin-sdk-go/data" @@ -235,7 +237,8 @@ func queryConditionScenario(desc string, fn queryConditionScenarioFunc) { ctx := &queryConditionTestContext{} ctx.result = &alerting.EvalContext{ - Rule: &alerting.Rule{}, + Rule: &alerting.Rule{}, + RequestValidator: &validations.OSSPluginRequestValidator{}, } fn(ctx) diff --git a/pkg/services/alerting/engine.go b/pkg/services/alerting/engine.go index 3ccbc0ef5c3..f836e581f97 100644 --- a/pkg/services/alerting/engine.go +++ b/pkg/services/alerting/engine.go @@ -6,16 +6,16 @@ import ( "fmt" "time" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - tlog "github.com/opentracing/opentracing-go/log" - "github.com/benbjohnson/clock" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/setting" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" + tlog "github.com/opentracing/opentracing-go/log" "golang.org/x/sync/errgroup" ) @@ -23,8 +23,9 @@ import ( // schedules alert evaluations and makes sure notifications // are sent. type AlertEngine struct { - RenderService rendering.Service `inject:""` - Bus bus.Bus `inject:""` + RenderService rendering.Service `inject:""` + Bus bus.Bus `inject:""` + RequestValidator models.PluginRequestValidator `inject:""` execQueue chan *Job ticker *Ticker @@ -164,7 +165,7 @@ func (e *AlertEngine) processJob(attemptID int, attemptChan chan int, cancelChan span := opentracing.StartSpan("alert execution") alertCtx = opentracing.ContextWithSpan(alertCtx, span) - evalContext := NewEvalContext(alertCtx, job.Rule) + evalContext := NewEvalContext(alertCtx, job.Rule, e.RequestValidator) evalContext.Ctx = alertCtx go func() { diff --git a/pkg/services/alerting/eval_context.go b/pkg/services/alerting/eval_context.go index dc3c4bd4b71..ddd35291a67 100644 --- a/pkg/services/alerting/eval_context.go +++ b/pkg/services/alerting/eval_context.go @@ -33,19 +33,22 @@ type EvalContext struct { NoDataFound bool PrevAlertState models.AlertStateType + RequestValidator models.PluginRequestValidator + Ctx context.Context } // NewEvalContext is the EvalContext constructor. -func NewEvalContext(alertCtx context.Context, rule *Rule) *EvalContext { +func NewEvalContext(alertCtx context.Context, rule *Rule, requestValidator models.PluginRequestValidator) *EvalContext { return &EvalContext{ - Ctx: alertCtx, - StartTime: time.Now(), - Rule: rule, - Logs: make([]*ResultLogEntry, 0), - EvalMatches: make([]*EvalMatch, 0), - log: log.New("alerting.evalContext"), - PrevAlertState: rule.State, + Ctx: alertCtx, + StartTime: time.Now(), + Rule: rule, + Logs: make([]*ResultLogEntry, 0), + EvalMatches: make([]*EvalMatch, 0), + log: log.New("alerting.evalContext"), + PrevAlertState: rule.State, + RequestValidator: requestValidator, } } diff --git a/pkg/services/alerting/eval_context_test.go b/pkg/services/alerting/eval_context_test.go index 14f0fba600d..f452c450148 100644 --- a/pkg/services/alerting/eval_context_test.go +++ b/pkg/services/alerting/eval_context_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/models" @@ -13,7 +15,7 @@ import ( ) func TestStateIsUpdatedWhenNeeded(t *testing.T) { - ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}) + ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}) t.Run("ok -> alerting", func(t *testing.T) { ctx.PrevAlertState = models.AlertStateOK @@ -198,7 +200,7 @@ func TestGetStateFromEvalContext(t *testing.T) { } for _, tc := range tcs { - evalContext := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}) + evalContext := NewEvalContext(context.Background(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}, &validations.OSSPluginRequestValidator{}) tc.applyFn(evalContext) newState := evalContext.GetNewState() diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go index f4284668807..218dffca158 100644 --- a/pkg/services/alerting/eval_handler_test.go +++ b/pkg/services/alerting/eval_handler_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/services/validations" + . "github.com/smartystreets/goconvey/convey" ) @@ -27,7 +29,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { Conditions: []Condition{&conditionStub{ firing: true, }}, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, true) @@ -37,7 +39,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { Convey("Show return triggered with single passing condition2", func() { context := NewEvalContext(context.TODO(), &Rule{ Conditions: []Condition{&conditionStub{firing: true, operator: "and"}}, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, true) @@ -50,7 +52,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: true, operator: "and", matches: []*EvalMatch{{}, {}}}, &conditionStub{firing: false, operator: "and"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, false) @@ -63,7 +65,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: true, operator: "and"}, &conditionStub{firing: false, operator: "or"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, true) @@ -76,7 +78,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: true, operator: "and"}, &conditionStub{firing: false, operator: "and"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, false) @@ -90,7 +92,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: true, operator: "and"}, &conditionStub{firing: false, operator: "or"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, true) @@ -104,7 +106,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: false, operator: "and"}, &conditionStub{firing: false, operator: "or"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, false) @@ -118,7 +120,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: false, operator: "and"}, &conditionStub{firing: true, operator: "and"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, false) @@ -132,7 +134,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: false, operator: "or"}, &conditionStub{firing: true, operator: "or"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, true) @@ -146,7 +148,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{firing: false, operator: "or"}, &conditionStub{firing: false, operator: "or"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, false) @@ -161,7 +163,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{operator: "or", noData: false}, &conditionStub{operator: "or", noData: false}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.NoDataFound, ShouldBeFalse) @@ -172,7 +174,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { Conditions: []Condition{ &conditionStub{operator: "and", noData: true}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.Firing, ShouldEqual, false) @@ -185,7 +187,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{operator: "and", noData: true}, &conditionStub{operator: "and", noData: false}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.NoDataFound, ShouldBeFalse) @@ -197,7 +199,7 @@ func TestAlertingEvaluationHandler(t *testing.T) { &conditionStub{operator: "or", noData: true}, &conditionStub{operator: "or", noData: false}, }, - }) + }, &validations.OSSPluginRequestValidator{}) handler.Eval(context) So(context.NoDataFound, ShouldBeTrue) diff --git a/pkg/services/alerting/notifier_test.go b/pkg/services/alerting/notifier_test.go index 78dcb86df13..f2c671c8547 100644 --- a/pkg/services/alerting/notifier_test.go +++ b/pkg/services/alerting/notifier_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/setting" @@ -19,17 +21,17 @@ import ( func TestNotificationService(t *testing.T) { testRule := &Rule{Name: "Test", Message: "Something is bad"} - evalCtx := NewEvalContext(context.Background(), testRule) + evalCtx := NewEvalContext(context.Background(), testRule, &validations.OSSPluginRequestValidator{}) testRuleTemplated := &Rule{Name: "Test latency ${quantile}", Message: "Something is bad on instance ${instance}"} - evalCtxWithMatch := NewEvalContext(context.Background(), testRuleTemplated) + evalCtxWithMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}) evalCtxWithMatch.EvalMatches = []*EvalMatch{{ Tags: map[string]string{ "instance": "localhost:3000", "quantile": "0.99", }, }} - evalCtxWithoutMatch := NewEvalContext(context.Background(), testRuleTemplated) + evalCtxWithoutMatch := NewEvalContext(context.Background(), testRuleTemplated, &validations.OSSPluginRequestValidator{}) notificationServiceScenario(t, "Given alert rule with upload image enabled should render and upload image and send notification", evalCtx, true, func(sc *scenarioContext) { diff --git a/pkg/services/alerting/notifiers/alertmanager_test.go b/pkg/services/alerting/notifiers/alertmanager_test.go index 58b553a257b..7f0c5ac12b9 100644 --- a/pkg/services/alerting/notifiers/alertmanager_test.go +++ b/pkg/services/alerting/notifiers/alertmanager_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/stretchr/testify/assert" "github.com/grafana/grafana/pkg/components/simplejson" @@ -66,7 +68,7 @@ func TestWhenAlertManagerShouldNotify(t *testing.T) { am := &AlertmanagerNotifier{log: log.New("test.logger")} evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ State: tc.prevState, - }) + }, &validations.OSSPluginRequestValidator{}) evalContext.Rule.State = tc.newState diff --git a/pkg/services/alerting/notifiers/base_test.go b/pkg/services/alerting/notifiers/base_test.go index 163b97cac5b..d85302c8589 100644 --- a/pkg/services/alerting/notifiers/base_test.go +++ b/pkg/services/alerting/notifiers/base_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/stretchr/testify/assert" "github.com/grafana/grafana/pkg/components/simplejson" @@ -169,7 +171,7 @@ func TestShouldSendAlertNotification(t *testing.T) { for _, tc := range tcs { evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ State: tc.prevState, - }) + }, &validations.OSSPluginRequestValidator{}) if tc.state == nil { tc.state = &models.AlertNotificationState{} diff --git a/pkg/services/alerting/notifiers/dingding_test.go b/pkg/services/alerting/notifiers/dingding_test.go index aa3f0b8852c..4a53a4e4b47 100644 --- a/pkg/services/alerting/notifiers/dingding_test.go +++ b/pkg/services/alerting/notifiers/dingding_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" @@ -48,7 +50,7 @@ func TestDingDingNotifier(t *testing.T) { &alerting.Rule{ State: models.AlertStateAlerting, Message: `{host="localhost"}`, - }) + }, &validations.OSSPluginRequestValidator{}) _, err = notifier.genBody(evalContext, "") So(err, ShouldBeNil) }) diff --git a/pkg/services/alerting/notifiers/opsgenie_test.go b/pkg/services/alerting/notifiers/opsgenie_test.go index a61e51c68be..d8e3d915df6 100644 --- a/pkg/services/alerting/notifiers/opsgenie_test.go +++ b/pkg/services/alerting/notifiers/opsgenie_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" @@ -78,7 +80,7 @@ func TestOpsGenieNotifier(t *testing.T) { Message: "someMessage", State: models.AlertStateAlerting, AlertRuleTags: tagPairs, - }) + }, &validations.OSSPluginRequestValidator{}) evalContext.IsTestRun = true receivedTags := make([]string, 0) diff --git a/pkg/services/alerting/notifiers/pagerduty_test.go b/pkg/services/alerting/notifiers/pagerduty_test.go index a182eaf1a67..83b1aa64988 100644 --- a/pkg/services/alerting/notifiers/pagerduty_test.go +++ b/pkg/services/alerting/notifiers/pagerduty_test.go @@ -5,6 +5,8 @@ import ( "strings" "testing" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/google/go-cmp/cmp" "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" @@ -138,7 +140,7 @@ func TestPagerdutyNotifier(t *testing.T) { Name: "someRule", Message: "someMessage", State: models.AlertStateAlerting, - }) + }, &validations.OSSPluginRequestValidator{}) evalContext.IsTestRun = true payloadJSON, err := pagerdutyNotifier.buildEventPayload(evalContext) @@ -194,7 +196,7 @@ func TestPagerdutyNotifier(t *testing.T) { Name: "someRule", Message: "someMessage", State: models.AlertStateAlerting, - }) + }, &validations.OSSPluginRequestValidator{}) evalContext.IsTestRun = true evalContext.EvalMatches = []*alerting.EvalMatch{ { @@ -272,7 +274,7 @@ func TestPagerdutyNotifier(t *testing.T) { {Key: "severity", Value: "warning"}, {Key: "dedup_key", Value: "key-" + strings.Repeat("x", 260)}, }, - }) + }, &validations.OSSPluginRequestValidator{}) evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" evalContext.IsTestRun = true @@ -350,7 +352,7 @@ func TestPagerdutyNotifier(t *testing.T) { {Key: "component", Value: "aComponent"}, {Key: "severity", Value: "info"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" evalContext.IsTestRun = true @@ -428,7 +430,7 @@ func TestPagerdutyNotifier(t *testing.T) { {Key: "component", Value: "aComponent"}, {Key: "severity", Value: "llama"}, }, - }) + }, &validations.OSSPluginRequestValidator{}) evalContext.ImagePublicURL = "http://somewhere.com/omg_dont_panic.png" evalContext.IsTestRun = true diff --git a/pkg/services/alerting/notifiers/pushover_test.go b/pkg/services/alerting/notifiers/pushover_test.go index 7dc8d1a1443..ae9982e4e00 100644 --- a/pkg/services/alerting/notifiers/pushover_test.go +++ b/pkg/services/alerting/notifiers/pushover_test.go @@ -5,6 +5,8 @@ import ( "strings" "testing" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/components/simplejson" @@ -75,7 +77,7 @@ func TestGenPushoverBody(t *testing.T) { evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ State: models.AlertStateAlerting, - }) + }, &validations.OSSPluginRequestValidator{}) _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") So(err, ShouldBeNil) @@ -86,7 +88,7 @@ func TestGenPushoverBody(t *testing.T) { evalContext := alerting.NewEvalContext(context.Background(), &alerting.Rule{ State: models.AlertStateOK, - }) + }, &validations.OSSPluginRequestValidator{}) _, pushoverBody, err := notifier.genPushoverBody(evalContext, "", "") So(err, ShouldBeNil) diff --git a/pkg/services/alerting/notifiers/telegram_test.go b/pkg/services/alerting/notifiers/telegram_test.go index 893081575f6..5a2db4c4869 100644 --- a/pkg/services/alerting/notifiers/telegram_test.go +++ b/pkg/services/alerting/notifiers/telegram_test.go @@ -4,6 +4,8 @@ import ( "context" "testing" + "github.com/grafana/grafana/pkg/services/validations" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" @@ -57,7 +59,7 @@ func TestTelegramNotifier(t *testing.T) { Name: "This is an alarm", Message: "Some kind of message.", State: models.AlertStateOK, - }) + }, &validations.OSSPluginRequestValidator{}) caption := generateImageCaption(evalContext, "http://grafa.url/abcdef", "") So(len(caption), ShouldBeLessThanOrEqualTo, 1024) @@ -73,7 +75,7 @@ func TestTelegramNotifier(t *testing.T) { Name: "This is an alarm", Message: "Some kind of message.", State: models.AlertStateOK, - }) + }, &validations.OSSPluginRequestValidator{}) caption := generateImageCaption(evalContext, "http://grafa.url/abcdefaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", @@ -91,7 +93,7 @@ func TestTelegramNotifier(t *testing.T) { Name: "This is an alarm", Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis scelerisque. Nulla ipsum ex, iaculis vitae vehicula sit amet, fermentum eu eros.", State: models.AlertStateOK, - }) + }, &validations.OSSPluginRequestValidator{}) caption := generateImageCaption(evalContext, "http://grafa.url/foo", @@ -108,7 +110,7 @@ func TestTelegramNotifier(t *testing.T) { Name: "This is an alarm", Message: "Some kind of message that is too long for appending to our pretty little message, this line is actually exactly 197 chars long and I will get there in the end I promise I will. Yes siree that's it. But suddenly Telegram increased the length so now we need some lorem ipsum to fix this test. Here we go: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus consectetur molestie cursus. Donec suscipit egestas nisi. Proin ut efficitur ex. Mauris mi augue, volutpat a nisi vel, euismod dictum arcu. Sed quis tempor eros, sed malesuada dolor. Ut orci augue, viverra sit amet blandit quis, faucibus sit amet ex. Duis condimentum efficitur lectus, id dignissim quam tempor id. Morbi sollicitudin rhoncus diam, id tincidunt lectus scelerisque vitae. Etiam imperdiet semper sem, vel eleifend ligula mollis eget. Etiam ultrices fringilla lacus, sit amet pharetra ex blandit quis. Suspendisse in egestas neque, et posuere lectus. Vestibulum eu ex dui. Sed molestie nulla a lobortis sceleri", State: models.AlertStateOK, - }) + }, &validations.OSSPluginRequestValidator{}) caption := generateImageCaption(evalContext, "http://grafa.url/foo", diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go index d9248e6a1eb..33d449b6322 100644 --- a/pkg/services/alerting/test_notification.go +++ b/pkg/services/alerting/test_notification.go @@ -3,6 +3,7 @@ package alerting import ( "context" "fmt" + "net/http" "github.com/grafana/grafana/pkg/components/securejsondata" @@ -83,7 +84,7 @@ func createTestEvalContext(cmd *NotificationTestCommand) *EvalContext { State: models.AlertStateAlerting, } - ctx := NewEvalContext(context.Background(), testRule) + ctx := NewEvalContext(context.Background(), testRule, fakeRequestValidator{}) if cmd.Settings.Get("uploadImage").MustBool(true) { ctx.ImagePublicURL = "https://grafana.com/assets/img/blog/mixed_styles.png" } @@ -109,3 +110,9 @@ func evalMatchesBasedOnState() []*EvalMatch { return matches } + +type fakeRequestValidator struct{} + +func (fakeRequestValidator) Validate(_ string, _ *http.Request) error { + return nil +} diff --git a/pkg/services/alerting/test_rule.go b/pkg/services/alerting/test_rule.go index c276572c261..8d1c1426b72 100644 --- a/pkg/services/alerting/test_rule.go +++ b/pkg/services/alerting/test_rule.go @@ -51,7 +51,7 @@ func handleAlertTestCommand(cmd *AlertTestCommand) error { func testAlertRule(rule *Rule) *EvalContext { handler := NewEvalHandler() - context := NewEvalContext(context.Background(), rule) + context := NewEvalContext(context.Background(), rule, fakeRequestValidator{}) context.IsTestRun = true context.IsDebug = true diff --git a/pkg/services/validations/oss.go b/pkg/services/validations/oss.go new file mode 100644 index 00000000000..dc6ab81b399 --- /dev/null +++ b/pkg/services/validations/oss.go @@ -0,0 +1,15 @@ +package validations + +import ( + "net/http" +) + +type OSSPluginRequestValidator struct{} + +func (*OSSPluginRequestValidator) Init() error { + return nil +} + +func (*OSSPluginRequestValidator) Validate(string, *http.Request) error { + return nil +}