Skip to content

Commit fc6862f

Browse files
authored
feat: add support for CORS httproute filter (#262)
1 parent 2505c06 commit fc6862f

File tree

5 files changed

+171
-5
lines changed

5 files changed

+171
-5
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,11 +297,11 @@ endif
297297

298298
.PHONY: install-gateway-api
299299
install-gateway-api: ## Install Gateway API CRDs into the K8s cluster specified in ~/.kube/config.
300-
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEAY_API_VERSION)/standard-install.yaml
300+
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEAY_API_VERSION)/experimental-install.yaml
301301

302302
.PHONY: uninstall-gateway-api
303303
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.
304-
kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEAY_API_VERSION)/standard-install.yaml
304+
kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEAY_API_VERSION)/experimental-install.yaml
305305

306306
.PHONY: install
307307
install: manifests kustomize install-gateway-api ## Install CRDs into the K8s cluster specified in ~/.kube/config.

api/adc/plugin_types.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ type IPRestrictConfig struct {
2727
// CorsConfig is the rule config for cors plugin.
2828
// +k8s:deepcopy-gen=true
2929
type CorsConfig struct {
30-
AllowOrigins string `json:"allow_origins,omitempty"`
31-
AllowMethods string `json:"allow_methods,omitempty"`
32-
AllowHeaders string `json:"allow_headers,omitempty"`
30+
AllowOrigins string `json:"allow_origins,omitempty"`
31+
AllowMethods string `json:"allow_methods,omitempty"`
32+
AllowHeaders string `json:"allow_headers,omitempty"`
33+
ExposeHeaders string `json:"expose_headers,omitempty"`
34+
AllowCredential bool `json:"allow_credential,omitempty"`
3335
}
3436

3537
// CSRfConfig is the rule config for csrf plugin.

api/adc/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,7 @@ const (
654654
PluginRedirect string = "redirect"
655655
PluginResponseRewrite string = "response-rewrite"
656656
PluginProxyMirror string = "proxy-mirror"
657+
PluginCORS string = "cors"
657658
)
658659

659660
// RewriteConfig is the rule config for proxy-rewrite plugin.

internal/adc/translator/httproute.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ func (t *Translator) fillPluginsFromHTTPRouteFilters(
6060
t.fillPluginFromHTTPResponseHeaderFilter(plugins, filter.ResponseHeaderModifier)
6161
case gatewayv1.HTTPRouteFilterExtensionRef:
6262
t.fillPluginFromExtensionRef(plugins, namespace, filter.ExtensionRef, tctx)
63+
case gatewayv1.HTTPRouteFilterCORS:
64+
t.fillPluginFromHTTPCORSFilter(plugins, filter.CORS)
6365
}
6466
}
6567
}
@@ -129,6 +131,50 @@ func (t *Translator) fillPluginFromURLRewriteFilter(plugins adctypes.Plugins, ur
129131
}
130132
}
131133

