Data source proxy: Convert 401 from data source to 400 (#28962) (#29095)

* Data source proxy: Convert 401 from data source to 400

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Arve Knudsen
2020-11-13 15:06:38 +01:00
committed by GitHub
parent 697e4f7037
commit 4cf6c916c7
2 changed files with 92 additions and 74 deletions
+80 -62
View File
@@ -102,13 +102,8 @@ func (proxy *DataSourceProxy) HandleRequest() {
return
}
proxyErrorLogger := logger.New("userId", proxy.ctx.UserId, "orgId", proxy.ctx.OrgId, "uname", proxy.ctx.Login, "path", proxy.ctx.Req.URL.Path, "remote_addr", proxy.ctx.RemoteAddr(), "referer", proxy.ctx.Req.Referer())
reverseProxy := &httputil.ReverseProxy{
Director: proxy.getDirector(),
FlushInterval: time.Millisecond * 200,
ErrorLog: log.New(&logWrapper{logger: proxyErrorLogger}, "", 0),
}
proxyErrorLogger := logger.New("userId", proxy.ctx.UserId, "orgId", proxy.ctx.OrgId, "uname", proxy.ctx.Login,
"path", proxy.ctx.Req.URL.Path, "remote_addr", proxy.ctx.RemoteAddr(), "referer", proxy.ctx.Req.Referer())
transport, err := proxy.ds.GetHttpTransport()
if err != nil {
@@ -116,16 +111,43 @@ func (proxy *DataSourceProxy) HandleRequest() {
return
}
reverseProxy.Transport = &handleResponseTransport{
transport: transport,
reverseProxy := &httputil.ReverseProxy{
Director: proxy.director,
FlushInterval: time.Millisecond * 200,
ErrorLog: log.New(&logWrapper{logger: proxyErrorLogger}, "", 0),
Transport: &handleResponseTransport{
transport: transport,
},
ModifyResponse: func(resp *http.Response) error {
if resp.StatusCode == 401 {
// The data source rejected the request as unauthorized, convert to 400 (bad request)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read data source response body: %w", err)
}
_ = resp.Body.Close()
proxyErrorLogger.Info("Authentication to data source failed", "body", string(body), "statusCode",
resp.StatusCode)
msg := "Authentication to data source failed"
*resp = http.Response{
StatusCode: 400,
Status: "Bad Request",
Body: ioutil.NopCloser(strings.NewReader(msg)),
ContentLength: int64(len(msg)),
}
}
return nil
},
}
proxy.logRequest()
span, ctx := opentracing.StartSpanFromContext(proxy.ctx.Req.Context(), "datasource reverse proxy")
defer span.Finish()
proxy.ctx.Req.Request = proxy.ctx.Req.WithContext(ctx)
defer span.Finish()
span.SetTag("datasource_id", proxy.ds.Id)
span.SetTag("datasource_type", proxy.ds.Type)
span.SetTag("user_id", proxy.ctx.SignedInUser.UserId)
@@ -152,68 +174,64 @@ func (proxy *DataSourceProxy) addTraceFromHeaderValue(span opentracing.Span, hea
}
}
func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
return func(req *http.Request) {
req.URL.Scheme = proxy.targetUrl.Scheme
req.URL.Host = proxy.targetUrl.Host
req.Host = proxy.targetUrl.Host
func (proxy *DataSourceProxy) director(req *http.Request) {
req.URL.Scheme = proxy.targetUrl.Scheme
req.URL.Host = proxy.targetUrl.Host
req.Host = proxy.targetUrl.Host
reqQueryVals := req.URL.Query()
reqQueryVals := req.URL.Query()
switch proxy.ds.Type {
case models.DS_INFLUXDB_08:
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath)
reqQueryVals.Add("u", proxy.ds.User)
reqQueryVals.Add("p", proxy.ds.DecryptedPassword())
req.URL.RawQuery = reqQueryVals.Encode()
case models.DS_INFLUXDB:
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
req.URL.RawQuery = reqQueryVals.Encode()
if !proxy.ds.BasicAuth {
req.Header.Del("Authorization")
req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.DecryptedPassword()))
}
default:
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
switch proxy.ds.Type {
case models.DS_INFLUXDB_08:
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, "db/"+proxy.ds.Database+"/"+proxy.proxyPath)
reqQueryVals.Add("u", proxy.ds.User)
reqQueryVals.Add("p", proxy.ds.DecryptedPassword())
req.URL.RawQuery = reqQueryVals.Encode()
case models.DS_INFLUXDB:
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
req.URL.RawQuery = reqQueryVals.Encode()
if !proxy.ds.BasicAuth {
req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.User, proxy.ds.DecryptedPassword()))
}
default:
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
}
if proxy.ds.BasicAuth {
req.Header.Del("Authorization")
req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser, proxy.ds.DecryptedBasicAuthPassword()))
if proxy.ds.BasicAuth {
req.Header.Set("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser,
proxy.ds.DecryptedBasicAuthPassword()))
}
dsAuth := req.Header.Get("X-DS-Authorization")
if len(dsAuth) > 0 {
req.Header.Del("X-DS-Authorization")
req.Header.Set("Authorization", dsAuth)
}
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()
}
}
dsAuth := req.Header.Get("X-DS-Authorization")
if len(dsAuth) > 0 {
req.Header.Del("X-DS-Authorization")
req.Header.Del("Authorization")
req.Header.Add("Authorization", dsAuth)
}
proxyutil.ClearCookieHeader(req, keepCookieNames)
proxyutil.PrepareProxyRequest(req)
applyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser)
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
keepCookieNames := []string{}
if proxy.ds.JsonData != nil {
if keepCookies := proxy.ds.JsonData.Get("keepCookies"); keepCookies != nil {
keepCookieNames = keepCookies.MustStringArray()
}
}
// Clear Origin and Referer to avoir CORS issues
req.Header.Del("Origin")
req.Header.Del("Referer")
proxyutil.ClearCookieHeader(req, keepCookieNames)
proxyutil.PrepareProxyRequest(req)
if proxy.route != nil {
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
}
req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion))
// Clear Origin and Referer to avoir CORS issues
req.Header.Del("Origin")
req.Header.Del("Referer")
if proxy.route != nil {
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
}
if proxy.ds.JsonData != nil && proxy.ds.JsonData.Get("oauthPassThru").MustBool() {
addOAuthPassThruAuth(proxy.ctx, req)
}
if proxy.ds.JsonData != nil && proxy.ds.JsonData.Get("oauthPassThru").MustBool() {
addOAuthPassThruAuth(proxy.ctx, req)
}
}
+12 -12
View File
@@ -276,7 +276,7 @@ func TestDSRouteRule(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
proxy.getDirector()(req)
proxy.director(req)
Convey("Can translate request url and path", func() {
So(req.URL.Host, ShouldEqual, "graphite:8080")
@@ -303,7 +303,7 @@ func TestDSRouteRule(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
proxy.getDirector()(req)
proxy.director(req)
Convey("Should add db to url", func() {
So(req.URL.Path, ShouldEqual, "/db/site/")
@@ -330,7 +330,7 @@ func TestDSRouteRule(t *testing.T) {
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req.Header.Set("Cookie", cookies)
proxy.getDirector()(&req)
proxy.director(&req)
Convey("Should clear all cookies", func() {
So(req.Header.Get("Cookie"), ShouldEqual, "")
@@ -357,7 +357,7 @@ func TestDSRouteRule(t *testing.T) {
cookies := "grafana_user=admin; grafana_remember=99; grafana_sess=11; JSESSION_ID=test"
req.Header.Set("Cookie", cookies)
proxy.getDirector()(&req)
proxy.director(&req)
Convey("Should keep named cookies", func() {
So(req.Header.Get("Cookie"), ShouldEqual, "JSESSION_ID=test")
@@ -379,7 +379,7 @@ func TestDSRouteRule(t *testing.T) {
req.Header.Add("X-Canary", "stillthere")
So(err, ShouldBeNil)
proxy.getDirector()(req)
proxy.director(req)
Convey("Should keep user request (including trailing slash)", func() {
So(req.URL.String(), ShouldEqual, "http://host/root/path/to/folder/")
@@ -436,7 +436,7 @@ func TestDSRouteRule(t *testing.T) {
req, err = http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
proxy.getDirector()(req)
proxy.director(req)
Convey("Should have access token in header", func() {
So(req.Header.Get("Authorization"), ShouldEqual, fmt.Sprintf("%s %s", "Bearer", "testtoken"))
@@ -518,7 +518,7 @@ func TestDSRouteRule(t *testing.T) {
plugin := &plugins.DataSourcePlugin{}
ds := &models.DataSource{Url: backend.URL, Type: models.DS_GRAPHITE}
responseRecorder := &CloseNotifierResponseRecorder{
responseRecorder := &closeNotifierResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
}
defer responseRecorder.Close()
@@ -657,17 +657,17 @@ func TestNewDataSourceProxy_MSSQL(t *testing.T) {
}
}
type CloseNotifierResponseRecorder struct {
type closeNotifierResponseRecorder struct {
*httptest.ResponseRecorder
closeChan chan bool
}
func (r *CloseNotifierResponseRecorder) CloseNotify() <-chan bool {
func (r *closeNotifierResponseRecorder) CloseNotify() <-chan bool {
r.closeChan = make(chan bool)
return r.closeChan
}
func (r *CloseNotifierResponseRecorder) Close() {
func (r *closeNotifierResponseRecorder) Close() {
close(r.closeChan)
}
@@ -685,7 +685,7 @@ func getDatasourceProxiedRequest(ctx *models.ReqContext, cfg *setting.Cfg) *http
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
proxy.getDirector()(req)
proxy.director(req)
return req
}
@@ -796,7 +796,7 @@ func runDatasourceAuthTest(test *Test) {
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
So(err, ShouldBeNil)
proxy.getDirector()(req)
proxy.director(req)
test.checkReq(req)
}