From 2a990d2d2c37a7d27069be20a46829b35cff9db5 Mon Sep 17 00:00:00 2001 From: Fernando Diaz Date: Wed, 29 Aug 2018 21:58:40 -0500 Subject: [PATCH] Add e2e tests for CORS and more Adds the missing e2e tests for Cross-Origin Resource Sharing(CORS). This will include all the CORS annotations. Also adds more unit tests. --- .../nginx-configuration/annotations.md | 31 +- .../ingress/annotations/cors/main_test.go | 79 ++++- test/e2e/annotations/clientbodybuffersize.go | 2 +- test/e2e/annotations/cors.go | 331 ++++++++++++++++++ 4 files changed, 419 insertions(+), 24 deletions(-) create mode 100644 test/e2e/annotations/cors.go diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md index c4b869038..c06cbb894 100644 --- a/docs/user-guide/nginx-configuration/annotations.md +++ b/docs/user-guide/nginx-configuration/annotations.md @@ -235,37 +235,42 @@ This is a global configuration for the ingress controller. In some cases could b ### Enable CORS -To enable Cross-Origin Resource Sharing (CORS) in an Ingress rule, -add the annotation `nginx.ingress.kubernetes.io/enable-cors: "true"`. -This will add a section in the server location enabling this functionality. +To enable Cross-Origin Resource Sharing (CORS) in an Ingress rule, add the annotation +`nginx.ingress.kubernetes.io/enable-cors: "true"`. This will add a section in the server +location enabling this functionality. CORS can be controlled with the following annotations: * `nginx.ingress.kubernetes.io/cors-allow-methods` - controls which methods are accepted. - This is a multi-valued field, separated by ',' and accepts only letters (upper and lower case). - Example: `nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"` + controls which methods are accepted. This is a multi-valued field, separated by ',' and + accepts only letters (upper and lower case). + - Default: `GET, PUT, POST, DELETE, PATCH, OPTIONS` + - Example: `nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS"` * `nginx.ingress.kubernetes.io/cors-allow-headers` - controls which headers are accepted. - This is a multi-valued field, separated by ',' and accepts letters, numbers, _ and -. - Example: `nginx.ingress.kubernetes.io/cors-allow-headers: "X-Forwarded-For, X-app123-XPTO"` + controls which headers are accepted. This is a multi-valued field, separated by ',' and accepts letters, + numbers, _ and -. + - Default: `DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization` + - Example: `nginx.ingress.kubernetes.io/cors-allow-headers: "X-Forwarded-For, X-app123-XPTO"` * `nginx.ingress.kubernetes.io/cors-allow-origin` - controls what's the accepted Origin for CORS and defaults to '*'. + controls what's the accepted Origin for CORS. This is a single field value, with the following format: `http(s)://origin-site.com` or `http(s)://origin-site.com:port` - Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443"` + - Default: `*` + - Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443"` * `nginx.ingress.kubernetes.io/cors-allow-credentials` controls if credentials can be passed during CORS operations. - Example: `nginx.ingress.kubernetes.io/cors-allow-credentials: "true"` + - Default: `true` + - Example: `nginx.ingress.kubernetes.io/cors-allow-credentials: "false"` * `nginx.ingress.kubernetes.io/cors-max-age` controls how long preflight requests can be cached. + Default: `1728000` Example: `nginx.ingress.kubernetes.io/cors-max-age: 600` !!! note - For more information please see [https://enable-cors.org](https://enable-cors.org/server_nginx.html) + For more information please see [https://enable-cors.org](https://enable-cors.org/server_nginx.html) as well as this [example](../../examples/cors/README.md). ### Server Alias diff --git a/internal/ingress/annotations/cors/main_test.go b/internal/ingress/annotations/cors/main_test.go index 8f9545429..84c11d334 100644 --- a/internal/ingress/annotations/cors/main_test.go +++ b/internal/ingress/annotations/cors/main_test.go @@ -62,41 +62,100 @@ func buildIngress() *extensions.Ingress { } } -func TestIngressCorsConfig(t *testing.T) { +func TestIngressCorsConfigValid(t *testing.T) { ing := buildIngress() data := map[string]string{} + + // Valid data[parser.GetAnnotationWithPrefix("enable-cors")] = "true" data[parser.GetAnnotationWithPrefix("cors-allow-headers")] = "DNT,X-CustomHeader, Keep-Alive,User-Agent" data[parser.GetAnnotationWithPrefix("cors-allow-credentials")] = "false" - data[parser.GetAnnotationWithPrefix("cors-allow-methods")] = "PUT, GET,OPTIONS, PATCH, $nginx_version" + data[parser.GetAnnotationWithPrefix("cors-allow-methods")] = "GET, PATCH" data[parser.GetAnnotationWithPrefix("cors-allow-origin")] = "https://origin123.test.com:4443" data[parser.GetAnnotationWithPrefix("cors-max-age")] = "600" ing.SetAnnotations(data) - corst, _ := NewParser(&resolver.Mock{}).Parse(ing) + corst, err := NewParser(&resolver.Mock{}).Parse(ing) + if err != nil { + t.Errorf("error parsing annotations: %v", err) + } + nginxCors, ok := corst.(*Config) if !ok { - t.Errorf("expected a Config type") + t.Errorf("expected a Config type but returned %t", corst) } if !nginxCors.CorsEnabled { - t.Errorf("expected cors enabled but returned %v", nginxCors.CorsEnabled) + t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("enable-cors")], nginxCors.CorsEnabled) + } + + if nginxCors.CorsAllowCredentials { + t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("cors-allow-credentials")], nginxCors.CorsAllowCredentials) } if nginxCors.CorsAllowHeaders != "DNT,X-CustomHeader, Keep-Alive,User-Agent" { - t.Errorf("expected headers not found. Found %v", nginxCors.CorsAllowHeaders) + t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("cors-allow-headers")], nginxCors.CorsAllowHeaders) } - if nginxCors.CorsAllowMethods != "GET, PUT, POST, DELETE, PATCH, OPTIONS" { - t.Errorf("expected default methods, but got %v", nginxCors.CorsAllowMethods) + if nginxCors.CorsAllowMethods != "GET, PATCH" { + t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("cors-allow-methods")], nginxCors.CorsAllowMethods) } if nginxCors.CorsAllowOrigin != "https://origin123.test.com:4443" { - t.Errorf("expected origin https://origin123.test.com:4443, but got %v", nginxCors.CorsAllowOrigin) + t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("cors-allow-origin")], nginxCors.CorsAllowOrigin) } if nginxCors.CorsMaxAge != 600 { - t.Errorf("expected max age 600, but got %v", nginxCors.CorsMaxAge) + t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("cors-max-age")], nginxCors.CorsMaxAge) + } +} + +func TestIngressCorsConfigInvalid(t *testing.T) { + ing := buildIngress() + + data := map[string]string{} + + // Valid + data[parser.GetAnnotationWithPrefix("enable-cors")] = "yes" + data[parser.GetAnnotationWithPrefix("cors-allow-headers")] = "@alright, #ingress" + data[parser.GetAnnotationWithPrefix("cors-allow-credentials")] = "no" + data[parser.GetAnnotationWithPrefix("cors-allow-methods")] = "GET, PATCH, $nginx" + data[parser.GetAnnotationWithPrefix("cors-allow-origin")] = "origin123.test.com:4443" + data[parser.GetAnnotationWithPrefix("cors-max-age")] = "abcd" + ing.SetAnnotations(data) + + corst, err := NewParser(&resolver.Mock{}).Parse(ing) + if err != nil { + t.Errorf("error parsing annotations: %v", err) + } + + nginxCors, ok := corst.(*Config) + if !ok { + t.Errorf("expected a Config type but returned %t", corst) + } + + if nginxCors.CorsEnabled { + t.Errorf("expected %v but returned %v", false, nginxCors.CorsEnabled) + } + + if !nginxCors.CorsAllowCredentials { + t.Errorf("expected %v but returned %v", true, nginxCors.CorsAllowCredentials) + } + + if nginxCors.CorsAllowHeaders != defaultCorsHeaders { + t.Errorf("expected %v but returned %v", defaultCorsHeaders, nginxCors.CorsAllowHeaders) + } + + if nginxCors.CorsAllowMethods != defaultCorsMethods { + t.Errorf("expected %v but returned %v", defaultCorsHeaders, nginxCors.CorsAllowMethods) + } + + if nginxCors.CorsAllowOrigin != "*" { + t.Errorf("expected %v but returned %v", "*", nginxCors.CorsAllowOrigin) + } + + if nginxCors.CorsMaxAge != defaultCorsMaxAge { + t.Errorf("expected %v but returned %v", defaultCorsMaxAge, nginxCors.CorsMaxAge) } } diff --git a/test/e2e/annotations/clientbodybuffersize.go b/test/e2e/annotations/clientbodybuffersize.go index cf6f26722..44bf6980e 100644 --- a/test/e2e/annotations/clientbodybuffersize.go +++ b/test/e2e/annotations/clientbodybuffersize.go @@ -28,7 +28,7 @@ import ( ) var _ = framework.IngressNginxDescribe("Annotations - Client-Body-Buffer-Size", func() { - f := framework.NewDefaultFramework("proxy") + f := framework.NewDefaultFramework("clientbodybuffersize") BeforeEach(func() { err := f.NewEchoDeploymentWithReplicas(2) diff --git a/test/e2e/annotations/cors.go b/test/e2e/annotations/cors.go new file mode 100644 index 000000000..4f5aa201a --- /dev/null +++ b/test/e2e/annotations/cors.go @@ -0,0 +1,331 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package annotations + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/parnurzeal/gorequest" + "net/http" + + v1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + "k8s.io/ingress-nginx/test/e2e/framework" +) + +var _ = framework.IngressNginxDescribe("Annotations - CORS", func() { + f := framework.NewDefaultFramework("cors") + + BeforeEach(func() { + err := f.NewEchoDeploymentWithReplicas(2) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + }) + + It("should enable cors", func() { + host := "cors.foo.com" + + ing, err := f.EnsureIngress(&v1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: host, + Namespace: f.IngressController.Namespace, + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/enable-cors": "true", + }, + }, + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: host, + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: v1beta1.IngressBackend{ + ServiceName: "http-svc", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(ing).NotTo(BeNil()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("more_set_headers 'Access-Control-Allow-Methods: GET, PUT, POST, DELETE, PATCH, OPTIONS';")) + }) + Expect(err).NotTo(HaveOccurred()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("more_set_headers 'Access-Control-Allow-Origin: *';")) + }) + Expect(err).NotTo(HaveOccurred()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("more_set_headers 'Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';")) + }) + Expect(err).NotTo(HaveOccurred()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("more_set_headers 'Access-Control-Max-Age: 1728000';")) + }) + Expect(err).NotTo(HaveOccurred()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("more_set_headers 'Access-Control-Allow-Credentials: true';")) + }) + Expect(err).NotTo(HaveOccurred()) + + uri := "/" + resp, _, errs := gorequest.New(). + Options(f.IngressController.HTTPURL+uri). + Set("Host", host). + End() + Expect(len(errs)).Should(BeNumerically("==", 0)) + Expect(resp.StatusCode).Should(Equal(http.StatusNoContent)) + }) + + It("should set cors methods to only allow POST, GET", func() { + host := "cors.foo.com" + + ing, err := f.EnsureIngress(&v1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: host, + Namespace: f.IngressController.Namespace, + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/enable-cors": "true", + "nginx.ingress.kubernetes.io/cors-allow-methods": "POST, GET", + }, + }, + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: host, + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: v1beta1.IngressBackend{ + ServiceName: "http-svc", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(ing).NotTo(BeNil()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("more_set_headers 'Access-Control-Allow-Methods: POST, GET';")) + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should set cors max-age", func() { + host := "cors.foo.com" + + ing, err := f.EnsureIngress(&v1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: host, + Namespace: f.IngressController.Namespace, + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/enable-cors": "true", + "nginx.ingress.kubernetes.io/cors-max-age": "200", + }, + }, + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: host, + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: v1beta1.IngressBackend{ + ServiceName: "http-svc", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(ing).NotTo(BeNil()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("more_set_headers 'Access-Control-Max-Age: 200';")) + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should disable cors allow credentials", func() { + host := "cors.foo.com" + + ing, err := f.EnsureIngress(&v1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: host, + Namespace: f.IngressController.Namespace, + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/enable-cors": "true", + "nginx.ingress.kubernetes.io/cors-allow-credentials": "false", + }, + }, + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: host, + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: v1beta1.IngressBackend{ + ServiceName: "http-svc", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(ing).NotTo(BeNil()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).ShouldNot(ContainSubstring("more_set_headers 'Access-Control-Allow-Credentials: true';")) + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should allow origin for cors", func() { + host := "cors.foo.com" + + ing, err := f.EnsureIngress(&v1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: host, + Namespace: f.IngressController.Namespace, + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/enable-cors": "true", + "nginx.ingress.kubernetes.io/cors-allow-origin": "https://origin.cors.com:8080", + }, + }, + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: host, + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: v1beta1.IngressBackend{ + ServiceName: "http-svc", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(ing).NotTo(BeNil()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("more_set_headers 'Access-Control-Allow-Origin: https://origin.cors.com:8080';")) + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should allow headers for cors", func() { + host := "cors.foo.com" + + ing, err := f.EnsureIngress(&v1beta1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: host, + Namespace: f.IngressController.Namespace, + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/enable-cors": "true", + "nginx.ingress.kubernetes.io/cors-allow-headers": "DNT, User-Agent", + }, + }, + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ + { + Host: host, + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ + { + Path: "/", + Backend: v1beta1.IngressBackend{ + ServiceName: "http-svc", + ServicePort: intstr.FromInt(80), + }, + }, + }, + }, + }, + }, + }, + }, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(ing).NotTo(BeNil()) + + err = f.WaitForNginxServer(host, + func(server string) bool { + return Expect(server).Should(ContainSubstring("more_set_headers 'Access-Control-Allow-Headers: DNT, User-Agent';")) + }) + Expect(err).NotTo(HaveOccurred()) + }) +})