/
acl.go
475 lines (435 loc) · 19.3 KB
/
acl.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
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
package data_messages
import (
"fmt"
log "github.com/sirupsen/logrus"
types "github.com/nttdots/go-dots/dots_common/types/data"
"github.com/nttdots/go-dots/dots_server/models"
"github.com/nttdots/go-dots/dots_common/messages"
)
type ACLsRequest struct {
ACLs types.ACLs `json:"ietf-dots-data-channel:acls"`
}
type ACLsResponse struct {
ACLs types.ACLs `json:"ietf-dots-data-channel:acls"`
}
// The data channel acl validator interface
type aclValidator interface {
ValidateWithName(*ACLsRequest, *models.Customer, string) (bool, string)
ValidateACL(*ACLsRequest, *models.Customer) (bool, string)
ValidatePendingLifetime(*types.ACL) (bool, string)
ValidateType(*types.ACL) (bool, string)
ValidateACEs(models.AddressRange,*types.ACL) (bool, string)
ValidateActions(string, *types.ACE) (bool, string)
ValidateStatistics(string, *types.ACE) (bool, string)
ValidateL3(string, *types.ActivationType, *types.ACLType, models.AddressRange, *types.Matches) (bool, string)
ValidateL4(string, *types.Matches) (bool, string)
ValidateExistIPv4OrIPv6(string, *types.Matches) (bool, string)
ValidateActivationType(string, *types.ActivationType, *types.Matches) (bool, string)
ValidateMatchType(string, *types.ACLType, *types.Matches) (bool, string)
ValidateDestinationIPv4(string, models.AddressRange, *types.Matches) (bool, string)
ValidateDestinationIPv6(string, models.AddressRange, *types.Matches) (bool, string)
ValidateProtocol(string, *types.Matches) (bool, string)
ValidateUnsupportedAttributes(string, *types.Matches) (bool, string)
ValidateExistTCPOrUDPOrICMP(string, *types.Matches) (bool, string)
ValidateTCP(string, *types.Matches) (bool, string)
ValidateUDP(string, *types.Matches) (bool, string)
ValidatePort(*types.PortRangeOrOperator) (bool)
}
// Return mitigation scope validator by input blocker type (goBgpScopeValidator or goAristaScopeValidator)
func GetAclValidator(blockerType string) (aclValidator) {
switch (blockerType) {
case models.BLOCKER_TYPE_GoBGP_FLOWSPEC:
flowspecAclValidator.blockerType = blockerType
return flowspecAclValidator
case models.BLOCKER_TYPE_GO_ARISTA:
aristaAclValidator.blockerType = blockerType
return aristaAclValidator
default:
log.Warnf("Unknown blocker type: %+v", blockerType)
}
return nil
}
// implements aliasValidatorBase
type aclValidatorBase struct {
blockerType string
}
func (v *aclValidatorBase) ValidatePort(p *types.PortRangeOrOperator) bool {
if p.LowerPort != nil {
if p.Operator != nil {
log.Error("Both 'lower-port' and 'operator' specified.")
return false
}
if p.Port != nil {
log.Error("Both 'lower-port' and 'port' specified.")
return false
}
if p.UpperPort != nil {
if *p.UpperPort < *p.LowerPort {
log.WithField("lower-port", *p.LowerPort).WithField("upper-port", *p.UpperPort).Error( "'upper-port' must be greater than or equal to 'lower-port'.")
return false
}
}
} else {
if p.Port == nil {
log.Error("Both 'lower-port' and 'port' unspecified.")
return false
}
if p.UpperPort != nil {
log.Error("Both 'port' and 'upper-port' specified.")
return false
}
}
return true
}
func (v *aclValidatorBase) ValidateACL(r *ACLsRequest, customer *models.Customer) (bool, string) {
errorMsg := ""
if len(r.ACLs.ACL) <= 0 {
log.WithField("len", len(r.ACLs.ACL)).Error("'acl' is not exist.")
errorMsg = fmt.Sprintf("Body Data Error : 'acl' is not exist")
return false, errorMsg
}
var aclNameList []string
for _,acl := range r.ACLs.ACL {
if acl.Name == "" {
log.Error("Missing required acl 'name' attribute.")
errorMsg = fmt.Sprintf("Body Data Error : Missing acl 'name'")
return false, errorMsg
}
if messages.Contains(aclNameList, acl.Name) {
log.Errorf("Duplicate acl 'name' = %+v", acl.Name)
errorMsg = fmt.Sprintf("Body Data Error : Duplicate acl 'name'(%v)", acl.Name)
return false, errorMsg
}
aclNameList = append(aclNameList, acl.Name)
isValid := false
validator := GetAclValidator(v.blockerType)
if isValid, errorMsg = validator.ValidatePendingLifetime(&acl); !isValid { return isValid, errorMsg }
if isValid, errorMsg = validator.ValidateType(&acl); !isValid { return isValid, errorMsg }
if isValid, errorMsg = validator.ValidateACEs(customer.CustomerNetworkInformation.AddressRange, &acl); !isValid { return isValid, errorMsg }
}
return true, ""
}
func (v *aclValidatorBase) ValidateWithName(r *ACLsRequest, customer *models.Customer, name string) (bool, string) {
if len(r.ACLs.ACL) > 1 {
log.WithField("len", len(r.ACLs.ACL)).Error("multiple 'acl'.")
errorMsg := fmt.Sprintf("Body Data Error : Have multiple 'acl' (%d)", len(r.ACLs.ACL))
return false, errorMsg
}
acl := r.ACLs.ACL[0]
if acl.Name != name {
log.WithField("name(req)", acl.Name).WithField("name(URI)", name).Error("request/URI name mismatch.")
errorMsg := fmt.Sprintf("Request/URI name mismatch : (%v) / (%v)", acl.Name, name)
return false, errorMsg
}
bValid, errorMsg := v.ValidateACL(r, customer)
if !bValid {
return false, errorMsg
}
return true, ""
}
/**
* Check if the acl pending lifetime is present
* parameters:
* acl the request acl
* return: bool
* true lifetime value is not present
* false lifetime value is present
*/
func (v *aclValidatorBase) ValidatePendingLifetime(acl *types.ACL) (bool, string) {
pendingLifetime := acl.PendingLifetime
if pendingLifetime != nil {
log.WithField("pending-lifetime", pendingLifetime).Errorf("'pending-lifetime' found at acl 'name'=%+v.", acl.Name)
errorMsg := fmt.Sprintf("Body Data Error : Found NoConfig Attribute 'pending-lifetime' (%v) at acl 'name'(%v)", pendingLifetime, acl.Name)
return false, errorMsg
}
return true, ""
}
/**
* Check type of acl is ipv4 or ipv6
* parameters:
* acl the request acl
* return: bool
* true: type of acl is ipv4 or ipv6
* false: type of acl is not ipv4 and ipv6
*/
func (v *aclValidatorBase) ValidateType(acl *types.ACL) (bool, string) {
aclType := acl.Type
if aclType != nil && *aclType != types.ACLType_IPv4ACLType && *aclType != types.ACLType_IPv6ACLType {
log.WithField("type", *aclType).Errorf("'type' must be 'ipv4-acl-type' or 'ipv6-acl-type' at acl 'name'=%+v.", acl.Name)
errorMsg := fmt.Sprintf("Body Data Error : 'type' must be 'ipv4-acl-type' or 'ipv6-acl-type'. Not support (%v) at acl 'name'(%v)", *aclType, acl.Name)
return false, errorMsg
}
return true, ""
}
/**
* Check validate for aces of acl
*/
func (v *aclValidatorBase) ValidateACEs(addressRange models.AddressRange, acl *types.ACL) (bool, string) {
isValid := false
errorMsg := ""
for _, ace := range acl.ACEs.ACE {
if isValid, errorMsg = v.ValidateActions(acl.Name, &ace); !isValid { return isValid, errorMsg }
if isValid, errorMsg = v.ValidateStatistics(acl.Name, &ace); !isValid { return isValid, errorMsg }
if ace.Matches != nil {
matches := ace.Matches
if isValid, errorMsg = v.ValidateL3(acl.Name, acl.ActivationType, acl.Type, addressRange, matches); !isValid { return isValid, errorMsg }
if isValid, errorMsg = v.ValidateL4(acl.Name, matches); !isValid { return isValid, errorMsg }
}
}
return true, ""
}
/**
* Check if action is present
* parameters:
* name: the name of acl request
* ace: the ace of acl request
* return: bool
* true: action value is present
* false: action value is not present
*/
func (v *aclValidatorBase) ValidateActions(name string, ace *types.ACE) (bool, string) {
action := ace.Actions
if action == nil || (action.Forwarding == nil && action.RateLimit == nil) {
log.Errorf("Missing required acl 'actions' attribute at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : Missing acl 'actions' at acl 'name'(%v)", name)
return false, errorMsg
}
return true, ""
}
/**
* Check if statistics is present
* parameters:
* name: the name of acl request
* ace: the ace of acl request
* return: bool
* true: statistics value is not present
* false: statistics value is present
*/
func (v *aclValidatorBase) ValidateStatistics(name string, ace *types.ACE) (bool, string) {
statistics := ace.Statistics
if statistics != nil {
log.WithField("statistics", *ace.Statistics).Errorf("'statistics' found at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : Found NoConfig Attribute 'statistics' (%v) at acl 'name'(%v)", statistics, name)
return false, errorMsg
}
return true, ""
}
/**
* Check validate for layer 3
*/
func (v *aclValidatorBase) ValidateL3(name string, activationType *types.ActivationType, aclType *types.ACLType, addressRange models.AddressRange, matches *types.Matches) (bool, string) {
isValid := false
errorMsg := ""
validator := GetAclValidator(v.blockerType)
if isValid, errorMsg = validator.ValidateExistIPv4OrIPv6(name, matches); !isValid { return isValid, errorMsg }
if isValid, errorMsg = validator.ValidateActivationType(name, activationType, matches); !isValid { return isValid, errorMsg }
if isValid, errorMsg = validator.ValidateMatchType(name, aclType,matches); !isValid { return isValid, errorMsg }
if isValid, errorMsg = validator.ValidateDestinationIPv4(name, addressRange, matches); !isValid { return isValid, errorMsg }
if isValid, errorMsg = validator.ValidateDestinationIPv6(name, addressRange, matches); !isValid { return isValid, errorMsg }
if isValid, errorMsg = validator.ValidateProtocol(name, matches); !isValid { return isValid, errorMsg }
if isValid, errorMsg = validator.ValidateUnsupportedAttributes(name, matches); !isValid { return isValid, errorMsg }
return true, ""
}
/**
* Check validate for layer 4
*/
func (v *aclValidatorBase) ValidateL4(name string, matches *types.Matches) (bool, string) {
isValid := false
errorMsg := ""
if isValid, errorMsg = v.ValidateExistTCPOrUDPOrICMP(name, matches); !isValid { return isValid, errorMsg }
if isValid, errorMsg = v.ValidateTCP(name, matches); !isValid { return isValid, errorMsg }
if isValid, errorMsg = v.ValidateUDP(name, matches); !isValid { return isValid, errorMsg }
return true, ""
}
/**
* Check if ipv4/ipv6 is present
* parameters:
* name: the name of acl request
* matches: the matches of ace in acl request
* return: bool
* true:
* - ipv4/ipv6 is present
* - ipv4 and ipv6 are not present
* false: ipv4 and ipv6 are present
*/
func (v *aclValidatorBase) ValidateExistIPv4OrIPv6(name string, matches *types.Matches) (bool, string) {
if matches.IPv4 != nil && matches.IPv6 != nil {
log.WithField("ipv4", *matches.IPv4).WithField("ipv6", *matches.IPv6).Errorf("Only one of 'ipv4' and 'ipv6' matches is allowed at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : Only one 'ipv4' or 'ipv6' of 'match' is allowed at acl 'name'(%v)", name)
return false, errorMsg
}
return true, ""
}
/**
* Check if activationType = 'immediate', destination of ipv4/ipv6 is present
* parameters:
* name: the name of acl request
* activationType: the activationType of acl request
* matches: the matches of ace in acl request
* return: bool
* true: destination of ipv4/ipv6 is present
* false: destination of ipv4/ipv6 is not present
*/
func (v *aclValidatorBase) ValidateActivationType(name string, activationType *types.ActivationType, matches *types.Matches) (bool, string) {
if activationType != nil && *activationType == types.ActivationType_Immediate {
if matches.IPv4 != nil && matches.IPv4.DestinationIPv4Network == nil {
log.Errorf("Missing 'destination-ipv4-network' value when ’activation-type’ is ’immediate’ at acl 'name'=%+v", name)
errorMsg := fmt.Sprintf("Body Data Error : 'destination-ipv4-network' value is required when ’activation-type’ is ’immediate’ at acl 'name'(%v)", name)
return false, errorMsg
}
if matches.IPv6 != nil && matches.IPv6.DestinationIPv6Network == nil {
log.Errorf("Missing 'destination-ipv6-network' value when ’activation-type’ is ’immediate’ at acl 'name'=%+v", name)
errorMsg := fmt.Sprintf("Body Data Error : 'destination-ipv6-network' value is required when ’activation-type’ is ’immediate’ at acl 'name' (%v)", name)
return false, errorMsg
}
}
return true, ""
}
/**
* Check if type of acl is ipv4, matches.IPv4 is present. If type of acl is ipv6, matches.IPv6 is present
* parameters:
* name: the name of acl request
* aclType: the type of acl request
* matches: the matches of ace in acl request
* return: bool
* true: if type of acl is ipv4, matches.IPv4 is present. If type of acl is ipv6, matches.IPv6 is present
* false: if type of acl is ipv4, matches.IPv6 is present. If type of acl is ipv6, matches.IPv4 is present
*/
func (v *aclValidatorBase) ValidateMatchType(name string, aclType *types.ACLType, matches *types.Matches) (bool, string) {
if aclType != nil {
switch *aclType {
case types.ACLType_IPv4ACLType:
if matches.IPv6 != nil {
log.WithField("ipv6", *matches.IPv6).Errorf("ACL with type 'ipv4-acl-type' must not have 'ace' with 'ipv6' matches at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : ACL with type 'ipv4-acl-type' must not have 'ace' with 'ipv6' matches at acl 'name'(%v)", name)
return false, errorMsg
}
case types.ACLType_IPv6ACLType:
if matches.IPv4 != nil {
log.WithField("ipv4", *matches.IPv4).Errorf("ACL with type 'ipv6-acl-type' must not have 'ace' with 'ipv4' matches at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : ACL with type 'ipv6-acl-type' must not have 'ace' with 'ipv4' matches at acl 'name'(%v)", name)
return false, errorMsg
}
}
}
return true, ""
}
/**
* Check valid destination ipv4 address
* parameters:
* name: the name of acl request
* addressRange: the range address
* matches: the matches of ace in acl request
* return: bool
* true: destination ipv4 is support in addressRange
* false: destination ipv4 is not support in addressRange
*/
func (v *aclValidatorBase) ValidateDestinationIPv4(name string, addressRange models.AddressRange, matches *types.Matches) (bool, string) {
if matches.IPv4 != nil && matches.IPv4.DestinationIPv4Network != nil{
destinationIpv4Network,_ := models.NewPrefix(matches.IPv4.DestinationIPv4Network.String())
validAddress,addressRange := destinationIpv4Network.CheckValidRangeIpAddress(addressRange)
if !validAddress {
log. Errorf("'destination-ipv4-network'with value = %+v is not supported within Portal ex-portal1 %+v at acl 'name'(%v)", destinationIpv4Network, addressRange, name)
errorMsg := fmt.Sprintf("Body Data Error : 'destination-ipv4-network' with value = %+v is not supported within Portal ex-portal1 %+v at acl 'name'(%v)", destinationIpv4Network, addressRange, name)
return false, errorMsg
}
}
return true, ""
}
/**
* Check valid destination ipv6 address
* parameters:
* name: the name of acl request
* addressRange: the range address
* matches: the matches of ace in acl request
* return: bool
* true: destination ipv6 is support in addressRange
* false: destination ipv6 is not support in addressRange
*/
func (v *aclValidatorBase) ValidateDestinationIPv6(name string, addressRange models.AddressRange, matches *types.Matches) (bool, string) {
if matches.IPv6 != nil && matches.IPv6.DestinationIPv6Network != nil{
destinationIpv6Network,_ := models.NewPrefix(matches.IPv6.DestinationIPv6Network.String())
validAddress,addressRange := destinationIpv6Network.CheckValidRangeIpAddress(addressRange)
if !validAddress {
log. Errorf("'destination-ipv6-network'with value = %+v is not supported within Portal ex-portal1 %+v at acl 'name'=%+v", destinationIpv6Network, addressRange, name)
errorMsg := fmt.Sprintf("Body Data Error : 'destination-ipv6-network' with value = %+v is not supported within Portal ex-portal1 (%v) at acl 'name'(%v)", destinationIpv6Network, addressRange, name)
return false, errorMsg
}
}
return true, ""
}
/**
* Check only existed tcp or udp or icmp
* parameters:
* name: the name of acl request
* matches: the matches of ace in acl request
* return: bool
* true: only existed tcp or udp or icmp
* false: existed tcp, udp, icmp
*/
func (v *aclValidatorBase) ValidateExistTCPOrUDPOrICMP(name string, matches *types.Matches) (bool, string) {
if (matches.TCP != nil && matches.UDP != nil) ||
(matches.UDP != nil && matches.ICMP != nil) ||
(matches.TCP != nil && matches.ICMP != nil) {
log.WithField("tcp", matches.TCP).WithField("udp", matches.UDP).WithField("icmp", matches.ICMP).Errorf("Only one of 'tcp', 'udp' and 'icmp' matches is allowed at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : Only one 'tcp', 'udp' and 'icmp' of 'match' is allowed at acl 'name'(%v)", name)
return false, errorMsg
}
return true, ""
}
/**
* Check valid TCP
* parameters:
* name: the name of acl request
* matches: the matches of ace in acl request
* return: bool
* true: tcp is valid
* false: tcp is invalid
*/
func (v *aclValidatorBase) ValidateTCP(name string, matches *types.Matches) (bool, string) {
if matches.TCP != nil {
tcp := matches.TCP
if tcp.SourcePort != nil && v.ValidatePort(tcp.SourcePort) == false {
log.WithField("source-port", *tcp.SourcePort).Errorf("Invalid 'source-port' at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : Invalid 'source-port' (%v) at acl 'name'(%v)", *tcp.SourcePort, name)
return false, errorMsg
}
if tcp.DestinationPort != nil && v.ValidatePort(tcp.DestinationPort) == false {
log.WithField("destination-port", *tcp.DestinationPort).Errorf("Invalid 'destination-port' at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : Invalid 'destination-port' (%v) at acl 'name'(%v)", *tcp.DestinationPort, name)
return false, errorMsg
}
// 'flags' and 'FlagsBitmask' must not set these fields in the same request
if tcp.Flags != nil && tcp.FlagsBitmask != nil {
log.Errorf("Only one of 'flags' and 'FlagsBitmask' is allowed at acl 'name' = %+v", name)
errorMsg := fmt.Sprintf("Body Data Error : Only one of 'flags' and 'FlagsBitmask' is allowed at acl 'name' (%v)", name)
return false, errorMsg
}
}
return true, ""
}
/**
* Check valid UDP
* parameters:
* name: the name of acl request
* matches: the matches of ace in acl request
* return: bool
* true: udp is valid
* false: udp is invalid
*/
func (v *aclValidatorBase) ValidateUDP(name string, matches *types.Matches) (bool, string) {
if matches.UDP != nil {
udp := matches.UDP
if udp.SourcePort != nil && v.ValidatePort(udp.SourcePort) == false {
log.WithField("source-port", *udp.SourcePort).Errorf("Invalid 'source-port' at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : Invalid 'source-port' (%v) at acl 'name'(%v)", *udp.SourcePort, name)
return false, errorMsg
}
if udp.DestinationPort != nil && v.ValidatePort(udp.DestinationPort) == false {
log.WithField("destination-port", *udp.DestinationPort).Errorf("Invalid 'destination-port' at acl 'name'=%+v.", name)
errorMsg := fmt.Sprintf("Body Data Error : Invalid 'destination-port' (%v) at acl 'name'(%v)", *udp.DestinationPort, name)
return false, errorMsg
}
}
return true, ""
}