From 96149f51bea0acaa2b0b0ac65ca81740742dc2d7 Mon Sep 17 00:00:00 2001 From: Anurag Rajawat Date: Wed, 20 Nov 2024 11:03:26 +0530 Subject: [PATCH 1/2] NGINX: Add NJS Signed-off-by: Anurag Rajawat --- images/nginx/rootfs/build.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/images/nginx/rootfs/build.sh b/images/nginx/rootfs/build.sh index 3baf775fca..3d08f7d397 100755 --- a/images/nginx/rootfs/build.sh +++ b/images/nginx/rootfs/build.sh @@ -106,6 +106,9 @@ export OPENTELEMETRY_CPP_VERSION="v1.11.0" # Check on https://github.com/open-telemetry/opentelemetry-proto export OPENTELEMETRY_PROTO_VERSION="v1.1.0" +# http://hg.nginx.org/njs +export NGINX_NJS_VERSION="0.8.4" + export BUILD_PATH=/tmp/build ARCH=$(uname -m) @@ -276,6 +279,9 @@ get_src efb767487ea3f6031577b9b224467ddbda2ad51a41c5867a47582d4ad85d609e \ get_src d74f86ada2329016068bc5a243268f1f555edd620b6a7d6ce89295e7d6cf18da \ "https://github.com/microsoft/mimalloc/archive/${MIMALOC_VERSION}.tar.gz" "mimalloc" +get_src 8191bff8491af9169a92e30e383ef8614717b0c6d40913d83b95051031e92321 \ + "http://hg.nginx.org/njs/archive/${NGINX_NJS_VERSION}.tar.gz" "njs" + # improve compilation times CORES=$(($(grep -c ^processor /proc/cpuinfo) - 1)) @@ -481,7 +487,8 @@ WITH_MODULES=" \ --add-dynamic-module=$BUILD_PATH/nginx-http-auth-digest \ --add-dynamic-module=$BUILD_PATH/ModSecurity-nginx \ --add-dynamic-module=$BUILD_PATH/ngx_http_geoip2_module \ - --add-dynamic-module=$BUILD_PATH/ngx_brotli" + --add-dynamic-module=$BUILD_PATH/ngx_brotli \ + --add-dynamic-module=$BUILD_PATH/njs/nginx" ./configure \ --prefix=/usr/local/nginx \ From 49ab05e7ad63ce0bf514ccda607bc61e1f6147a9 Mon Sep 17 00:00:00 2001 From: Elizabeth Martin Campos Date: Wed, 27 Nov 2024 12:24:52 +0100 Subject: [PATCH 2/2] Replace CORS handling logic with NJS scripting (avoid using Nginx 'if' directives) --- .../ingress/controller/template/template.go | 7 +- .../controller/template/template_test.go | 33 + rootfs/etc/nginx/js/nginx/ngx_handle_cors.js | 21 + rootfs/etc/nginx/template/nginx.tmpl | 38 +- test/e2e/annotations/cors.go | 568 +++++++----------- 5 files changed, 288 insertions(+), 379 deletions(-) create mode 100644 rootfs/etc/nginx/js/nginx/ngx_handle_cors.js diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go index ed052e4ecf..5447482817 100644 --- a/internal/ingress/controller/template/template.go +++ b/internal/ingress/controller/template/template.go @@ -300,6 +300,7 @@ var funcMap = text_template.FuncMap{ "getenv": os.Getenv, "contains": strings.Contains, "split": strings.Split, + "join": strings.Join, "hasPrefix": strings.HasPrefix, "hasSuffix": strings.HasSuffix, "trimSpace": strings.TrimSpace, @@ -1675,10 +1676,10 @@ func buildOriginRegex(origin string) string { func buildCorsOriginRegex(corsOrigins []string) string { if len(corsOrigins) == 1 && corsOrigins[0] == "*" { - return "set $http_origin *;\nset $cors 'true';" + return ".*" } - originsRegex := "if ($http_origin ~* (" + originsRegex := "(" for i, origin := range corsOrigins { originTrimmed := strings.TrimSpace(origin) if originTrimmed != "" { @@ -1689,6 +1690,6 @@ func buildCorsOriginRegex(corsOrigins []string) string { } } } - originsRegex += ")$ ) { set $cors 'true'; }" + originsRegex += ")" return originsRegex } diff --git a/internal/ingress/controller/template/template_test.go b/internal/ingress/controller/template/template_test.go index 6553f5daf9..d00e2f999d 100644 --- a/internal/ingress/controller/template/template_test.go +++ b/internal/ingress/controller/template/template_test.go @@ -1953,3 +1953,36 @@ func TestCleanConf(t *testing.T) { t.Errorf("cleanConf result don't match with expected: %s", diff) } } + +func TestBuildCorsOriginRegex(t *testing.T) { + origins := []string{"http://foo.bar "} + + result := buildCorsOriginRegex(origins) + + expected := `((http://foo\.bar))` + if result != expected { + t.Errorf("expected '%v' but returned '%v'", expected, result) + } +} + +func TestBuildCorsOriginRegexWithMultipleOrigins(t *testing.T) { + origins := []string{" http://foo.bar", "http://*.bar"} + + result := buildCorsOriginRegex(origins) + + expected := `((http://foo\.bar)|(http://[A-Za-z0-9\-]+\.bar))` + if result != expected { + t.Errorf("expected '%v' but returned '%v'", expected, result) + } +} + +func TestBuildCorsOriginRegexWithWildcard(t *testing.T) { + origins := []string{"*"} + + result := buildCorsOriginRegex(origins) + + expected := `.*` + if result != expected { + t.Errorf("expected '%v' but returned '%v'", expected, result) + } +} diff --git a/rootfs/etc/nginx/js/nginx/ngx_handle_cors.js b/rootfs/etc/nginx/js/nginx/ngx_handle_cors.js new file mode 100644 index 0000000000..880590448a --- /dev/null +++ b/rootfs/etc/nginx/js/nginx/ngx_handle_cors.js @@ -0,0 +1,21 @@ +function handle_cors(req) { + const originsRegex = new RegExp(`${req.variables.cors_origins_regex}$`, 'i'); + + if (originsRegex.test(req.headersIn['Origin'])) { + const allowedOrigins = req.variables.cors_allowed_origins.split(','); + + req.headersOut['Access-Control-Allow-Origin'] = allowedOrigins.length === 1 && allowedOrigins[0] === '*' ? '*' : req.headersIn['Origin']; + req.headersOut['Access-Control-Allow-Methods'] = req.variables.cors_allow_methods; + req.headersOut['Access-Control-Allow-Headers'] = req.variables.cors_allow_headers; + req.headersOut['Access-Control-Max-Age'] = req.variables.cors_max_age; + if (req.variables.cors_allow_credentials) req.headersOut['Access-Control-Allow-Credentials'] = req.variables.cors_allow_credentials; + if (req.variables.cors_expose_headers) req.headersOut['Access-Control-Expose-Headers'] = req.variables.cors_expose_headers; + + if (req.method === 'OPTIONS') { + req.headersOut['Content-Type'] = 'text/plain charset=UTF-8'; + req.headersOut['Content-Length'] = '0'; + } + } +} + +export default {handle_cors}; diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl index ad41ec7ee7..8d60f44a18 100644 --- a/rootfs/etc/nginx/template/nginx.tmpl +++ b/rootfs/etc/nginx/template/nginx.tmpl @@ -74,6 +74,8 @@ http { init_worker_by_lua_file /etc/nginx/lua/ngx_conf_init_worker.lua; + js_import njs_handle_cors from /etc/nginx/js/nginx/ngx_handle_cors.js; + {{/* Enable the real_ip module only if we use either X-Forwarded headers or Proxy Protocol. */}} {{/* we use the value of the real IP for the geo_ip module */}} {{ if or (or $cfg.UseForwardedHeaders $cfg.UseProxyProtocol) $cfg.EnableRealIP }} @@ -837,33 +839,19 @@ stream { {{/* CORS support from https://michielkalkman.com/snippets/nginx-cors-open-configuration.html */}} {{ define "CORS" }} {{ $cors := .CorsConfig }} - # Cors Preflight methods needs additional options and different Return Code - {{ if $cors.CorsAllowOrigin }} - {{ buildCorsOriginRegex $cors.CorsAllowOrigin }} - {{ end }} - if ($request_method = 'OPTIONS') { - set $cors ${cors}options; - } - if ($cors = "true") { - more_set_headers 'Access-Control-Allow-Origin: $http_origin'; - {{ if $cors.CorsAllowCredentials }} more_set_headers 'Access-Control-Allow-Credentials: {{ $cors.CorsAllowCredentials }}'; {{ end }} - more_set_headers 'Access-Control-Allow-Methods: {{ $cors.CorsAllowMethods }}'; - more_set_headers 'Access-Control-Allow-Headers: {{ $cors.CorsAllowHeaders }}'; - {{ if not (empty $cors.CorsExposeHeaders) }} more_set_headers 'Access-Control-Expose-Headers: {{ $cors.CorsExposeHeaders }}'; {{ end }} - more_set_headers 'Access-Control-Max-Age: {{ $cors.CorsMaxAge }}'; - } + set $cors_origins_regex '{{ buildCorsOriginRegex $cors.CorsAllowOrigin }}'; + set $cors_allowed_origins '{{ join $cors.CorsAllowOrigin "," }}'; + set $cors_allow_methods '{{ $cors.CorsAllowMethods }}'; + set $cors_allow_headers '{{ $cors.CorsAllowHeaders }}'; + set $cors_max_age '{{ $cors.CorsMaxAge }}'; + {{ if $cors.CorsAllowCredentials }} set $cors_allow_credentials {{ $cors.CorsAllowCredentials }}; {{ end }} + {{ if not (empty $cors.CorsExposeHeaders) }} set $cors_expose_headers '{{ $cors.CorsExposeHeaders }}'; {{ end }} + + js_header_filter njs_handle_cors.handle_cors; - if ($cors = "trueoptions") { - more_set_headers 'Access-Control-Allow-Origin: $http_origin'; - {{ if $cors.CorsAllowCredentials }} more_set_headers 'Access-Control-Allow-Credentials: {{ $cors.CorsAllowCredentials }}'; {{ end }} - more_set_headers 'Access-Control-Allow-Methods: {{ $cors.CorsAllowMethods }}'; - more_set_headers 'Access-Control-Allow-Headers: {{ $cors.CorsAllowHeaders }}'; - {{ if not (empty $cors.CorsExposeHeaders) }} more_set_headers 'Access-Control-Expose-Headers: {{ $cors.CorsExposeHeaders }}'; {{ end }} - more_set_headers 'Access-Control-Max-Age: {{ $cors.CorsMaxAge }}'; - more_set_headers 'Content-Type: text/plain charset=UTF-8'; - more_set_headers 'Content-Length: 0'; - return 204; + if ($request_method = 'OPTIONS') { + return 204; } {{ end }} diff --git a/test/e2e/annotations/cors.go b/test/e2e/annotations/cors.go index 58f4445f70..9e7df6c6d0 100644 --- a/test/e2e/annotations/cors.go +++ b/test/e2e/annotations/cors.go @@ -46,22 +46,26 @@ var _ = framework.DescribeAnnotation("cors-*", func() { ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) f.EnsureIngress(ing) - f.WaitForNginxServer(host, - func(server string) bool { - return strings.Contains(server, "more_set_headers 'Access-Control-Allow-Methods: GET, PUT, POST, DELETE, PATCH, OPTIONS';") && - strings.Contains(server, "more_set_headers 'Access-Control-Allow-Origin: $http_origin';") && - strings.Contains(server, "more_set_headers 'Access-Control-Allow-Headers: DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';") && - strings.Contains(server, "more_set_headers 'Access-Control-Max-Age: 1728000';") && - strings.Contains(server, "more_set_headers 'Access-Control-Allow-Credentials: true';") && - strings.Contains(server, "set $http_origin *;") && - strings.Contains(server, "$cors 'true';") - }) + f.WaitForNginxServer(host, func(server string) bool { + return strings.Contains(server, "set $cors_allow_methods 'GET, PUT, POST, DELETE, PATCH, OPTIONS';") && + strings.Contains(server, "set $cors_allow_headers 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';") && + strings.Contains(server, "set $cors_max_age '1728000';") && + strings.Contains(server, "set $cors_allow_credentials true;") + }) f.HTTPTestClient(). GET("/"). WithHeader("Host", host). Expect(). - Status(http.StatusOK) + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{"*"}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) ginkgo.It("should set cors methods to only allow POST, GET", func() { @@ -74,10 +78,23 @@ var _ = framework.DescribeAnnotation("cors-*", func() { ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) f.EnsureIngress(ing) - f.WaitForNginxServer(host, - func(server string) bool { - return strings.Contains(server, "more_set_headers 'Access-Control-Allow-Methods: POST, GET';") - }) + f.WaitForNginxServer(host, func(server string) bool { + return strings.Contains(server, "set $cors_allow_methods 'POST, GET';") + }) + + f.HTTPTestClient(). + GET("/"). + WithHeader("Host", host). + Expect(). + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{"*"}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"POST, GET"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) ginkgo.It("should set cors max-age", func() { @@ -90,10 +107,23 @@ var _ = framework.DescribeAnnotation("cors-*", func() { ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) f.EnsureIngress(ing) - f.WaitForNginxServer(host, - func(server string) bool { - return strings.Contains(server, "more_set_headers 'Access-Control-Max-Age: 200';") - }) + f.WaitForNginxServer(host, func(server string) bool { + return strings.Contains(server, "set $cors_max_age '200';") + }) + + f.HTTPTestClient(). + GET("/"). + WithHeader("Host", host). + Expect(). + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{"*"}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"200"}) }) ginkgo.It("should disable cors allow credentials", func() { @@ -106,10 +136,23 @@ var _ = framework.DescribeAnnotation("cors-*", func() { ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) f.EnsureIngress(ing) - f.WaitForNginxServer(host, - func(server string) bool { - return !strings.Contains(server, "more_set_headers 'Access-Control-Allow-Credentials: true';") - }) + f.WaitForNginxServer(host, func(server string) bool { + return !strings.Contains(server, "set $cors_allow_credentials true;") + }) + + f.HTTPTestClient(). + GET("/"). + WithHeader("Host", host). + Expect(). + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{"*"}). + NotContainsKey("Access-Control-Allow-Credentials"). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) ginkgo.It("should allow origin for cors", func() { @@ -128,15 +171,27 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Host", host). WithHeader("Origin", origin). Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{origin}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) f.HTTPTestClient(). GET("/"). WithHeader("Host", host). - WithHeader("Origin", origin). Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin}) + Status(http.StatusOK). + Headers(). + NotContainsKey("Access-Control-Allow-Origin"). + NotContainsKey("Access-Control-Allow-Credentials"). + NotContainsKey("Access-Control-Allow-Methods"). + NotContainsKey("Access-Control-Allow-Headers"). + NotContainsKey("Access-Control-Max-Age") }) ginkgo.It("should allow headers for cors", func() { @@ -149,10 +204,23 @@ var _ = framework.DescribeAnnotation("cors-*", func() { ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) f.EnsureIngress(ing) - f.WaitForNginxServer(host, - func(server string) bool { - return strings.Contains(server, "more_set_headers 'Access-Control-Allow-Headers: DNT, User-Agent';") - }) + f.WaitForNginxServer(host, func(server string) bool { + return strings.Contains(server, "set $cors_allow_headers 'DNT, User-Agent';") + }) + + f.HTTPTestClient(). + GET("/"). + WithHeader("Host", host). + Expect(). + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{"*"}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT, User-Agent"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) ginkgo.It("should expose headers for cors", func() { @@ -165,10 +233,24 @@ var _ = framework.DescribeAnnotation("cors-*", func() { ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) f.EnsureIngress(ing) - f.WaitForNginxServer(host, - func(server string) bool { - return strings.Contains(server, "more_set_headers 'Access-Control-Expose-Headers: X-CustomResponseHeader, X-CustomSecondHeader';") - }) + f.WaitForNginxServer(host, func(server string) bool { + return strings.Contains(server, "set $cors_expose_headers 'X-CustomResponseHeader, X-CustomSecondHeader';") + }) + + f.HTTPTestClient(). + GET("/"). + WithHeader("Host", host). + Expect(). + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{"*"}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Expose-Headers", []string{"X-CustomResponseHeader, X-CustomSecondHeader"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) ginkgo.It("should allow - single origin for multiple cors values", func() { @@ -187,15 +269,15 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Host", host). WithHeader("Origin", origin). Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin}) + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{origin}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) ginkgo.It("should not allow - single origin for multiple cors values", func() { @@ -215,7 +297,12 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Host", host). WithHeader("Origin", origin). Expect(). - Headers().NotContainsKey("Access-Control-Allow-Origin") + Headers(). + NotContainsKey("Access-Control-Allow-Origin"). + NotContainsKey("Access-Control-Allow-Credentials"). + NotContainsKey("Access-Control-Allow-Methods"). + NotContainsKey("Access-Control-Allow-Headers"). + NotContainsKey("Access-Control-Max-Age") }) ginkgo.It("should allow correct origins - single origin for multiple cors values", func() { @@ -236,14 +323,12 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Host", host). WithHeader("Origin", badOrigin). Expect(). - Headers().NotContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin1). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") + Headers(). + NotContainsKey("Access-Control-Allow-Origin"). + NotContainsKey("Access-Control-Allow-Credentials"). + NotContainsKey("Access-Control-Allow-Methods"). + NotContainsKey("Access-Control-Allow-Headers"). + NotContainsKey("Access-Control-Max-Age") f.HTTPTestClient(). GET("/"). @@ -251,14 +336,13 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Origin", origin1). Expect(). Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin1}) - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin2). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") + ValueEqual("Access-Control-Allow-Origin", []string{origin1}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) f.HTTPTestClient(). GET("/"). @@ -266,10 +350,16 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Origin", origin2). Expect(). Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin2}) + ValueEqual("Access-Control-Allow-Origin", []string{origin2}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) - ginkgo.It("should not break functionality", func() { + ginkgo.It("should allow wildcard origin", func() { host := corsHost annotations := map[string]string{ "nginx.ingress.kubernetes.io/enable-cors": "true", @@ -283,40 +373,18 @@ var _ = framework.DescribeAnnotation("cors-*", func() { GET("/"). WithHeader("Host", host). Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{"*"}) - }) - - ginkgo.It("should not break functionality - without `*`", func() { - host := corsHost - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/enable-cors": "true", - } - - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) - f.EnsureIngress(ing) - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{"*"}) + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{"*"}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) - ginkgo.It("should not break functionality with extra domain", func() { + ginkgo.It("should not break functionality with wildcard and extra domain", func() { host := corsHost annotations := map[string]string{ "nginx.ingress.kubernetes.io/enable-cors": "true", @@ -330,62 +398,15 @@ var _ = framework.DescribeAnnotation("cors-*", func() { GET("/"). WithHeader("Host", host). Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{"*"}) - }) - - ginkgo.It("should not match", func() { - host := corsHost - origin := "https://fooxbar.com" - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/enable-cors": "true", - "nginx.ingress.kubernetes.io/cors-allow-origin": "https://foo.bar.com", - } - - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) - f.EnsureIngress(ing) - - // the client should still receive a response but browsers should block the request - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Headers().NotContainsKey("Access-Control-Allow-Origin") - }) - - ginkgo.It("should allow - single origin with required port", func() { - host := corsHost - origin := originHost - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/enable-cors": "true", - "nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://origin.com:8080", - } - - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) - f.EnsureIngress(ing) - - // the client should still receive a response but browsers should block the request - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin}) + Status(http.StatusOK). + Headers(). + ValueEqual("Access-Control-Allow-Origin", []string{"*"}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) ginkgo.It("should not allow - single origin with port and origin without port", func() { @@ -404,7 +425,12 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Host", host). WithHeader("Origin", origin). Expect(). - Headers().NotContainsKey("Access-Control-Allow-Origin") + Headers(). + NotContainsKey("Access-Control-Allow-Origin"). + NotContainsKey("Access-Control-Allow-Credentials"). + NotContainsKey("Access-Control-Allow-Methods"). + NotContainsKey("Access-Control-Allow-Headers"). + NotContainsKey("Access-Control-Max-Age") }) ginkgo.It("should not allow - single origin without port and origin with required port", func() { @@ -424,7 +450,12 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Host", host). WithHeader("Origin", origin). Expect(). - Headers().NotContainsKey("Access-Control-Allow-Origin") + Headers(). + NotContainsKey("Access-Control-Allow-Origin"). + NotContainsKey("Access-Control-Allow-Credentials"). + NotContainsKey("Access-Control-Allow-Methods"). + NotContainsKey("Access-Control-Allow-Headers"). + NotContainsKey("Access-Control-Max-Age") }) ginkgo.It("should allow - matching origin with wildcard origin (2 subdomains)", func() { @@ -439,27 +470,19 @@ var _ = framework.DescribeAnnotation("cors-*", func() { ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) f.EnsureIngress(ing) - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - f.HTTPTestClient(). GET("/"). WithHeader("Host", host). WithHeader("Origin", origin). Expect(). Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin}) - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin2). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") + ValueEqual("Access-Control-Allow-Origin", []string{origin}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) f.HTTPTestClient(). GET("/"). @@ -467,7 +490,13 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Origin", origin2). Expect(). Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin2}) + ValueEqual("Access-Control-Allow-Origin", []string{origin2}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) ginkgo.It("should not allow - unmatching origin with wildcard origin (2 subdomains)", func() { @@ -487,187 +516,12 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Host", host). WithHeader("Origin", origin). Expect(). - Headers().NotContainsKey("Access-Control-Allow-Origin") - }) - - ginkgo.It("should allow - matching origin+port with wildcard origin", func() { - host := corsHost - origin := "http://abc.origin.com:8080" - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/enable-cors": "true", - "nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://*.origin.com:8080", - } - - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) - f.EnsureIngress(ing) - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin}) - }) - - ginkgo.It("should not allow - portless origin with wildcard origin", func() { - host := corsHost - origin := "http://abc.origin.com" - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/enable-cors": "true", - "nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://*.origin.com:8080", - } - - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) - f.EnsureIngress(ing) - - // the client should still receive a response but browsers should block the request - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Headers().NotContainsKey("Access-Control-Allow-Origin") - }) - - ginkgo.It("should allow correct origins - missing subdomain + origin with wildcard origin and correct origin", func() { - host := corsHost - badOrigin := originHost - origin := "http://bar.origin.com:8080" - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/enable-cors": "true", - "nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://*.origin.com:8080", - } - - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) - f.EnsureIngress(ing) - - // the client should still receive a response but browsers should block the request - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", badOrigin). - Expect(). - Headers().NotContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin}) - }) - - ginkgo.It("should allow - missing origins (should allow all origins)", func() { - host := corsHost - origin := "http://origin.com" - origin2 := "http://book.origin.com" - origin3 := "test.origin.com" - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/enable-cors": "true", - "nginx.ingress.kubernetes.io/cors-allow-origin": " ", - } - - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) - f.EnsureIngress(ing) - - // the client should still receive a response but browsers should block the request - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{"*"}) - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin2). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin2). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{"*"}) - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin3). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin3). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{"*"}) - }) - - ginkgo.It("should allow correct origin but not others - cors allow origin annotations contain trailing comma", func() { - host := corsHost - annotations := map[string]string{ - "nginx.ingress.kubernetes.io/enable-cors": "true", - "nginx.ingress.kubernetes.io/cors-allow-origin": "https://origin-123.cors.com:8080, ,https://origin-321.cors.com:8080,", - } - - ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations) - f.EnsureIngress(ing) - - origin1 := "https://origin-123.cors.com:8080" - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin1). - Expect(). - Headers().ContainsKey("Access-Control-Allow-Origin") - - origin2 := "https://origin-321.cors.com:8080" - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin2). - Expect(). - Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{origin2}) - - origin3 := "https://unknown.cors.com:8080" - f.HTTPTestClient(). - GET("/"). - WithHeader("Host", host). - WithHeader("Origin", origin3). - Expect(). Headers(). - NotContainsKey("Access-Control-Allow-Origin") + NotContainsKey("Access-Control-Allow-Origin"). + NotContainsKey("Access-Control-Allow-Credentials"). + NotContainsKey("Access-Control-Allow-Methods"). + NotContainsKey("Access-Control-Allow-Headers"). + NotContainsKey("Access-Control-Max-Age") }) ginkgo.It("should allow - origins with non-http[s] protocols", func() { @@ -688,7 +542,13 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Origin", origin). Expect(). Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{"test://localhost"}) + ValueEqual("Access-Control-Allow-Origin", []string{origin}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) f.HTTPTestClient(). GET("/"). @@ -696,6 +556,12 @@ var _ = framework.DescribeAnnotation("cors-*", func() { WithHeader("Origin", origin2). Expect(). Status(http.StatusOK).Headers(). - ValueEqual("Access-Control-Allow-Origin", []string{"tauri://localhost:3000"}) + ValueEqual("Access-Control-Allow-Origin", []string{origin2}). + ValueEqual("Access-Control-Allow-Credentials", []string{"true"}). + ValueEqual("Access-Control-Allow-Methods", + []string{"GET, PUT, POST, DELETE, PATCH, OPTIONS"}). + ValueEqual("Access-Control-Allow-Headers", + []string{"DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"}). + ValueEqual("Access-Control-Max-Age", []string{"1728000"}) }) })