From 4256f32af1295256d450312b7edab1a105794480 Mon Sep 17 00:00:00 2001 From: Ivan Matmati Date: Wed, 20 Apr 2022 11:58:41 +0200 Subject: [PATCH] TEST: add CORS e2e tests --- deploy/tests/e2e/client.go | 1 + deploy/tests/e2e/cors/config/deploy.yaml.tmpl | 64 ++++++ .../tests/e2e/cors/config/patternfile-a.yml | 14 ++ .../e2e/cors/config/patternfile-empty.yml | 6 + deploy/tests/e2e/cors/cors_test.go | 192 ++++++++++++++++++ deploy/tests/e2e/cors/suite_test.go | 57 ++++++ 6 files changed, 334 insertions(+) create mode 100644 deploy/tests/e2e/cors/config/deploy.yaml.tmpl create mode 100644 deploy/tests/e2e/cors/config/patternfile-a.yml create mode 100644 deploy/tests/e2e/cors/config/patternfile-empty.yml create mode 100644 deploy/tests/e2e/cors/cors_test.go create mode 100644 deploy/tests/e2e/cors/suite_test.go diff --git a/deploy/tests/e2e/client.go b/deploy/tests/e2e/client.go index b1b22bdb..ac4ef549 100644 --- a/deploy/tests/e2e/client.go +++ b/deploy/tests/e2e/client.go @@ -121,6 +121,7 @@ func (c *Client) Do() (res *http.Response, close func() error, err error) { } } c.Req.Host = c.Host + c.Req.Header["Origin"] = []string{c.Req.URL.Scheme + "://" + c.Host} c.Req.URL.Host = c.Host c.Req.URL.Path = c.Path res, err = client.Do(c.Req) diff --git a/deploy/tests/e2e/cors/config/deploy.yaml.tmpl b/deploy/tests/e2e/cors/config/deploy.yaml.tmpl new file mode 100644 index 00000000..9ad05635 --- /dev/null +++ b/deploy/tests/e2e/cors/config/deploy.yaml.tmpl @@ -0,0 +1,64 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: http-echo +spec: + replicas: 1 + selector: + matchLabels: + app: http-echo + template: + metadata: + labels: + app: http-echo + spec: + containers: + - name: http-echo + image: haproxytech/http-echo:latest + imagePullPolicy: Never + args: + - --default-response=hostname + ports: + - name: http + containerPort: 8888 + protocol: TCP + - name: https + containerPort: 8443 + protocol: TCP +--- +kind: Service +apiVersion: v1 +metadata: + name: http-echo +spec: + ports: + - name: http + protocol: TCP + port: 80 + targetPort: http + - name: https + protocol: TCP + port: 443 + targetPort: https + selector: + app: http-echo +--- +kind: Ingress +apiVersion: networking.k8s.io/v1beta1 +metadata: + name: http-echo + annotations: + ingress.class: haproxy +{{range .IngAnnotations}} + {{ .Key }}: {{ .Value }} +{{end}} +spec: + rules: + - host: {{ .Host }} + http: + paths: + - path: / + backend: + serviceName: http-echo + servicePort: http diff --git a/deploy/tests/e2e/cors/config/patternfile-a.yml b/deploy/tests/e2e/cors/config/patternfile-a.yml new file mode 100644 index 00000000..21301089 --- /dev/null +++ b/deploy/tests/e2e/cors/config/patternfile-a.yml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: patternfiles + namespace: haproxy-controller +data: + cors-allow-credentials: 'false' + cors-allow-headers: >- + x-api-key + cors-allow-methods: GET, POST + cors-allow-origin: >- + ^https://configmap.com?$ + cors-enable: 'true' + cors-max-age: 100s diff --git a/deploy/tests/e2e/cors/config/patternfile-empty.yml b/deploy/tests/e2e/cors/config/patternfile-empty.yml new file mode 100644 index 00000000..306ba976 --- /dev/null +++ b/deploy/tests/e2e/cors/config/patternfile-empty.yml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: patternfiles + namespace: haproxy-controller +data: {} diff --git a/deploy/tests/e2e/cors/cors_test.go b/deploy/tests/e2e/cors/cors_test.go new file mode 100644 index 00000000..2f3c87e0 --- /dev/null +++ b/deploy/tests/e2e/cors/cors_test.go @@ -0,0 +1,192 @@ +// Copyright 2019 HAProxy Technologies LLC +// +// 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. + +//go:build e2e_sequential + +package cors + +import ( + "net/http" + + "github.com/haproxytech/kubernetes-ingress/deploy/tests/e2e" +) + +const ( + AccessControlAllowOrigin = "Access-Control-Allow-Origin" + AccessControlAllowMethods = "Access-Control-Allow-Methods" + AccessControlAllowHeaders = "Access-Control-Allow-Headers" + AccessControlMaxAge = "Access-Control-Max-Age" + AccessControlAllowCredential = "Access-Control-Allow-Credentials" + AnnotationCorsEnable = "cors-enable" + AnnotationCorsOrigin = "cors-allow-origin" + AnnotationCorsMethods = "cors-allow-methods" + AnnotationCorsHeaders = "cors-allow-headers" + AnnotationCorsAge = "cors-max-age" + AnnotationCorsCredential = "cors-allow-credentials" + Star = "*" +) + +func (suite *CorsSuite) Test_Ingress_Alone() { + suite.Run("Default", func() { + expectedHeaders := http.Header{ + AccessControlAllowOrigin: {Star}, + AccessControlAllowMethods: {Star}, + AccessControlAllowHeaders: {Star}, + AccessControlMaxAge: {"5"}, + } + suite.tmplData.IngAnnotations = []struct{ Key, Value string }{ + {AnnotationCorsEnable, q("true")}, + } + + suite.NoError(suite.test.Apply("config/deploy.yaml.tmpl", suite.test.GetNS(), suite.tmplData)) + + suite.eventuallyReturns(expectedHeaders, http.Header{}) + }) + + suite.Run("CorsOriginAlone", func() { + expectedHeaders := http.Header{ + AccessControlAllowOrigin: {"http://" + suite.tmplData.Host}, + AccessControlAllowMethods: {Star}, + AccessControlAllowHeaders: {Star}, + AccessControlMaxAge: {"5"}, + } + suite.tmplData.IngAnnotations = []struct{ Key, Value string }{ + {AnnotationCorsEnable, q("true")}, + {AnnotationCorsOrigin, q("http://" + suite.tmplData.Host)}, + } + + suite.NoError(suite.test.Apply("config/deploy.yaml.tmpl", suite.test.GetNS(), suite.tmplData)) + + suite.eventuallyReturns(expectedHeaders, http.Header{}) + }) + + suite.Run("CorsMethodsAlone", func() { + expectedHeaders := http.Header{ + AccessControlAllowOrigin: {Star}, + AccessControlAllowMethods: {"GET"}, + AccessControlAllowHeaders: {Star}, + AccessControlMaxAge: {"5"}, + } + suite.tmplData.IngAnnotations = []struct{ Key, Value string }{ + {AnnotationCorsEnable, q("true")}, + {AnnotationCorsMethods, q("GET")}, + } + + suite.NoError(suite.test.Apply("config/deploy.yaml.tmpl", suite.test.GetNS(), suite.tmplData)) + + suite.eventuallyReturns(expectedHeaders, http.Header{}) + }) + + suite.Run("CorsMethodsHeaders", func() { + expectedHeaders := http.Header{ + AccessControlAllowOrigin: {Star}, + AccessControlAllowMethods: {Star}, + AccessControlAllowHeaders: {"Accept"}, + AccessControlMaxAge: {"5"}, + } + suite.tmplData.IngAnnotations = []struct{ Key, Value string }{ + {AnnotationCorsEnable, q("true")}, + {AnnotationCorsHeaders, q("Accept")}, + } + + suite.NoError(suite.test.Apply("config/deploy.yaml.tmpl", suite.test.GetNS(), suite.tmplData)) + + suite.eventuallyReturns(expectedHeaders, http.Header{}) + }) + + suite.Run("CorsMethodsAge", func() { + expectedHeaders := http.Header{ + AccessControlAllowOrigin: {Star}, + AccessControlAllowMethods: {Star}, + AccessControlAllowHeaders: {Star}, + AccessControlMaxAge: {"500"}, + } + suite.tmplData.IngAnnotations = []struct{ Key, Value string }{ + {AnnotationCorsEnable, q("true")}, + {AnnotationCorsAge, q("500s")}, + } + + suite.NoError(suite.test.Apply("config/deploy.yaml.tmpl", suite.test.GetNS(), suite.tmplData)) + + suite.eventuallyReturns(expectedHeaders, http.Header{}) + }) + + suite.Run("CorsMethodsCredential", func() { + expectedHeaders := http.Header{ + AccessControlAllowOrigin: {Star}, + AccessControlAllowMethods: {Star}, + AccessControlAllowHeaders: {Star}, + AccessControlAllowCredential: {"true"}, + AccessControlMaxAge: {"5"}, + } + suite.tmplData.IngAnnotations = []struct{ Key, Value string }{ + {AnnotationCorsEnable, q("true")}, + {AnnotationCorsCredential, q("true")}, + } + + suite.NoError(suite.test.Apply("config/deploy.yaml.tmpl", suite.test.GetNS(), suite.tmplData)) + + suite.eventuallyReturns(expectedHeaders, http.Header{}) + }) + + suite.Run("CorsDisable", func() { + unexpectedHeaders := http.Header{ + AccessControlAllowOrigin: {}, + AccessControlAllowMethods: {}, + AccessControlAllowHeaders: {}, + AccessControlMaxAge: {}, + AccessControlAllowCredential: {}, + } + + suite.tmplData.IngAnnotations = []struct{ Key, Value string }{ + {AnnotationCorsEnable, q("false")}, + {AnnotationCorsOrigin, q("http://wrong.com")}, + {AnnotationCorsCredential, q("true")}, + {AnnotationCorsMethods, q("GET")}, + {AnnotationCorsHeaders, q("Accept")}, + } + + suite.NoError(suite.test.Apply("config/deploy.yaml.tmpl", suite.test.GetNS(), suite.tmplData)) + + suite.eventuallyReturns(http.Header{}, unexpectedHeaders) + }) +} + +func (suite *CorsSuite) eventuallyReturns(expecedHeaders, unexpectedHeaders http.Header) { + suite.Eventually(func() bool { + res, cls, err := suite.client.Do() + if err != nil { + suite.T().Logf("Connection ERROR: %s", err.Error()) + return false + } + defer cls() + for expectedHeader, expectedValues := range expecedHeaders { + values, ok := res.Header[expectedHeader] + if !ok || len(values) != 1 || values[0] != expectedValues[0] { + return false + } + + } + for unexpectedHeader := range unexpectedHeaders { + if _, ok := res.Header[unexpectedHeader]; ok { + return false + } + } + return true + }, e2e.WaitDuration, e2e.TickDuration) +} + +func q(value string) string { + return "\"" + value + "\"" +} diff --git a/deploy/tests/e2e/cors/suite_test.go b/deploy/tests/e2e/cors/suite_test.go new file mode 100644 index 00000000..9606bdd5 --- /dev/null +++ b/deploy/tests/e2e/cors/suite_test.go @@ -0,0 +1,57 @@ +// Copyright 2019 HAProxy Technologies LLC +// +// 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. + +//go:build e2e_sequential + +package cors + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/haproxytech/kubernetes-ingress/deploy/tests/e2e" +) + +type CorsSuite struct { + suite.Suite + test e2e.Test + client *e2e.Client + tmplData tmplData +} + +type tmplData struct { + IngAnnotations []struct{ Key, Value string } + Host string +} + +func (suite *CorsSuite) SetupSuite() { + var err error + suite.test, err = e2e.NewTest() + suite.NoError(err) + suite.tmplData = tmplData{Host: suite.test.GetNS() + ".test"} + suite.client, err = e2e.NewHTTPClient(suite.tmplData.Host) + suite.NoError(err) + + suite.NoError(suite.test.Apply("config/patternfile-empty.yml", "", nil)) +} + +func (suite *CorsSuite) TearDownSuite() { + suite.test.Apply("config/patternfile-empty.yml", "", nil) + suite.test.TearDown() +} + +func TestCorsSuite(t *testing.T) { + suite.Run(t, new(CorsSuite)) +}