mirror of https://github.com/grafana/grafana
AuthZ: add headers for IP range AC checks for data source proxy requests (#81662)
* add a middleware that appens headers for IP range AC to data source proxy requests * update code * add tests * fix a mistake * add logging * refactor to reuse code * small cleanup * skip the plugins middleware if the header is already set * skip the plugins middleware if the header is already setpull/83971/head
parent
401265522e
commit
2c5b72e844
@ -0,0 +1,34 @@ |
||||
package httpclientprovider |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" |
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/clientmiddleware" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
const GrafanaRequestIDHeaderMiddlewareName = "grafana-request-id-header-middleware" |
||||
|
||||
func GrafanaRequestIDHeaderMiddleware(cfg *setting.Cfg, logger log.Logger) sdkhttpclient.Middleware { |
||||
return sdkhttpclient.NamedMiddlewareFunc(GrafanaRequestIDHeaderMiddlewareName, func(opts sdkhttpclient.Options, next http.RoundTripper) http.RoundTripper { |
||||
return sdkhttpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) { |
||||
if req.Header.Get(clientmiddleware.GrafanaRequestID) != "" { |
||||
logger.Debug("Request already has a Grafana request ID header", "request_id", req.Header.Get(clientmiddleware.GrafanaRequestID)) |
||||
return next.RoundTrip(req) |
||||
} |
||||
|
||||
if !clientmiddleware.IsRequestURLInAllowList(req.URL, cfg) { |
||||
logger.Debug("Data source URL not among the allow-listed URLs", "url", req.URL.String()) |
||||
return next.RoundTrip(req) |
||||
} |
||||
|
||||
for k, v := range clientmiddleware.GetGrafanaRequestIDHeaders(req, cfg, logger) { |
||||
req.Header.Set(k, v) |
||||
} |
||||
|
||||
return next.RoundTrip(req) |
||||
}) |
||||
}) |
||||
} |
@ -0,0 +1,113 @@ |
||||
package httpclientprovider |
||||
|
||||
import ( |
||||
"crypto/hmac" |
||||
"crypto/sha256" |
||||
"encoding/hex" |
||||
"net/http" |
||||
"net/url" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" |
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/clientmiddleware" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestGrafanaRequestIDHeaderMiddleware(t *testing.T) { |
||||
testCases := []struct { |
||||
description string |
||||
allowedURLs []*url.URL |
||||
requestURL string |
||||
remoteAddress string |
||||
expectGrafanaRequestIDHeaders bool |
||||
expectPrivateRequestHeader bool |
||||
}{ |
||||
{ |
||||
description: "With target URL in the allowed URL list and remote address specified, should add headers to the request but the request should not be marked as private", |
||||
allowedURLs: []*url.URL{{ |
||||
Scheme: "https", |
||||
Host: "grafana.com", |
||||
}}, |
||||
requestURL: "https://grafana.com/api/some/path", |
||||
remoteAddress: "1.2.3.4", |
||||
expectGrafanaRequestIDHeaders: true, |
||||
expectPrivateRequestHeader: false, |
||||
}, |
||||
{ |
||||
description: "With target URL in the allowed URL list and remote address not specified, should add headers to the request and the request should be marked as private", |
||||
allowedURLs: []*url.URL{{ |
||||
Scheme: "https", |
||||
Host: "grafana.com", |
||||
}}, |
||||
requestURL: "https://grafana.com/api/some/path", |
||||
expectGrafanaRequestIDHeaders: true, |
||||
expectPrivateRequestHeader: true, |
||||
}, |
||||
{ |
||||
description: "With target URL not in the allowed URL list, should not add headers to the request", |
||||
allowedURLs: []*url.URL{{ |
||||
Scheme: "https", |
||||
Host: "grafana.com", |
||||
}}, |
||||
requestURL: "https://fake-grafana.com/api/some/path", |
||||
expectGrafanaRequestIDHeaders: false, |
||||
}, |
||||
} |
||||
|
||||
for _, tc := range testCases { |
||||
t.Run(tc.description, func(t *testing.T) { |
||||
ctx := &testContext{} |
||||
finalRoundTripper := ctx.createRoundTripper("final") |
||||
cfg := setting.NewCfg() |
||||
cfg.IPRangeACEnabled = false |
||||
cfg.IPRangeACAllowedURLs = tc.allowedURLs |
||||
cfg.IPRangeACSecretKey = "secret" |
||||
mw := GrafanaRequestIDHeaderMiddleware(cfg, log.New("test")) |
||||
rt := mw.CreateMiddleware(httpclient.Options{}, finalRoundTripper) |
||||
require.NotNil(t, rt) |
||||
middlewareName, ok := mw.(httpclient.MiddlewareName) |
||||
require.True(t, ok) |
||||
require.Equal(t, GrafanaRequestIDHeaderMiddlewareName, middlewareName.MiddlewareName()) |
||||
|
||||
req, err := http.NewRequest(http.MethodGet, tc.requestURL, nil) |
||||
require.NoError(t, err) |
||||
req.RemoteAddr = tc.remoteAddress |
||||
res, err := rt.RoundTrip(req) |
||||
require.NoError(t, err) |
||||
require.NotNil(t, res) |
||||
if res.Body != nil { |
||||
require.NoError(t, res.Body.Close()) |
||||
} |
||||
require.Len(t, ctx.callChain, 1) |
||||
require.ElementsMatch(t, []string{"final"}, ctx.callChain) |
||||
|
||||
if !tc.expectGrafanaRequestIDHeaders { |
||||
require.Len(t, req.Header.Values(clientmiddleware.GrafanaRequestID), 0) |
||||
require.Len(t, req.Header.Values(clientmiddleware.GrafanaSignedRequestID), 0) |
||||
} else { |
||||
require.Len(t, req.Header.Values(clientmiddleware.GrafanaRequestID), 1) |
||||
require.Len(t, req.Header.Values(clientmiddleware.GrafanaSignedRequestID), 1) |
||||
requestID := req.Header.Get(clientmiddleware.GrafanaRequestID) |
||||
|
||||
instance := hmac.New(sha256.New, []byte(cfg.IPRangeACSecretKey)) |
||||
_, err = instance.Write([]byte(requestID)) |
||||
require.NoError(t, err) |
||||
computed := hex.EncodeToString(instance.Sum(nil)) |
||||
|
||||
require.Equal(t, req.Header.Get(clientmiddleware.GrafanaSignedRequestID), computed) |
||||
|
||||
if tc.remoteAddress == "" { |
||||
require.Equal(t, req.Header.Get(clientmiddleware.GrafanaInternalRequest), "true") |
||||
} else { |
||||
require.Len(t, req.Header.Values(clientmiddleware.XRealIPHeader), 1) |
||||
require.Equal(t, req.Header.Get(clientmiddleware.XRealIPHeader), tc.remoteAddress) |
||||
|
||||
// Internal header should not be set
|
||||
require.Len(t, req.Header.Values(clientmiddleware.GrafanaInternalRequest), 0) |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
} |
Loading…
Reference in new issue