134+
func (t *Translator) fillPluginFromHTTPCORSFilter(plugins adctypes.Plugins, cors *gatewayv1.HTTPCORSFilter) {
135+
pluginName := adctypes.PluginCORS
136+
obj := plugins[pluginName]
137+
var plugin *adctypes.CorsConfig
138+
if obj == nil {
139+
plugin = &adctypes.CorsConfig{}
140+
plugins[pluginName] = plugin
141+
} else {
142+
plugin = obj.(*adctypes.CorsConfig)
143+
}
144+
145+
if len(cors.AllowOrigins) > 0 {
146+
origins := make([]string, len(cors.AllowOrigins))
147+
for i, allowOrigin := range cors.AllowOrigins {
148+
origins[i] = string(allowOrigin)
149+
}
150+
plugin.AllowOrigins = strings.Join(origins, ",")
151+
}
152+
153+
if len(cors.AllowHeaders) > 0 {
154+
headers := make([]string, len(cors.AllowHeaders))
155+
for i, allowHeader := range cors.AllowHeaders {
156+
headers[i] = string(allowHeader)
157+
}
158+
plugin.AllowHeaders = strings.Join(headers, ",")
159+
}
160+
161+
if len(cors.AllowMethods) > 0 {
162+
methods := make([]string, len(cors.AllowMethods))
163+
for i, allowMethod := range cors.AllowMethods {
164+
methods[i] = string(allowMethod)
165+
}
166+
plugin.AllowMethods = strings.Join(methods, ",")
167+
}
168+
if len(cors.ExposeHeaders) > 0 {
169+
exposeHeaders := make([]string, len(cors.ExposeHeaders))
170+
for i, exposeHeader := range cors.ExposeHeaders {
171+
exposeHeaders[i] = string(exposeHeader)
172+
}
173+
plugin.ExposeHeaders = strings.Join(exposeHeaders, ",")
174+
}
175+
plugin.AllowCredential = bool(cors.AllowCredentials)
176+
}
177+
132178
func (t *Translator) fillPluginFromHTTPRequestHeaderFilter(plugins adctypes.Plugins, reqHeaderModifier *gatewayv1.HTTPHeaderFilter) {
133179
pluginName := adctypes.PluginProxyRewrite
134180
obj := plugins[pluginName]

test/e2e/gatewayapi/httproute.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1659,6 +1659,76 @@ spec:
16591659
port: 80
16601660
`
16611661

1662+
var corsTestService = `
1663+
apiVersion: v1
1664+
kind: Service
1665+
metadata:
1666+
name: cors-test-service
1667+
spec:
1668+
selector:
1669+
app: cors-test
1670+
ports:
1671+
- port: 80
1672+
targetPort: 5678
1673+
---
1674+
apiVersion: apps/v1
1675+
kind: Deployment
1676+
metadata:
1677+
name: cors-test
1678+
spec:
1679+
replicas: 1
1680+
selector:
1681+
matchLabels:
1682+
app: cors-test
1683+
template:
1684+
metadata:
1685+
labels:
1686+
app: cors-test
1687+
spec:
1688+
containers:
1689+
- name: cors-test
1690+
image: hashicorp/http-echo
1691+
args: ["-text=hello", "-listen=:5678"]
1692+
ports:
1693+
- containerPort: 5678
1694+
`
1695+
1696+
var corsFilter = `
1697+
apiVersion: gateway.networking.k8s.io/v1
1698+
kind: HTTPRoute
1699+
metadata:
1700+
name: http-route-cors
1701+
namespace: %s
1702+
spec:
1703+
parentRefs:
1704+
- name: %s
1705+
hostnames:
1706+
- cors-test.example
1707+
rules:
1708+
- matches:
1709+
- path:
1710+
type: PathPrefix
1711+
value: /
1712+
filters:
1713+
- type: CORS
1714+
cors:
1715+
allowOrigins:
1716+
- http://example.com
1717+
allowMethods:
1718+
- GET
1719+
- POST
1720+
- PUT
1721+
- DELETE
1722+
allowHeaders:
1723+
- "Origin"
1724+
exposeHeaders:
1725+
- "Origin"
1726+
allowCredentials: true
1727+
backendRefs:
1728+
- name: cors-test-service
1729+
port: 80
1730+
`
1731+
16621732
BeforeEach(beforeEachHTTP)
16631733

16641734
It("HTTPRoute RequestHeaderModifier", func() {
@@ -1927,6 +1997,53 @@ spec:
19271997
Interval: time.Second * 2,
19281998
})
19291999
})
2000+
It("HTTPRoute CORS Filter", func() {
2001+
By("create test service and deployment")
2002+
Expect(s.CreateResourceFromStringWithNamespace(corsTestService, s.Namespace())).
2003+
NotTo(HaveOccurred(), "creating CORS test service")
2004+
2005+
By("create HTTPRoute with CORS filter")
2006+
s.ResourceApplied("HTTPRoute", "http-route-cors", fmt.Sprintf(corsFilter, s.Namespace(), s.Namespace()), 1)
2007+
By("test simple GET request with CORS headers from allowed origin")
2008+
s.RequestAssert(&scaffold.RequestAssert{
2009+
Method: "GET",
2010+
Path: "/",
2011+
Host: "cors-test.example",
2012+
Headers: map[string]string{
2013+
"Origin": "http://example.com",
2014+
},
2015+
Checks: []scaffold.ResponseCheckFunc{
2016+
scaffold.WithExpectedStatus(http.StatusOK),
2017+
scaffold.WithExpectedBodyContains("hello"),
2018+
scaffold.WithExpectedHeaders(map[string]string{
2019+
"Access-Control-Allow-Origin": "http://example.com",
2020+
"Access-Control-Allow-Methods": "GET,POST,PUT,DELETE",
2021+
"Access-Control-Allow-Headers": "Origin",
2022+
"Access-Control-Expose-Headers": "Origin",
2023+
"Access-Control-Allow-Credentials": "true",
2024+
}),
2025+
},
2026+
Timeout: time.Second * 30,
2027+
Interval: time.Second * 2,
2028+
})
2029+
2030+
By("test simple GET request with CORS headers from disallowed origin")
2031+
s.RequestAssert(&scaffold.RequestAssert{
2032+
Method: "GET",
2033+
Path: "/",
2034+
Host: "cors-test.example",
2035+
Headers: map[string]string{
2036+
"Origin": "http://disallowed.com",
2037+
},
2038+
Checks: []scaffold.ResponseCheckFunc{
2039+
scaffold.WithExpectedStatus(http.StatusOK),
2040+
scaffold.WithExpectedBodyContains("hello"),
2041+
scaffold.WithExpectedNotHeader("Access-Control-Allow-Origin"),
2042+
},
2043+
Timeout: time.Second * 30,
2044+
Interval: time.Second * 2,
2045+
})
2046+
})
19302047
})
19312048

19322049
Context("HTTPRoute Multiple Backend", func() {

0 commit comments

Comments
 (0)