From a7813275a5fe2867f5a81690eb406480ea10a96d Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 31 May 2022 17:58:06 +0200 Subject: [PATCH] Plugins: Support headers field for check health (#49930) --- ...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 a42e1afb43a..3c61460d768 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( github.com/grafana/cuetsy v0.0.2 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 cc0645a57a2..4329fa191fd 100644 --- a/go.sum +++ b/go.sum @@ -1461,8 +1461,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 }