sigs.k8s.io/gateway-api@v1.0.0/apis/v1alpha2/validation/grpcroute.go (about) 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 }