From 2793bb1d779eb74682fc00503d6697eb8c5b1b76 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 5 Apr 2017 23:59:12 -0500 Subject: [PATCH 1/3] Add meta redirect for Influx Enterprise similar to meta client. --- enterprise/meta.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/enterprise/meta.go b/enterprise/meta.go index cd08730b7..81cc7a485 100644 --- a/enterprise/meta.go +++ b/enterprise/meta.go @@ -384,7 +384,12 @@ func (d *defaultClient) Do(URL *url.URL, path, method string, params map[string] req.Header.Set("Content-Type", "application/json") } - res, err := http.DefaultClient.Do(req) + // Meta servers will redirect (307) to leader. We need + // special handling to preserve authentication headers. + client := &http.Client{ + CheckRedirect: AuthedCheckRedirect, + } + res, err := client.Do(req) if err != nil { return nil, err } @@ -404,6 +409,22 @@ func (d *defaultClient) Do(URL *url.URL, path, method string, params map[string] } +// AuthedCheckRedirect tries to follow the Influx Enterprise pattern of +// redirecting to the leader but preserving authentication headers. +func AuthedCheckRedirect(req *http.Request, via []*http.Request) error { + if len(via) >= 10 { + return errors.New("too many redirects") + } else if len(via) == 0 { + return nil + } + for attr, val := range via[0].Header { + if _, ok := req.Header[attr]; !ok { + req.Header[attr] = val + } + } + return nil +} + // Do is a cancelable function to interface with Influx Enterprise's Meta API func (m *MetaClient) Do(ctx context.Context, method, path string, params map[string]string, body io.Reader) (*http.Response, error) { type result struct { From 96e8d5b125d734a33f084fe67b4959a65ed019d5 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 6 Apr 2017 00:54:06 -0500 Subject: [PATCH 2/3] Update CHANGELOG to mention fixing redirects to influx enterprise meta --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92b964393..0ca5b5adf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ 1. [#1179](https://github.com/influxdata/chronograf/pull/1179): Admin Databases Page will render a database without retention policies 1. [#1128](https://github.com/influxdata/chronograf/pull/1128): No more ghost dashboards 👻 1. [#1189](https://github.com/influxdata/chronograf/pull/1189): Clicking inside the graph header edit box will no longer blur the field. Use the Escape key for that behavior instead. + 1. [#1195](https://github.com/influxdata/chronograf/issues/1195): Chronograf was not redirecting with authentiation for Influx Enterprise Meta service ### Features 1. [#1112](https://github.com/influxdata/chronograf/pull/1112): Add ability to delete a dashboard From 7081e8631257950d4d560103d502894e802fdd28 Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Thu, 6 Apr 2017 14:01:27 -0500 Subject: [PATCH 3/3] Update meta to only include authorization header. --- enterprise/meta.go | 7 +++-- enterprise/meta_test.go | 57 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/enterprise/meta.go b/enterprise/meta.go index 81cc7a485..28518aa1d 100644 --- a/enterprise/meta.go +++ b/enterprise/meta.go @@ -417,10 +417,9 @@ func AuthedCheckRedirect(req *http.Request, via []*http.Request) error { } else if len(via) == 0 { return nil } - for attr, val := range via[0].Header { - if _, ok := req.Header[attr]; !ok { - req.Header[attr] = val - } + preserve := "Authorization" + if auth, ok := via[0].Header[preserve]; ok { + req.Header[preserve] = auth } return nil } diff --git a/enterprise/meta_test.go b/enterprise/meta_test.go index 96150d054..f8a1c3ffc 100644 --- a/enterprise/meta_test.go +++ b/enterprise/meta_test.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "net/http" + "net/http/httptest" "net/url" "reflect" "testing" @@ -1396,3 +1397,59 @@ func (c *MockClient) Do(URL *url.URL, path, method string, params map[string]str Body: ioutil.NopCloser(bytes.NewReader(c.Body)), }, nil } + +func Test_AuthedCheckRedirect_Do(t *testing.T) { + var ts2URL string + ts1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + want := http.Header{ + "Referer": []string{ts2URL}, + "Accept-Encoding": []string{"gzip"}, + "Authorization": []string{"hunter2"}, + } + for k, v := range want { + if !reflect.DeepEqual(r.Header[k], v) { + t.Errorf("Request.Header = %#v; want %#v", r.Header[k], v) + } + } + if t.Failed() { + w.Header().Set("Result", "got errors") + } else { + w.Header().Set("Result", "ok") + } + })) + defer ts1.Close() + + ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, ts1.URL, http.StatusFound) + })) + defer ts2.Close() + ts2URL = ts2.URL + + tr := &http.Transport{} + defer tr.CloseIdleConnections() + + c := &http.Client{ + Transport: tr, + CheckRedirect: AuthedCheckRedirect, + } + + req, _ := http.NewRequest("GET", ts2.URL, nil) + req.Header.Add("Cookie", "foo=bar") + req.Header.Add("Authorization", "hunter2") + req.Header.Add("Howdy", "doody") + req.Header.Set("User-Agent", "Darth Vader, an extraterrestrial from the Planet Vulcan") + + res, err := c.Do(req) + if err != nil { + t.Fatal(err) + } + + defer res.Body.Close() + if res.StatusCode != 200 { + t.Fatal(res.Status) + } + + if got := res.Header.Get("Result"); got != "ok" { + t.Errorf("result = %q; want ok", got) + } +}