Skip to content

Commit ff24ee1

Browse files
committed
Add regex support for path matching
Problem: As regex path matching is missing support for NGF. Solution: Add support for regex path matching, only allow a full path rewrite/redirect after the regex path match. Testing: Add additional unit test case for regex path match.
1 parent 24dee2b commit ff24ee1

File tree

14 files changed

+691
-132
lines changed

14 files changed

+691
-132
lines changed

internal/controller/nginx/config/servers.go

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ func initializeExternalLocations(
414414
}
415415
if !exactPathExists {
416416
externalLocExact := http.Location{
417-
Path: exactPath(externalLocPath),
417+
Path: exactPath(rule.Path),
418418
Type: locType,
419419
}
420420
extLocations = append(extLocations, externalLocExact)
@@ -458,23 +458,22 @@ func updateLocation(
458458
mirrorPercentage *float64,
459459
) http.Location {
460460
filters := matchRule.Filters
461-
path := pathRule.Path
462461
grpc := pathRule.GRPC
463462

464463
if filters.InvalidFilter != nil {
465464
location.Return = &http.Return{Code: http.StatusInternalServerError}
466465
return location
467466
}
468467

469-
location = updateLocationMirrorRoute(location, path, grpc)
468+
location = updateLocationMirrorRoute(location, pathRule.Path, grpc)
470469
location.Includes = append(location.Includes, createIncludesFromLocationSnippetsFilters(filters.SnippetsFilters)...)
471470

472471
if filters.RequestRedirect != nil {
473-
return updateLocationRedirectFilter(location, filters.RequestRedirect, listenerPort, path)
472+
return updateLocationRedirectFilter(location, filters.RequestRedirect, listenerPort, pathRule)
474473
}
475474

476-
location = updateLocationRewriteFilter(location, filters.RequestURLRewrite, path)
477-
location = updateLocationMirrorFilters(location, filters.RequestMirrors, path, mirrorPercentage)
475+
location = updateLocationRewriteFilter(location, filters.RequestURLRewrite, pathRule)
476+
location = updateLocationMirrorFilters(location, filters.RequestMirrors, pathRule.Path, mirrorPercentage)
478477
location = updateLocationProxySettings(location, matchRule, grpc, keepAliveCheck)
479478

480479
return location
@@ -495,9 +494,9 @@ func updateLocationRedirectFilter(
495494
location http.Location,
496495
redirectFilter *dataplane.HTTPRequestRedirectFilter,
497496
listenerPort int32,
498-
path string,
497+
pathRule dataplane.PathRule,
499498
) http.Location {
500-
ret, rewrite := createReturnAndRewriteConfigForRedirectFilter(redirectFilter, listenerPort, path)
499+
ret, rewrite := createReturnAndRewriteConfigForRedirectFilter(redirectFilter, listenerPort, pathRule)
501500
if rewrite.MainRewrite != "" {
502501
location.Rewrites = append(location.Rewrites, rewrite.MainRewrite)
503502
}
@@ -509,9 +508,9 @@ func updateLocationRedirectFilter(
509508
func updateLocationRewriteFilter(
510509
location http.Location,
511510
rewriteFilter *dataplane.HTTPURLRewriteFilter,
512-
path string,
511+
pathRule dataplane.PathRule,
513512
) http.Location {
514-
rewrites := createRewritesValForRewriteFilter(rewriteFilter, path)
513+
rewrites := createRewritesValForRewriteFilter(rewriteFilter, pathRule)
515514
if rewrites != nil {
516515
if location.Type == http.InternalLocationType && rewrites.InternalRewrite != "" {
517516
location.Rewrites = append(location.Rewrites, rewrites.InternalRewrite)
@@ -658,7 +657,7 @@ func createProxySSLVerify(v *dataplane.VerifyTLS) *http.ProxySSLVerify {
658657
func createReturnAndRewriteConfigForRedirectFilter(
659658
filter *dataplane.HTTPRequestRedirectFilter,
660659
listenerPort int32,
661-
path string,
660+
pathRule dataplane.PathRule,
662661
) (*http.Return, *rewriteConfig) {
663662
if filter == nil {
664663
return nil, nil
@@ -702,7 +701,12 @@ func createReturnAndRewriteConfigForRedirectFilter(
702701

703702
rewrites := &rewriteConfig{}
704703
if filter.Path != nil {
705-
rewrites.MainRewrite = createMainRewriteForFilters(filter.Path, path)
704+
mainRewrite := createMainRewriteForFilters(filter.Path, pathRule)
705+
if mainRewrite == "" {
706+
// Invalid configuration for the rewrite filter
707+
return nil, nil
708+
}
709+
rewrites.MainRewrite = mainRewrite
706710
body = fmt.Sprintf("%s://%s$uri$is_args$args", scheme, hostnamePort)
707711
}
708712

@@ -712,19 +716,27 @@ func createReturnAndRewriteConfigForRedirectFilter(
712716
}, rewrites
713717
}
714718

715-
func createMainRewriteForFilters(pathModifier *dataplane.HTTPPathModifier, path string) string {
719+
func createMainRewriteForFilters(pathModifier *dataplane.HTTPPathModifier, pathRule dataplane.PathRule) string {
716720
var mainRewrite string
717721
switch pathModifier.Type {
718722
case dataplane.ReplaceFullPath:
719-
mainRewrite = fmt.Sprintf("^ %s", pathModifier.Replacement)
723+
// should we keep the args here?
724+
mainRewrite = fmt.Sprintf("^ %s$is_args$args", pathModifier.Replacement)
720725
case dataplane.ReplacePrefixMatch:
726+
// ReplacePrefixMatch is only compatible with a PathPrefix HTTPRouteMatch.
727+
// ReplaceFullPath is compatible with PathTypeExact/PathTypePrefix/PathTypeRegularExpression HTTPRouteMatch.
728+
// see https://gateway-api.sigs.k8s.io/reference/spec/?h=replaceprefixmatch#httppathmodifier
729+
if pathRule.PathType != dataplane.PathTypePrefix {
730+
return ""
731+
}
732+
721733
filterPrefix := pathModifier.Replacement
722734
if filterPrefix == "" {
723735
filterPrefix = "/"
724736
}
725737

726738
// capture everything following the configured prefix up to the first ?, if present.
727-
regex := fmt.Sprintf("^%s([^?]*)?", path)
739+
regex := fmt.Sprintf("^%s([^?]*)?", pathRule.Path)
728740
// replace the configured prefix with the filter prefix, append the captured segment,
729741
// and include the request arguments stored in nginx variable $args.
730742
// https://nginx.org/en/docs/http/ngx_http_core_module.html#var_args
@@ -733,13 +745,13 @@ func createMainRewriteForFilters(pathModifier *dataplane.HTTPPathModifier, path
733745
// if configured prefix does not end in /, but replacement prefix does end in /,
734746
// then make sure that we *require* but *don't capture* a trailing slash in the request,
735747
// otherwise we'll get duplicate slashes in the full replacement
736-
if strings.HasSuffix(filterPrefix, "/") && !strings.HasSuffix(path, "/") {
737-
regex = fmt.Sprintf("^%s(?:/([^?]*))?", path)
748+
if strings.HasSuffix(filterPrefix, "/") && !strings.HasSuffix(pathRule.Path, "/") {
749+
regex = fmt.Sprintf("^%s(?:/([^?]*))?", pathRule.Path)
738750
}
739751

740752
// if configured prefix ends in / we won't capture it for a request (since it's not in the regex),
741753
// so append it to the replacement prefix if the replacement prefix doesn't already end in /
742-
if strings.HasSuffix(path, "/") && !strings.HasSuffix(filterPrefix, "/") {
754+
if strings.HasSuffix(pathRule.Path, "/") && !strings.HasSuffix(filterPrefix, "/") {
743755
replacement = fmt.Sprintf("%s/$1?$args?", filterPrefix)
744756
}
745757

@@ -749,7 +761,10 @@ func createMainRewriteForFilters(pathModifier *dataplane.HTTPPathModifier, path
749761
return mainRewrite
750762
}
751763

752-
func createRewritesValForRewriteFilter(filter *dataplane.HTTPURLRewriteFilter, path string) *rewriteConfig {
764+
func createRewritesValForRewriteFilter(
765+
filter *dataplane.HTTPURLRewriteFilter,
766+
pathRule dataplane.PathRule,
767+
) *rewriteConfig {
753768
if filter == nil {
754769
return nil
755770
}
@@ -758,8 +773,13 @@ func createRewritesValForRewriteFilter(filter *dataplane.HTTPURLRewriteFilter, p
758773
if filter.Path != nil {
759774
rewrites.InternalRewrite = "^ $request_uri"
760775

761-
// for URLRewriteFilter, we add a break to the rewrite to prevent further processing of the request.
762-
rewrites.MainRewrite = fmt.Sprintf("%s break", createMainRewriteForFilters(filter.Path, path))
776+
mainRewrite := createMainRewriteForFilters(filter.Path, pathRule)
777+
if mainRewrite == "" {
778+
// Invalid configuration for the rewrite filter
779+
return nil
780+
}
781+
// For URLRewriteFilter, add "break" to prevent further processing of the request.
782+
rewrites.MainRewrite = fmt.Sprintf("%s break", mainRewrite)
763783
}
764784

765785
return rewrites
@@ -977,19 +997,31 @@ func exactPath(path string) string {
977997
return fmt.Sprintf("= %s", path)
978998
}
979999

1000+
func prefixPath(path string) string {
1001+
return fmt.Sprintf("^~ %s", path)
1002+
}
1003+
1004+
func regularExpressionPath(path string) string {
1005+
return fmt.Sprintf("~ %s", path)
1006+
}
1007+
9801008
// createPath builds the location path depending on the path type.
9811009
func createPath(rule dataplane.PathRule) string {
9821010
switch rule.PathType {
9831011
case dataplane.PathTypeExact:
9841012
return exactPath(rule.Path)
1013+
case dataplane.PathTypePrefix:
1014+
return prefixPath(rule.Path)
1015+
case dataplane.PathTypeRegularExpression:
1016+
return regularExpressionPath(rule.Path)
9851017
default:
986-
return rule.Path
1018+
return "" // should never happen because path type is validated earlier
9871019
}
9881020
}
9891021

9901022
func createDefaultRootLocation() http.Location {
9911023
return http.Location{
992-
Path: "/",
1024+
Path: "= /",
9931025
Return: &http.Return{Code: http.StatusNotFound},
9941026
}
9951027
}

0 commit comments

Comments
 (0)