Skip to content

Commit cb69f53

Browse files
authored
feat: add support for CORS httproutefilter (#2548)
1 parent 2d57b6a commit cb69f53

File tree

5 files changed

+172
-5
lines changed

5 files changed

+172
-5
lines changed

Makefile

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

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

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

271271
.PHONY: install
272272
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
@@ -608,6 +608,7 @@ const (
608608
PluginRedirect string = "redirect"
609609
PluginResponseRewrite string = "response-rewrite"
610610
PluginProxyMirror string = "proxy-mirror"
611+
PluginCORS string = "cors"
611612
)
612613

613614
// 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: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,76 @@ spec:
17021702
port: 80
17031703
`
17041704

1705+
var corsTestService = `
1706+
apiVersion: v1
1707+
kind: Service
1708+
metadata:
1709+
name: cors-test-service
1710+
spec:
1711+
selector:
1712+
app: cors-test
1713+
ports:
1714+
- port: 80
1715+
targetPort: 5678
1716+
---
1717+
apiVersion: apps/v1
1718+
kind: Deployment
1719+
metadata:
1720+
name: cors-test
1721+
spec:
1722+
replicas: 1
1723+
selector:
1724+
matchLabels:
1725+
app: cors-test
1726+
template:
1727+
metadata:
1728+
labels:
1729+
app: cors-test
1730+
spec:
1731+
containers:
1732+
- name: cors-test
1733+
image: hashicorp/http-echo
1734+
args: ["-text=hello", "-listen=:5678"]
1735+
ports:
1736+
- containerPort: 5678
1737+
`
1738+
1739+
var corsFilter = `
1740+
apiVersion: gateway.networking.k8s.io/v1
1741+
kind: HTTPRoute
1742+
metadata:
1743+
name: http-route-cors
1744+
namespace: %s
1745+
spec:
1746+
parentRefs:
1747+
- name: %s
1748+
hostnames:
1749+
- cors-test.example
1750+
rules:
1751+
- matches:
1752+
- path:
1753+
type: PathPrefix
1754+
value: /
1755+
filters:
1756+
- type: CORS
1757+
cors:
1758+
allowOrigins:
1759+
- http://example.com
1760+
allowMethods:
1761+
- GET
1762+
- POST
1763+
- PUT
1764+
- DELETE
1765+
allowHeaders:
1766+
- "Origin"
1767+
exposeHeaders:
1768+
- "Origin"
1769+
allowCredentials: true
1770+
backendRefs:
1771+
- name: cors-test-service
1772+
port: 80
1773+
`
1774+
17051775
BeforeEach(beforeEachHTTP)
17061776

17071777
It("HTTPRoute RequestHeaderModifier", func() {
@@ -1970,6 +2040,54 @@ spec:
19702040
Interval: time.Second * 2,
19712041
})
19722042
})
2043+
2044+
It("HTTPRoute CORS Filter", func() {
2045+
By("create test service and deployment")
2046+
Expect(s.CreateResourceFromStringWithNamespace(corsTestService, s.Namespace())).
2047+
NotTo(HaveOccurred(), "creating CORS test service")
2048+
2049+
By("create HTTPRoute with CORS filter")
2050+
s.ResourceApplied("HTTPRoute", "http-route-cors", fmt.Sprintf(corsFilter, s.Namespace(), s.Namespace()), 1)
2051+
By("test simple GET request with CORS headers from allowed origin")
2052+
s.RequestAssert(&scaffold.RequestAssert{
2053+
Method: "GET",
2054+
Path: "/",
2055+
Host: "cors-test.example",
2056+
Headers: map[string]string{
2057+
"Origin": "http://example.com",
2058+
},
2059+
Checks: []scaffold.ResponseCheckFunc{
2060+
scaffold.WithExpectedStatus(http.StatusOK),
2061+
scaffold.WithExpectedBodyContains("hello"),
2062+
scaffold.WithExpectedHeaders(map[string]string{
2063+
"Access-Control-Allow-Origin": "http://example.com",
2064+
"Access-Control-Allow-Methods": "GET,POST,PUT,DELETE",
2065+
"Access-Control-Allow-Headers": "Origin",
2066+
"Access-Control-Expose-Headers": "Origin",
2067+
"Access-Control-Allow-Credentials": "true",
2068+
}),
2069+
},
2070+
Timeout: time.Second * 30,
2071+
Interval: time.Second * 2,
2072+
})
2073+
2074+
By("test simple GET request with CORS headers from disallowed origin")
2075+
s.RequestAssert(&scaffold.RequestAssert{
2076+
Method: "GET",
2077+
Path: "/",
2078+
Host: "cors-test.example",
2079+
Headers: map[string]string{
2080+
"Origin": "http://disallowed.com",
2081+
},
2082+
Checks: []scaffold.ResponseCheckFunc{
2083+
scaffold.WithExpectedStatus(http.StatusOK),
2084+
scaffold.WithExpectedBodyContains("hello"),
2085+
scaffold.WithExpectedNotHeader("Access-Control-Allow-Origin"),
2086+
},
2087+
Timeout: time.Second * 30,
2088+
Interval: time.Second * 2,
2089+
})
2090+
})
19732091
})
19742092

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

0 commit comments

Comments
 (0)