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 <leo@xlson.com>
This commit is contained in:
Joan López de la Franca Beltran 2021-02-03 20:47:45 +01:00 committed by GitHub
parent 4a324e3d74
commit 6415d2802e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 224 additions and 102 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

12
pkg/models/validations.go Normal file
View File

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

View File

@ -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 != "" {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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{}

View File

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

View File

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

View File

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

View File

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

View File

@ -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",

View File

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

View File

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

View File

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