@@ -17,11 +17,22 @@ limitations under the License.
17
17
package validation
18
18
19
19
import (
20
+ "net/http"
21
+ "strings"
22
+
20
23
"k8s.io/apimachinery/pkg/util/validation/field"
21
24
22
25
gatewayv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
23
26
)
24
27
28
+ var (
29
+ // repeatableGRPCRouteFilters are filter types that are allowed to be
30
+ // repeated multiple times in a rule.
31
+ repeatableGRPCRouteFilters = []gatewayv1a2.GRPCRouteFilterType {
32
+ gatewayv1a2 .GRPCRouteFilterExtensionRef ,
33
+ }
34
+ )
35
+
25
36
// ValidateGRPCRoute validates GRPCRoute according to the Gateway API specification.
26
37
// For additional details of the GRPCRoute spec, refer to:
27
38
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute
@@ -44,6 +55,10 @@ func validateGRPCRouteRules(rules []gatewayv1a2.GRPCRouteRule, path *field.Path)
44
55
var errs field.ErrorList
45
56
for i , rule := range rules {
46
57
errs = append (errs , validateRuleMatches (rule .Matches , path .Index (i ).Child ("matches" ))... )
58
+ errs = append (errs , validateGRPCRouteFilters (rule .Filters , path .Index (i ).Child (("filters" )))... )
59
+ for j , backendRef := range rule .BackendRefs {
60
+ errs = append (errs , validateGRPCRouteFilters (backendRef .Filters , path .Child ("rules" ).Index (i ).Child ("backendRefs" ).Index (j ))... )
61
+ }
47
62
}
48
63
return errs
49
64
}
@@ -54,8 +69,129 @@ func validateRuleMatches(matches []gatewayv1a2.GRPCRouteMatch, path *field.Path)
54
69
var errs field.ErrorList
55
70
for i , m := range matches {
56
71
if m .Method != nil && m .Method .Service == nil && m .Method .Method == nil {
57
- errs = append (errs , field .Required (path .Index (i ).Child ("methods" ), "one or both of `service` or `method` must be specified" ))
58
- return errs
72
+ errs = append (errs , field .Required (path .Index (i ).Child ("method" ), "one or both of `service` or `method` must be specified" ))
73
+ }
74
+ if m .Headers != nil {
75
+ errs = append (errs , validateGRPCHeaderMatches (m .Headers , path .Index (i ).Child ("headers" ))... )
76
+ }
77
+ }
78
+ return errs
79
+ }
80
+
81
+ // validateGRPCHeaderMatches validates that no header name is matched more than
82
+ // once (case-insensitive), and that at least one of service or method was
83
+ // provided.
84
+ func validateGRPCHeaderMatches (matches []gatewayv1a2.GRPCHeaderMatch , path * field.Path ) field.ErrorList {
85
+ var errs field.ErrorList
86
+ counts := map [string ]int {}
87
+
88
+ for _ , match := range matches {
89
+ // Header names are case-insensitive.
90
+ counts [strings .ToLower (string (match .Name ))]++
91
+ }
92
+
93
+ for name , count := range counts {
94
+ if count > 1 {
95
+ errs = append (errs , field .Invalid (path , http .CanonicalHeaderKey (name ), "cannot match the same header multiple times in the same rule" ))
96
+ }
97
+ }
98
+
99
+ return errs
100
+ }
101
+
102
+ // validateGRPCRouteFilterType validates that only the expected fields are
103
+ // set for the specified filter type.
104
+ func validateGRPCRouteFilterType (filter gatewayv1a2.GRPCRouteFilter , path * field.Path ) field.ErrorList {
105
+ var errs field.ErrorList
106
+ if filter .ExtensionRef != nil && filter .Type != gatewayv1a2 .GRPCRouteFilterExtensionRef {
107
+ errs = append (errs , field .Invalid (path , filter .ExtensionRef , "must be nil if the GRPCRouteFilter.Type is not ExtensionRef" ))
108
+ }
109
+ if filter .ExtensionRef == nil && filter .Type == gatewayv1a2 .GRPCRouteFilterExtensionRef {
110
+ errs = append (errs , field .Required (path , "filter.ExtensionRef must be specified for ExtensionRef GRPCRouteFilter.Type" ))
111
+ }
112
+ if filter .RequestHeaderModifier != nil && filter .Type != gatewayv1a2 .GRPCRouteFilterRequestHeaderModifier {
113
+ errs = append (errs , field .Invalid (path , filter .RequestHeaderModifier , "must be nil if the GRPCRouteFilter.Type is not RequestHeaderModifier" ))
114
+ }
115
+ if filter .RequestHeaderModifier == nil && filter .Type == gatewayv1a2 .GRPCRouteFilterRequestHeaderModifier {
116
+ errs = append (errs , field .Required (path , "filter.RequestHeaderModifier must be specified for RequestHeaderModifier GRPCRouteFilter.Type" ))
117
+ }
118
+ if filter .ResponseHeaderModifier != nil && filter .Type != gatewayv1a2 .GRPCRouteFilterResponseHeaderModifier {
119
+ errs = append (errs , field .Invalid (path , filter .ResponseHeaderModifier , "must be nil if the GRPCRouteFilter.Type is not ResponseHeaderModifier" ))
120
+ }
121
+ if filter .ResponseHeaderModifier == nil && filter .Type == gatewayv1a2 .GRPCRouteFilterResponseHeaderModifier {
122
+ errs = append (errs , field .Required (path , "filter.ResponseHeaderModifier must be specified for ResponseHeaderModifier GRPCRouteFilter.Type" ))
123
+ }
124
+ if filter .RequestMirror != nil && filter .Type != gatewayv1a2 .GRPCRouteFilterRequestMirror {
125
+ errs = append (errs , field .Invalid (path , filter .RequestMirror , "must be nil if the GRPCRouteFilter.Type is not RequestMirror" ))
126
+ }
127
+ if filter .RequestMirror == nil && filter .Type == gatewayv1a2 .GRPCRouteFilterRequestMirror {
128
+ errs = append (errs , field .Required (path , "filter.RequestMirror must be specified for RequestMirror GRPCRouteFilter.Type" ))
129
+ }
130
+ return errs
131
+ }
132
+
133
+ // validateGRPCRouteFilters validates that a list of core and extended filters
134
+ // is used at most once and that the filter type matches its value
135
+ func validateGRPCRouteFilters (filters []gatewayv1a2.GRPCRouteFilter , path * field.Path ) field.ErrorList {
136
+ var errs field.ErrorList
137
+ counts := map [gatewayv1a2.GRPCRouteFilterType ]int {}
138
+
139
+ for i , filter := range filters {
140
+ counts [filter .Type ]++
141
+ if filter .RequestHeaderModifier != nil {
142
+ errs = append (errs , validateGRPCHeaderModifier (* filter .RequestHeaderModifier , path .Index (i ).Child ("requestHeaderModifier" ))... )
143
+ }
144
+ if filter .ResponseHeaderModifier != nil {
145
+ errs = append (errs , validateGRPCHeaderModifier (* filter .ResponseHeaderModifier , path .Index (i ).Child ("responseHeaderModifier" ))... )
146
+ }
147
+ errs = append (errs , validateGRPCRouteFilterType (filter , path .Index (i ))... )
148
+ }
149
+ // custom filters don't have any validation
150
+ for _ , key := range repeatableGRPCRouteFilters {
151
+ delete (counts , key )
152
+ }
153
+
154
+ for filterType , count := range counts {
155
+ if count > 1 {
156
+ errs = append (errs , field .Invalid (path , filterType , "cannot be used multiple times in the same rule" ))
157
+ }
158
+ }
159
+ return errs
160
+ }
161
+
162
+ // validateGRPCHeaderModifier ensures that multiple actions cannot be set for
163
+ // the same header.
164
+ func validateGRPCHeaderModifier (filter gatewayv1a2.HTTPHeaderFilter , path * field.Path ) field.ErrorList {
165
+ var errs field.ErrorList
166
+ singleAction := make (map [string ]bool )
167
+ for i , action := range filter .Add {
168
+ if needsErr , ok := singleAction [strings .ToLower (string (action .Name ))]; ok {
169
+ if needsErr {
170
+ errs = append (errs , field .Invalid (path .Child ("add" ), filter .Add [i ], "cannot specify multiple actions for header" ))
171
+ }
172
+ singleAction [strings .ToLower (string (action .Name ))] = false
173
+ } else {
174
+ singleAction [strings .ToLower (string (action .Name ))] = true
175
+ }
176
+ }
177
+ for i , action := range filter .Set {
178
+ if needsErr , ok := singleAction [strings .ToLower (string (action .Name ))]; ok {
179
+ if needsErr {
180
+ errs = append (errs , field .Invalid (path .Child ("set" ), filter .Set [i ], "cannot specify multiple actions for header" ))
181
+ }
182
+ singleAction [strings .ToLower (string (action .Name ))] = false
183
+ } else {
184
+ singleAction [strings .ToLower (string (action .Name ))] = true
185
+ }
186
+ }
187
+ for i , action := range filter .Remove {
188
+ if needsErr , ok := singleAction [strings .ToLower (action )]; ok {
189
+ if needsErr {
190
+ errs = append (errs , field .Invalid (path .Child ("remove" ), filter .Remove [i ], "cannot specify multiple actions for header" ))
191
+ }
192
+ singleAction [strings .ToLower (action )] = false
193
+ } else {
194
+ singleAction [strings .ToLower (action )] = true
59
195
}
60
196
}
61
197
return errs
0 commit comments