diff --git a/agent/http.go b/agent/http.go index c07d6df5db..4a84260921 100644 --- a/agent/http.go +++ b/agent/http.go @@ -322,9 +322,25 @@ func (s *HTTPServer) handler(enableDebug bool) http.Handler { fs := assetFS() uifs = fs } + uifs = &redirectFS{fs: &templatedIndexFS{fs: uifs, templateVars: s.GenerateHTMLTemplateVars}} - mux.Handle("/robots.txt", http.FileServer(uifs)) - mux.Handle(s.agent.config.UIContentPath, http.StripPrefix(s.agent.config.UIContentPath, http.FileServer(uifs))) + // create a http handler using the ui file system + // and the headers specified by the http_config.response_headers user config + uifsWithHeaders := serveHandlerWithHeaders( + http.FileServer(uifs), + s.agent.config.HTTPResponseHeaders, + ) + mux.Handle( + "/robots.txt", + uifsWithHeaders, + ) + mux.Handle( + s.agent.config.UIContentPath, + http.StripPrefix( + s.agent.config.UIContentPath, + uifsWithHeaders, + ), + ) } // Wrap the whole mux with a handler that bans URLs with non-printable @@ -752,6 +768,14 @@ func setHeaders(resp http.ResponseWriter, headers map[string]string) { } } +// serveHandlerWithHeaders is used to serve a http.Handler with the specified headers +func serveHandlerWithHeaders(h http.Handler, headers map[string]string) http.HandlerFunc { + return func(resp http.ResponseWriter, req *http.Request) { + setHeaders(resp, headers) + h.ServeHTTP(resp, req) + } +} + // parseWait is used to parse the ?wait and ?index query params // Returns true on error func parseWait(resp http.ResponseWriter, req *http.Request, b structs.QueryOptionsCompat) bool { diff --git a/agent/http_test.go b/agent/http_test.go index 5fe80c3045..b02a6982c7 100644 --- a/agent/http_test.go +++ b/agent/http_test.go @@ -431,6 +431,36 @@ func TestHTTPAPIResponseHeaders(t *testing.T) { t.Fatalf("bad X-XSS-Protection header: expected %q, got %q", "1; mode=block", xss) } } +func TestUIResponseHeaders(t *testing.T) { + t.Parallel() + a := NewTestAgent(t, t.Name(), ` + http_config { + response_headers = { + "Access-Control-Allow-Origin" = "*" + "X-Frame-Options" = "SAMEORIGIN" + } + } + `) + defer a.Shutdown() + + resp := httptest.NewRecorder() + handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + return nil, nil + } + + req, _ := http.NewRequest("GET", "/ui", nil) + a.srv.wrap(handler, []string{"GET"})(resp, req) + + origin := resp.Header().Get("Access-Control-Allow-Origin") + if origin != "*" { + t.Fatalf("bad Access-Control-Allow-Origin: expected %q, got %q", "*", origin) + } + + frameOptions := resp.Header().Get("X-Frame-Options") + if frameOptions != "SAMEORIGIN" { + t.Fatalf("bad X-XSS-Protection header: expected %q, got %q", "SAMEORIGIN", frameOptions) + } +} func TestContentTypeIsJSON(t *testing.T) { t.Parallel()