diff --git a/apis/v1alpha2/validation/httproute.go b/apis/v1alpha2/validation/httproute.go index 70a3c0254b..309f91b49e 100644 --- a/apis/v1alpha2/validation/httproute.go +++ b/apis/v1alpha2/validation/httproute.go @@ -17,6 +17,9 @@ limitations under the License. package validation import ( + "fmt" + "strings" + "k8s.io/apimachinery/pkg/util/validation/field" gatewayv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -28,6 +31,9 @@ var ( repeatableHTTPRouteFilters = []gatewayv1a2.HTTPRouteFilterType{ gatewayv1a2.HTTPRouteFilterExtensionRef, } + + invalidPathSequences = []string{"//", "/./", "/../", "%2f", "%2F", "#"} + invalidPathSuffixes = []string{"/..", "/."} ) // ValidateHTTPRoute validates HTTPRoute according to the Gateway API specification. @@ -93,3 +99,41 @@ func validateHTTPBackendUniqueFilters(ref []gatewayv1a2.HTTPBackendRef, path *fi } return errs } + +// webhook validation of HTTPPathMatch +func validateHTTPPathMatch(path *gatewayv1a2.HTTPPathMatch, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if path.Type == nil { + return append(allErrs, field.Required(fldPath.Child("pathType"), "pathType must be specified")) + } + + if path.Value == nil { + return append(allErrs, field.Required(fldPath.Child("pathValue"), "pathValue must not be nil.")) + } + + switch *path.Type { + case gatewayv1a2.PathMatchExact, gatewayv1a2.PathMatchPrefix: + if !strings.HasPrefix(*path.Value, "/") { + allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path, "must be an absolute path")) + } + if len(*path.Value) > 0 { + for _, invalidSeq := range invalidPathSequences { + if strings.Contains(*path.Value, invalidSeq) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path, fmt.Sprintf("must not contain '%s'", invalidSeq))) + } + } + + for _, invalidSuff := range invalidPathSuffixes { + if strings.HasSuffix(*path.Value, invalidSuff) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("path"), path, fmt.Sprintf("cannot end with '%s'", invalidSuff))) + } + } + } + case gatewayv1a2.PathMatchRegularExpression: + default: + pathTypes := []string{string(gatewayv1a2.PathMatchExact), string(gatewayv1a2.PathMatchPrefix), string(gatewayv1a2.PathMatchRegularExpression)} + allErrs = append(allErrs, field.NotSupported(fldPath.Child("pathType"), *path.Type, pathTypes)) + } + return allErrs +} diff --git a/apis/v1alpha2/validation/httproute_test.go b/apis/v1alpha2/validation/httproute_test.go index 26ef140f26..ba1ce76b9f 100644 --- a/apis/v1alpha2/validation/httproute_test.go +++ b/apis/v1alpha2/validation/httproute_test.go @@ -390,3 +390,44 @@ func TestValidateHTTPBackendUniqueFilters(t *testing.T) { }) } } + +func TestValidateHTTPPathMatch(t *testing.T) { + tests := []struct { + name string + path *gatewayv1a2.HTTPPathMatch + errCount int + }{ + { + name: "invalid httpRoute prefix", + path: &gatewayv1a2.HTTPPathMatch{ + Type: pkgutils.PathMatchTypePtr("Prefix"), + Value: utilpointer.String("/."), + }, + errCount: 1, + }, + { + name: "invalid httpRoute Exact", + path: &gatewayv1a2.HTTPPathMatch{ + Type: pkgutils.PathMatchTypePtr("Exact"), + Value: utilpointer.String("/foo/./bar"), + }, + errCount: 1, + }, + { + name: "invalid httpRoute prefix", + path: &gatewayv1a2.HTTPPathMatch{ + Type: pkgutils.PathMatchTypePtr("Prefix"), + Value: utilpointer.String("/"), + }, + errCount: 0, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + errs := validateHTTPPathMatch(tc.path, field.NewPath("spec").Child("rules").Child("matches").Child("path")) + if len(errs) != tc.errCount { + t.Errorf("TestValidateHTTPPathMatch() got %v errors, want %v errors", len(errs), tc.errCount) + } + }) + } +}