diff --git a/CHANGELOG.md b/CHANGELOG.md index 6791b8f16c3..ac7e93e1ace 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ IMPROVEMENTS: * drivers: Interpret Nomad variables in environment variables/args [GH-653] * core: Populate job status [GH-663] * core/cli: Print short identifiers and UX cleanup [GH-675, GH-693, GH-692] + * core/api: Allow users to set arbitrary headers via agent config [GH-699] BUG FIXES: * cli: Handle parsing of un-named ports [GH-604] diff --git a/command/agent/config.go b/command/agent/config.go index 1d567d653e5..7982dcbe5d9 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -101,6 +101,10 @@ type Config struct { // List of config files that have been loaded (in order) Files []string + + // HTTPAPIResponseHeaders allows users to configure the Nomad http agent to + // set arbritrary headers on API responses + HTTPAPIResponseHeaders map[string]string `hcl:"http_api_response_headers"` } // AtlasConfig is used to enable an parameterize the Atlas integration diff --git a/command/agent/config_test.go b/command/agent/config_test.go index 160895afaf9..2662e1155a7 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -453,6 +453,9 @@ func TestConfig_LoadConfigString(t *testing.T) { Join: true, Endpoint: "127.0.0.1:1234", }, + HTTPAPIResponseHeaders: map[string]string{ + "Access-Control-Allow-Origin": "*", + }, } // Check parsing @@ -531,4 +534,7 @@ atlas { join = true endpoint = "127.0.0.1:1234" } +http_api_response_headers { + Access-Control-Allow-Origin = "*" +} ` diff --git a/command/agent/http.go b/command/agent/http.go index ca5b2010478..6b36971faf2 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -152,6 +152,7 @@ func (e *codedError) Code() int { // wrap is used to wrap functions to make them more convenient func (s *HTTPServer) wrap(handler func(resp http.ResponseWriter, req *http.Request) (interface{}, error)) func(resp http.ResponseWriter, req *http.Request) { f := func(resp http.ResponseWriter, req *http.Request) { + setHeaders(resp, s.agent.config.HTTPAPIResponseHeaders) // Invoke the handler reqURL := req.URL.String() start := time.Now() @@ -229,6 +230,13 @@ func setMeta(resp http.ResponseWriter, m *structs.QueryMeta) { setKnownLeader(resp, m.KnownLeader) } +// setHeaders is used to set canonical response header fields +func setHeaders(resp http.ResponseWriter, headers map[string]string) { + for field, value := range headers { + resp.Header().Set(http.CanonicalHeaderKey(field), value) + } +} + // 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.QueryOptions) bool { diff --git a/command/agent/http_test.go b/command/agent/http_test.go index b9da798208c..8001d722e9d 100644 --- a/command/agent/http_test.go +++ b/command/agent/http_test.go @@ -104,6 +104,26 @@ func TestSetMeta(t *testing.T) { } } +func TestSetHeaders(t *testing.T) { + s := makeHTTPServer(t, nil) + s.Agent.config.HTTPAPIResponseHeaders = map[string]string{"foo": "bar"} + defer s.Cleanup() + + resp := httptest.NewRecorder() + handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) { + return &structs.Job{Name: "foo"}, nil + } + + req, _ := http.NewRequest("GET", "/v1/kv/key", nil) + s.Server.wrap(handler)(resp, req) + header := resp.Header().Get("foo") + + if header != "bar" { + t.Fatalf("expected header: %v, actual: %v", "bar", header) + } + +} + func TestContentTypeIsJSON(t *testing.T) { s := makeHTTPServer(t, nil) defer s.Cleanup() diff --git a/website/source/docs/agent/config.html.md b/website/source/docs/agent/config.html.md index 3c76bcee312..4319762198f 100644 --- a/website/source/docs/agent/config.html.md +++ b/website/source/docs/agent/config.html.md @@ -190,6 +190,15 @@ nodes, unless otherwise specified: * `disable_anonymous_signature`: Disables providing an anonymous signature for de-duplication with the update check. See `disable_update_check`. +* `http_api_response_headers`: This object allows adding headers to the + HTTP API responses. For example, the following config can be used to enable + CORS on the HTTP API endpoints: + ``` + http_api_response_headers { + Access-Control-Allow-Origin = "*" + } + ``` + ## Server-specific Options The following options are applicable to server agents only and need not be