Skip to content

Commit a9e59cc

Browse files
committed
[feat gw-api]modify redirect ReplacePrefixMatch support behavior
1 parent b65911e commit a9e59cc

File tree

6 files changed

+111
-26
lines changed

6 files changed

+111
-26
lines changed

docs/guide/gateway/l7gateway.md

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ information see the [Gateway API Conformance Page](https://gateway-api.sigs.k8s.
185185
| HTTPRouteRule - HTTPRouteFilter - RequestHeaderModifier | Core | ❌-- [Limited Support](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/header-modification.html) |
186186
| HTTPRouteRule - HTTPRouteFilter - ResponseHeaderModifier | Core | ❌ |
187187
| HTTPRouteRule - HTTPRouteFilter - RequestMirror | Extended | ❌ |
188-
| HTTPRouteRule - HTTPRouteFilter - RequestRedirect | Core | |
188+
| HTTPRouteRule - HTTPRouteFilter - RequestRedirect | Core | ✅ -- See [ReplacePrefixMatch Limitation](#requestredirect-path-modification-replaceprefixmatch-limitation) below |
189189
| HTTPRouteRule - HTTPRouteFilter - UrlRewrite | Extended | ✅ |
190190
| HTTPRouteRule - HTTPRouteFilter - CORS | Extended | ❌ |
191191
| HTTPRouteRule - HTTPRouteFilter - ExternalAuth | Extended | ❌ -- Use [ListenerRuleConfigurations](customization.md#customizing-l7-routing-rules) |
@@ -200,8 +200,43 @@ information see the [Gateway API Conformance Page](https://gateway-api.sigs.k8s.
200200
Backend TLS is not supported by AWS ALB Gateway. For more information on how AWS ALB communicates with targets using encryption,
201201
please see the [AWS documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html#target-group-routing-configuration).
202202

203-
204-
203+
##### RequestRedirect Path Modification ReplacePrefixMatch Limitation
204+
205+
The AWS Load Balancer Controller supports HTTPRoute RequestRedirect filters with both `ReplaceFullPath` and `ReplacePrefixMatch` path modification types.
206+
207+
**ReplacePrefixMatch Behavior:**
208+
209+
The behavior of `ReplacePrefixMatch` depends on whether other redirect components are modified:
210+
211+
1. **With scheme/port/hostname changes** - Path suffixes are preserved:
212+
```yaml
213+
filters:
214+
- type: RequestRedirect
215+
requestRedirect:
216+
scheme: HTTPS # or port/hostname
217+
path:
218+
type: ReplacePrefixMatch
219+
replacePrefixMatch: /new-prefix
220+
```
221+
- Request: `/old-prefix/path/to/resource`
222+
- Redirects to: `/new-prefix/path/to/resource` ✅ (suffix preserved)
223+
224+
2. **Without other component changes** - Only prefix is replaced, suffixes are NOT preserved:
225+
```yaml
226+
filters:
227+
- type: RequestRedirect
228+
requestRedirect:
229+
path:
230+
type: ReplacePrefixMatch
231+
replacePrefixMatch: /new-prefix
232+
```
233+
- Request: `/old-prefix/path/to/resource`
234+
- Redirects to: `/new-prefix` ❌ (suffix lost)
235+
236+
**Recommendations:**
237+
238+
- For path-only redirects with exact paths, use `ReplaceFullPath`
239+
- To preserve path suffixes with prefix replacement, also modify `scheme`, `port`, or `hostname`
205240

206241
#### Examples
207242

pkg/gateway/routeutils/route_rule_action.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -259,12 +259,14 @@ func buildHttpRedirectAction(filter *gwv1.HTTPRequestRedirectFilter, redirectCon
259259
path = filter.Path.ReplaceFullPath
260260
isComponentSpecified = true
261261
} else if filter.Path.ReplacePrefixMatch != nil {
262-
pathValue := *filter.Path.ReplacePrefixMatch
263-
if strings.ContainsAny(pathValue, "*?") {
264-
return nil, errors.Errorf("ReplacePrefixMatch shouldn't contain wildcards: %v", pathValue)
262+
// Use #{path} if other components are modified (avoids redirect loop)
263+
// Otherwise use literal prefix (no suffix preservation)
264+
if filter.Scheme != nil || filter.Port != nil || filter.Hostname != nil {
265+
pathVariable := "/#{path}"
266+
path = &pathVariable
267+
} else {
268+
path = filter.Path.ReplacePrefixMatch
265269
}
266-
processedPath := fmt.Sprintf("%s/*", pathValue)
267-
path = &processedPath
268270
isComponentSpecified = true
269271
}
270272
}

pkg/gateway/routeutils/route_rule_action_test.go

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package routeutils
22

33
import (
44
"context"
5+
"testing"
6+
57
awssdk "github.com/aws/aws-sdk-go-v2/aws"
68
"github.com/stretchr/testify/assert"
79
corev1 "k8s.io/api/core/v1"
@@ -13,7 +15,6 @@ import (
1315
"sigs.k8s.io/controller-runtime/pkg/client"
1416
"sigs.k8s.io/controller-runtime/pkg/client/fake"
1517
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
16-
"testing"
1718
)
1819

1920
func Test_buildHttpRedirectAction(t *testing.T) {
@@ -27,7 +28,6 @@ func Test_buildHttpRedirectAction(t *testing.T) {
2728
query := "test-query"
2829
replaceFullPath := "/new-path"
2930
replacePrefixPath := "/new-prefix-path"
30-
replacePrefixPathAfterProcessing := "/new-prefix-path/*"
3131
invalidPath := "/invalid-path*"
3232

3333
tests := []struct {
@@ -66,8 +66,43 @@ func Test_buildHttpRedirectAction(t *testing.T) {
6666
wantErr: false,
6767
},
6868
{
69-
name: "redirect with prefix match",
69+
name: "redirect with prefix match only - uses literal prefix",
70+
filter: &gwv1.HTTPRequestRedirectFilter{
71+
Path: &gwv1.HTTPPathModifier{
72+
Type: gwv1.PrefixMatchHTTPPathModifier,
73+
ReplacePrefixMatch: &replacePrefixPath,
74+
},
75+
},
76+
want: &elbv2model.Action{
77+
Type: elbv2model.ActionTypeRedirect,
78+
RedirectConfig: &elbv2model.RedirectActionConfig{
79+
Path: &replacePrefixPath,
80+
},
81+
},
82+
wantErr: false,
83+
},
84+
{
85+
name: "redirect with prefix match and scheme - uses #{path}",
86+
filter: &gwv1.HTTPRequestRedirectFilter{
87+
Scheme: &scheme,
88+
Path: &gwv1.HTTPPathModifier{
89+
Type: gwv1.PrefixMatchHTTPPathModifier,
90+
ReplacePrefixMatch: &replacePrefixPath,
91+
},
92+
},
93+
want: &elbv2model.Action{
94+
Type: elbv2model.ActionTypeRedirect,
95+
RedirectConfig: &elbv2model.RedirectActionConfig{
96+
Path: awssdk.String("/#{path}"),
97+
Protocol: &expectedScheme,
98+
},
99+
},
100+
wantErr: false,
101+
},
102+
{
103+
name: "redirect with prefix match and port - uses #{path}",
70104
filter: &gwv1.HTTPRequestRedirectFilter{
105+
Port: (*gwv1.PortNumber)(&port),
71106
Path: &gwv1.HTTPPathModifier{
72107
Type: gwv1.PrefixMatchHTTPPathModifier,
73108
ReplacePrefixMatch: &replacePrefixPath,
@@ -76,7 +111,26 @@ func Test_buildHttpRedirectAction(t *testing.T) {
76111
want: &elbv2model.Action{
77112
Type: elbv2model.ActionTypeRedirect,
78113
RedirectConfig: &elbv2model.RedirectActionConfig{
79-
Path: &replacePrefixPathAfterProcessing,
114+
Path: awssdk.String("/#{path}"),
115+
Port: &portString,
116+
},
117+
},
118+
wantErr: false,
119+
},
120+
{
121+
name: "redirect with prefix match and hostname - uses #{path}",
122+
filter: &gwv1.HTTPRequestRedirectFilter{
123+
Hostname: (*gwv1.PreciseHostname)(&hostname),
124+
Path: &gwv1.HTTPPathModifier{
125+
Type: gwv1.PrefixMatchHTTPPathModifier,
126+
ReplacePrefixMatch: &replacePrefixPath,
127+
},
128+
},
129+
want: &elbv2model.Action{
130+
Type: elbv2model.ActionTypeRedirect,
131+
RedirectConfig: &elbv2model.RedirectActionConfig{
132+
Path: awssdk.String("/#{path}"),
133+
Host: &hostname,
80134
},
81135
},
82136
wantErr: false,
@@ -106,17 +160,6 @@ func Test_buildHttpRedirectAction(t *testing.T) {
106160
want: nil,
107161
wantErr: true,
108162
},
109-
{
110-
name: "path with wildcards in ReplacePrefixMatch",
111-
filter: &gwv1.HTTPRequestRedirectFilter{
112-
Path: &gwv1.HTTPPathModifier{
113-
Type: gwv1.PrefixMatchHTTPPathModifier,
114-
ReplacePrefixMatch: &invalidPath,
115-
},
116-
},
117-
want: nil,
118-
wantErr: true,
119-
},
120163
}
121164

122165
for _, tt := range tests {

pkg/gateway/routeutils/route_rule_transform.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package routeutils
22

33
import (
44
"fmt"
5+
"strings"
6+
57
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
68
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
7-
"strings"
89
)
910

1011
const (
@@ -35,6 +36,10 @@ func buildHTTPRuleTransforms(rule *gwv1.HTTPRouteRule, httpMatch *gwv1.HTTPRoute
3536
transforms = append(transforms, generateHostHeaderRewriteTransform(*rf.URLRewrite.Hostname))
3637
}
3738
}
39+
// Handle RequestRedirect with ReplacePrefixMatch as URLRewrite
40+
if rf.RequestRedirect != nil && rf.RequestRedirect.Path != nil && rf.RequestRedirect.Path.ReplacePrefixMatch != nil {
41+
transforms = append(transforms, generateURLRewritePathTransform(*rf.RequestRedirect.Path, httpMatch))
42+
}
3843
}
3944
}
4045

test/e2e/gateway/alb_instance_target_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the
473473
httpExp := httpexpect.New(tf.LoggerReporter, fmt.Sprintf("http://%v", dnsName))
474474
httpExp.GET("/api/v1/users").WithRedirectPolicy(httpexpect.DontFollowRedirects).Expect().
475475
Status(302).
476-
Header("Location").Equal("https://api.example.com:80/v2/*")
476+
Header("Location").Equal("https://api.example.com:80/v2/v1/users")
477477
})
478478

479479
By("testing redirect with scheme and port change", func() {

test/e2e/gateway/alb_ip_target_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo
459459
httpExp := httpexpect.New(tf.LoggerReporter, fmt.Sprintf("http://%v", dnsName))
460460
httpExp.GET("/api/v1/users").WithRedirectPolicy(httpexpect.DontFollowRedirects).Expect().
461461
Status(302).
462-
Header("Location").Equal("https://api.example.com:80/v2/*")
462+
Header("Location").Equal("https://api.example.com:80/v2/v1/users")
463463
})
464464

465465
By("testing redirect with scheme and port change", func() {

0 commit comments

Comments
 (0)