This is an automated email from the ASF dual-hosted git repository.

ashishtiwari 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 cb69f53c feat: add support for CORS httproutefilter (#2548)
cb69f53c is described below

commit cb69f53cf1e6a79805047a1deadcfa93be67264b
Author: Ashish Tiwari <ashishjaitiwari15112...@gmail.com>
AuthorDate: Tue Sep 9 16:32:08 2025 +0530

    feat: add support for CORS httproutefilter (#2548)
---
 Makefile                             |   4 +-
 api/adc/plugin_types.go              |   8 ++-
 api/adc/types.go                     |   1 +
 internal/adc/translator/httproute.go |  46 ++++++++++++++
 test/e2e/gatewayapi/httproute.go     | 118 +++++++++++++++++++++++++++++++++++
 5 files changed, 172 insertions(+), 5 deletions(-)

diff --git a/Makefile b/Makefile
index 24f34762..478dddf5 100644
--- a/Makefile
+++ b/Makefile
@@ -262,11 +262,11 @@ endif
 
 .PHONY: install-gateway-api
 install-gateway-api: ## Install Gateway API CRDs into the K8s cluster 
specified in ~/.kube/config.
-       kubectl apply -f 
https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEAY_API_VERSION)/standard-install.yaml
+       kubectl apply -f 
https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEAY_API_VERSION)/experimental-install.yaml
 
 .PHONY: uninstall-gateway-api
 uninstall-gateway-api: ## Uninstall Gateway API CRDs from the K8s cluster 
specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource 
not found errors during deletion.
-       kubectl delete -f 
https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEAY_API_VERSION)/standard-install.yaml
+       kubectl delete -f 
https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEAY_API_VERSION)/experimental-install.yaml
 
 .PHONY: install
 install: manifests kustomize install-gateway-api ## Install CRDs into the K8s 
cluster specified in ~/.kube/config.
diff --git a/api/adc/plugin_types.go b/api/adc/plugin_types.go
index 1c2cd888..6d230886 100644
--- a/api/adc/plugin_types.go
+++ b/api/adc/plugin_types.go
@@ -27,9 +27,11 @@ type IPRestrictConfig struct {
 // CorsConfig is the rule config for cors plugin.
 // +k8s:deepcopy-gen=true
 type CorsConfig struct {
-       AllowOrigins string `json:"allow_origins,omitempty"`
-       AllowMethods string `json:"allow_methods,omitempty"`
-       AllowHeaders string `json:"allow_headers,omitempty"`
+       AllowOrigins    string `json:"allow_origins,omitempty"`
+       AllowMethods    string `json:"allow_methods,omitempty"`
+       AllowHeaders    string `json:"allow_headers,omitempty"`
+       ExposeHeaders   string `json:"expose_headers,omitempty"`
+       AllowCredential bool   `json:"allow_credential,omitempty"`
 }
 
 // CSRfConfig is the rule config for csrf plugin.
diff --git a/api/adc/types.go b/api/adc/types.go
index db4d2d71..5b162bc3 100644
--- a/api/adc/types.go
+++ b/api/adc/types.go
@@ -608,6 +608,7 @@ const (
        PluginRedirect        string = "redirect"
        PluginResponseRewrite string = "response-rewrite"
        PluginProxyMirror     string = "proxy-mirror"
+       PluginCORS            string = "cors"
 )
 
 // RewriteConfig is the rule config for proxy-rewrite plugin.
diff --git a/internal/adc/translator/httproute.go 
b/internal/adc/translator/httproute.go
index 90816258..bd2b021e 100644
--- a/internal/adc/translator/httproute.go
+++ b/internal/adc/translator/httproute.go
@@ -60,6 +60,8 @@ func (t *Translator) fillPluginsFromHTTPRouteFilters(
                        t.fillPluginFromHTTPResponseHeaderFilter(plugins, 
filter.ResponseHeaderModifier)
                case gatewayv1.HTTPRouteFilterExtensionRef:
                        t.fillPluginFromExtensionRef(plugins, namespace, 
filter.ExtensionRef, tctx)
+               case gatewayv1.HTTPRouteFilterCORS:
+                       t.fillPluginFromHTTPCORSFilter(plugins, filter.CORS)
                }
        }
 }
@@ -129,6 +131,50 @@ func (t *Translator) 
fillPluginFromURLRewriteFilter(plugins adctypes.Plugins, ur
        }
 }
 
