1
0
Fork 0

Add HTTPUrlRewrite Filter in Gateway API

This commit is contained in:
Manuel Zapf 2024-06-13 17:06:04 +02:00 committed by GitHub
parent 3ca667a3d4
commit a696f7c654
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 754 additions and 110 deletions

View file

@ -0,0 +1,58 @@
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: http
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: http-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
hostnames:
- "example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /foo
backendRefs:
- name: whoami
port: 80
weight: 1
kind: Service
group: ""
filters:
- type: URLRewrite
urlRewrite:
hostname: www.foo.bar
path:
type: ReplacePrefixMatch
replacePrefixMatch: /xyz

View file

@ -0,0 +1,57 @@
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: http
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: http-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
hostnames:
- "example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /foo
backendRefs:
- name: whoami
port: 80
weight: 1
kind: Service
group: ""
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplaceFullPath
replaceFullPath: /bar

View file

@ -0,0 +1,55 @@
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: http
protocol: HTTP
port: 80
allowedRoutes:
kinds:
- kind: HTTPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: http-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
hostnames:
- "example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /foo
backendRefs:
- name: whoami
port: 80
weight: 1
kind: Service
group: ""
filters:
- type: URLRewrite
urlRewrite:
hostname: www.foo.bar

View file

@ -301,15 +301,19 @@ func (p *Provider) loadHTTPBackendRef(namespace string, backendRef gatev1.HTTPBa
}
func (p *Provider) loadMiddlewares(conf *dynamic.Configuration, namespace, routerName string, filters []gatev1.HTTPRouteFilter, pathMatch *gatev1.HTTPPathMatch) ([]string, error) {
pm := ptr.Deref(pathMatch, gatev1.HTTPPathMatch{
Type: ptr.To(gatev1.PathMatchPathPrefix),
Value: ptr.To("/"),
})
middlewares := make(map[string]*dynamic.Middleware)
for i, filter := range filters {
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
switch filter.Type {
case gatev1.HTTPRouteFilterRequestRedirect:
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
middlewares[name] = createRequestRedirect(filter.RequestRedirect, pathMatch)
middlewares[name] = createRequestRedirect(filter.RequestRedirect, pm)
case gatev1.HTTPRouteFilterRequestHeaderModifier:
name := fmt.Sprintf("%s-%s-%d", routerName, strings.ToLower(string(filter.Type)), i)
middlewares[name] = createRequestHeaderModifier(filter.RequestHeaderModifier)
case gatev1.HTTPRouteFilterExtensionRef:
@ -320,6 +324,14 @@ func (p *Provider) loadMiddlewares(conf *dynamic.Configuration, namespace, route
middlewares[name] = middleware
case gatev1.HTTPRouteFilterURLRewrite:
var err error
middleware, err := createURLRewrite(filter.URLRewrite, pm)
if err != nil {
return nil, fmt.Errorf("invalid filter %s: %w", filter.Type, err)
}
middlewares[name] = middleware
default:
// As per the spec: https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
// In all cases where incompatible or unsupported filters are
@ -560,7 +572,7 @@ func createRequestHeaderModifier(filter *gatev1.HTTPHeaderFilter) *dynamic.Middl
}
}
func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch *gatev1.HTTPPathMatch) *dynamic.Middleware {
func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch gatev1.HTTPPathMatch) *dynamic.Middleware {
var hostname *string
if filter.Hostname != nil {
hostname = ptr.To(string(*filter.Hostname))
@ -599,6 +611,37 @@ func createRequestRedirect(filter *gatev1.HTTPRequestRedirectFilter, pathMatch *
}
}
func createURLRewrite(filter *gatev1.HTTPURLRewriteFilter, pathMatch gatev1.HTTPPathMatch) (*dynamic.Middleware, error) {
if filter.Path == nil && filter.Hostname == nil {
return nil, errors.New("empty configuration")
}
var host *string
if filter.Hostname != nil {
host = ptr.To(string(*filter.Hostname))
}
var path *string
var pathPrefix *string
if filter.Path != nil {
switch filter.Path.Type {
case gatev1.FullPathHTTPPathModifier:
path = filter.Path.ReplaceFullPath
case gatev1.PrefixMatchHTTPPathModifier:
path = filter.Path.ReplacePrefixMatch
pathPrefix = pathMatch.Value
}
}
return &dynamic.Middleware{
URLRewrite: &dynamic.URLRewrite{
Hostname: host,
Path: path,
PathPrefix: pathPrefix,
},
}, nil
}
func getProtocol(portSpec corev1.ServicePort) string {
protocol := "http"
if portSpec.Port == 443 || strings.HasPrefix(portSpec.Name, "https") {

View file

@ -1734,6 +1734,212 @@ func TestLoadHTTPRoutes(t *testing.T) {
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple HTTPRoute URL rewrite FullPath",
paths: []string{"services.yml", "httproute/filter_url_rewrite_fullpath.yml"},
entryPoints: map[string]Entrypoint{"web": {
Address: ":80",
}},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8": {
EntryPoints: []string{"web"},
Service: "default-http-app-1-my-gateway-web-0-wrr",
Rule: "Host(`example.com`) && (Path(`/foo`) || PathPrefix(`/foo/`))",
RuleSyntax: "v3",
Priority: 10412,
Middlewares: []string{"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0": {
URLRewrite: &dynamic.URLRewrite{
Path: ptr.To("/bar"),
},
},
},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-0-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-80",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
"default-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple HTTPRoute URL rewrite Hostname",
paths: []string{"services.yml", "httproute/filter_url_rewrite_hostname.yml"},
entryPoints: map[string]Entrypoint{"web": {
Address: ":80",
}},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8": {
EntryPoints: []string{"web"},
Service: "default-http-app-1-my-gateway-web-0-wrr",
Rule: "Host(`example.com`) && (Path(`/foo`) || PathPrefix(`/foo/`))",
RuleSyntax: "v3",
Priority: 10412,
Middlewares: []string{"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0": {
URLRewrite: &dynamic.URLRewrite{
Hostname: ptr.To("www.foo.bar"),
},
},
},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-0-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-80",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
"default-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Simple HTTPRoute URL rewrite Combined",
paths: []string{"services.yml", "httproute/filter_url_rewrite_combined.yml"},
entryPoints: map[string]Entrypoint{"web": {
Address: ":80",
}},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8": {
EntryPoints: []string{"web"},
Service: "default-http-app-1-my-gateway-web-0-wrr",
Rule: "Host(`example.com`) && (Path(`/foo`) || PathPrefix(`/foo/`))",
RuleSyntax: "v3",
Priority: 10412,
Middlewares: []string{"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0"},
},
},
Middlewares: map[string]*dynamic.Middleware{
"default-http-app-1-my-gateway-web-0-7f90cf546b15efadf2f8-urlrewrite-0": {
URLRewrite: &dynamic.URLRewrite{
Hostname: ptr.To("www.foo.bar"),
Path: ptr.To("/xyz"),
PathPrefix: ptr.To("/foo"),
},
},
},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-0-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
Name: "default-whoami-80",
Weight: func(i int) *int { return &i }(1),
},
},
},
},
"default-whoami-80": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: ptr.To(true),
ResponseForwarding: &dynamic.ResponseForwarding{
FlushInterval: ptypes.Duration(100 * time.Millisecond),
},
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
}
for _, test := range testCases {