From 088125ae2817002ac09de91afca428be62ef5247 Mon Sep 17 00:00:00 2001 From: "Grot (@grafanabot)" <43478413+grafanabot@users.noreply.github.com> Date: Tue, 31 May 2022 12:22:35 -0400 Subject: [PATCH] Plugins: Support headers field for check health (#49930) (#49949) (cherry picked from commit a7813275a5fe2867f5a81690eb406480ea10a96d) Co-authored-by: Marcus Efraimsson --- ...d-authentication-for-data-source-plugins.md | 18 ++++++++++++++++++ go.mod | 2 +- go.sum | 4 ++-- pkg/api/datasources.go | 17 +++++++++++++++++ pkg/api/pluginproxy/ds_proxy.go | 9 +-------- pkg/api/plugins.go | 1 + pkg/models/datasource.go | 12 ++++++++++++ .../backendplugin/grpcplugin/client_v2.go | 2 +- pkg/services/query/query.go | 10 ++-------- 9 files changed, 55 insertions(+), 20 deletions(-) diff --git a/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md b/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md index 4385945f1a7..a0a33d47341 100644 --- a/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md +++ b/docs/sources/developers/plugins/add-authentication-for-data-source-plugins.md @@ -293,6 +293,17 @@ To allow Grafana to pass the access token to the plugin, update the data source When configured, Grafana will pass the user's token to the plugin in an Authorization header, available on the `QueryDataRequest` object on the `QueryData` request in your backend data source. ```go +func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + token := strings.Fields(req.Headers["Authorization"]) + var ( + tokenType = token[0] + accessToken = token[1] + ) + + // ... + return &backend.CheckHealthResult{Status: backend.HealthStatusOk}, nil +} + func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { token := strings.Fields(req.Headers["Authorization"]) var ( @@ -309,6 +320,13 @@ func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataReque In addition, if the user's token includes an ID token, Grafana will pass the user's ID token to the plugin in an `X-ID-Token` header, available on the `QueryDataRequest` object on the `QueryData` request in your backend data source. ```go +func (ds *dataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { + idToken := req.Headers["X-ID-Token"] + + // ... + return &backend.CheckHealthResult{Status: backend.HealthStatusOk}, nil +} + func (ds *dataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { idToken := req.Headers["X-ID-Token"] diff --git a/go.mod b/go.mod index 3900b7e23b5..9175b472c39 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( github.com/grafana/cuetsy v0.0.1 github.com/grafana/grafana-aws-sdk v0.10.3 github.com/grafana/grafana-azure-sdk-go v1.2.0 - github.com/grafana/grafana-plugin-sdk-go v0.134.0 + github.com/grafana/grafana-plugin-sdk-go v0.135.0 github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/hashicorp/go-hclog v1.0.0 diff --git a/go.sum b/go.sum index 0f8a1421224..20ae68b9b25 100644 --- a/go.sum +++ b/go.sum @@ -1451,8 +1451,8 @@ github.com/grafana/grafana-plugin-sdk-go v0.94.0/go.mod h1:3VXz4nCv6wH5SfgB3mlW3 github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= github.com/grafana/grafana-plugin-sdk-go v0.125.0/go.mod h1:9YiJ5GUxIsIEUC0qR9+BJVP5M7mCSP6uc6Ne62YKkgc= github.com/grafana/grafana-plugin-sdk-go v0.129.0/go.mod h1:4edtosZepfQF9jkQwRywJsNSJzXTHmzbmcVcAl8MEQc= -github.com/grafana/grafana-plugin-sdk-go v0.134.0 h1:8j8vsvhU3GabRPWB1EnFKSWt60yQVFPEE/5P1QV2gFw= -github.com/grafana/grafana-plugin-sdk-go v0.134.0/go.mod h1:jmrxelOJKrIK0yrsIzcotS8pbqPZozbmJgGy7k3hK1k= +github.com/grafana/grafana-plugin-sdk-go v0.135.0 h1:IQrwA/RCPr5IhE3lVxIpgEXwZohx7gp3rJ1KJa0KT5g= +github.com/grafana/grafana-plugin-sdk-go v0.135.0/go.mod h1:jmrxelOJKrIK0yrsIzcotS8pbqPZozbmJgGy7k3hK1k= github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa h1:+pXjAxavVR2FKKNsuuCXGCWEj8XGc1Af6SPiyBpzU2A= github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa/go.mod h1:0O8o/juxNSKN/e+DzWDTRkl7Zm8CkZcz0NDqEdojlrk= github.com/grafana/saml v0.0.0-20211007135653-aed1b2edd86b h1:YiSGp34F4V0G08HHx1cJBf2GVgwYAkXQjzuVs1t8jYk= diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index f6a87092e92..1c43d9f7263 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources/permissions" "github.com/grafana/grafana/pkg/util" + "github.com/grafana/grafana/pkg/util/proxyutil" "github.com/grafana/grafana/pkg/web" ) @@ -568,6 +569,7 @@ func (hs *HTTPServer) checkDatasourceHealth(c *models.ReqContext, ds *models.Dat PluginID: plugin.ID, DataSourceInstanceSettings: dsInstanceSettings, }, + Headers: map[string]string{}, } var dsURL string @@ -580,6 +582,21 @@ func (hs *HTTPServer) checkDatasourceHealth(c *models.ReqContext, ds *models.Dat return response.Error(http.StatusForbidden, "Access denied", err) } + if hs.DataProxy.OAuthTokenService.IsOAuthPassThruEnabled(ds) { + if token := hs.DataProxy.OAuthTokenService.GetCurrentOAuthToken(c.Req.Context(), c.SignedInUser); token != nil { + req.Headers["Authorization"] = fmt.Sprintf("%s %s", token.Type(), token.AccessToken) + idToken, ok := token.Extra("id_token").(string) + if ok && idToken != "" { + req.Headers["X-ID-Token"] = idToken + } + } + } + + proxyutil.ClearCookieHeader(c.Req, ds.AllowedCookies()) + if cookieStr := c.Req.Header.Get("Cookie"); cookieStr != "" { + req.Headers["Cookie"] = cookieStr + } + resp, err := hs.pluginClient.CheckHealth(c.Req.Context(), req) if err != nil { return translatePluginRequestErrorToAPIError(err) diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index d17ddee68f9..a2feb41535d 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -222,14 +222,7 @@ func (proxy *DataSourceProxy) director(req *http.Request) { applyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser) - keepCookieNames := []string{} - if proxy.ds.JsonData != nil { - if keepCookies := proxy.ds.JsonData.Get("keepCookies"); keepCookies != nil { - keepCookieNames = keepCookies.MustStringArray() - } - } - - proxyutil.ClearCookieHeader(req, keepCookieNames) + proxyutil.ClearCookieHeader(req, proxy.ds.AllowedCookies()) req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion)) jsonData := make(map[string]interface{}) diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index b3e973c628d..b502ccd91b6 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -318,6 +318,7 @@ func (hs *HTTPServer) CheckHealth(c *models.ReqContext) response.Response { resp, err := hs.pluginClient.CheckHealth(c.Req.Context(), &backend.CheckHealthRequest{ PluginContext: pCtx, + Headers: map[string]string{}, }) if err != nil { return translatePluginRequestErrorToAPIError(err) diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index 377a07fe14f..58d1f391c8e 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -67,6 +67,18 @@ type DataSource struct { Updated time.Time `json:"updated"` } +// AllowedCookies parses the jsondata.keepCookies and returns a list of +// allowed cookies, otherwise an empty list. +func (ds DataSource) AllowedCookies() []string { + if ds.JsonData != nil { + if keepCookies := ds.JsonData.Get("keepCookies"); keepCookies != nil { + return keepCookies.MustStringArray() + } + } + + return []string{} +} + // ---------------------- // COMMANDS diff --git a/pkg/plugins/backendplugin/grpcplugin/client_v2.go b/pkg/plugins/backendplugin/grpcplugin/client_v2.go index 6d44516bda3..f6063217c6e 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_v2.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_v2.go @@ -115,7 +115,7 @@ func (c *ClientV2) CheckHealth(ctx context.Context, req *backend.CheckHealthRequ } protoContext := backend.ToProto().PluginContext(req.PluginContext) - protoResp, err := c.DiagnosticsClient.CheckHealth(ctx, &pluginv2.CheckHealthRequest{PluginContext: protoContext}) + protoResp, err := c.DiagnosticsClient.CheckHealth(ctx, &pluginv2.CheckHealthRequest{PluginContext: protoContext, Headers: req.Headers}) if err != nil { if status.Code(err) == codes.Unimplemented { diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index 2bd50694b59..082696f6691 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -151,14 +151,8 @@ func (s *Service) handleQueryData(ctx context.Context, user *models.SignedInUser req.Headers[k] = v } - if parsedReq.httpRequest != nil && parsedReq.httpRequest.Header.Get("Cookie") != "" && ds.JsonData != nil { - keepCookieNames := []string{} - - if keepCookies := ds.JsonData.Get("keepCookies"); keepCookies != nil { - keepCookieNames = keepCookies.MustStringArray() - } - - proxyutil.ClearCookieHeader(parsedReq.httpRequest, keepCookieNames) + if parsedReq.httpRequest != nil { + proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies()) if cookieStr := parsedReq.httpRequest.Header.Get("Cookie"); cookieStr != "" { req.Headers["Cookie"] = cookieStr }