+func (t *Translator) fillPluginFromHTTPCORSFilter(plugins adctypes.Plugins, 
cors *gatewayv1.HTTPCORSFilter) {
+       pluginName := adctypes.PluginCORS
+       obj := plugins[pluginName]
+       var plugin *adctypes.CorsConfig
+       if obj == nil {
+               plugin = &adctypes.CorsConfig{}
+               plugins[pluginName] = plugin
+       } else {
+               plugin = obj.(*adctypes.CorsConfig)
+       }
+
+       if len(cors.AllowOrigins) > 0 {
+               origins := make([]string, len(cors.AllowOrigins))
+               for i, allowOrigin := range cors.AllowOrigins {
+                       origins[i] = string(allowOrigin)
+               }
+               plugin.AllowOrigins = strings.Join(origins, ",")
+       }
+
+       if len(cors.AllowHeaders) > 0 {
+               headers := make([]string, len(cors.AllowHeaders))
+               for i, allowHeader := range cors.AllowHeaders {
+                       headers[i] = string(allowHeader)
+               }
+               plugin.AllowHeaders = strings.Join(headers, ",")
+       }
+
+       if len(cors.AllowMethods) > 0 {
+               methods := make([]string, len(cors.AllowMethods))
+               for i, allowMethod := range cors.AllowMethods {
+                       methods[i] = string(allowMethod)
+               }
+               plugin.AllowMethods = strings.Join(methods, ",")
+       }
+       if len(cors.ExposeHeaders) > 0 {
+               exposeHeaders := make([]string, len(cors.ExposeHeaders))
+               for i, exposeHeader := range cors.ExposeHeaders {
+                       exposeHeaders[i] = string(exposeHeader)
+               }
+               plugin.ExposeHeaders = strings.Join(exposeHeaders, ",")
+       }
+       plugin.AllowCredential = bool(cors.AllowCredentials)
+}
+
 func (t *Translator) fillPluginFromHTTPRequestHeaderFilter(plugins 
adctypes.Plugins, reqHeaderModifier *gatewayv1.HTTPHeaderFilter) {
        pluginName := adctypes.PluginProxyRewrite
        obj := plugins[pluginName]
diff --git a/test/e2e/gatewayapi/httproute.go b/test/e2e/gatewayapi/httproute.go
index 2ee93beb..4dfe6e09 100644
--- a/test/e2e/gatewayapi/httproute.go
+++ b/test/e2e/gatewayapi/httproute.go
@@ -1702,6 +1702,76 @@ spec:
       port: 80
 `
 
+               var corsTestService = `
+apiVersion: v1
+kind: Service
+metadata:
+  name: cors-test-service
+spec:
+  selector:
+    app: cors-test
+  ports:
+  - port: 80
+    targetPort: 5678
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: cors-test
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: cors-test
+  template:
+    metadata:
+      labels:
+        app: cors-test
+    spec:
+      containers:
+      - name: cors-test
+        image: hashicorp/http-echo
+        args: ["-text=hello", "-listen=:5678"]
+        ports:
+        - containerPort: 5678
+`
+
+               var corsFilter = `
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: http-route-cors
+  namespace: %s
+spec:
+  parentRefs:
+  - name: %s
+  hostnames:
+  - cors-test.example
+  rules:
+  - matches:
+    - path:
+        type: PathPrefix
+        value: /
+    filters:
+    - type: CORS
+      cors:
+        allowOrigins:
+        - http://example.com
+        allowMethods: 
+        - GET
+        - POST
+        - PUT
+        - DELETE
+        allowHeaders: 
+        - "Origin"
+        exposeHeaders: 
+        - "Origin"
+        allowCredentials: true
+    backendRefs:
+    - name: cors-test-service
+      port: 80
+`
+
                BeforeEach(beforeEachHTTP)
 
                It("HTTPRoute RequestHeaderModifier", func() {
@@ -1970,6 +2040,54 @@ spec:
                                Interval: time.Second * 2,
                        })
                })
+
+               It("HTTPRoute CORS Filter", func() {
+                       By("create test service and deployment")
+                       
Expect(s.CreateResourceFromStringWithNamespace(corsTestService, s.Namespace())).
+                               NotTo(HaveOccurred(), "creating CORS test 
service")
+
+                       By("create HTTPRoute with CORS filter")
+                       s.ResourceApplied("HTTPRoute", "http-route-cors", 
fmt.Sprintf(corsFilter, s.Namespace(), s.Namespace()), 1)
+                       By("test simple GET request with CORS headers from 
allowed origin")
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/",
+                               Host:   "cors-test.example",
+                               Headers: map[string]string{
+                                       "Origin": "http://example.com";,
+                               },
+                               Checks: []scaffold.ResponseCheckFunc{
+                                       
scaffold.WithExpectedStatus(http.StatusOK),
+                                       
scaffold.WithExpectedBodyContains("hello"),
+                                       
scaffold.WithExpectedHeaders(map[string]string{
+                                               "Access-Control-Allow-Origin":  
    "http://example.com";,
+                                               "Access-Control-Allow-Methods": 
    "GET,POST,PUT,DELETE",
+                                               "Access-Control-Allow-Headers": 
    "Origin",
+                                               
"Access-Control-Expose-Headers":    "Origin",
+                                               
"Access-Control-Allow-Credentials": "true",
+                                       }),
+                               },
+                               Timeout:  time.Second * 30,
+                               Interval: time.Second * 2,
+                       })
+
+                       By("test simple GET request with CORS headers from 
disallowed origin")
+                       s.RequestAssert(&scaffold.RequestAssert{
+                               Method: "GET",
+                               Path:   "/",
+                               Host:   "cors-test.example",
+                               Headers: map[string]string{
+                                       "Origin": "http://disallowed.com";,
+                               },
+                               Checks: []scaffold.ResponseCheckFunc{
+                                       
scaffold.WithExpectedStatus(http.StatusOK),
+                                       
scaffold.WithExpectedBodyContains("hello"),
+                                       
scaffold.WithExpectedNotHeader("Access-Control-Allow-Origin"),
+                               },
+                               Timeout:  time.Second * 30,
+                               Interval: time.Second * 2,
+                       })
+               })
        })
 
        Context("HTTPRoute Multiple Backend", func() {

Reply via email to