-
Notifications
You must be signed in to change notification settings - Fork 479
/
istio_validation.go
391 lines (359 loc) · 11.9 KB
/
istio_validation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
package models
import (
"encoding/json"
)
// NamespaceValidations represents a set of IstioValidations grouped by namespace
type NamespaceValidations map[string]IstioValidations
// IstioValidationKey is the key value composed of an Istio ObjectType and Name.
type IstioValidationKey struct {
ObjectType string `json:"objectType"`
Name string `json:"name"`
Namespace string `json:"namespace"`
}
// IstioValidationSummary represents the number of errors/warnings of a set of Istio Validations.
type IstioValidationSummary struct {
// Number of validations with error severity
// required: true
// example: 2
Errors int `json:"errors"`
// Number of Istio Objects analyzed
// required: true
// example: 6
ObjectCount int `json:"objectCount"`
// Number of validations with warning severity
// required: true
// example: 4
Warnings int `json:"warnings"`
}
// IstioValidations represents a set of IstioValidation grouped by IstioValidationKey.
type IstioValidations map[IstioValidationKey]*IstioValidation
// IstioValidation represents a list of checks associated to an Istio object.
// swagger:model
type IstioValidation struct {
// Name of the object itself
// required: true
// example: reviews
Name string `json:"name"`
// Type of the object
// required: true
// example: virtualservice
ObjectType string `json:"objectType"`
// Represents validity of the object: in case of warning, validity remains as true
// required: true
// example: false
Valid bool `json:"valid"`
// Array of checks. It might be empty.
Checks []*IstioCheck `json:"checks"`
// Related objects (only validation errors)
References []IstioValidationKey `json:"references"`
}
// IstioCheck represents an individual check.
// swagger:model
type IstioCheck struct {
// Description of the check
// required: true
// example: Weight sum should be 100
Message string `json:"message"`
// Indicates the level of importance: error or warning
// required: true
// example: error
Severity SeverityLevel `json:"severity"`
// String that describes where in the yaml file is the check located
// example: spec/http[0]/route
Path string `json:"path"`
}
type SeverityLevel string
const (
ErrorSeverity SeverityLevel = "error"
WarningSeverity SeverityLevel = "warning"
Unknown SeverityLevel = "unknown"
)
var ObjectTypeSingular = map[string]string{
"gateways": "gateway",
"virtualservices": "virtualservice",
"destinationrules": "destinationrule",
"serviceentries": "serviceentry",
"rules": "rule",
"quotaspecs": "quotaspec",
"quotaspecbindings": "quotaspecbinding",
"meshpolicies": "meshpolicy",
"servicemeshpolicies": "servicemeshpolicy",
"policies": "policy",
"serviceroles": "servicerole",
"servicerolebindings": "servicerolebinding",
"clusterrbacconfigs": "clusterrbacconfig",
"authorizationpolicies": "authorizationpolicy",
"sidecars": "sidecar",
}
var checkDescriptors = map[string]IstioCheck{
"authorizationpolicy.source.namespacenotfound": {
Message: "KIA0101 Namespace not found for this rule",
Severity: WarningSeverity,
},
"authorizationpolicy.to.wrongmethod": {
Message: "KIA0102 Only HTTP methods and fully-qualified gRPC names are allowed",
Severity: WarningSeverity,
},
"authorizationpolicy.selector.workloadnotfound": {
Message: "KIA0103 No matching workload found for authorization policy selector in this namespace",
Severity: WarningSeverity,
},
"authorizationpolicy.nodest.matchingregistry": {
Message: "KIA0104 This host has no matching entry in the service registry",
Severity: ErrorSeverity,
},
"destinationrules.multimatch": {
Message: "KIA0201 More than one DestinationRules for the same host subset combination",
Severity: WarningSeverity,
},
"destinationrules.nodest.matchingregistry": {
Message: "KIA0202 This host has no matching entry in the service registry (service, workload or service entries)",
Severity: ErrorSeverity,
},
"destinationrules.nodest.subsetlabels": {
Message: "KIA0203 This subset's labels are not found in any matching host",
Severity: ErrorSeverity,
},
"destinationrules.trafficpolicy.notlssettings": {
Message: "KIA0204 mTLS settings of a non-local Destination Rule are overridden",
Severity: WarningSeverity,
},
"destinationrules.mtls.meshpolicymissing": {
Message: "KIA0205 MeshPolicy enabling mTLS is missing",
Severity: ErrorSeverity,
},
"destinationrules.mtls.nspolicymissing": {
Message: "KIA0206 Policy enabling namespace-wide mTLS is missing",
Severity: ErrorSeverity,
},
"destinationrules.mtls.policymtlsenabled": {
Message: "KIA0207 Policy with TLS strict mode found, it should be permissive",
Severity: ErrorSeverity,
},
"destinationrules.mtls.meshpolicymtlsenabled": {
Message: "KIA0208 MeshPolicy enabling mTLS found, permissive policy is needed",
Severity: ErrorSeverity,
},
"destinationrules.mtls.servicemeshpolicymissing": {
Message: "KIA0209 ServiceMeshPolicy enabling mTLS is missing",
Severity: ErrorSeverity,
},
"destinationrules.mtls.servicemeshpolicymtlsenabled": {
Message: "KIA0210 ServiceMeshPolicy enabling mTLS found, permissive policy is needed",
Severity: ErrorSeverity,
},
"gateways.multimatch": {
Message: "KIA0301 More than one Gateway for the same host port combination",
Severity: WarningSeverity,
},
"gateways.selector": {
Message: "KIA0302 No matching workload found for gateway selector in this namespace",
Severity: WarningSeverity,
},
"meshpolicies.mtls.destinationrulemissing": {
Message: "KIA0401 Mesh-wide Destination Rule enabling mTLS is missing",
Severity: ErrorSeverity,
},
"policies.mtls.destinationrulemissing": {
Message: "KIA0501 Destination Rule enabling namespace-wide mTLS is missing",
Severity: ErrorSeverity,
},
"port.name.mismatch": {
Message: "KIA0601 Port name must follow <protocol>[-suffix] form",
Severity: ErrorSeverity,
},
"service.deployment.port.mismatch": {
Message: "KIA0701 Deployment exposing same port as Service not found",
Severity: WarningSeverity,
},
"servicemeshpolicies.mtls.destinationrulemissing": {
Message: "KIA0801 Mesh-wide Destination Rule enabling mTLS is missing",
Severity: ErrorSeverity,
},
"servicerole.invalid.services": {
Message: "KIA0901 Unable to find all the defined services",
Severity: ErrorSeverity,
},
"servicerole.invalid.namespace": {
Message: "KIA0902 ServiceRole can only point to current namespace",
Severity: ErrorSeverity,
},
"servicerolebinding.invalid.role": {
Message: "KIA0903 ServiceRole does not exists in this namespace",
Severity: ErrorSeverity,
},
"sidecar.selector.workloadnotfound": {
Message: "KIA1001 No matching workload found for authorization policy selector in this namespace",
Severity: WarningSeverity,
},
"sidecar.multimatch": {
Message: "KIA1002 More than one selector-less Sidecar in the same namespace",
Severity: ErrorSeverity,
},
"sidecar.egress.invalidhostformat": {
Message: "KIA1003 Invalid host format. 'namespace/dnsName' format expected",
Severity: ErrorSeverity,
},
"sidecar.egress.servicenotfound": {
Message: "KIA1004 This host has no matching entry in the service registry",
Severity: WarningSeverity,
},
"virtualservices.nohost.hostnotfound": {
Message: "KIA1101 DestinationWeight on route doesn't have a valid service (host not found)",
Severity: ErrorSeverity,
},
"virtualservices.nogateway": {
Message: "KIA1102 VirtualService is pointing to a non-existent gateway",
Severity: ErrorSeverity,
},
"virtualservices.nohost.invalidprotocol": {
Message: "KIA1103 VirtualService doesn't define any valid route protocol",
Severity: ErrorSeverity,
},
"virtualservices.route.singleweight": {
Message: "KIA1104 The weight is assumed to be 100 because there is only one route destination",
Severity: WarningSeverity,
},
"virtualservices.route.repeatedsubset": {
Message: "KIA1105 This subset is already referenced in another route destination",
Severity: WarningSeverity,
},
"virtualservices.singlehost": {
Message: "KIA1106 More than one Virtual Service for same host",
Severity: WarningSeverity,
},
"virtualservices.subsetpresent.subsetnotfound": {
Message: "KIA1107 Subset not found",
Severity: WarningSeverity,
},
"virtualservices.subsetpresent.destinationmandatory": {
Message: "KIA1108 Destination field is mandatory",
Severity: ErrorSeverity,
},
"validation.unable.cross-namespace": {
Message: "KIA0001 Unable to verify the validity, cross-namespace validation is not supported for this field",
Severity: Unknown,
},
}
func Build(checkId string, path string) IstioCheck {
check := checkDescriptors[checkId]
check.Path = path
return check
}
func BuildKey(objectType, name, namespace string) IstioValidationKey {
return IstioValidationKey{ObjectType: objectType, Namespace: namespace, Name: name}
}
func CheckMessage(checkId string) string {
return checkDescriptors[checkId].Message
}
func (iv IstioValidations) FilterBySingleType(objectType, name string) IstioValidations {
fiv := IstioValidations{}
for k, v := range iv {
// We don't want to filter other types
if k.ObjectType != objectType {
fiv[k] = v
} else {
// But for this exact type we're strict
if k.Name == name {
fiv[k] = v
}
}
}
return fiv
}
func (iv IstioValidations) FilterByKey(objectType, name string) IstioValidations {
fiv := IstioValidations{}
for k, v := range iv {
if k.Name == name && k.ObjectType == objectType {
fiv[k] = v
}
}
return fiv
}
// FilterByTypes takes an input as ObjectTypes, transforms to singular types and filters the validations
func (iv IstioValidations) FilterByTypes(objectTypes []string) IstioValidations {
types := make(map[string]bool, len(objectTypes))
for _, objectType := range objectTypes {
types[ObjectTypeSingular[objectType]] = true
}
fiv := IstioValidations{}
for k, v := range iv {
if _, found := types[k.ObjectType]; found {
fiv[k] = v
}
}
return fiv
}
func (iv IstioValidations) MergeValidations(validations IstioValidations) IstioValidations {
for key, validation := range validations {
v, ok := iv[key]
if !ok {
iv[key] = validation
} else {
AddUnique:
for _, toAdd := range validation.Checks {
for _, existing := range v.Checks {
if toAdd.Path == existing.Path &&
toAdd.Severity == existing.Severity &&
toAdd.Message == existing.Message {
continue AddUnique
}
}
v.Checks = append(v.Checks, toAdd)
}
v.Valid = v.Valid && validation.Valid
AddUniqueReference:
for _, toAdd := range validation.References {
for _, existing := range v.References {
if toAdd == existing {
continue AddUniqueReference
}
}
v.References = append(v.References, toAdd)
}
}
}
return iv
}
func (iv IstioValidations) MergeReferences(validations IstioValidations) IstioValidations {
for _, currentValidations := range iv {
if currentValidations.References == nil {
currentValidations.References = make([]IstioValidationKey, 0, len(validations))
}
for k := range validations {
currentValidations.References = append(currentValidations.References, k)
}
}
return iv
}
func (iv IstioValidations) SummarizeValidation(ns string) IstioValidationSummary {
ivs := IstioValidationSummary{}
for k, v := range iv {
if k.Namespace == ns {
ivs.mergeSummaries(v.Checks)
}
}
return ivs
}
func (summary *IstioValidationSummary) mergeSummaries(cs []*IstioCheck) {
for _, c := range cs {
if c.Severity == ErrorSeverity {
summary.Errors += 1
} else if c.Severity == WarningSeverity {
summary.Warnings += 1
}
}
summary.ObjectCount += 1
}
// MarshalJSON implements the json.Marshaler interface.
func (iv IstioValidations) MarshalJSON() ([]byte, error) {
out := make(map[string]map[string]*IstioValidation)
for k, v := range iv {
_, ok := out[k.ObjectType]
if !ok {
out[k.ObjectType] = make(map[string]*IstioValidation)
}
out[k.ObjectType][k.Name] = v
}
return json.Marshal(out)
}