This is an automated email from the ASF dual-hosted git repository.
ronething pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new 6b708576 feat: support cors annotations for ingress (#2618)
6b708576 is described below
commit 6b7085765c99815a9fef3f1838cea85dcecee747
Author: Ashing Zheng <[email protected]>
AuthorDate: Mon Oct 27 14:41:19 2025 +0800
feat: support cors annotations for ingress (#2618)
Signed-off-by: Ashing Zheng <[email protected]>
---
.../adc/translator/annotations/plugins/cors.go | 45 +++++++++++++
.../translator/annotations/plugins/cors_test.go | 48 ++++++++++++++
.../adc/translator/annotations/plugins/plugins.go | 1 +
internal/adc/translator/annotations_test.go | 18 ++++++
internal/webhook/v1/ingress_webhook.go | 4 --
internal/webhook/v1/ingress_webhook_test.go | 24 -------
test/e2e/ingress/annotations.go | 73 ++++++++++++++++++++++
test/e2e/webhook/ingress.go | 69 --------------------
8 files changed, 185 insertions(+), 97 deletions(-)
diff --git a/internal/adc/translator/annotations/plugins/cors.go
b/internal/adc/translator/annotations/plugins/cors.go
new file mode 100644
index 00000000..2b4cd3d6
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/cors.go
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 plugins
+
+import (
+ adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+type cors struct{}
+
+// NewCorsHandler creates a handler to convert annotations about
+// CORS to APISIX cors plugin.
+func NewCorsHandler() PluginAnnotationsHandler {
+ return &cors{}
+}
+
+func (c *cors) PluginName() string {
+ return "cors"
+}
+
+func (c *cors) Handle(e annotations.Extractor) (any, error) {
+ if !e.GetBoolAnnotation(annotations.AnnotationsEnableCors) {
+ return nil, nil
+ }
+
+ return &adctypes.CorsConfig{
+ AllowOrigins:
e.GetStringAnnotation(annotations.AnnotationsCorsAllowOrigin),
+ AllowMethods:
e.GetStringAnnotation(annotations.AnnotationsCorsAllowMethods),
+ AllowHeaders:
e.GetStringAnnotation(annotations.AnnotationsCorsAllowHeaders),
+ }, nil
+}
diff --git a/internal/adc/translator/annotations/plugins/cors_test.go
b/internal/adc/translator/annotations/plugins/cors_test.go
new file mode 100644
index 00000000..9b22f918
--- /dev/null
+++ b/internal/adc/translator/annotations/plugins/cors_test.go
@@ -0,0 +1,48 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 plugins
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ adctypes "github.com/apache/apisix-ingress-controller/api/adc"
+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+func TestCorsHandler(t *testing.T) {
+ anno := map[string]string{
+ annotations.AnnotationsEnableCors: "true",
+ annotations.AnnotationsCorsAllowHeaders: "abc,def",
+ annotations.AnnotationsCorsAllowOrigin: "https://a.com",
+ annotations.AnnotationsCorsAllowMethods: "GET,HEAD",
+ }
+ p := NewCorsHandler()
+ out, err := p.Handle(annotations.NewExtractor(anno))
+ assert.Nil(t, err, "checking given error")
+ config := out.(*adctypes.CorsConfig)
+ assert.Equal(t, "abc,def", config.AllowHeaders)
+ assert.Equal(t, "https://a.com", config.AllowOrigins)
+ assert.Equal(t, "GET,HEAD", config.AllowMethods)
+
+ assert.Equal(t, "cors", p.PluginName())
+
+ anno[annotations.AnnotationsEnableCors] = "false"
+ out, err = p.Handle(annotations.NewExtractor(anno))
+ assert.Nil(t, err, "checking given error")
+ assert.Nil(t, out, "checking given output")
+}
diff --git a/internal/adc/translator/annotations/plugins/plugins.go
b/internal/adc/translator/annotations/plugins/plugins.go
index 5243e27b..ee1e6206 100644
--- a/internal/adc/translator/annotations/plugins/plugins.go
+++ b/internal/adc/translator/annotations/plugins/plugins.go
@@ -37,6 +37,7 @@ var (
handlers = []PluginAnnotationsHandler{
NewRedirectHandler(),
+ NewCorsHandler(),
}
)
diff --git a/internal/adc/translator/annotations_test.go
b/internal/adc/translator/annotations_test.go
index 2e43e035..279d7beb 100644
--- a/internal/adc/translator/annotations_test.go
+++ b/internal/adc/translator/annotations_test.go
@@ -189,6 +189,24 @@ func TestTranslateIngressAnnotations(t *testing.T) {
},
},
},
+ {
+ name: "cors plugin",
+ anno: map[string]string{
+ annotations.AnnotationsEnableCors: "true",
+ annotations.AnnotationsCorsAllowOrigin:
"https://example.com",
+ annotations.AnnotationsCorsAllowHeaders:
"header-a,header-b",
+ annotations.AnnotationsCorsAllowMethods:
"GET,POST",
+ },
+ expected: &IngressConfig{
+ Plugins: adctypes.Plugins{
+ "cors": &adctypes.CorsConfig{
+ AllowOrigins:
"https://example.com",
+ AllowHeaders:
"header-a,header-b",
+ AllowMethods: "GET,POST",
+ },
+ },
+ },
+ },
}
for _, tt := range tests {
diff --git a/internal/webhook/v1/ingress_webhook.go
b/internal/webhook/v1/ingress_webhook.go
index c3c78d2d..4f940a2d 100644
--- a/internal/webhook/v1/ingress_webhook.go
+++ b/internal/webhook/v1/ingress_webhook.go
@@ -42,10 +42,6 @@ var unsupportedAnnotations = []string{
"k8s.apisix.apache.org/use-regex",
"k8s.apisix.apache.org/enable-websocket",
"k8s.apisix.apache.org/plugin-config-name",
- "k8s.apisix.apache.org/enable-cors",
- "k8s.apisix.apache.org/cors-allow-origin",
- "k8s.apisix.apache.org/cors-allow-headers",
- "k8s.apisix.apache.org/cors-allow-methods",
"k8s.apisix.apache.org/enable-csrf",
"k8s.apisix.apache.org/csrf-key",
"k8s.apisix.apache.org/rewrite-target",
diff --git a/internal/webhook/v1/ingress_webhook_test.go
b/internal/webhook/v1/ingress_webhook_test.go
index 89f3fa6d..b775a819 100644
--- a/internal/webhook/v1/ingress_webhook_test.go
+++ b/internal/webhook/v1/ingress_webhook_test.go
@@ -104,30 +104,6 @@ func
TestIngressCustomValidator_ValidateCreate_SupportedAnnotations(t *testing.T
assert.Empty(t, warnings)
}
-func TestIngressCustomValidator_ValidateUpdate_UnsupportedAnnotations(t
*testing.T) {
- validator := buildIngressValidator(t)
- oldObj := &networkingv1.Ingress{}
- obj := &networkingv1.Ingress{
- ObjectMeta: metav1.ObjectMeta{
- Name: "test-ingress",
- Namespace: "default",
- Annotations: map[string]string{
- "k8s.apisix.apache.org/enable-cors":
"true",
- "k8s.apisix.apache.org/cors-allow-origin": "*",
- },
- },
- }
-
- warnings, err := validator.ValidateUpdate(context.TODO(), oldObj, obj)
- assert.NoError(t, err)
- assert.Len(t, warnings, 2)
-
- // Check that warnings contain the expected unsupported annotations
- warningsStr := strings.Join(warnings, " ")
- assert.Contains(t, warningsStr, "k8s.apisix.apache.org/enable-cors")
- assert.Contains(t, warningsStr,
"k8s.apisix.apache.org/cors-allow-origin")
-}
-
func TestIngressCustomValidator_ValidateDelete_NoWarnings(t *testing.T) {
validator := buildIngressValidator(t)
obj := &networkingv1.Ingress{
diff --git a/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go
index 0f3079e3..57e9b8ad 100644
--- a/test/e2e/ingress/annotations.go
+++ b/test/e2e/ingress/annotations.go
@@ -19,6 +19,7 @@ package ingress
import (
"context"
+ "encoding/json"
"fmt"
"net/http"
"time"
@@ -102,6 +103,31 @@ spec:
port:
number: 443
`
+
+ ingressCORS = `
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: cors
+ annotations:
+ k8s.apisix.apache.org/enable-cors: "true"
+ k8s.apisix.apache.org/cors-allow-origin: "https://allowed.example"
+ k8s.apisix.apache.org/cors-allow-methods: "GET,POST"
+ k8s.apisix.apache.org/cors-allow-headers: "Origin,Authorization"
+spec:
+ ingressClassName: %s
+ rules:
+ - host: cors.example
+ http:
+ paths:
+ - path: /get
+ pathType: Exact
+ backend:
+ service:
+ name: nginx
+ port:
+ number: 80
+`
)
BeforeEach(func() {
s.DeployNginx(framework.NginxOptions{
@@ -167,6 +193,53 @@ spec:
Expect(upstreams[0].Timeout.Send).To(Equal(3),
"checking Upstream send timeout")
Expect(upstreams[0].Timeout.Connect).To(Equal(4),
"checking Upstream connect timeout")
})
+
+ It("cors annotations", func() {
+
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressCORS,
s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")
+
+ s.RequestAssert(&scaffold.RequestAssert{
+ Method: "GET",
+ Path: "/get",
+ Host: "cors.example",
+ Headers: map[string]string{
+ "Origin": "https://allowed.example",
+ },
+ Checks: []scaffold.ResponseCheckFunc{
+
scaffold.WithExpectedStatus(http.StatusOK),
+
scaffold.WithExpectedHeaders(map[string]string{
+ "Access-Control-Allow-Origin":
"https://allowed.example",
+ "Access-Control-Allow-Methods":
"GET,POST",
+ "Access-Control-Allow-Headers":
"Origin,Authorization",
+ }),
+ },
+ })
+
+ s.RequestAssert(&scaffold.RequestAssert{
+ Method: "GET",
+ Path: "/get",
+ Host: "cors.example",
+ Headers: map[string]string{
+ "Origin": "https://blocked.example",
+ },
+ Checks: []scaffold.ResponseCheckFunc{
+
scaffold.WithExpectedStatus(http.StatusOK),
+
scaffold.WithExpectedNotHeader("Access-Control-Allow-Origin"),
+ },
+ })
+
+ routes, err :=
s.DefaultDataplaneResource().Route().List(context.Background())
+ Expect(err).NotTo(HaveOccurred(), "listing Service")
+ Expect(routes).To(HaveLen(1), "checking Route length")
+ Expect(routes[0].Plugins).To(HaveKey("cors"), "checking
Route plugins")
+ jsonBytes, err :=
json.Marshal(routes[0].Plugins["cors"])
+ Expect(err).NotTo(HaveOccurred(), "marshalling cors
plugin config")
+ var corsConfig map[string]any
+ err = json.Unmarshal(jsonBytes, &corsConfig)
+ Expect(err).NotTo(HaveOccurred(), "unmarshalling cors
plugin config")
+
Expect(corsConfig["allow_origins"]).To(Equal("https://allowed.example"),
"checking cors allow origins")
+
Expect(corsConfig["allow_methods"]).To(Equal("GET,POST"), "checking cors allow
methods")
+
Expect(corsConfig["allow_headers"]).To(Equal("Origin,Authorization"), "checking
cors allow headers")
+ })
})
Context("Plugins", func() {
diff --git a/test/e2e/webhook/ingress.go b/test/e2e/webhook/ingress.go
index 37608fb2..69a7f3ef 100644
--- a/test/e2e/webhook/ingress.go
+++ b/test/e2e/webhook/ingress.go
@@ -87,74 +87,5 @@ spec:
})
})
- It("should warn about unsupported annotations on update",
func() {
- By("creating Ingress without unsupported annotations")
- initialIngressYAML := fmt.Sprintf(`
-apiVersion: networking.k8s.io/v1
-kind: Ingress
-metadata:
- name: test-webhook-update
- namespace: %s
-spec:
- ingressClassName: %s
- rules:
- - host: webhook-test-update.example.com
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service:
- name: httpbin-service-e2e-test
- port:
- number: 80
-`, s.Namespace(), s.Namespace())
-
- output, err :=
s.CreateResourceFromStringAndGetOutput(initialIngressYAML)
- Expect(err).ShouldNot(HaveOccurred())
- Expect(output).ShouldNot(ContainSubstring(`Warning`))
-
- s.RequestAssert(&scaffold.RequestAssert{
- Method: "GET",
- Path: "/get",
- Host: "webhook-test-update.example.com",
- Check:
scaffold.WithExpectedStatus(http.StatusOK),
- })
-
- By("updating Ingress with unsupported annotations")
- updatedIngressYAML := fmt.Sprintf(`
-apiVersion: networking.k8s.io/v1
-kind: Ingress
-metadata:
- name: test-webhook-update
- namespace: %s
- annotations:
- k8s.apisix.apache.org/enable-cors: "true"
-spec:
- ingressClassName: %s
- rules:
- - host: webhook-test-update.example.com
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- service:
- name: httpbin-service-e2e-test
- port:
- number: 80
-`, s.Namespace(), s.Namespace())
-
- output, err =
s.CreateResourceFromStringAndGetOutput(updatedIngressYAML)
- Expect(err).ShouldNot(HaveOccurred())
- Expect(output).To(ContainSubstring(`Warning: Annotation
'k8s.apisix.apache.org/enable-cors' is not supported`))
-
- s.RequestAssert(&scaffold.RequestAssert{
- Method: "GET",
- Path: "/get",
- Host: "webhook-test-update.example.com",
- Check:
scaffold.WithExpectedStatus(http.StatusOK),
- })
- })
})
})