From 87501ac6ecac4e5ca5f8a81c69dd4e1085ab8815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20=C3=81lvarez?= Date: Mon, 10 Feb 2020 16:09:00 +0100 Subject: [PATCH] Make list of Access-Control-Allow-Headers configurable. The configurable list is appended to Content-Type, Content-Encoding and Accept, which are allways included. Closes #2497 --- _meta/beat.yml | 4 ++++ apm-server.docker.yml | 4 ++++ apm-server.yml | 4 ++++ beater/api/mux.go | 2 +- beater/config/config_test.go | 3 +++ beater/config/rum.go | 2 ++ beater/middleware/cors_middleware.go | 7 ++++--- beater/middleware/cors_middleware_test.go | 18 ++++++++++++------ 8 files changed, 34 insertions(+), 10 deletions(-) diff --git a/_meta/beat.yml b/_meta/beat.yml index 86758a5b780..5cf387ef6b7 100644 --- a/_meta/beat.yml +++ b/_meta/beat.yml @@ -239,6 +239,10 @@ apm-server: # If an item in the list is a single '*', everything will be allowed. #allow_origins : ['*'] + # A list of Access-Control-Allow-Headers to allow RUM requests, in addition to "Content-Type", + # "Content-Encoding", and "Accept" + #allow_headers : [] + # Regexp to be matched against a stacktrace frame's `file_name` and `abs_path` attributes. # If the regexp matches, the stacktrace frame is considered to be a library frame. #library_pattern: "node_modules|bower_components|~" diff --git a/apm-server.docker.yml b/apm-server.docker.yml index 3fd11312e44..400aaa02e1c 100644 --- a/apm-server.docker.yml +++ b/apm-server.docker.yml @@ -239,6 +239,10 @@ apm-server: # If an item in the list is a single '*', everything will be allowed. #allow_origins : ['*'] + # A list of Access-Control-Allow-Headers to allow RUM requests, in addition to "Content-Type", + # "Content-Encoding", and "Accept" + #allow_headers : [] + # Regexp to be matched against a stacktrace frame's `file_name` and `abs_path` attributes. # If the regexp matches, the stacktrace frame is considered to be a library frame. #library_pattern: "node_modules|bower_components|~" diff --git a/apm-server.yml b/apm-server.yml index 178bfba7fb8..3ed3ddd8350 100644 --- a/apm-server.yml +++ b/apm-server.yml @@ -239,6 +239,10 @@ apm-server: # If an item in the list is a single '*', everything will be allowed. #allow_origins : ['*'] + # A list of Access-Control-Allow-Headers to allow RUM requests, in addition to "Content-Type", + # "Content-Encoding", and "Accept" + #allow_headers : [] + # Regexp to be matched against a stacktrace frame's `file_name` and `abs_path` attributes. # If the regexp matches, the stacktrace frame is considered to be a library frame. #library_pattern: "node_modules|bower_components|~" diff --git a/beater/api/mux.go b/beater/api/mux.go index 82690b94618..70180d1b8c6 100644 --- a/beater/api/mux.go +++ b/beater/api/mux.go @@ -217,7 +217,7 @@ func rumMiddleware(cfg *config.Config, _ *authorization.Handler, m map[request.R return append(apmMiddleware(m), middleware.SetRumFlagMiddleware(), middleware.SetIPRateLimitMiddleware(cfg.RumConfig.EventRate), - middleware.CORSMiddleware(cfg.RumConfig.AllowOrigins), + middleware.CORSMiddleware(cfg.RumConfig.AllowOrigins, cfg.RumConfig.AllowHeaders), middleware.KillSwitchMiddleware(cfg.RumConfig.IsEnabled(), msg)) } diff --git a/beater/config/config_test.go b/beater/config/config_test.go index abdec411473..2a63ee09acc 100644 --- a/beater/config/config_test.go +++ b/beater/config/config_test.go @@ -77,6 +77,7 @@ func Test_UnpackConfig(t *testing.T) { "lru_size": 2000, }, "allow_origins": []string{"example*"}, + "allow_headers": []string{"Authorization"}, "source_mapping": map[string]interface{}{ "cache": map[string]interface{}{ "expiration": 8 * time.Minute, @@ -135,6 +136,7 @@ func Test_UnpackConfig(t *testing.T) { LruSize: 2000, }, AllowOrigins: []string{"example*"}, + AllowHeaders: []string{"Authorization"}, SourceMapping: &SourceMapping{ Cache: &Cache{Expiration: 8 * time.Minute}, IndexPattern: "apm-test*", @@ -239,6 +241,7 @@ func Test_UnpackConfig(t *testing.T) { LruSize: 1000, }, AllowOrigins: []string{"*"}, + AllowHeaders: []string{}, SourceMapping: &SourceMapping{ Cache: &Cache{ Expiration: 7 * time.Second, diff --git a/beater/config/rum.go b/beater/config/rum.go index 0bd9c621fe6..183aa030e52 100644 --- a/beater/config/rum.go +++ b/beater/config/rum.go @@ -45,6 +45,7 @@ type RumConfig struct { Enabled *bool `config:"enabled"` EventRate *EventRate `config:"event_rate"` AllowOrigins []string `config:"allow_origins"` + AllowHeaders []string `config:"allow_headers"` LibraryPattern string `config:"library_pattern"` ExcludeFromGrouping string `config:"exclude_from_grouping"` SourceMapping *SourceMapping `config:"source_mapping"` @@ -146,6 +147,7 @@ func defaultRum(beatVersion string) *RumConfig { LruSize: defaultEventRateLRUSize, }, AllowOrigins: []string{allowAllOrigins}, + AllowHeaders: []string{}, SourceMapping: &SourceMapping{ Cache: &Cache{ Expiration: defaultSourcemapCacheExpiration, diff --git a/beater/middleware/cors_middleware.go b/beater/middleware/cors_middleware.go index 37f8fa76331..cfbce0683ab 100644 --- a/beater/middleware/cors_middleware.go +++ b/beater/middleware/cors_middleware.go @@ -29,13 +29,13 @@ import ( ) var ( - supportedHeaders = strings.Join([]string{headers.ContentType, headers.ContentEncoding, headers.Accept}, ", ") + supportedHeaders = []string{headers.ContentType, headers.ContentEncoding, headers.Accept} supportedMethods = strings.Join([]string{http.MethodPost, http.MethodOptions}, ", ") ) // CORSMiddleware returns a middleware serving preflight OPTION requests and terminating requests if they do not // match the required valid origin. -func CORSMiddleware(allowedOrigins []string) Middleware { +func CORSMiddleware(allowedOrigins, allowedHeaders []string) Middleware { var isAllowed = func(origin string) bool { for _, allowed := range allowedOrigins { if glob.Glob(allowed, origin) { @@ -65,7 +65,8 @@ func CORSMiddleware(allowedOrigins []string) Middleware { // required if Access-Control-Request-Method and Access-Control-Request-Headers are in the requestHeaders c.Header().Set(headers.AccessControlAllowMethods, supportedMethods) - c.Header().Set(headers.AccessControlAllowHeaders, supportedHeaders) + h := append(allowedHeaders, supportedHeaders...) + c.Header().Set(headers.AccessControlAllowHeaders, strings.Join(h, ", ")) c.Header().Set(headers.AccessControlExposeHeaders, headers.Etag) diff --git a/beater/middleware/cors_middleware_test.go b/beater/middleware/cors_middleware_test.go index 4af675f3e5b..b9ef94e1d4c 100644 --- a/beater/middleware/cors_middleware_test.go +++ b/beater/middleware/cors_middleware_test.go @@ -32,10 +32,10 @@ import ( func TestCORSMiddleware(t *testing.T) { - cors := func(origin string, allowedOrigins []string, m string) (*request.Context, *httptest.ResponseRecorder) { + cors := func(origin string, allowedOrigins, allowedHeaders []string, m string) (*request.Context, *httptest.ResponseRecorder) { c, rec := beatertest.ContextWithResponseRecorder(m, "/") c.Request.Header.Set(headers.Origin, origin) - Apply(CORSMiddleware(allowedOrigins), beatertest.Handler202)(c) + Apply(CORSMiddleware(allowedOrigins, allowedHeaders), beatertest.Handler202)(c) return c, rec } @@ -50,19 +50,19 @@ func TestCORSMiddleware(t *testing.T) { } t.Run("OPTIONSValidOrigin", func(t *testing.T) { - _, rec := cors("wxyz", []string{"w*yz"}, http.MethodOptions) + _, rec := cors("wxyz", []string{"w*yz"}, nil, http.MethodOptions) checkPreflightHeaders(t, rec) assert.Equal(t, "wxyz", rec.Header().Get(headers.AccessControlAllowOrigin)) }) t.Run("OPTIONSInvalidOrigin", func(t *testing.T) { - _, rec := cors("xyz", []string{"xy.*i"}, http.MethodOptions) + _, rec := cors("xyz", []string{"xy.*i"}, nil, http.MethodOptions) checkPreflightHeaders(t, rec) assert.Empty(t, rec.Header().Get(headers.AccessControlAllowOrigin)) }) t.Run("GETAllowedOrigins", func(t *testing.T) { for _, origin := range []string{"", "wxyz", "testingx"} { - _, rec := cors(origin, []string{"*", "testing.*"}, http.MethodPost) + _, rec := cors(origin, []string{"*", "testing.*"}, nil, http.MethodPost) assert.Equal(t, http.StatusAccepted, rec.Code) assert.Equal(t, origin, rec.Header().Get(headers.AccessControlAllowOrigin)) @@ -71,7 +71,7 @@ func TestCORSMiddleware(t *testing.T) { t.Run("GETForbidden", func(t *testing.T) { for _, origin := range []string{"", "wxyz", "test"} { - _, rec := cors(origin, []string{"xyz", "testing.*"}, http.MethodPost) + _, rec := cors(origin, []string{"xyz", "testing.*"}, nil, http.MethodPost) assert.Equal(t, http.StatusForbidden, rec.Code) assert.Equal(t, @@ -81,4 +81,10 @@ func TestCORSMiddleware(t *testing.T) { rec.Body.String()) } }) + + t.Run("AllowedHeaders", func(t *testing.T) { + _, rec := cors("xyz", []string{"xyz"}, []string{"Authorization"}, http.MethodOptions) + assert.Contains(t, rec.Header().Get(headers.AccessControlAllowHeaders), "Authorization") + }) + }