Backend plugins: Prepare and clean request headers before resource calls (#22321)

Moves common request proxy utilities to proxyutil package with
support for removing X-Forwarded-Host, X-Forwarded-Port,
X-Forwarded-Proto headers, setting X-Forwarded-For header
and cleaning Cookie header.
Using the proxyutil package to prepare and clean request
headers before resource calls.

Closes #21512
This commit is contained in:
Marcus Efraimsson
2020-03-03 11:45:16 +01:00
committed by GitHub
parent 8b122ee464
commit e6cec8dbdc
10 changed files with 225 additions and 160 deletions

View File

@ -6,6 +6,9 @@ import (
"sync"
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util/proxyutil"
"github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana/pkg/infra/log"
@ -41,7 +44,7 @@ type Manager interface {
// CheckHealth checks the health of a registered backend plugin.
CheckHealth(ctx context.Context, pluginID string) (*CheckHealthResult, error)
// CallResource calls a plugin resource.
CallResource(ctx context.Context, req CallResourceRequest) (*CallResourceResult, error)
CallResource(pluginConfig PluginConfig, ctx *models.ReqContext, path string)
}
type manager struct {
@ -170,18 +173,46 @@ func (m *manager) CheckHealth(ctx context.Context, pluginID string) (*CheckHealt
}
// CallResource calls a plugin resource.
func (m *manager) CallResource(ctx context.Context, req CallResourceRequest) (*CallResourceResult, error) {
func (m *manager) CallResource(config PluginConfig, c *models.ReqContext, path string) {
m.pluginsMu.RLock()
p, registered := m.plugins[req.Config.PluginID]
p, registered := m.plugins[config.PluginID]
m.pluginsMu.RUnlock()
if !registered {
return nil, ErrPluginNotRegistered
c.JsonApiErr(404, "Plugin not registered", nil)
return
}
res, err := p.callResource(ctx, req)
clonedReq := c.Req.Clone(c.Req.Context())
keepCookieNames := []string{}
if config.JSONData != nil {
if keepCookies := config.JSONData.Get("keepCookies"); keepCookies != nil {
keepCookieNames = keepCookies.MustStringArray()
}
}
proxyutil.ClearCookieHeader(clonedReq, keepCookieNames)
proxyutil.PrepareProxyRequest(clonedReq)
body, err := c.Req.Body().Bytes()
if err != nil {
return nil, err
c.JsonApiErr(500, "Failed to read request body", err)
return
}
req := CallResourceRequest{
Config: config,
Path: path,
Method: clonedReq.Method,
URL: clonedReq.URL.String(),
Headers: clonedReq.Header,
Body: body,
}
res, err := p.callResource(clonedReq.Context(), req)
if err != nil {
c.JsonApiErr(500, "Failed to call resource", err)
return
}
// Make sure a content type always is returned in response
@ -189,7 +220,20 @@ func (m *manager) CallResource(ctx context.Context, req CallResourceRequest) (*C
res.Headers["Content-Type"] = []string{"application/json"}
}
return res, nil
for k, values := range res.Headers {
if k == "Set-Cookie" {
continue
}
for _, v := range values {
c.Resp.Header().Add(k, v)
}
}
c.WriteHeader(res.Status)
if _, err := c.Write(res.Body); err != nil {
p.logger.Error("Failed to write resource response", "error", err)
}
}
func startPluginAndRestartKilledProcesses(ctx context.Context, p *BackendPlugin) error {