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 <[email protected]>
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() {