github.com/crowdsecurity/crowdsec@v1.6.1/pkg/apiserver/papi_cmd.go (about) 1 package apiserver 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "time" 7 8 log "github.com/sirupsen/logrus" 9 10 "github.com/crowdsecurity/go-cs-lib/ptr" 11 12 "github.com/crowdsecurity/crowdsec/pkg/apiclient" 13 "github.com/crowdsecurity/crowdsec/pkg/models" 14 "github.com/crowdsecurity/crowdsec/pkg/modelscapi" 15 "github.com/crowdsecurity/crowdsec/pkg/types" 16 ) 17 18 type deleteDecisions struct { 19 UUID string `json:"uuid"` 20 Decisions []string `json:"decisions"` 21 } 22 23 type blocklistLink struct { 24 // blocklist name 25 Name string `json:"name"` 26 // blocklist url 27 Url string `json:"url"` 28 // blocklist remediation 29 Remediation string `json:"remediation"` 30 // blocklist scope 31 Scope string `json:"scope,omitempty"` 32 // blocklist duration 33 Duration string `json:"duration,omitempty"` 34 } 35 36 type forcePull struct { 37 Blocklist *blocklistLink `json:"blocklist,omitempty"` 38 } 39 40 type listUnsubscribe struct { 41 Name string `json:"name"` 42 } 43 44 func DecisionCmd(message *Message, p *Papi, sync bool) error { 45 switch message.Header.OperationCmd { 46 case "delete": 47 data, err := json.Marshal(message.Data) 48 if err != nil { 49 return err 50 } 51 52 UUIDs := make([]string, 0) 53 deleteDecisionMsg := deleteDecisions{ 54 Decisions: make([]string, 0), 55 } 56 57 if err := json.Unmarshal(data, &deleteDecisionMsg); err != nil { 58 return fmt.Errorf("message for '%s' contains bad data format: %w", message.Header.OperationType, err) 59 } 60 61 UUIDs = append(UUIDs, deleteDecisionMsg.Decisions...) 62 log.Infof("Decisions UUIDs to remove: %+v", UUIDs) 63 64 filter := make(map[string][]string) 65 filter["uuid"] = UUIDs 66 _, deletedDecisions, err := p.DBClient.SoftDeleteDecisionsWithFilter(filter) 67 68 if err != nil { 69 return fmt.Errorf("unable to delete decisions %+v: %w", UUIDs, err) 70 } 71 72 decisions := make([]*models.Decision, 0) 73 74 for _, deletedDecision := range deletedDecisions { 75 log.Infof("Decision from '%s' for '%s' (%s) has been deleted", deletedDecision.Origin, deletedDecision.Value, deletedDecision.Type) 76 dec := &models.Decision{ 77 UUID: deletedDecision.UUID, 78 Origin: &deletedDecision.Origin, 79 Scenario: &deletedDecision.Scenario, 80 Scope: &deletedDecision.Scope, 81 Value: &deletedDecision.Value, 82 ID: int64(deletedDecision.ID), 83 Until: deletedDecision.Until.String(), 84 Type: &deletedDecision.Type, 85 } 86 decisions = append(decisions, dec) 87 } 88 p.Channels.DeleteDecisionChannel <- decisions 89 default: 90 return fmt.Errorf("unknown command '%s' for operation type '%s'", message.Header.OperationCmd, message.Header.OperationType) 91 } 92 93 return nil 94 } 95 96 func AlertCmd(message *Message, p *Papi, sync bool) error { 97 switch message.Header.OperationCmd { 98 case "add": 99 data, err := json.Marshal(message.Data) 100 if err != nil { 101 return err 102 } 103 104 alert := &models.Alert{} 105 106 if err := json.Unmarshal(data, alert); err != nil { 107 return fmt.Errorf("message for '%s' contains bad alert format: %w", message.Header.OperationType, err) 108 } 109 110 log.Infof("Received order %s from PAPI (%d decisions)", alert.UUID, len(alert.Decisions)) 111 112 /*Fix the alert with missing mandatory items*/ 113 if alert.StartAt == nil || *alert.StartAt == "" { 114 log.Warnf("Alert %d has no StartAt, setting it to now", alert.ID) 115 alert.StartAt = ptr.Of(time.Now().UTC().Format(time.RFC3339)) 116 } 117 118 if alert.StopAt == nil || *alert.StopAt == "" { 119 log.Warnf("Alert %d has no StopAt, setting it to now", alert.ID) 120 alert.StopAt = ptr.Of(time.Now().UTC().Format(time.RFC3339)) 121 } 122 123 alert.EventsCount = ptr.Of(int32(0)) 124 alert.Capacity = ptr.Of(int32(0)) 125 alert.Leakspeed = ptr.Of("") 126 alert.Simulated = ptr.Of(false) 127 alert.ScenarioHash = ptr.Of("") 128 alert.ScenarioVersion = ptr.Of("") 129 alert.Message = ptr.Of("") 130 alert.Scenario = ptr.Of("") 131 alert.Source = &models.Source{} 132 133 //if we're setting Source.Scope to types.ConsoleOrigin, it messes up the alert's value 134 if len(alert.Decisions) >= 1 { 135 alert.Source.Scope = alert.Decisions[0].Scope 136 alert.Source.Value = alert.Decisions[0].Value 137 } else { 138 log.Warningf("No decision found in alert for Polling API (%s : %s)", message.Header.Source.User, message.Header.Message) 139 alert.Source.Scope = ptr.Of(types.ConsoleOrigin) 140 alert.Source.Value = &message.Header.Source.User 141 } 142 143 alert.Scenario = &message.Header.Message 144 145 for _, decision := range alert.Decisions { 146 if *decision.Scenario == "" { 147 decision.Scenario = &message.Header.Message 148 } 149 150 log.Infof("Adding decision for '%s' with UUID: %s", *decision.Value, decision.UUID) 151 } 152 153 //use a different method : alert and/or decision might already be partially present in the database 154 _, err = p.DBClient.CreateOrUpdateAlert("", alert) 155 if err != nil { 156 log.Errorf("Failed to create alerts in DB: %s", err) 157 } else { 158 p.Channels.AddAlertChannel <- []*models.Alert{alert} 159 } 160 161 default: 162 return fmt.Errorf("unknown command '%s' for operation type '%s'", message.Header.OperationCmd, message.Header.OperationType) 163 } 164 165 return nil 166 } 167 168 func ManagementCmd(message *Message, p *Papi, sync bool) error { 169 if sync { 170 p.Logger.Infof("Ignoring management command from PAPI in sync mode") 171 return nil 172 } 173 174 switch message.Header.OperationCmd { 175 176 case "blocklist_unsubscribe": 177 data, err := json.Marshal(message.Data) 178 if err != nil { 179 return err 180 } 181 unsubscribeMsg := listUnsubscribe{} 182 if err := json.Unmarshal(data, &unsubscribeMsg); err != nil { 183 return fmt.Errorf("message for '%s' contains bad data format: %s", message.Header.OperationType, err) 184 } 185 if unsubscribeMsg.Name == "" { 186 return fmt.Errorf("message for '%s' contains bad data format: missing blocklist name", message.Header.OperationType) 187 } 188 p.Logger.Infof("Received blocklist_unsubscribe command from PAPI, unsubscribing from blocklist %s", unsubscribeMsg.Name) 189 190 filter := make(map[string][]string) 191 filter["origin"] = []string{types.ListOrigin} 192 filter["scenario"] = []string{unsubscribeMsg.Name} 193 194 _, deletedDecisions, err := p.DBClient.SoftDeleteDecisionsWithFilter(filter) 195 if err != nil { 196 return fmt.Errorf("unable to delete decisions for list %s : %w", unsubscribeMsg.Name, err) 197 } 198 p.Logger.Infof("deleted %d decisions for list %s", len(deletedDecisions), unsubscribeMsg.Name) 199 200 case "reauth": 201 p.Logger.Infof("Received reauth command from PAPI, resetting token") 202 p.apiClient.GetClient().Transport.(*apiclient.JWTTransport).ResetToken() 203 case "force_pull": 204 data, err := json.Marshal(message.Data) 205 if err != nil { 206 return err 207 } 208 forcePullMsg := forcePull{} 209 if err := json.Unmarshal(data, &forcePullMsg); err != nil { 210 return fmt.Errorf("message for '%s' contains bad data format: %s", message.Header.OperationType, err) 211 } 212 213 if forcePullMsg.Blocklist == nil { 214 p.Logger.Infof("Received force_pull command from PAPI, pulling community and 3rd-party blocklists") 215 err = p.apic.PullTop(true) 216 if err != nil { 217 return fmt.Errorf("failed to force pull operation: %s", err) 218 } 219 } else { 220 p.Logger.Infof("Received force_pull command from PAPI, pulling blocklist %s", forcePullMsg.Blocklist.Name) 221 err = p.apic.PullBlocklist(&modelscapi.BlocklistLink{ 222 Name: &forcePullMsg.Blocklist.Name, 223 URL: &forcePullMsg.Blocklist.Url, 224 Remediation: &forcePullMsg.Blocklist.Remediation, 225 Scope: &forcePullMsg.Blocklist.Scope, 226 Duration: &forcePullMsg.Blocklist.Duration, 227 }, true) 228 if err != nil { 229 return fmt.Errorf("failed to force pull operation: %w", err) 230 } 231 } 232 default: 233 return fmt.Errorf("unknown command '%s' for operation type '%s'", message.Header.OperationCmd, message.Header.OperationType) 234 } 235 236 return nil 237 }