Merge pull request #84706 from yue9944882/feat/add-partial-non-resource-url-validation
Feature: Validates partial path for flow-schema's non-resource-url rules
This commit is contained in:
		| @@ -18,6 +18,8 @@ package validation | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"k8s.io/apimachinery/pkg/api/validation" | 	"k8s.io/apimachinery/pkg/api/validation" | ||||||
| 	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" | 	apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" | ||||||
| 	"k8s.io/apimachinery/pkg/util/sets" | 	"k8s.io/apimachinery/pkg/util/sets" | ||||||
| @@ -191,9 +193,17 @@ func ValidateFlowSchemaNonResourcePolicyRule(rule *flowcontrol.NonResourcePolicy | |||||||
|  |  | ||||||
| 	if len(rule.NonResourceURLs) == 0 { | 	if len(rule.NonResourceURLs) == 0 { | ||||||
| 		allErrs = append(allErrs, field.Required(fldPath.Child("nonResourceURLs"), "nonResourceURLs must contain at least one value")) | 		allErrs = append(allErrs, field.Required(fldPath.Child("nonResourceURLs"), "nonResourceURLs must contain at least one value")) | ||||||
| 	} else if len(rule.NonResourceURLs) > 1 && hasWildcard(rule.NonResourceURLs) { | 	} else if hasWildcard(rule.NonResourceURLs) { | ||||||
|  | 		if len(rule.NonResourceURLs) > 1 { | ||||||
| 			allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "if '*' is present, must not specify other non-resource URLs")) | 			allErrs = append(allErrs, field.Invalid(fldPath.Child("nonResourceURLs"), rule.NonResourceURLs, "if '*' is present, must not specify other non-resource URLs")) | ||||||
| 		} | 		} | ||||||
|  | 	} else { | ||||||
|  | 		for i, nonResourceURL := range rule.NonResourceURLs { | ||||||
|  | 			if err := ValidateNonResourceURLPath(nonResourceURL, fldPath.Child("nonResourceURLs").Index(i)); err != nil { | ||||||
|  | 				allErrs = append(allErrs, err) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return allErrs | 	return allErrs | ||||||
| } | } | ||||||
| @@ -332,6 +342,35 @@ func ValidatePriorityLevelConfigurationCondition(condition *flowcontrol.Priority | |||||||
| 	return allErrs | 	return allErrs | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // ValidateNonResourceURLPath validates non-resource-url path by following rules: | ||||||
|  | //   1. Slash must be the leading character of the path | ||||||
|  | //   2. White-space is forbidden in the path | ||||||
|  | //   3. Continuous/double slash is forbidden in the path | ||||||
|  | //   4. Wildcard "*" should only do suffix glob matching. Note that wildcard also matches slashes. | ||||||
|  | func ValidateNonResourceURLPath(path string, fldPath *field.Path) *field.Error { | ||||||
|  | 	if len(path) == 0 { | ||||||
|  | 		return field.Invalid(fldPath, path, "must not be empty") | ||||||
|  | 	} | ||||||
|  | 	if path == "/" { // root path | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !strings.HasPrefix(path, "/") { | ||||||
|  | 		return field.Invalid(fldPath, path, "must start with slash") | ||||||
|  | 	} | ||||||
|  | 	if strings.Contains(path, " ") { | ||||||
|  | 		return field.Invalid(fldPath, path, "must not contain white-space") | ||||||
|  | 	} | ||||||
|  | 	if strings.Contains(path, "//") { | ||||||
|  | 		return field.Invalid(fldPath, path, "must not contain double slash") | ||||||
|  | 	} | ||||||
|  | 	wildcardCount := strings.Count(path, "*") | ||||||
|  | 	if wildcardCount > 1 || (wildcardCount == 1 && path[len(path)-2:] != "/*") { | ||||||
|  | 		return field.Invalid(fldPath, path, "wildcard can only do suffix matching") | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func hasWildcard(operations []string) bool { | func hasWildcard(operations []string) bool { | ||||||
| 	for _, o := range operations { | 	for _, o := range operations { | ||||||
| 		if o == "*" { | 		if o == "*" { | ||||||
|   | |||||||
| @@ -626,3 +626,84 @@ func TestValidatePriorityLevelConfigurationStatus(t *testing.T) { | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestValidateNonResourceURLPath(t *testing.T) { | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		name           string | ||||||
|  | 		path           string | ||||||
|  | 		expectingError bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:           "empty string should fail", | ||||||
|  | 			path:           "", | ||||||
|  | 			expectingError: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "no slash should fail", | ||||||
|  | 			path:           "foo", | ||||||
|  | 			expectingError: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "single slash should work", | ||||||
|  | 			path:           "/", | ||||||
|  | 			expectingError: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "continuous slash should fail", | ||||||
|  | 			path:           "//", | ||||||
|  | 			expectingError: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "/foo slash should work", | ||||||
|  | 			path:           "/foo", | ||||||
|  | 			expectingError: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "multiple continuous slashes should fail", | ||||||
|  | 			path:           "/////", | ||||||
|  | 			expectingError: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "ending up with slash should work", | ||||||
|  | 			path:           "/apis/", | ||||||
|  | 			expectingError: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "ending up with wildcard should work", | ||||||
|  | 			path:           "/healthz/*", | ||||||
|  | 			expectingError: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "single wildcard inside the path should fail", | ||||||
|  | 			path:           "/healthz/*/foo", | ||||||
|  | 			expectingError: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "white-space in the path should fail", | ||||||
|  | 			path:           "/healthz/foo bar", | ||||||
|  | 			expectingError: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "wildcard plus plain path should fail", | ||||||
|  | 			path:           "/health*", | ||||||
|  | 			expectingError: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "wildcard plus plain path should fail 2", | ||||||
|  | 			path:           "/health*/foo", | ||||||
|  | 			expectingError: true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:           "multiple wildcard internal and suffix should fail", | ||||||
|  | 			path:           "/*/*", | ||||||
|  | 			expectingError: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 	for _, testCase := range testCases { | ||||||
|  | 		t.Run(testCase.name, func(t *testing.T) { | ||||||
|  | 			err := ValidateNonResourceURLPath(testCase.path, field.NewPath("")) | ||||||
|  | 			assert.Equal(t, testCase.expectingError, err != nil, | ||||||
|  | 				"actual error: %v", err) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot