From bae2055b0be497e4b5a4ec60608e141e94e44132 Mon Sep 17 00:00:00 2001 From: Joe Blubaugh Date: Tue, 10 May 2022 09:54:18 +0800 Subject: [PATCH] Alerting: Apply Custom Headers to datasource queries. (#47860) (#48851) Backport 53a4f39 from #47860 --- pkg/tsdb/legacydata/service/service.go | 139 +++++++++++++------- pkg/tsdb/legacydata/service/service_test.go | 33 +++++ 2 files changed, 123 insertions(+), 49 deletions(-) diff --git a/pkg/tsdb/legacydata/service/service.go b/pkg/tsdb/legacydata/service/service.go index b946d255252..25c8fc18519 100644 --- a/pkg/tsdb/legacydata/service/service.go +++ b/pkg/tsdb/legacydata/service/service.go @@ -3,9 +3,11 @@ package service import ( "context" "fmt" + "strings" "time" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/adapters" @@ -14,6 +16,11 @@ import ( "github.com/grafana/grafana/pkg/tsdb/legacydata" ) +const ( + headerName = "httpHeaderName" + headerValue = "httpHeaderValue" +) + var oAuthIsOAuthPassThruEnabledFunc = func(oAuthTokenService oauthtoken.OAuthTokenService, ds *models.DataSource) bool { return oAuthTokenService.IsOAuthPassThruEnabled(ds) } @@ -35,65 +42,19 @@ func ProvideService(pluginsClient plugins.Client, oAuthTokenService oauthtoken.O //nolint: staticcheck // legacydata.DataResponse deprecated func (h *Service) HandleRequest(ctx context.Context, ds *models.DataSource, query legacydata.DataQuery) (legacydata.DataResponse, error) { - jsonDataBytes, err := ds.JsonData.MarshalJSON() + decryptedJsonData := h.dataSourcesService.DecryptedValues(ds) + req, err := generateRequest(ctx, ds, decryptedJsonData, query) if err != nil { return legacydata.DataResponse{}, err } - instanceSettings := &backend.DataSourceInstanceSettings{ - ID: ds.Id, - Name: ds.Name, - URL: ds.Url, - Database: ds.Database, - User: ds.User, - BasicAuthEnabled: ds.BasicAuth, - BasicAuthUser: ds.BasicAuthUser, - JSONData: jsonDataBytes, - DecryptedSecureJSONData: h.dataSourcesService.DecryptedValues(ds), - Updated: ds.Updated, - UID: ds.Uid, - } - - if query.Headers == nil { - query.Headers = make(map[string]string) - } - + // Attach Auth information if oAuthIsOAuthPassThruEnabledFunc(h.oAuthTokenService, ds) { if token := h.oAuthTokenService.GetCurrentOAuthToken(ctx, query.User); token != nil { - delete(query.Headers, "Authorization") query.Headers["Authorization"] = fmt.Sprintf("%s %s", token.Type(), token.AccessToken) } } - req := &backend.QueryDataRequest{ - PluginContext: backend.PluginContext{ - OrgID: ds.OrgId, - PluginID: ds.Type, - User: adapters.BackendUserFromSignedInUser(query.User), - DataSourceInstanceSettings: instanceSettings, - }, - Queries: []backend.DataQuery{}, - Headers: query.Headers, - } - - for _, q := range query.Queries { - modelJSON, err := q.Model.MarshalJSON() - if err != nil { - return legacydata.DataResponse{}, err - } - req.Queries = append(req.Queries, backend.DataQuery{ - RefID: q.RefID, - Interval: time.Duration(q.IntervalMS) * time.Millisecond, - MaxDataPoints: q.MaxDataPoints, - TimeRange: backend.TimeRange{ - From: query.TimeRange.GetFromAsTimeUTC(), - To: query.TimeRange.GetToAsTimeUTC(), - }, - QueryType: q.QueryType, - JSON: modelJSON, - }) - } - resp, err := h.pluginsClient.QueryData(ctx, req) if err != nil { return legacydata.DataResponse{}, err @@ -126,4 +87,84 @@ func (h *Service) HandleRequest(ctx context.Context, ds *models.DataSource, quer return tR, nil } +func generateRequest(ctx context.Context, ds *models.DataSource, decryptedJsonData map[string]string, query legacydata.DataQuery) (*backend.QueryDataRequest, error) { + jsonDataBytes, err := ds.JsonData.MarshalJSON() + if err != nil { + return nil, err + } + + instanceSettings := &backend.DataSourceInstanceSettings{ + ID: ds.Id, + Name: ds.Name, + URL: ds.Url, + Database: ds.Database, + User: ds.User, + BasicAuthEnabled: ds.BasicAuth, + BasicAuthUser: ds.BasicAuthUser, + JSONData: jsonDataBytes, + DecryptedSecureJSONData: decryptedJsonData, + Updated: ds.Updated, + UID: ds.Uid, + } + + if query.Headers == nil { + query.Headers = make(map[string]string) + } + + req := &backend.QueryDataRequest{ + PluginContext: backend.PluginContext{ + OrgID: ds.OrgId, + PluginID: ds.Type, + User: adapters.BackendUserFromSignedInUser(query.User), + DataSourceInstanceSettings: instanceSettings, + }, + Queries: []backend.DataQuery{}, + Headers: query.Headers, + } + + // Apply Configured Custom Headers to query request. + for k, v := range customHeaders(ds.JsonData, instanceSettings.DecryptedSecureJSONData) { + req.Headers[k] = v + } + + for _, q := range query.Queries { + modelJSON, err := q.Model.MarshalJSON() + if err != nil { + return nil, err + } + req.Queries = append(req.Queries, backend.DataQuery{ + RefID: q.RefID, + Interval: time.Duration(q.IntervalMS) * time.Millisecond, + MaxDataPoints: q.MaxDataPoints, + TimeRange: backend.TimeRange{ + From: query.TimeRange.GetFromAsTimeUTC(), + To: query.TimeRange.GetToAsTimeUTC(), + }, + QueryType: q.QueryType, + JSON: modelJSON, + }) + } + return req, nil +} + +func customHeaders(jsonData *simplejson.Json, decryptedJsonData map[string]string) map[string]string { + if jsonData == nil { + return nil + } + + data := jsonData.MustMap() + + headers := map[string]string{} + for k := range data { + if strings.HasPrefix(k, headerName) { + if header, ok := data[k].(string); ok { + valueKey := strings.ReplaceAll(k, headerName, headerValue) + headers[header] = decryptedJsonData[valueKey] + } + } + } + + return headers +} + var _ legacydata.RequestHandler = &Service{} diff --git a/pkg/tsdb/legacydata/service/service_test.go b/pkg/tsdb/legacydata/service/service_test.go index 7e895da093a..5f220ebb5e3 100644 --- a/pkg/tsdb/legacydata/service/service_test.go +++ b/pkg/tsdb/legacydata/service/service_test.go @@ -57,6 +57,39 @@ func TestHandleRequest(t *testing.T) { }) } +func Test_generateRequest(t *testing.T) { + t.Run("Should attach custom headers to request if present", func(t *testing.T) { + jsonData := simplejson.New() + jsonData.Set(headerName+"testOne", "x-test-one") + jsonData.Set("testOne", "x-test-wrong") + jsonData.Set(headerName+"testTwo", "x-test-two") + + decryptedJsonData := map[string]string{ + headerValue + "testOne": "secret-value-one", + headerValue + "testTwo": "secret-value-two", + "something": "else", + } + + ds := &models.DataSource{Id: 12, Type: "unregisteredType", JsonData: jsonData} + query := legacydata.DataQuery{ + TimeRange: &legacydata.DataTimeRange{}, + Queries: []legacydata.DataSubQuery{ + {RefID: "A", DataSource: &models.DataSource{Id: 1, Type: "test"}, Model: simplejson.New()}, + {RefID: "B", DataSource: &models.DataSource{Id: 1, Type: "test"}, Model: simplejson.New()}, + }, + } + + req, err := generateRequest(context.Background(), ds, decryptedJsonData, query) + require.NoError(t, err) + require.NotNil(t, req) + require.EqualValues(t, + map[string]string{ + "x-test-one": "secret-value-one", + "x-test-two": "secret-value-two", + }, req.Headers) + }) +} + type fakePluginsClient struct { plugins.Client backend.QueryDataHandlerFunc