...

Source file src/sigs.k8s.io/gateway-api/apis/v1alpha2/validation/grpcroute.go

Documentation: sigs.k8s.io/gateway-api/apis/v1alpha2/validation

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"fmt"
    21  	"net/http"
    22  	"regexp"
    23  	"strings"
    24  
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  
    27  	gatewayv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
    28  )
    29  
    30  var (
    31  	// repeatableGRPCRouteFilters are filter types that are allowed to be
    32  	// repeated multiple times in a rule.
    33  	repeatableGRPCRouteFilters = []gatewayv1a2.GRPCRouteFilterType{
    34  		gatewayv1a2.GRPCRouteFilterExtensionRef,
    35  		gatewayv1a2.GRPCRouteFilterRequestMirror,
    36  	}
    37  	validServiceName      = `^(?i)\.?[a-z_][a-z_0-9]*(\.[a-z_][a-z_0-9]*)*$`
    38  	validServiceNameRegex = regexp.MustCompile(validServiceName)
    39  	validMethodName       = `^[A-Za-z_][A-Za-z_0-9]*$`
    40  	validMethodNameRegex  = regexp.MustCompile(validMethodName)
    41  )
    42  
    43  // ValidateGRPCRoute validates GRPCRoute according to the Gateway API specification.
    44  // For additional details of the GRPCRoute spec, refer to:
    45  // https://gateway-api.sigs.k8s.io/v1alpha2/reference/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute
    46  func ValidateGRPCRoute(route *gatewayv1a2.GRPCRoute) field.ErrorList {
    47  	return validateGRPCRouteSpec(&route.Spec, field.NewPath("spec"))
    48  }
    49  
    50  // validateRouteSpec validates that required fields of spec are set according to the
    51  // Gateway API specification.
    52  func validateGRPCRouteSpec(spec *gatewayv1a2.GRPCRouteSpec, path *field.Path) field.ErrorList {
    53  	var errs field.ErrorList
    54  	errs = append(errs, validateGRPCRouteRules(spec.Rules, path.Child("rules"))...)
    55  	errs = append(errs, validateParentRefs(spec.ParentRefs, path.Child("spec"))...)
    56  	return errs
    57  }
    58  
    59  // validateGRPCRouteRules validates whether required fields of rules are set according
    60  // to the Gateway API specification.
    61  func validateGRPCRouteRules(rules []gatewayv1a2.GRPCRouteRule, path *field.Path) field.ErrorList {
    62  	var errs field.ErrorList
    63  	for i, rule := range rules {
    64  		errs = append(errs, validateRuleMatches(rule.Matches, path.Index(i).Child("matches"))...)
    65  		errs = append(errs, validateGRPCRouteFilters(rule.Filters, path.Index(i).Child(("filters")))...)
    66  		for j, backendRef := range rule.BackendRefs {
    67  			errs = append(errs, validateGRPCRouteFilters(backendRef.Filters, path.Child("rules").Index(i).Child("backendRefs").Index(j))...)
    68  		}
    69  	}
    70  	return errs
    71  }
    72  
    73  // validateRuleMatches validates GRPCMethodMatch
    74  func validateRuleMatches(matches []gatewayv1a2.GRPCRouteMatch, path *field.Path) field.ErrorList {
    75  	var errs field.ErrorList
    76  	for i, m := range matches {
    77  		if m.Method != nil {
    78  			if m.Method.Service == nil && m.Method.Method == nil {
    79  				errs = append(errs, field.Required(path.Index(i).Child("method"), "one or both of `service` or `method` must be specified"))
    80  			}
    81  			// GRPCRoute method matcher admits two types: Exact and RegularExpression.
    82  			// If not specified, the match will be treated as type Exact (also the default value for this field).
    83  			if m.Method.Type == nil || *m.Method.Type == gatewayv1a2.GRPCMethodMatchExact {
    84  				if m.Method.Service != nil && !validServiceNameRegex.MatchString(*m.Method.Service) {
    85  					errs = append(errs, field.Invalid(path.Index(i).Child("method"), *m.Method.Service,
    86  						fmt.Sprintf("must only contain valid characters (matching %s)", validServiceName)))
    87  				}
    88  				if m.Method.Method != nil && !validMethodNameRegex.MatchString(*m.Method.Method) {
    89  					errs = append(errs, field.Invalid(path.Index(i).Child("method"), *m.Method.Method,
    90  						fmt.Sprintf("must only contain valid characters (matching %s)", validMethodName)))
    91  				}
    92  			}
    93  		}
    94  		if m.Headers != nil {
    95  			errs = append(errs, validateGRPCHeaderMatches(m.Headers, path.Index(i).Child("headers"))...)
    96  		}
    97  	}
    98  	return errs
    99  }
   100  
   101  // validateGRPCHeaderMatches validates that no header name is matched more than
   102  // once (case-insensitive), and that at least one of service or method was
   103  // provided.
   104  func validateGRPCHeaderMatches(matches []gatewayv1a2.GRPCHeaderMatch, path *field.Path) field.ErrorList {
   105  	var errs field.ErrorList
   106  	counts := map[string]int{}
   107  
   108  	for _, match := range matches {
   109  		// Header names are case-insensitive.
   110  		counts[strings.ToLower(string(match.Name))]++
   111  	}
   112  
   113  	for name, count := range counts {
   114  		if count > 1 {
   115  			errs = append(errs, field.Invalid(path, http.CanonicalHeaderKey(name), "cannot match the same header multiple times in the same rule"))
   116  		}
   117  	}
   118  
   119  	return errs
   120  }
   121  
   122  // validateGRPCRouteFilterType validates that only the expected fields are
   123  // set for the specified filter type.
   124  func validateGRPCRouteFilterType(filter gatewayv1a2.GRPCRouteFilter, path *field.Path) field.ErrorList {
   125  	var errs field.ErrorList
   126  	if filter.ExtensionRef != nil && filter.Type != gatewayv1a2.GRPCRouteFilterExtensionRef {
   127  		errs = append(errs, field.Invalid(path, filter.ExtensionRef, "must be nil if the GRPCRouteFilter.Type is not ExtensionRef"))
   128  	}
   129  	if filter.ExtensionRef == nil && filter.Type == gatewayv1a2.GRPCRouteFilterExtensionRef {
   130  		errs = append(errs, field.Required(path, "filter.ExtensionRef must be specified for ExtensionRef GRPCRouteFilter.Type"))
   131  	}
   132  	if filter.RequestHeaderModifier != nil && filter.Type != gatewayv1a2.GRPCRouteFilterRequestHeaderModifier {
   133  		errs = append(errs, field.Invalid(path, filter.RequestHeaderModifier, "must be nil if the GRPCRouteFilter.Type is not RequestHeaderModifier"))
   134  	}
   135  	if filter.RequestHeaderModifier == nil && filter.Type == gatewayv1a2.GRPCRouteFilterRequestHeaderModifier {
   136  		errs = append(errs, field.Required(path, "filter.RequestHeaderModifier must be specified for RequestHeaderModifier GRPCRouteFilter.Type"))
   137  	}
   138  	if filter.ResponseHeaderModifier != nil && filter.Type != gatewayv1a2.GRPCRouteFilterResponseHeaderModifier {
   139  		errs = append(errs, field.Invalid(path, filter.ResponseHeaderModifier, "must be nil if the GRPCRouteFilter.Type is not ResponseHeaderModifier"))
   140  	}
   141  	if filter.ResponseHeaderModifier == nil && filter.Type == gatewayv1a2.GRPCRouteFilterResponseHeaderModifier {
   142  		errs = append(errs, field.Required(path, "filter.ResponseHeaderModifier must be specified for ResponseHeaderModifier GRPCRouteFilter.Type"))
   143  	}
   144  	if filter.RequestMirror != nil && filter.Type != gatewayv1a2.GRPCRouteFilterRequestMirror {
   145  		errs = append(errs, field.Invalid(path, filter.RequestMirror, "must be nil if the GRPCRouteFilter.Type is not RequestMirror"))
   146  	}
   147  	if filter.RequestMirror == nil && filter.Type == gatewayv1a2.GRPCRouteFilterRequestMirror {
   148  		errs = append(errs, field.Required(path, "filter.RequestMirror must be specified for RequestMirror GRPCRouteFilter.Type"))
   149  	}
   150  	return errs
   151  }
   152  
   153  // validateGRPCRouteFilters validates that a list of core and extended filters
   154  // is used at most once and that the filter type matches its value
   155  func validateGRPCRouteFilters(filters []gatewayv1a2.GRPCRouteFilter, path *field.Path) field.ErrorList {
   156  	var errs field.ErrorList
   157  	counts := map[gatewayv1a2.GRPCRouteFilterType]int{}
   158  
   159  	for i, filter := range filters {
   160  		counts[filter.Type]++
   161  		if filter.RequestHeaderModifier != nil {
   162  			errs = append(errs, validateGRPCHeaderModifier(*filter.RequestHeaderModifier, path.Index(i).Child("requestHeaderModifier"))...)
   163  		}
   164  		if filter.ResponseHeaderModifier != nil {
   165  			errs = append(errs, validateGRPCHeaderModifier(*filter.ResponseHeaderModifier, path.Index(i).Child("responseHeaderModifier"))...)
   166  		}
   167  		errs = append(errs, validateGRPCRouteFilterType(filter, path.Index(i))...)
   168  	}
   169  	// repeatableGRPCRouteFilters filters can be used more than once
   170  	for _, key := range repeatableGRPCRouteFilters {
   171  		delete(counts, key)
   172  	}
   173  
   174  	for filterType, count := range counts {
   175  		if count > 1 {
   176  			errs = append(errs, field.Invalid(path, filterType, "cannot be used multiple times in the same rule"))
   177  		}
   178  	}
   179  	return errs
   180  }
   181  
   182  // validateGRPCHeaderModifier ensures that multiple actions cannot be set for
   183  // the same header.
   184  func validateGRPCHeaderModifier(filter gatewayv1a2.HTTPHeaderFilter, path *field.Path) field.ErrorList {
   185  	var errs field.ErrorList
   186  	singleAction := make(map[string]bool)
   187  	for i, action := range filter.Add {
   188  		if needsErr, ok := singleAction[strings.ToLower(string(action.Name))]; ok {
   189  			if needsErr {
   190  				errs = append(errs, field.Invalid(path.Child("add"), filter.Add[i], "cannot specify multiple actions for header"))
   191  			}
   192  			singleAction[strings.ToLower(string(action.Name))] = false
   193  		} else {
   194  			singleAction[strings.ToLower(string(action.Name))] = true
   195  		}
   196  	}
   197  	for i, action := range filter.Set {
   198  		if needsErr, ok := singleAction[strings.ToLower(string(action.Name))]; ok {
   199  			if needsErr {
   200  				errs = append(errs, field.Invalid(path.Child("set"), filter.Set[i], "cannot specify multiple actions for header"))
   201  			}
   202  			singleAction[strings.ToLower(string(action.Name))] = false
   203  		} else {
   204  			singleAction[strings.ToLower(string(action.Name))] = true
   205  		}
   206  	}
   207  	for i, name := range filter.Remove {
   208  		if needsErr, ok := singleAction[strings.ToLower(name)]; ok {
   209  			if needsErr {
   210  				errs = append(errs, field.Invalid(path.Child("remove"), filter.Remove[i], "cannot specify multiple actions for header"))
   211  			}
   212  			singleAction[strings.ToLower(name)] = false
   213  		} else {
   214  			singleAction[strings.ToLower(name)] = true
   215  		}
   216  	}
   217  	return errs
   218  }
   219  

View as plain text