mirror of
				https://github.com/grafana/grafana.git
				synced 2025-02-25 18:55:37 -06:00 
			
		
		
		
	Prometheus: Templating queries should pass on custom headers (#50344)
* pass on all headers except for accept headers * touch up and testing * add custom header values to resource queries * remove my picture. oops * handle gzip responses as well * fix linting issues * add my space * no lint * removed cookies from being proxied * clean up and handle errors from io.reader.Close() calls
This commit is contained in:
		
				
					committed by
					
						
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							63ed5367c3
						
					
				
				
					commit
					49dc9da9c1
				
			@@ -1,11 +1,15 @@
 | 
			
		||||
package resource
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"compress/gzip"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/grafana/grafana-plugin-sdk-go/backend"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/infra/httpclient"
 | 
			
		||||
@@ -16,8 +20,54 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Resource struct {
 | 
			
		||||
	provider *client.Provider
 | 
			
		||||
	log      log.Logger
 | 
			
		||||
	provider      *client.Provider
 | 
			
		||||
	log           log.Logger
 | 
			
		||||
	customHeaders map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Hop-by-hop headers. These are removed when sent to the backend.
 | 
			
		||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
 | 
			
		||||
var hopHeaders = []string{
 | 
			
		||||
	"Connection",
 | 
			
		||||
	"Keep-Alive",
 | 
			
		||||
	"Proxy-Authenticate",
 | 
			
		||||
	"Proxy-Authorization",
 | 
			
		||||
	"Te", // canonicalized version of "TE"
 | 
			
		||||
	"Trailers",
 | 
			
		||||
	"Transfer-Encoding",
 | 
			
		||||
	"Upgrade",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The following headers will be removed from the request
 | 
			
		||||
var stopHeaders = []string{
 | 
			
		||||
	"cookie",
 | 
			
		||||
	"Cookie",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func delHopHeaders(header http.Header) {
 | 
			
		||||
	for _, h := range hopHeaders {
 | 
			
		||||
		header.Del(h)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func delStopHeaders(header http.Header) {
 | 
			
		||||
	for _, h := range stopHeaders {
 | 
			
		||||
		header.Del(h)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func addHeaders(header http.Header, toAdd map[string]string) {
 | 
			
		||||
	for k, v := range toAdd {
 | 
			
		||||
		header.Add(k, v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func normalizeReqHeaders(headers map[string][]string) map[string]string {
 | 
			
		||||
	h := make(map[string]string, len(headers))
 | 
			
		||||
	for k, v := range headers {
 | 
			
		||||
		h[k] = strings.Join(v, ",")
 | 
			
		||||
	}
 | 
			
		||||
	return h
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func New(
 | 
			
		||||
@@ -34,14 +84,46 @@ func New(
 | 
			
		||||
 | 
			
		||||
	p := client.NewProvider(settings, jsonData, httpClientProvider, cfg, features, plog)
 | 
			
		||||
 | 
			
		||||
	customHeaders := make(map[string]string)
 | 
			
		||||
	var jsonDataMap map[string]interface{}
 | 
			
		||||
 | 
			
		||||
	err := json.Unmarshal(settings.JSONData, &jsonDataMap)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	index := 1
 | 
			
		||||
	for {
 | 
			
		||||
		headerNameSuffix := fmt.Sprintf("httpHeaderName%d", index)
 | 
			
		||||
		headerValueSuffix := fmt.Sprintf("httpHeaderValue%d", index)
 | 
			
		||||
 | 
			
		||||
		key := jsonDataMap[headerNameSuffix]
 | 
			
		||||
		if key == nil {
 | 
			
		||||
			// No (more) header values are available
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if val, ok := settings.DecryptedSecureJSONData[headerValueSuffix]; ok {
 | 
			
		||||
			switch k := key.(type) {
 | 
			
		||||
			case string:
 | 
			
		||||
				customHeaders[k] = val
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		index++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &Resource{
 | 
			
		||||
		log:      plog,
 | 
			
		||||
		provider: p,
 | 
			
		||||
		log:           plog,
 | 
			
		||||
		provider:      p,
 | 
			
		||||
		customHeaders: customHeaders,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *Resource) Execute(ctx context.Context, req *backend.CallResourceRequest) (int, []byte, error) {
 | 
			
		||||
	client, err := r.provider.GetClient(reqHeaders(req.Headers))
 | 
			
		||||
	delHopHeaders(req.Headers)
 | 
			
		||||
	delStopHeaders(req.Headers)
 | 
			
		||||
	addHeaders(req.Headers, r.customHeaders)
 | 
			
		||||
	client, err := r.provider.GetClient(normalizeReqHeaders(req.Headers))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 500, nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -65,25 +147,29 @@ func (r *Resource) fetch(ctx context.Context, client *client.Client, req *backen
 | 
			
		||||
		return statusCode, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer resp.Body.Close() //nolint (we don't care about the error being returned by resp.Body.Close())
 | 
			
		||||
	defer func() {
 | 
			
		||||
		err = resp.Body.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	data, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	// Check that the server actually sent compressed data
 | 
			
		||||
	var reader io.ReadCloser
 | 
			
		||||
	switch resp.Header.Get("Content-Encoding") {
 | 
			
		||||
	case "gzip":
 | 
			
		||||
		reader, err = gzip.NewReader(resp.Body)
 | 
			
		||||
		defer func() {
 | 
			
		||||
			err = reader.Close()
 | 
			
		||||
		}()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 500, nil, err
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		reader = resp.Body
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data, err := ioutil.ReadAll(reader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 500, nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return resp.StatusCode, data, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func reqHeaders(headers map[string][]string) map[string]string {
 | 
			
		||||
	// Keep only the authorization header, incase downstream the authorization header is required.
 | 
			
		||||
	// Strip all the others out as appropriate headers will be applied to speak with prometheus.
 | 
			
		||||
	h := make(map[string]string)
 | 
			
		||||
	accessValues := headers["Authorization"]
 | 
			
		||||
 | 
			
		||||
	if len(accessValues) > 0 {
 | 
			
		||||
		h["Authorization"] = accessValues[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return h
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user