1
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
32
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
44
45
46 func ValidateGRPCRoute(route *gatewayv1a2.GRPCRoute) field.ErrorList {
47 return validateGRPCRouteSpec(&route.Spec, field.NewPath("spec"))
48 }
49
50
51
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
60
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
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
82
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
102
103
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
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
123
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
154
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
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
183
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