From: Tom Clegg Date: Mon, 26 Jun 2017 18:53:53 +0000 (-0400) Subject: 11901: Require management token for health checks. X-Git-Tag: 1.1.0~170^2~3 X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/47b2e4988e9bfb69ef00de1c900a20f714af5f2f 11901: Require management token for health checks. Arvados-DCO-1.1-Signed-off-by: Tom Clegg --- diff --git a/sdk/go/arvadostest/fixtures.go b/sdk/go/arvadostest/fixtures.go index 7e21da4982..cdab4633b4 100644 --- a/sdk/go/arvadostest/fixtures.go +++ b/sdk/go/arvadostest/fixtures.go @@ -7,6 +7,7 @@ const ( AdminToken = "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h" AnonymousToken = "4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi" DataManagerToken = "320mkve8qkswstz7ff61glpk3mhgghmg67wmic7elw4z41pke1" + ManagementToken = "jg3ajndnq63sywcd50gbs5dskdc9ckkysb0nsqmfz08nwf17nl" ActiveUserUUID = "zzzzz-tpzed-xurymjxw79nv3jz" SpectatorUserUUID = "zzzzz-tpzed-l1s2piq4t4mps8r" UserAgreementCollection = "zzzzz-4zz18-uukreo9rbgwsujr" // user_agreement_in_anonymously_accessible_project diff --git a/services/ws/config.go b/services/ws/config.go index 79c2f232da..cf82cf8e10 100644 --- a/services/ws/config.go +++ b/services/ws/config.go @@ -17,6 +17,8 @@ type wsConfig struct { PingTimeout arvados.Duration ClientEventQueue int ServerEventQueue int + + ManagementToken string } func defaultConfig() wsConfig { diff --git a/services/ws/router.go b/services/ws/router.go index b2c94e7109..77744974d3 100644 --- a/services/ws/router.go +++ b/services/ws/router.go @@ -53,10 +53,13 @@ func (rtr *router) setup() { rtr.mux = http.NewServeMux() rtr.mux.Handle("/websocket", rtr.makeServer(newSessionV0)) rtr.mux.Handle("/arvados/v1/events.ws", rtr.makeServer(newSessionV1)) - rtr.mux.HandleFunc("/debug.json", jsonHandler(rtr.DebugStatus)) - rtr.mux.HandleFunc("/status.json", jsonHandler(rtr.Status)) - rtr.mux.HandleFunc("/_health/ping", jsonHandler(rtr.HealthFunc(func() error { return nil }))) - rtr.mux.HandleFunc("/_health/db", jsonHandler(rtr.HealthFunc(rtr.eventSource.DBHealth))) + rtr.mux.Handle("/debug.json", rtr.jsonHandler(rtr.DebugStatus)) + rtr.mux.Handle("/status.json", rtr.jsonHandler(rtr.Status)) + + health := http.NewServeMux() + rtr.mux.Handle("/_health/", rtr.mgmtAuth(health)) + health.Handle("/_health/ping", rtr.jsonHandler(rtr.HealthFunc(func() error { return nil }))) + health.Handle("/_health/db", rtr.jsonHandler(rtr.HealthFunc(rtr.eventSource.DBHealth))) } func (rtr *router) makeServer(newSession sessionFactory) *websocket.Server { @@ -142,16 +145,30 @@ func (rtr *router) ServeHTTP(resp http.ResponseWriter, req *http.Request) { rtr.mux.ServeHTTP(resp, req) } -func jsonHandler(fn func() interface{}) http.HandlerFunc { - return func(resp http.ResponseWriter, req *http.Request) { - logger := logger(req.Context()) - resp.Header().Set("Content-Type", "application/json") - enc := json.NewEncoder(resp) +func (rtr *router) mgmtAuth(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if rtr.Config.ManagementToken == "" { + http.Error(w, "disabled", http.StatusNotFound) + } else if ah := r.Header.Get("Authorization"); ah == "" { + http.Error(w, "authorization required", http.StatusUnauthorized) + } else if ah != "Bearer "+rtr.Config.ManagementToken { + http.Error(w, "authorization error", http.StatusForbidden) + } else { + h.ServeHTTP(w, r) + } + }) +} + +func (rtr *router) jsonHandler(fn func() interface{}) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + logger := logger(r.Context()) + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) err := enc.Encode(fn()) if err != nil { msg := "encode failed" logger.WithError(err).Error(msg) - http.Error(resp, msg, http.StatusInternalServerError) + http.Error(w, msg, http.StatusInternalServerError) } - } + }) } diff --git a/services/ws/server_test.go b/services/ws/server_test.go index 57c734af2d..da7440d0e0 100644 --- a/services/ws/server_test.go +++ b/services/ws/server_test.go @@ -7,6 +7,7 @@ import ( "time" "git.curoverse.com/arvados.git/sdk/go/arvados" + "git.curoverse.com/arvados.git/sdk/go/arvadostest" check "gopkg.in/check.v1" ) @@ -28,6 +29,7 @@ func (*serverSuite) testConfig() *wsConfig { cfg.Client = *(arvados.NewClientFromEnv()) cfg.Postgres = testDBConfig() cfg.Listen = ":" + cfg.ManagementToken = arvadostest.ManagementToken return &cfg } @@ -64,9 +66,21 @@ func (s *serverSuite) TestBadDB(c *check.C) { func (s *serverSuite) TestHealth(c *check.C) { go s.srv.Run() s.srv.WaitReady() - resp, err := http.Get("http://" + s.srv.listener.Addr().String() + "/_health/ping") - c.Check(err, check.IsNil) - buf, err := ioutil.ReadAll(resp.Body) - c.Check(err, check.IsNil) - c.Check(string(buf), check.Equals, `{"health":"OK"}`+"\n") + for _, token := range []string{"", "foo", s.cfg.ManagementToken} { + req, err := http.NewRequest("GET", "http://"+s.srv.listener.Addr().String()+"/_health/ping", nil) + c.Assert(err, check.IsNil) + if token != "" { + req.Header.Add("Authorization", "Bearer "+token) + } + resp, err := http.DefaultClient.Do(req) + c.Check(err, check.IsNil) + if token == s.cfg.ManagementToken { + c.Check(resp.StatusCode, check.Equals, http.StatusOK) + buf, err := ioutil.ReadAll(resp.Body) + c.Check(err, check.IsNil) + c.Check(string(buf), check.Equals, `{"health":"OK"}`+"\n") + } else { + c.Check(resp.StatusCode, check.Not(check.Equals), http.StatusOK) + } + } }