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
15 changes: 15 additions & 0 deletions pkg/gateway/model/model_build_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,21 @@ func (l listenerBuilderImpl) buildListenerRules(stack core.Stack, ls *elbv2model
})
}
actions := buildL7ListenerActions(targetGroupTuples)

// configure actions based on filters
switch route.GetRouteKind() {
case routeutils.HTTPRouteKind:
httpRule := rule.GetRawRouteRule().(*gwv1.HTTPRouteRule)
if len(httpRule.Filters) > 0 {
finalActions, err := routeutils.BuildHttpRuleActionsBasedOnFilter(httpRule.Filters)
if err != nil {
return err
}
actions = finalActions
}
// TODO: add case for GRPC
}

albRules = append(albRules, elbv2model.Rule{
Conditions: conditionsList,
Actions: actions,
Expand Down
85 changes: 85 additions & 0 deletions pkg/gateway/routeutils/route_rule_condition.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package routeutils

import (
"fmt"
"github.com/pkg/errors"
elbv2model "sigs.k8s.io/aws-load-balancer-controller/pkg/model/elbv2"
gwv1 "sigs.k8s.io/gateway-api/apis/v1"
Expand Down Expand Up @@ -144,3 +145,87 @@ func buildGrpcRouteRuleConditions(matches RouteRule) ([][]elbv2model.RuleConditi
var conditions [][]elbv2model.RuleCondition
return conditions, nil
}

func BuildHttpRuleActionsBasedOnFilter(filters []gwv1.HTTPRouteFilter) ([]elbv2model.Action, error) {
for _, filter := range filters {
switch filter.Type {
case gwv1.HTTPRouteFilterRequestHeaderModifier:
// TODO: decide behavior for request header modifier
case gwv1.HTTPRouteFilterRequestRedirect:
return buildHttpRedirectAction(filter.RequestRedirect)
case gwv1.HTTPRouteFilterResponseHeaderModifier:
// TODO: decide behavior for response header modifier
}
}
return nil, nil
}

// buildHttpRedirectAction configure filter attributes to RedirectActionConfig
// gateway api has no attribute to specify query
func buildHttpRedirectAction(filter *gwv1.HTTPRequestRedirectFilter) ([]elbv2model.Action, error) {
isComponentSpecified := false
var statusCode string
if filter.StatusCode != nil {
statusCodeStr := fmt.Sprintf("HTTP_%d", *filter.StatusCode)
statusCode = statusCodeStr
}

var port *string
if filter.Port != nil {
portStr := fmt.Sprintf("%d", *filter.Port)
port = &portStr
isComponentSpecified = true
}

var protocol *string
if filter.Scheme != nil {
upperScheme := strings.ToUpper(*filter.Scheme)
if upperScheme != "HTTP" && upperScheme != "HTTPS" {
return nil, errors.Errorf("unsupported redirect scheme: %v", upperScheme)
}
protocol = &upperScheme
isComponentSpecified = true
}

var path *string
if filter.Path != nil {
if filter.Path.ReplaceFullPath != nil {
pathValue := *filter.Path.ReplaceFullPath
if strings.ContainsAny(pathValue, "*?") {
return nil, errors.Errorf("ReplaceFullPath shouldn't contain wildcards: %v", pathValue)
}
path = filter.Path.ReplaceFullPath
isComponentSpecified = true
} else if filter.Path.ReplacePrefixMatch != nil {
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you probably don't need this check as it's just best-effort and if clients want to create re-direct loops they can.

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

action := elbv2model.Action{
Type: elbv2model.ActionTypeRedirect,
RedirectConfig: &elbv2model.RedirectActionConfig{
Host: hostname,
Path: path,
Port: port,
Protocol: protocol,
StatusCode: statusCode,
},
}
return []elbv2model.Action{action}, nil
}
115 changes: 115 additions & 0 deletions pkg/gateway/routeutils/route_rule_condition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,118 @@ func Test_buildHttpMethodCondition(t *testing.T) {
})
}
}

func Test_buildHttpRedirectAction(t *testing.T) {

scheme := "https"
expectedScheme := "HTTPS"
invalidScheme := "invalid"
hostname := "example.com"
port := int32(80)
portString := "80"
statusCode := 301
replaceFullPath := "/new-path"
replacePrefixPath := "/new-prefix-path"
replacePrefixPathAfterProcessing := "/new-prefix-path/*"
invalidPath := "/invalid-path*"

tests := []struct {
name string
filter *gwv1.HTTPRequestRedirectFilter
want []elbv2model.Action
wantErr bool
}{
{
name: "redirect with all fields provided",
filter: &gwv1.HTTPRequestRedirectFilter{
Scheme: &scheme,
Hostname: (*gwv1.PreciseHostname)(&hostname),
Port: (*gwv1.PortNumber)(&port),
StatusCode: &statusCode,
Path: &gwv1.HTTPPathModifier{
Type: gwv1.FullPathHTTPPathModifier,
ReplaceFullPath: &replaceFullPath,
},
},
want: []elbv2model.Action{
{
Type: elbv2model.ActionTypeRedirect,
RedirectConfig: &elbv2model.RedirectActionConfig{
Host: &hostname,
Path: &replaceFullPath,
Port: &portString,
Protocol: &expectedScheme,
StatusCode: "HTTP_301",
},
},
},
wantErr: false,
},
{
name: "redirect with prefix match",
filter: &gwv1.HTTPRequestRedirectFilter{
Path: &gwv1.HTTPPathModifier{
Type: gwv1.PrefixMatchHTTPPathModifier,
ReplacePrefixMatch: &replacePrefixPath,
},
},
want: []elbv2model.Action{
{
Type: elbv2model.ActionTypeRedirect,
RedirectConfig: &elbv2model.RedirectActionConfig{
Path: &replacePrefixPathAfterProcessing,
},
},
},
wantErr: false,
},
{
name: "redirect with no component provided",
filter: &gwv1.HTTPRequestRedirectFilter{},
want: nil,
wantErr: true,
},
{
name: "invalid scheme provided",
filter: &gwv1.HTTPRequestRedirectFilter{
Scheme: &invalidScheme,
},
want: nil,
wantErr: true,
},
{
name: "path with wildcards in ReplaceFullPath",
filter: &gwv1.HTTPRequestRedirectFilter{
Path: &gwv1.HTTPPathModifier{
Type: gwv1.FullPathHTTPPathModifier,
ReplaceFullPath: &invalidPath,
},
},
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 {
t.Run(tt.name, func(t *testing.T) {
got, err := buildHttpRedirectAction(tt.filter)
if tt.wantErr {
assert.Error(t, err)
return
}
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
Loading