github.com/crowdsecurity/crowdsec@v1.6.1/pkg/database/alerts.go (about) 1 package database 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "sort" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/mattn/go-sqlite3" 13 14 "github.com/davecgh/go-spew/spew" 15 "github.com/pkg/errors" 16 log "github.com/sirupsen/logrus" 17 18 "github.com/crowdsecurity/go-cs-lib/slicetools" 19 20 "github.com/crowdsecurity/crowdsec/pkg/database/ent" 21 "github.com/crowdsecurity/crowdsec/pkg/database/ent/alert" 22 "github.com/crowdsecurity/crowdsec/pkg/database/ent/decision" 23 "github.com/crowdsecurity/crowdsec/pkg/database/ent/event" 24 "github.com/crowdsecurity/crowdsec/pkg/database/ent/meta" 25 "github.com/crowdsecurity/crowdsec/pkg/database/ent/predicate" 26 "github.com/crowdsecurity/crowdsec/pkg/models" 27 "github.com/crowdsecurity/crowdsec/pkg/types" 28 ) 29 30 const ( 31 paginationSize = 100 // used to queryAlert to avoid 'too many SQL variable' 32 defaultLimit = 100 // default limit of element to returns when query alerts 33 bulkSize = 50 // bulk size when create alerts 34 maxLockRetries = 10 // how many times to retry a bulk operation when sqlite3.ErrBusy is encountered 35 ) 36 37 func formatAlertCN(source models.Source) string { 38 cn := source.Cn 39 40 if source.AsNumber != "" { 41 cn += "/" + source.AsNumber 42 } 43 44 return cn 45 } 46 47 func formatAlertSource(alert *models.Alert) string { 48 if alert.Source == nil || alert.Source.Scope == nil || *alert.Source.Scope == "" { 49 return "empty source" 50 } 51 52 if *alert.Source.Scope == types.Ip { 53 ret := "ip " + *alert.Source.Value 54 55 cn := formatAlertCN(*alert.Source) 56 if cn != "" { 57 ret += " (" + cn + ")" 58 } 59 60 return ret 61 } 62 63 if *alert.Source.Scope == types.Range { 64 ret := "range " + *alert.Source.Value 65 66 cn := formatAlertCN(*alert.Source) 67 if cn != "" { 68 ret += " (" + cn + ")" 69 } 70 71 return ret 72 } 73 74 return *alert.Source.Scope + " " + *alert.Source.Value 75 } 76 77 func formatAlertAsString(machineID string, alert *models.Alert) []string { 78 src := formatAlertSource(alert) 79 80 msg := "empty scenario" 81 if alert.Scenario != nil && *alert.Scenario != "" { 82 msg = *alert.Scenario 83 } else if alert.Message != nil && *alert.Message != "" { 84 msg = *alert.Message 85 } 86 87 reason := fmt.Sprintf("%s by %s", msg, src) 88 89 if len(alert.Decisions) == 0 { 90 return []string{fmt.Sprintf("(%s) alert : %s", machineID, reason)} 91 } 92 93 var retStr []string 94 95 if alert.Decisions[0].Origin != nil && *alert.Decisions[0].Origin == types.CscliImportOrigin { 96 return []string{fmt.Sprintf("(%s) alert : %s", machineID, reason)} 97 } 98 99 for i, decisionItem := range alert.Decisions { 100 decision := "" 101 if alert.Simulated != nil && *alert.Simulated { 102 decision = "(simulated alert)" 103 } else if decisionItem.Simulated != nil && *decisionItem.Simulated { 104 decision = "(simulated decision)" 105 } 106 107 if log.GetLevel() >= log.DebugLevel { 108 /*spew is expensive*/ 109 log.Debugf("%s", spew.Sdump(decisionItem)) 110 } 111 112 if len(alert.Decisions) > 1 { 113 reason = fmt.Sprintf("%s for %d/%d decisions", msg, i+1, len(alert.Decisions)) 114 } 115 116 var machineIDOrigin string 117 if machineID == "" { 118 machineIDOrigin = *decisionItem.Origin 119 } else { 120 machineIDOrigin = fmt.Sprintf("%s/%s", machineID, *decisionItem.Origin) 121 } 122 123 decision += fmt.Sprintf("%s %s on %s %s", *decisionItem.Duration, 124 *decisionItem.Type, *decisionItem.Scope, *decisionItem.Value) 125 retStr = append(retStr, 126 fmt.Sprintf("(%s) %s : %s", machineIDOrigin, reason, decision)) 127 } 128 129 return retStr 130 } 131 132 // CreateOrUpdateAlert is specific to PAPI : It checks if alert already exists, otherwise inserts it 133 // if alert already exists, it checks it associated decisions already exists 134 // if some associated decisions are missing (ie. previous insert ended up in error) it inserts them 135 func (c *Client) CreateOrUpdateAlert(machineID string, alertItem *models.Alert) (string, error) { 136 if alertItem.UUID == "" { 137 return "", fmt.Errorf("alert UUID is empty") 138 } 139 140 alerts, err := c.Ent.Alert.Query().Where(alert.UUID(alertItem.UUID)).WithDecisions().All(c.CTX) 141 142 if err != nil && !ent.IsNotFound(err) { 143 return "", fmt.Errorf("unable to query alerts for uuid %s: %w", alertItem.UUID, err) 144 } 145 146 //alert wasn't found, insert it (expected hotpath) 147 if ent.IsNotFound(err) || len(alerts) == 0 { 148 alertIDs, err := c.CreateAlert(machineID, []*models.Alert{alertItem}) 149 if err != nil { 150 return "", fmt.Errorf("unable to create alert: %w", err) 151 } 152 153 return alertIDs[0], nil 154 } 155 156 //this should never happen 157 if len(alerts) > 1 { 158 return "", fmt.Errorf("multiple alerts found for uuid %s", alertItem.UUID) 159 } 160 161 log.Infof("Alert %s already exists, checking associated decisions", alertItem.UUID) 162 163 //alert is found, check for any missing decisions 164 165 newUuids := make([]string, len(alertItem.Decisions)) 166 for i, decItem := range alertItem.Decisions { 167 newUuids[i] = decItem.UUID 168 } 169 170 foundAlert := alerts[0] 171 foundUuids := make([]string, len(foundAlert.Edges.Decisions)) 172 173 for i, decItem := range foundAlert.Edges.Decisions { 174 foundUuids[i] = decItem.UUID 175 } 176 177 sort.Strings(foundUuids) 178 sort.Strings(newUuids) 179 180 missingUuids := []string{} 181 182 for idx, uuid := range newUuids { 183 if len(foundUuids) < idx+1 || uuid != foundUuids[idx] { 184 log.Warningf("Decision with uuid %s not found in alert %s", uuid, foundAlert.UUID) 185 missingUuids = append(missingUuids, uuid) 186 } 187 } 188 189 if len(missingUuids) == 0 { 190 log.Warningf("alert %s was already complete with decisions %+v", alertItem.UUID, foundUuids) 191 return "", nil 192 } 193 194 // add any and all missing decisions based on their uuids 195 // prepare missing decisions 196 missingDecisions := []*models.Decision{} 197 198 for _, uuid := range missingUuids { 199 for _, newDecision := range alertItem.Decisions { 200 if newDecision.UUID == uuid { 201 missingDecisions = append(missingDecisions, newDecision) 202 } 203 } 204 } 205 206 //add missing decisions 207 log.Debugf("Adding %d missing decisions to alert %s", len(missingDecisions), foundAlert.UUID) 208 209 decisionBuilders := []*ent.DecisionCreate{} 210 211 for _, decisionItem := range missingDecisions { 212 var start_ip, start_sfx, end_ip, end_sfx int64 213 var sz int 214 215 /*if the scope is IP or Range, convert the value to integers */ 216 if strings.ToLower(*decisionItem.Scope) == "ip" || strings.ToLower(*decisionItem.Scope) == "range" { 217 sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(*decisionItem.Value) 218 if err != nil { 219 log.Errorf("invalid addr/range '%s': %s", *decisionItem.Value, err) 220 continue 221 } 222 } 223 224 decisionDuration, err := time.ParseDuration(*decisionItem.Duration) 225 if err != nil { 226 log.Warningf("invalid duration %s for decision %s", *decisionItem.Duration, decisionItem.UUID) 227 continue 228 } 229 230 //use the created_at from the alert instead 231 alertTime, err := time.Parse(time.RFC3339, alertItem.CreatedAt) 232 if err != nil { 233 log.Errorf("unable to parse alert time %s : %s", alertItem.CreatedAt, err) 234 235 alertTime = time.Now() 236 } 237 238 decisionUntil := alertTime.UTC().Add(decisionDuration) 239 240 decisionBuilder := c.Ent.Decision.Create(). 241 SetUntil(decisionUntil). 242 SetScenario(*decisionItem.Scenario). 243 SetType(*decisionItem.Type). 244 SetStartIP(start_ip). 245 SetStartSuffix(start_sfx). 246 SetEndIP(end_ip). 247 SetEndSuffix(end_sfx). 248 SetIPSize(int64(sz)). 249 SetValue(*decisionItem.Value). 250 SetScope(*decisionItem.Scope). 251 SetOrigin(*decisionItem.Origin). 252 SetSimulated(*alertItem.Simulated). 253 SetUUID(decisionItem.UUID) 254 255 decisionBuilders = append(decisionBuilders, decisionBuilder) 256 } 257 258 decisions := []*ent.Decision{} 259 260 builderChunks := slicetools.Chunks(decisionBuilders, c.decisionBulkSize) 261 262 for _, builderChunk := range builderChunks { 263 decisionsCreateRet, err := c.Ent.Decision.CreateBulk(builderChunk...).Save(c.CTX) 264 if err != nil { 265 return "", fmt.Errorf("creating alert decisions: %w", err) 266 } 267 268 decisions = append(decisions, decisionsCreateRet...) 269 } 270 271 //now that we bulk created missing decisions, let's update the alert 272 273 decisionChunks := slicetools.Chunks(decisions, c.decisionBulkSize) 274 275 for _, decisionChunk := range decisionChunks { 276 err = c.Ent.Alert.Update().Where(alert.UUID(alertItem.UUID)).AddDecisions(decisionChunk...).Exec(c.CTX) 277 if err != nil { 278 return "", fmt.Errorf("updating alert %s: %w", alertItem.UUID, err) 279 } 280 } 281 282 return "", nil 283 } 284 285 // UpdateCommunityBlocklist is called to update either the community blocklist (or other lists the user subscribed to) 286 // it takes care of creating the new alert with the associated decisions, and it will as well deleted the "older" overlapping decisions: 287 // 1st pull, you get decisions [1,2,3]. it inserts [1,2,3] 288 // 2nd pull, you get decisions [1,2,3,4]. it inserts [1,2,3,4] and will try to delete [1,2,3,4] with a different alert ID and same origin 289 func (c *Client) UpdateCommunityBlocklist(alertItem *models.Alert) (int, int, int, error) { 290 if alertItem == nil { 291 return 0, 0, 0, fmt.Errorf("nil alert") 292 } 293 294 if alertItem.StartAt == nil { 295 return 0, 0, 0, fmt.Errorf("nil start_at") 296 } 297 298 startAtTime, err := time.Parse(time.RFC3339, *alertItem.StartAt) 299 if err != nil { 300 return 0, 0, 0, errors.Wrapf(ParseTimeFail, "start_at field time '%s': %s", *alertItem.StartAt, err) 301 } 302 303 if alertItem.StopAt == nil { 304 return 0, 0, 0, fmt.Errorf("nil stop_at") 305 } 306 307 stopAtTime, err := time.Parse(time.RFC3339, *alertItem.StopAt) 308 if err != nil { 309 return 0, 0, 0, errors.Wrapf(ParseTimeFail, "stop_at field time '%s': %s", *alertItem.StopAt, err) 310 } 311 312 ts, err := time.Parse(time.RFC3339, *alertItem.StopAt) 313 if err != nil { 314 c.Log.Errorf("While parsing StartAt of item %s : %s", *alertItem.StopAt, err) 315 316 ts = time.Now().UTC() 317 } 318 319 alertB := c.Ent.Alert. 320 Create(). 321 SetScenario(*alertItem.Scenario). 322 SetMessage(*alertItem.Message). 323 SetEventsCount(*alertItem.EventsCount). 324 SetStartedAt(startAtTime). 325 SetStoppedAt(stopAtTime). 326 SetSourceScope(*alertItem.Source.Scope). 327 SetSourceValue(*alertItem.Source.Value). 328 SetSourceIp(alertItem.Source.IP). 329 SetSourceRange(alertItem.Source.Range). 330 SetSourceAsNumber(alertItem.Source.AsNumber). 331 SetSourceAsName(alertItem.Source.AsName). 332 SetSourceCountry(alertItem.Source.Cn). 333 SetSourceLatitude(alertItem.Source.Latitude). 334 SetSourceLongitude(alertItem.Source.Longitude). 335 SetCapacity(*alertItem.Capacity). 336 SetLeakSpeed(*alertItem.Leakspeed). 337 SetSimulated(*alertItem.Simulated). 338 SetScenarioVersion(*alertItem.ScenarioVersion). 339 SetScenarioHash(*alertItem.ScenarioHash) 340 341 alertRef, err := alertB.Save(c.CTX) 342 if err != nil { 343 return 0, 0, 0, errors.Wrapf(BulkError, "error creating alert : %s", err) 344 } 345 346 if len(alertItem.Decisions) == 0 { 347 return alertRef.ID, 0, 0, nil 348 } 349 350 txClient, err := c.Ent.Tx(c.CTX) 351 if err != nil { 352 return 0, 0, 0, errors.Wrapf(BulkError, "error creating transaction : %s", err) 353 } 354 355 DecOrigin := CapiMachineID 356 357 if *alertItem.Decisions[0].Origin == CapiMachineID || *alertItem.Decisions[0].Origin == CapiListsMachineID { 358 DecOrigin = *alertItem.Decisions[0].Origin 359 } else { 360 log.Warningf("unexpected origin %s", *alertItem.Decisions[0].Origin) 361 } 362 363 deleted := 0 364 inserted := 0 365 366 decisionBuilders := make([]*ent.DecisionCreate, 0, len(alertItem.Decisions)) 367 valueList := make([]string, 0, len(alertItem.Decisions)) 368 369 for _, decisionItem := range alertItem.Decisions { 370 var start_ip, start_sfx, end_ip, end_sfx int64 371 var sz int 372 373 if decisionItem.Duration == nil { 374 log.Warning("nil duration in community decision") 375 continue 376 } 377 378 duration, err := time.ParseDuration(*decisionItem.Duration) 379 if err != nil { 380 rollbackErr := txClient.Rollback() 381 if rollbackErr != nil { 382 log.Errorf("rollback error: %s", rollbackErr) 383 } 384 385 return 0, 0, 0, errors.Wrapf(ParseDurationFail, "decision duration '%+v' : %s", *decisionItem.Duration, err) 386 } 387 388 if decisionItem.Scope == nil { 389 log.Warning("nil scope in community decision") 390 continue 391 } 392 393 /*if the scope is IP or Range, convert the value to integers */ 394 if strings.ToLower(*decisionItem.Scope) == "ip" || strings.ToLower(*decisionItem.Scope) == "range" { 395 sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(*decisionItem.Value) 396 if err != nil { 397 rollbackErr := txClient.Rollback() 398 if rollbackErr != nil { 399 log.Errorf("rollback error: %s", rollbackErr) 400 } 401 402 return 0, 0, 0, errors.Wrapf(InvalidIPOrRange, "invalid addr/range %s : %s", *decisionItem.Value, err) 403 } 404 } 405 406 /*bulk insert some new decisions*/ 407 decisionBuilder := c.Ent.Decision.Create(). 408 SetUntil(ts.Add(duration)). 409 SetScenario(*decisionItem.Scenario). 410 SetType(*decisionItem.Type). 411 SetStartIP(start_ip). 412 SetStartSuffix(start_sfx). 413 SetEndIP(end_ip). 414 SetEndSuffix(end_sfx). 415 SetIPSize(int64(sz)). 416 SetValue(*decisionItem.Value). 417 SetScope(*decisionItem.Scope). 418 SetOrigin(*decisionItem.Origin). 419 SetSimulated(*alertItem.Simulated). 420 SetOwner(alertRef) 421 422 decisionBuilders = append(decisionBuilders, decisionBuilder) 423 424 /*for bulk delete of duplicate decisions*/ 425 if decisionItem.Value == nil { 426 log.Warning("nil value in community decision") 427 continue 428 } 429 430 valueList = append(valueList, *decisionItem.Value) 431 } 432 433 deleteChunks := slicetools.Chunks(valueList, c.decisionBulkSize) 434 435 for _, deleteChunk := range deleteChunks { 436 // Deleting older decisions from capi 437 deletedDecisions, err := txClient.Decision.Delete(). 438 Where(decision.And( 439 decision.OriginEQ(DecOrigin), 440 decision.Not(decision.HasOwnerWith(alert.IDEQ(alertRef.ID))), 441 decision.ValueIn(deleteChunk...), 442 )).Exec(c.CTX) 443 if err != nil { 444 rollbackErr := txClient.Rollback() 445 if rollbackErr != nil { 446 log.Errorf("rollback error: %s", rollbackErr) 447 } 448 449 return 0, 0, 0, fmt.Errorf("while deleting older community blocklist decisions: %w", err) 450 } 451 452 deleted += deletedDecisions 453 } 454 455 builderChunks := slicetools.Chunks(decisionBuilders, c.decisionBulkSize) 456 457 for _, builderChunk := range builderChunks { 458 insertedDecisions, err := txClient.Decision.CreateBulk(builderChunk...).Save(c.CTX) 459 if err != nil { 460 rollbackErr := txClient.Rollback() 461 if rollbackErr != nil { 462 log.Errorf("rollback error: %s", rollbackErr) 463 } 464 465 return 0, 0, 0, fmt.Errorf("while bulk creating decisions: %w", err) 466 } 467 468 inserted += len(insertedDecisions) 469 } 470 471 log.Debugf("deleted %d decisions for %s vs %s", deleted, DecOrigin, *alertItem.Decisions[0].Origin) 472 473 err = txClient.Commit() 474 if err != nil { 475 rollbackErr := txClient.Rollback() 476 if rollbackErr != nil { 477 log.Errorf("rollback error: %s", rollbackErr) 478 } 479 480 return 0, 0, 0, fmt.Errorf("error committing transaction: %w", err) 481 } 482 483 return alertRef.ID, inserted, deleted, nil 484 } 485 486 func (c *Client) createDecisionChunk(simulated bool, stopAtTime time.Time, decisions []*models.Decision) ([]*ent.Decision, error) { 487 decisionCreate := []*ent.DecisionCreate{} 488 489 for _, decisionItem := range decisions { 490 var start_ip, start_sfx, end_ip, end_sfx int64 491 var sz int 492 493 duration, err := time.ParseDuration(*decisionItem.Duration) 494 if err != nil { 495 return nil, errors.Wrapf(ParseDurationFail, "decision duration '%+v' : %s", *decisionItem.Duration, err) 496 } 497 498 /*if the scope is IP or Range, convert the value to integers */ 499 if strings.ToLower(*decisionItem.Scope) == "ip" || strings.ToLower(*decisionItem.Scope) == "range" { 500 sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(*decisionItem.Value) 501 if err != nil { 502 log.Errorf("invalid addr/range '%s': %s", *decisionItem.Value, err) 503 continue 504 } 505 } 506 507 newDecision := c.Ent.Decision.Create(). 508 SetUntil(stopAtTime.Add(duration)). 509 SetScenario(*decisionItem.Scenario). 510 SetType(*decisionItem.Type). 511 SetStartIP(start_ip). 512 SetStartSuffix(start_sfx). 513 SetEndIP(end_ip). 514 SetEndSuffix(end_sfx). 515 SetIPSize(int64(sz)). 516 SetValue(*decisionItem.Value). 517 SetScope(*decisionItem.Scope). 518 SetOrigin(*decisionItem.Origin). 519 SetSimulated(simulated). 520 SetUUID(decisionItem.UUID) 521 522 decisionCreate = append(decisionCreate, newDecision) 523 } 524 525 if len(decisionCreate) == 0 { 526 return nil, nil 527 } 528 529 ret, err := c.Ent.Decision.CreateBulk(decisionCreate...).Save(c.CTX) 530 if err != nil { 531 return nil, err 532 } 533 534 return ret, nil 535 } 536 537 func (c *Client) createAlertChunk(machineID string, owner *ent.Machine, alerts []*models.Alert) ([]string, error) { 538 alertBuilders := []*ent.AlertCreate{} 539 alertDecisions := [][]*ent.Decision{} 540 541 for _, alertItem := range alerts { 542 var metas []*ent.Meta 543 var events []*ent.Event 544 545 startAtTime, err := time.Parse(time.RFC3339, *alertItem.StartAt) 546 if err != nil { 547 c.Log.Errorf("CreateAlertBulk: Failed to parse startAtTime '%s', defaulting to now: %s", *alertItem.StartAt, err) 548 549 startAtTime = time.Now().UTC() 550 } 551 552 stopAtTime, err := time.Parse(time.RFC3339, *alertItem.StopAt) 553 if err != nil { 554 c.Log.Errorf("CreateAlertBulk: Failed to parse stopAtTime '%s', defaulting to now: %s", *alertItem.StopAt, err) 555 556 stopAtTime = time.Now().UTC() 557 } 558 /*display proper alert in logs*/ 559 for _, disp := range formatAlertAsString(machineID, alertItem) { 560 c.Log.Info(disp) 561 } 562 563 //let's track when we strip or drop data, notify outside of loop to avoid spam 564 stripped := false 565 dropped := false 566 567 if len(alertItem.Events) > 0 { 568 eventBulk := make([]*ent.EventCreate, len(alertItem.Events)) 569 570 for i, eventItem := range alertItem.Events { 571 ts, err := time.Parse(time.RFC3339, *eventItem.Timestamp) 572 if err != nil { 573 c.Log.Errorf("CreateAlertBulk: Failed to parse event timestamp '%s', defaulting to now: %s", *eventItem.Timestamp, err) 574 575 ts = time.Now().UTC() 576 } 577 578 marshallMetas, err := json.Marshal(eventItem.Meta) 579 if err != nil { 580 return nil, errors.Wrapf(MarshalFail, "event meta '%v' : %s", eventItem.Meta, err) 581 } 582 583 //the serialized field is too big, let's try to progressively strip it 584 if event.SerializedValidator(string(marshallMetas)) != nil { 585 stripped = true 586 587 valid := false 588 stripSize := 2048 589 590 for !valid && stripSize > 0 { 591 for _, serializedItem := range eventItem.Meta { 592 if len(serializedItem.Value) > stripSize*2 { 593 serializedItem.Value = serializedItem.Value[:stripSize] + "<stripped>" 594 } 595 } 596 597 marshallMetas, err = json.Marshal(eventItem.Meta) 598 if err != nil { 599 return nil, errors.Wrapf(MarshalFail, "event meta '%v' : %s", eventItem.Meta, err) 600 } 601 602 if event.SerializedValidator(string(marshallMetas)) == nil { 603 valid = true 604 } 605 606 stripSize /= 2 607 } 608 609 //nothing worked, drop it 610 if !valid { 611 dropped = true 612 stripped = false 613 marshallMetas = []byte("") 614 } 615 } 616 617 eventBulk[i] = c.Ent.Event.Create(). 618 SetTime(ts). 619 SetSerialized(string(marshallMetas)) 620 } 621 622 if stripped { 623 c.Log.Warningf("stripped 'serialized' field (machine %s / scenario %s)", machineID, *alertItem.Scenario) 624 } 625 626 if dropped { 627 c.Log.Warningf("dropped 'serialized' field (machine %s / scenario %s)", machineID, *alertItem.Scenario) 628 } 629 630 events, err = c.Ent.Event.CreateBulk(eventBulk...).Save(c.CTX) 631 if err != nil { 632 return nil, errors.Wrapf(BulkError, "creating alert events: %s", err) 633 } 634 } 635 636 if len(alertItem.Meta) > 0 { 637 metaBulk := make([]*ent.MetaCreate, len(alertItem.Meta)) 638 for i, metaItem := range alertItem.Meta { 639 metaBulk[i] = c.Ent.Meta.Create(). 640 SetKey(metaItem.Key). 641 SetValue(metaItem.Value) 642 } 643 644 metas, err = c.Ent.Meta.CreateBulk(metaBulk...).Save(c.CTX) 645 if err != nil { 646 return nil, errors.Wrapf(BulkError, "creating alert meta: %s", err) 647 } 648 } 649 650 decisions := []*ent.Decision{} 651 652 decisionChunks := slicetools.Chunks(alertItem.Decisions, c.decisionBulkSize) 653 for _, decisionChunk := range decisionChunks { 654 decisionRet, err := c.createDecisionChunk(*alertItem.Simulated, stopAtTime, decisionChunk) 655 if err != nil { 656 return nil, fmt.Errorf("creating alert decisions: %w", err) 657 } 658 659 decisions = append(decisions, decisionRet...) 660 } 661 662 discarded := len(alertItem.Decisions) - len(decisions) 663 if discarded > 0 { 664 c.Log.Warningf("discarded %d decisions for %s", discarded, alertItem.UUID) 665 } 666 667 // if all decisions were discarded, discard the alert too 668 if discarded > 0 && len(decisions) == 0 { 669 c.Log.Warningf("dropping alert %s with invalid decisions", alertItem.UUID) 670 continue 671 } 672 673 alertBuilder := c.Ent.Alert. 674 Create(). 675 SetScenario(*alertItem.Scenario). 676 SetMessage(*alertItem.Message). 677 SetEventsCount(*alertItem.EventsCount). 678 SetStartedAt(startAtTime). 679 SetStoppedAt(stopAtTime). 680 SetSourceScope(*alertItem.Source.Scope). 681 SetSourceValue(*alertItem.Source.Value). 682 SetSourceIp(alertItem.Source.IP). 683 SetSourceRange(alertItem.Source.Range). 684 SetSourceAsNumber(alertItem.Source.AsNumber). 685 SetSourceAsName(alertItem.Source.AsName). 686 SetSourceCountry(alertItem.Source.Cn). 687 SetSourceLatitude(alertItem.Source.Latitude). 688 SetSourceLongitude(alertItem.Source.Longitude). 689 SetCapacity(*alertItem.Capacity). 690 SetLeakSpeed(*alertItem.Leakspeed). 691 SetSimulated(*alertItem.Simulated). 692 SetScenarioVersion(*alertItem.ScenarioVersion). 693 SetScenarioHash(*alertItem.ScenarioHash). 694 SetUUID(alertItem.UUID). 695 AddEvents(events...). 696 AddMetas(metas...) 697 698 if owner != nil { 699 alertBuilder.SetOwner(owner) 700 } 701 702 alertBuilders = append(alertBuilders, alertBuilder) 703 alertDecisions = append(alertDecisions, decisions) 704 } 705 706 if len(alertBuilders) == 0 { 707 log.Warningf("no alerts to create, discarded?") 708 return nil, nil 709 } 710 711 alertsCreateBulk, err := c.Ent.Alert.CreateBulk(alertBuilders...).Save(c.CTX) 712 if err != nil { 713 return nil, errors.Wrapf(BulkError, "bulk creating alert : %s", err) 714 } 715 716 ret := make([]string, len(alertsCreateBulk)) 717 for i, a := range alertsCreateBulk { 718 ret[i] = strconv.Itoa(a.ID) 719 720 d := alertDecisions[i] 721 decisionsChunk := slicetools.Chunks(d, c.decisionBulkSize) 722 723 for _, d2 := range decisionsChunk { 724 retry := 0 725 726 for retry < maxLockRetries { 727 // so much for the happy path... but sqlite3 errors work differently 728 _, err := c.Ent.Alert.Update().Where(alert.IDEQ(a.ID)).AddDecisions(d2...).Save(c.CTX) 729 if err == nil { 730 break 731 } 732 733 if sqliteErr, ok := err.(sqlite3.Error); ok { 734 if sqliteErr.Code == sqlite3.ErrBusy { 735 // sqlite3.Error{ 736 // Code: 5, 737 // ExtendedCode: 5, 738 // SystemErrno: 0, 739 // err: "database is locked", 740 // } 741 retry++ 742 log.Warningf("while updating decisions, sqlite3.ErrBusy: %s, retry %d of %d", err, retry, maxLockRetries) 743 time.Sleep(1 * time.Second) 744 745 continue 746 } 747 } 748 749 return nil, fmt.Errorf("error while updating decisions: %w", err) 750 } 751 } 752 } 753 754 return ret, nil 755 } 756 757 func (c *Client) CreateAlert(machineID string, alertList []*models.Alert) ([]string, error) { 758 var owner *ent.Machine 759 var err error 760 761 if machineID != "" { 762 owner, err = c.QueryMachineByID(machineID) 763 if err != nil { 764 if !errors.Is(err, UserNotExists) { 765 return nil, fmt.Errorf("machine '%s': %w", machineID, err) 766 } 767 768 c.Log.Debugf("CreateAlertBulk: Machine Id %s doesn't exist", machineID) 769 770 owner = nil 771 } 772 } 773 774 c.Log.Debugf("writing %d items", len(alertList)) 775 776 alertChunks := slicetools.Chunks(alertList, bulkSize) 777 alertIDs := []string{} 778 779 for _, alertChunk := range alertChunks { 780 ids, err := c.createAlertChunk(machineID, owner, alertChunk) 781 if err != nil { 782 return nil, fmt.Errorf("machine '%s': %w", machineID, err) 783 } 784 785 alertIDs = append(alertIDs, ids...) 786 } 787 788 return alertIDs, nil 789 } 790 791 func AlertPredicatesFromFilter(filter map[string][]string) ([]predicate.Alert, error) { 792 predicates := make([]predicate.Alert, 0) 793 794 var err error 795 var start_ip, start_sfx, end_ip, end_sfx int64 796 var hasActiveDecision bool 797 var ip_sz int 798 var contains = true 799 800 /*if contains is true, return bans that *contains* the given value (value is the inner) 801 else, return bans that are *contained* by the given value (value is the outer)*/ 802 803 /*the simulated filter is a bit different : if it's not present *or* set to false, specifically exclude records with simulated to true */ 804 if v, ok := filter["simulated"]; ok { 805 if v[0] == "false" { 806 predicates = append(predicates, alert.SimulatedEQ(false)) 807 } 808 } 809 810 if _, ok := filter["origin"]; ok { 811 filter["include_capi"] = []string{"true"} 812 } 813 814 for param, value := range filter { 815 switch param { 816 case "contains": 817 contains, err = strconv.ParseBool(value[0]) 818 if err != nil { 819 return nil, errors.Wrapf(InvalidFilter, "invalid contains value : %s", err) 820 } 821 case "scope": 822 var scope = value[0] 823 if strings.ToLower(scope) == "ip" { 824 scope = types.Ip 825 } else if strings.ToLower(scope) == "range" { 826 scope = types.Range 827 } 828 829 predicates = append(predicates, alert.SourceScopeEQ(scope)) 830 case "value": 831 predicates = append(predicates, alert.SourceValueEQ(value[0])) 832 case "scenario": 833 predicates = append(predicates, alert.HasDecisionsWith(decision.ScenarioEQ(value[0]))) 834 case "ip", "range": 835 ip_sz, start_ip, start_sfx, end_ip, end_sfx, err = types.Addr2Ints(value[0]) 836 if err != nil { 837 return nil, errors.Wrapf(InvalidIPOrRange, "unable to convert '%s' to int: %s", value[0], err) 838 } 839 case "since": 840 duration, err := ParseDuration(value[0]) 841 if err != nil { 842 return nil, fmt.Errorf("while parsing duration: %w", err) 843 } 844 845 since := time.Now().UTC().Add(-duration) 846 if since.IsZero() { 847 return nil, fmt.Errorf("empty time now() - %s", since.String()) 848 } 849 850 predicates = append(predicates, alert.StartedAtGTE(since)) 851 case "created_before": 852 duration, err := ParseDuration(value[0]) 853 if err != nil { 854 return nil, fmt.Errorf("while parsing duration: %w", err) 855 } 856 857 since := time.Now().UTC().Add(-duration) 858 if since.IsZero() { 859 return nil, fmt.Errorf("empty time now() - %s", since.String()) 860 } 861 862 predicates = append(predicates, alert.CreatedAtLTE(since)) 863 case "until": 864 duration, err := ParseDuration(value[0]) 865 if err != nil { 866 return nil, fmt.Errorf("while parsing duration: %w", err) 867 } 868 869 until := time.Now().UTC().Add(-duration) 870 if until.IsZero() { 871 return nil, fmt.Errorf("empty time now() - %s", until.String()) 872 } 873 874 predicates = append(predicates, alert.StartedAtLTE(until)) 875 case "decision_type": 876 predicates = append(predicates, alert.HasDecisionsWith(decision.TypeEQ(value[0]))) 877 case "origin": 878 predicates = append(predicates, alert.HasDecisionsWith(decision.OriginEQ(value[0]))) 879 case "include_capi": //allows to exclude one or more specific origins 880 if value[0] == "false" { 881 predicates = append(predicates, alert.And( 882 //do not show alerts with active decisions having origin CAPI or lists 883 alert.And( 884 alert.Not(alert.HasDecisionsWith(decision.OriginEQ(types.CAPIOrigin))), 885 alert.Not(alert.HasDecisionsWith(decision.OriginEQ(types.ListOrigin))), 886 ), 887 alert.Not( 888 alert.And( 889 //do not show neither alerts with no decisions if the Source Scope is lists: or CAPI 890 alert.Not(alert.HasDecisions()), 891 alert.Or( 892 alert.SourceScopeHasPrefix(types.ListOrigin+":"), 893 alert.SourceScopeEQ(types.CommunityBlocklistPullSourceScope), 894 ), 895 ), 896 ), 897 ), 898 ) 899 900 } else if value[0] != "true" { 901 log.Errorf("Invalid bool '%s' for include_capi", value[0]) 902 } 903 case "has_active_decision": 904 if hasActiveDecision, err = strconv.ParseBool(value[0]); err != nil { 905 return nil, errors.Wrapf(ParseType, "'%s' is not a boolean: %s", value[0], err) 906 } 907 908 if hasActiveDecision { 909 predicates = append(predicates, alert.HasDecisionsWith(decision.UntilGTE(time.Now().UTC()))) 910 } else { 911 predicates = append(predicates, alert.Not(alert.HasDecisions())) 912 } 913 case "limit": 914 continue 915 case "sort": 916 continue 917 case "simulated": 918 continue 919 case "with_decisions": 920 continue 921 default: 922 return nil, errors.Wrapf(InvalidFilter, "Filter parameter '%s' is unknown (=%s)", param, value[0]) 923 } 924 } 925 926 if ip_sz == 4 { 927 if contains { /*decision contains {start_ip,end_ip}*/ 928 predicates = append(predicates, alert.And( 929 alert.HasDecisionsWith(decision.StartIPLTE(start_ip)), 930 alert.HasDecisionsWith(decision.EndIPGTE(end_ip)), 931 alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))), 932 )) 933 } else { /*decision is contained within {start_ip,end_ip}*/ 934 predicates = append(predicates, alert.And( 935 alert.HasDecisionsWith(decision.StartIPGTE(start_ip)), 936 alert.HasDecisionsWith(decision.EndIPLTE(end_ip)), 937 alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))), 938 )) 939 } 940 } else if ip_sz == 16 { 941 if contains { /*decision contains {start_ip,end_ip}*/ 942 predicates = append(predicates, alert.And( 943 //matching addr size 944 alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))), 945 alert.Or( 946 //decision.start_ip < query.start_ip 947 alert.HasDecisionsWith(decision.StartIPLT(start_ip)), 948 alert.And( 949 //decision.start_ip == query.start_ip 950 alert.HasDecisionsWith(decision.StartIPEQ(start_ip)), 951 //decision.start_suffix <= query.start_suffix 952 alert.HasDecisionsWith(decision.StartSuffixLTE(start_sfx)), 953 )), 954 alert.Or( 955 //decision.end_ip > query.end_ip 956 alert.HasDecisionsWith(decision.EndIPGT(end_ip)), 957 alert.And( 958 //decision.end_ip == query.end_ip 959 alert.HasDecisionsWith(decision.EndIPEQ(end_ip)), 960 //decision.end_suffix >= query.end_suffix 961 alert.HasDecisionsWith(decision.EndSuffixGTE(end_sfx)), 962 ), 963 ), 964 )) 965 } else { /*decision is contained within {start_ip,end_ip}*/ 966 predicates = append(predicates, alert.And( 967 //matching addr size 968 alert.HasDecisionsWith(decision.IPSizeEQ(int64(ip_sz))), 969 alert.Or( 970 //decision.start_ip > query.start_ip 971 alert.HasDecisionsWith(decision.StartIPGT(start_ip)), 972 alert.And( 973 //decision.start_ip == query.start_ip 974 alert.HasDecisionsWith(decision.StartIPEQ(start_ip)), 975 //decision.start_suffix >= query.start_suffix 976 alert.HasDecisionsWith(decision.StartSuffixGTE(start_sfx)), 977 )), 978 alert.Or( 979 //decision.end_ip < query.end_ip 980 alert.HasDecisionsWith(decision.EndIPLT(end_ip)), 981 alert.And( 982 //decision.end_ip == query.end_ip 983 alert.HasDecisionsWith(decision.EndIPEQ(end_ip)), 984 //decision.end_suffix <= query.end_suffix 985 alert.HasDecisionsWith(decision.EndSuffixLTE(end_sfx)), 986 ), 987 ), 988 )) 989 } 990 } else if ip_sz != 0 { 991 return nil, errors.Wrapf(InvalidFilter, "Unknown ip size %d", ip_sz) 992 } 993 994 return predicates, nil 995 } 996 997 func BuildAlertRequestFromFilter(alerts *ent.AlertQuery, filter map[string][]string) (*ent.AlertQuery, error) { 998 preds, err := AlertPredicatesFromFilter(filter) 999 if err != nil { 1000 return nil, err 1001 } 1002 1003 return alerts.Where(preds...), nil 1004 } 1005 1006 func (c *Client) AlertsCountPerScenario(filters map[string][]string) (map[string]int, error) { 1007 var res []struct { 1008 Scenario string 1009 Count int 1010 } 1011 1012 ctx := context.Background() 1013 1014 query := c.Ent.Alert.Query() 1015 1016 query, err := BuildAlertRequestFromFilter(query, filters) 1017 1018 if err != nil { 1019 return nil, fmt.Errorf("failed to build alert request: %w", err) 1020 } 1021 1022 err = query.GroupBy(alert.FieldScenario).Aggregate(ent.Count()).Scan(ctx, &res) 1023 1024 if err != nil { 1025 return nil, fmt.Errorf("failed to count alerts per scenario: %w", err) 1026 } 1027 1028 counts := make(map[string]int) 1029 1030 for _, r := range res { 1031 counts[r.Scenario] = r.Count 1032 } 1033 1034 return counts, nil 1035 } 1036 1037 func (c *Client) TotalAlerts() (int, error) { 1038 return c.Ent.Alert.Query().Count(c.CTX) 1039 } 1040 1041 func (c *Client) QueryAlertWithFilter(filter map[string][]string) ([]*ent.Alert, error) { 1042 sort := "DESC" // we sort by desc by default 1043 1044 if val, ok := filter["sort"]; ok { 1045 if val[0] != "ASC" && val[0] != "DESC" { 1046 c.Log.Errorf("invalid 'sort' parameter: %s", val) 1047 } else { 1048 sort = val[0] 1049 } 1050 } 1051 1052 limit := defaultLimit 1053 1054 if val, ok := filter["limit"]; ok { 1055 limitConv, err := strconv.Atoi(val[0]) 1056 if err != nil { 1057 return nil, errors.Wrapf(QueryFail, "bad limit in parameters: %s", val) 1058 } 1059 1060 limit = limitConv 1061 } 1062 1063 offset := 0 1064 ret := make([]*ent.Alert, 0) 1065 1066 for { 1067 alerts := c.Ent.Alert.Query() 1068 1069 alerts, err := BuildAlertRequestFromFilter(alerts, filter) 1070 if err != nil { 1071 return nil, err 1072 } 1073 1074 //only if with_decisions is present and set to false, we exclude this 1075 if val, ok := filter["with_decisions"]; ok && val[0] == "false" { 1076 c.Log.Debugf("skipping decisions") 1077 } else { 1078 alerts = alerts. 1079 WithDecisions() 1080 } 1081 1082 alerts = alerts. 1083 WithEvents(). 1084 WithMetas(). 1085 WithOwner() 1086 1087 if limit == 0 { 1088 limit, err = alerts.Count(c.CTX) 1089 if err != nil { 1090 return nil, fmt.Errorf("unable to count nb alerts: %s", err) 1091 } 1092 } 1093 1094 if sort == "ASC" { 1095 alerts = alerts.Order(ent.Asc(alert.FieldCreatedAt), ent.Asc(alert.FieldID)) 1096 } else { 1097 alerts = alerts.Order(ent.Desc(alert.FieldCreatedAt), ent.Desc(alert.FieldID)) 1098 } 1099 1100 result, err := alerts.Limit(paginationSize).Offset(offset).All(c.CTX) 1101 if err != nil { 1102 return nil, errors.Wrapf(QueryFail, "pagination size: %d, offset: %d: %s", paginationSize, offset, err) 1103 } 1104 1105 if diff := limit - len(ret); diff < paginationSize { 1106 if len(result) < diff { 1107 ret = append(ret, result...) 1108 c.Log.Debugf("Pagination done, %d < %d", len(result), diff) 1109 1110 break 1111 } 1112 1113 ret = append(ret, result[0:diff]...) 1114 } else { 1115 ret = append(ret, result...) 1116 } 1117 1118 if len(ret) == limit || len(ret) == 0 || len(ret) < paginationSize { 1119 c.Log.Debugf("Pagination done len(ret) = %d", len(ret)) 1120 break 1121 } 1122 1123 offset += paginationSize 1124 } 1125 1126 return ret, nil 1127 } 1128 1129 func (c *Client) DeleteAlertGraphBatch(alertItems []*ent.Alert) (int, error) { 1130 idList := make([]int, 0) 1131 for _, alert := range alertItems { 1132 idList = append(idList, alert.ID) 1133 } 1134 1135 _, err := c.Ent.Event.Delete(). 1136 Where(event.HasOwnerWith(alert.IDIn(idList...))).Exec(c.CTX) 1137 if err != nil { 1138 c.Log.Warningf("DeleteAlertGraphBatch : %s", err) 1139 return 0, errors.Wrapf(DeleteFail, "alert graph delete batch events") 1140 } 1141 1142 _, err = c.Ent.Meta.Delete(). 1143 Where(meta.HasOwnerWith(alert.IDIn(idList...))).Exec(c.CTX) 1144 if err != nil { 1145 c.Log.Warningf("DeleteAlertGraphBatch : %s", err) 1146 return 0, errors.Wrapf(DeleteFail, "alert graph delete batch meta") 1147 } 1148 1149 _, err = c.Ent.Decision.Delete(). 1150 Where(decision.HasOwnerWith(alert.IDIn(idList...))).Exec(c.CTX) 1151 if err != nil { 1152 c.Log.Warningf("DeleteAlertGraphBatch : %s", err) 1153 return 0, errors.Wrapf(DeleteFail, "alert graph delete batch decisions") 1154 } 1155 1156 deleted, err := c.Ent.Alert.Delete(). 1157 Where(alert.IDIn(idList...)).Exec(c.CTX) 1158 if err != nil { 1159 c.Log.Warningf("DeleteAlertGraphBatch : %s", err) 1160 return deleted, errors.Wrapf(DeleteFail, "alert graph delete batch") 1161 } 1162 1163 c.Log.Debug("Done batch delete alerts") 1164 1165 return deleted, nil 1166 } 1167 1168 func (c *Client) DeleteAlertGraph(alertItem *ent.Alert) error { 1169 // delete the associated events 1170 _, err := c.Ent.Event.Delete(). 1171 Where(event.HasOwnerWith(alert.IDEQ(alertItem.ID))).Exec(c.CTX) 1172 if err != nil { 1173 c.Log.Warningf("DeleteAlertGraph : %s", err) 1174 return errors.Wrapf(DeleteFail, "event with alert ID '%d'", alertItem.ID) 1175 } 1176 1177 // delete the associated meta 1178 _, err = c.Ent.Meta.Delete(). 1179 Where(meta.HasOwnerWith(alert.IDEQ(alertItem.ID))).Exec(c.CTX) 1180 if err != nil { 1181 c.Log.Warningf("DeleteAlertGraph : %s", err) 1182 return errors.Wrapf(DeleteFail, "meta with alert ID '%d'", alertItem.ID) 1183 } 1184 1185 // delete the associated decisions 1186 _, err = c.Ent.Decision.Delete(). 1187 Where(decision.HasOwnerWith(alert.IDEQ(alertItem.ID))).Exec(c.CTX) 1188 if err != nil { 1189 c.Log.Warningf("DeleteAlertGraph : %s", err) 1190 return errors.Wrapf(DeleteFail, "decision with alert ID '%d'", alertItem.ID) 1191 } 1192 1193 // delete the alert 1194 err = c.Ent.Alert.DeleteOne(alertItem).Exec(c.CTX) 1195 if err != nil { 1196 c.Log.Warningf("DeleteAlertGraph : %s", err) 1197 return errors.Wrapf(DeleteFail, "alert with ID '%d'", alertItem.ID) 1198 } 1199 1200 return nil 1201 } 1202 1203 func (c *Client) DeleteAlertByID(id int) error { 1204 alertItem, err := c.Ent.Alert.Query().Where(alert.IDEQ(id)).Only(c.CTX) 1205 if err != nil { 1206 return err 1207 } 1208 1209 return c.DeleteAlertGraph(alertItem) 1210 } 1211 1212 func (c *Client) DeleteAlertWithFilter(filter map[string][]string) (int, error) { 1213 preds, err := AlertPredicatesFromFilter(filter) 1214 if err != nil { 1215 return 0, err 1216 } 1217 1218 return c.Ent.Alert.Delete().Where(preds...).Exec(c.CTX) 1219 } 1220 1221 func (c *Client) GetAlertByID(alertID int) (*ent.Alert, error) { 1222 alert, err := c.Ent.Alert.Query().Where(alert.IDEQ(alertID)).WithDecisions().WithEvents().WithMetas().WithOwner().First(c.CTX) 1223 if err != nil { 1224 /*record not found, 404*/ 1225 if ent.IsNotFound(err) { 1226 log.Warningf("GetAlertByID (not found): %s", err) 1227 return &ent.Alert{}, ItemNotFound 1228 } 1229 1230 c.Log.Warningf("GetAlertByID : %s", err) 1231 1232 return &ent.Alert{}, QueryFail 1233 } 1234 1235 return alert, nil 1236 }