Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion docs/guide/gateway/l7gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ information see the [Gateway API Conformance Page](https://gateway-api.sigs.k8s.
| HTTPRouteRule - HTTPRouteFilter - RequestHeaderModifier | Core | ❌-- [Limited Support](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/header-modification.html) |
| HTTPRouteRule - HTTPRouteFilter - ResponseHeaderModifier | Core | ❌ |
| HTTPRouteRule - HTTPRouteFilter - RequestMirror | Extended | ❌ |
| HTTPRouteRule - HTTPRouteFilter - RequestRedirect | Core | |
| HTTPRouteRule - HTTPRouteFilter - RequestRedirect | Core | ✅ -- See [ReplacePrefixMatch Limitation](#requestredirect-path-modification-replaceprefixmatch-limitation) below |
| HTTPRouteRule - HTTPRouteFilter - UrlRewrite | Extended | ✅ |
| HTTPRouteRule - HTTPRouteFilter - CORS | Extended | ❌ |
| HTTPRouteRule - HTTPRouteFilter - ExternalAuth | Extended | ❌ -- Use [ListenerRuleConfigurations](customization.md#customizing-l7-routing-rules) |
Expand All @@ -200,8 +200,44 @@ information see the [Gateway API Conformance Page](https://gateway-api.sigs.k8s.
Backend TLS is not supported by AWS ALB Gateway. For more information on how AWS ALB communicates with targets using encryption,
please see the [AWS documentation](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html#target-group-routing-configuration).

##### RequestRedirect Path Modification ReplacePrefixMatch Limitation

The AWS Load Balancer Controller supports HTTPRoute RequestRedirect filters with both `ReplaceFullPath` and `ReplacePrefixMatch` path modification types.

**ReplacePrefixMatch Behavior:**

We support `ReplacePrefixMatch` with limitations:

1. **With scheme/port/hostname changes** - Works as expected:
```yaml
filters:
- type: RequestRedirect
requestRedirect:
scheme: HTTPS # or port/hostname
path:
type: ReplacePrefixMatch
replacePrefixMatch: /new-prefix
```
- Request: `/old-prefix/path/to/resource`
- Redirects to: `/new-prefix/path/to/resource` ✅ (suffix preserved)

2. **Without other component changes** - AWS ALB will reject with redirect loop error:
```yaml
filters:
- type: RequestRedirect
requestRedirect:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /new-prefix
```
- This configuration will be rejected by the API with "InvalidLoadBalancerAction: The redirect configuration is not valid because it creates a loop." ❌

**Recommendations:**

- For path-only redirects, use `ReplaceFullPath` instead
- To use `ReplacePrefixMatch`, you must also modify `scheme`, `port`, or `hostname`

**Important**: If one HTTPRoute rule has an invalid redirect configuration (e.g., path-only redirect with `ReplacePrefixMatch` that cause redirect loop), the controller will fail to create that listener rule and stop processing subsequent rules in the same HTTPRoute. This means valid rules with lower precedence (shorter paths, later in the route) will not be created.

#### Examples

Expand Down
13 changes: 1 addition & 12 deletions pkg/gateway/routeutils/route_rule_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ func buildHttpRuleRedirectActionsBasedOnFilter(filters []gwv1.HTTPRouteFilter, r
// buildHttpRedirectAction configure filter attributes to RedirectActionConfig
// gateway api has no attribute to specify query, use listener rule configuration
func buildHttpRedirectAction(filter *gwv1.HTTPRequestRedirectFilter, redirectConfig *elbv2gw.RedirectActionConfig) (*elbv2model.Action, error) {
isComponentSpecified := false
var statusCode string
if filter.StatusCode != nil {
statusCodeStr := fmt.Sprintf("HTTP_%d", *filter.StatusCode)
Expand All @@ -236,7 +235,6 @@ func buildHttpRedirectAction(filter *gwv1.HTTPRequestRedirectFilter, redirectCon
if filter.Port != nil {
portStr := fmt.Sprintf("%d", *filter.Port)
port = &portStr
isComponentSpecified = true
}

var protocol *string
Expand All @@ -246,7 +244,6 @@ func buildHttpRedirectAction(filter *gwv1.HTTPRequestRedirectFilter, redirectCon
return nil, errors.Errorf("unsupported redirect scheme: %v", upperScheme)
}
protocol = &upperScheme
isComponentSpecified = true
}

var path *string
Expand All @@ -257,26 +254,18 @@ func buildHttpRedirectAction(filter *gwv1.HTTPRequestRedirectFilter, redirectCon
return nil, errors.Errorf("ReplaceFullPath shouldn't contain wildcards: %v", pathValue)
}
path = filter.Path.ReplaceFullPath
isComponentSpecified = true
} else if filter.Path.ReplacePrefixMatch != nil {
//url rewrite will handle path transform
pathValue := *filter.Path.ReplacePrefixMatch
if strings.ContainsAny(pathValue, "*?") {
return nil, errors.Errorf("ReplacePrefixMatch shouldn't contain wildcards: %v", pathValue)
}
processedPath := fmt.Sprintf("%s/*", pathValue)
path = &processedPath
isComponentSpecified = true
}
}

var hostname *string
if filter.Hostname != nil {
hostname = (*string)(filter.Hostname)
isComponentSpecified = true
}

if !isComponentSpecified {
return nil, errors.Errorf("To avoid a redirect loop, you must modify at least one of the following components: protocol, port, hostname or path.")
}

var query *string
Expand Down
26 changes: 5 additions & 21 deletions pkg/gateway/routeutils/route_rule_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package routeutils

import (
"context"
"testing"

awssdk "github.com/aws/aws-sdk-go-v2/aws"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
Expand All @@ -13,7 +15,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
"testing"
)

func Test_buildHttpRedirectAction(t *testing.T) {
Expand All @@ -27,7 +28,6 @@ func Test_buildHttpRedirectAction(t *testing.T) {
query := "test-query"
replaceFullPath := "/new-path"
replacePrefixPath := "/new-prefix-path"
replacePrefixPathAfterProcessing := "/new-prefix-path/*"
invalidPath := "/invalid-path*"

tests := []struct {
Expand Down Expand Up @@ -66,8 +66,9 @@ func Test_buildHttpRedirectAction(t *testing.T) {
wantErr: false,
},
{
name: "redirect with prefix match",
name: "redirect with prefix - no path in redirect config",
filter: &gwv1.HTTPRequestRedirectFilter{
Hostname: (*gwv1.PreciseHostname)(&hostname),
Path: &gwv1.HTTPPathModifier{
Type: gwv1.PrefixMatchHTTPPathModifier,
ReplacePrefixMatch: &replacePrefixPath,
Expand All @@ -76,17 +77,11 @@ func Test_buildHttpRedirectAction(t *testing.T) {
want: &elbv2model.Action{
Type: elbv2model.ActionTypeRedirect,
RedirectConfig: &elbv2model.RedirectActionConfig{
Path: &replacePrefixPathAfterProcessing,
Host: &hostname,
},
},
wantErr: false,
},
{
name: "redirect with no component provided",
filter: &gwv1.HTTPRequestRedirectFilter{},
want: nil,
wantErr: true,
},
{
name: "invalid scheme provided",
filter: &gwv1.HTTPRequestRedirectFilter{
Expand All @@ -106,17 +101,6 @@ func Test_buildHttpRedirectAction(t *testing.T) {
want: nil,
wantErr: true,
},
{
name: "path with wildcards in ReplacePrefixMatch",
filter: &gwv1.HTTPRequestRedirectFilter{
Path: &gwv1.HTTPPathModifier{
Type: gwv1.PrefixMatchHTTPPathModifier,
ReplacePrefixMatch: &invalidPath,
},
},
want: nil,
wantErr: true,
},
}

for _, tt := range tests {
Expand Down
7 changes: 6 additions & 1 deletion pkg/gateway/routeutils/route_rule_transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package routeutils

import (
"fmt"
"strings"

elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
"strings"
)

const (
Expand Down Expand Up @@ -35,6 +36,10 @@ func buildHTTPRuleTransforms(rule *gwv1.HTTPRouteRule, httpMatch *gwv1.HTTPRoute
transforms = append(transforms, generateHostHeaderRewriteTransform(*rf.URLRewrite.Hostname))
}
}
// Handle RequestRedirect with ReplacePrefixMatch as URLRewrite
if rf.RequestRedirect != nil && rf.RequestRedirect.Path != nil && rf.RequestRedirect.Path.ReplacePrefixMatch != nil {
transforms = append(transforms, generateURLRewritePathTransform(*rf.RequestRedirect.Path, httpMatch))
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion test/e2e/gateway/alb_instance_target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ var _ = Describe("test k8s alb gateway using instance targets reconciled by the
httpExp := httpexpect.New(tf.LoggerReporter, fmt.Sprintf("http://%v", dnsName))
httpExp.GET("/api/v1/users").WithRedirectPolicy(httpexpect.DontFollowRedirects).Expect().
Status(302).
Header("Location").Equal("https://api.example.com:80/v2/*")
Header("Location").Equal("https://api.example.com:80/v2/v1/users")
})

By("testing redirect with scheme and port change", func() {
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/gateway/alb_ip_target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ var _ = Describe("test k8s alb gateway using ip targets reconciled by the aws lo
httpExp := httpexpect.New(tf.LoggerReporter, fmt.Sprintf("http://%v", dnsName))
httpExp.GET("/api/v1/users").WithRedirectPolicy(httpexpect.DontFollowRedirects).Expect().
Status(302).
Header("Location").Equal("https://api.example.com:80/v2/*")
Header("Location").Equal("https://api.example.com:80/v2/v1/users")
})

By("testing redirect with scheme and port change", func() {
Expand Down
Loading