github.com/crowdsecurity/crowdsec@v1.6.1/pkg/database/flush.go (about) 1 package database 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/go-co-op/gocron" 8 log "github.com/sirupsen/logrus" 9 10 "github.com/crowdsecurity/crowdsec/pkg/csconfig" 11 "github.com/crowdsecurity/crowdsec/pkg/database/ent/alert" 12 "github.com/crowdsecurity/crowdsec/pkg/database/ent/bouncer" 13 "github.com/crowdsecurity/crowdsec/pkg/database/ent/decision" 14 "github.com/crowdsecurity/crowdsec/pkg/database/ent/event" 15 "github.com/crowdsecurity/crowdsec/pkg/database/ent/machine" 16 "github.com/crowdsecurity/crowdsec/pkg/types" 17 ) 18 19 20 func (c *Client) StartFlushScheduler(config *csconfig.FlushDBCfg) (*gocron.Scheduler, error) { 21 maxItems := 0 22 maxAge := "" 23 if config.MaxItems != nil && *config.MaxItems <= 0 { 24 return nil, fmt.Errorf("max_items can't be zero or negative number") 25 } 26 if config.MaxItems != nil { 27 maxItems = *config.MaxItems 28 } 29 if config.MaxAge != nil && *config.MaxAge != "" { 30 maxAge = *config.MaxAge 31 } 32 33 // Init & Start cronjob every minute for alerts 34 scheduler := gocron.NewScheduler(time.UTC) 35 job, err := scheduler.Every(1).Minute().Do(c.FlushAlerts, maxAge, maxItems) 36 if err != nil { 37 return nil, fmt.Errorf("while starting FlushAlerts scheduler: %w", err) 38 } 39 40 job.SingletonMode() 41 // Init & Start cronjob every hour for bouncers/agents 42 if config.AgentsGC != nil { 43 if config.AgentsGC.Cert != nil { 44 duration, err := ParseDuration(*config.AgentsGC.Cert) 45 if err != nil { 46 return nil, fmt.Errorf("while parsing agents cert auto-delete duration: %w", err) 47 } 48 config.AgentsGC.CertDuration = &duration 49 } 50 if config.AgentsGC.LoginPassword != nil { 51 duration, err := ParseDuration(*config.AgentsGC.LoginPassword) 52 if err != nil { 53 return nil, fmt.Errorf("while parsing agents login/password auto-delete duration: %w", err) 54 } 55 config.AgentsGC.LoginPasswordDuration = &duration 56 } 57 if config.AgentsGC.Api != nil { 58 log.Warning("agents auto-delete for API auth is not supported (use cert or login_password)") 59 } 60 } 61 if config.BouncersGC != nil { 62 if config.BouncersGC.Cert != nil { 63 duration, err := ParseDuration(*config.BouncersGC.Cert) 64 if err != nil { 65 return nil, fmt.Errorf("while parsing bouncers cert auto-delete duration: %w", err) 66 } 67 config.BouncersGC.CertDuration = &duration 68 } 69 if config.BouncersGC.Api != nil { 70 duration, err := ParseDuration(*config.BouncersGC.Api) 71 if err != nil { 72 return nil, fmt.Errorf("while parsing bouncers api auto-delete duration: %w", err) 73 } 74 config.BouncersGC.ApiDuration = &duration 75 } 76 if config.BouncersGC.LoginPassword != nil { 77 log.Warning("bouncers auto-delete for login/password auth is not supported (use cert or api)") 78 } 79 } 80 baJob, err := scheduler.Every(1).Minute().Do(c.FlushAgentsAndBouncers, config.AgentsGC, config.BouncersGC) 81 if err != nil { 82 return nil, fmt.Errorf("while starting FlushAgentsAndBouncers scheduler: %w", err) 83 } 84 85 baJob.SingletonMode() 86 scheduler.StartAsync() 87 88 return scheduler, nil 89 } 90 91 92 func (c *Client) FlushOrphans() { 93 /* While it has only been linked to some very corner-case bug : https://github.com/crowdsecurity/crowdsec/issues/778 */ 94 /* We want to take care of orphaned events for which the parent alert/decision has been deleted */ 95 eventsCount, err := c.Ent.Event.Delete().Where(event.Not(event.HasOwner())).Exec(c.CTX) 96 if err != nil { 97 c.Log.Warningf("error while deleting orphan events: %s", err) 98 return 99 } 100 if eventsCount > 0 { 101 c.Log.Infof("%d deleted orphan events", eventsCount) 102 } 103 104 eventsCount, err = c.Ent.Decision.Delete().Where( 105 decision.Not(decision.HasOwner())).Where(decision.UntilLTE(time.Now().UTC())).Exec(c.CTX) 106 107 if err != nil { 108 c.Log.Warningf("error while deleting orphan decisions: %s", err) 109 return 110 } 111 if eventsCount > 0 { 112 c.Log.Infof("%d deleted orphan decisions", eventsCount) 113 } 114 } 115 116 func (c *Client) flushBouncers(bouncersCfg *csconfig.AuthGCCfg) { 117 if bouncersCfg == nil { 118 return 119 } 120 121 if bouncersCfg.ApiDuration != nil { 122 log.Debug("trying to delete old bouncers from api") 123 124 deletionCount, err := c.Ent.Bouncer.Delete().Where( 125 bouncer.LastPullLTE(time.Now().UTC().Add(-*bouncersCfg.ApiDuration)), 126 ).Where( 127 bouncer.AuthTypeEQ(types.ApiKeyAuthType), 128 ).Exec(c.CTX) 129 if err != nil { 130 c.Log.Errorf("while auto-deleting expired bouncers (api key): %s", err) 131 } else if deletionCount > 0 { 132 c.Log.Infof("deleted %d expired bouncers (api auth)", deletionCount) 133 } 134 } 135 136 if bouncersCfg.CertDuration != nil { 137 log.Debug("trying to delete old bouncers from cert") 138 139 deletionCount, err := c.Ent.Bouncer.Delete().Where( 140 bouncer.LastPullLTE(time.Now().UTC().Add(-*bouncersCfg.CertDuration)), 141 ).Where( 142 bouncer.AuthTypeEQ(types.TlsAuthType), 143 ).Exec(c.CTX) 144 if err != nil { 145 c.Log.Errorf("while auto-deleting expired bouncers (api key): %s", err) 146 } else if deletionCount > 0 { 147 c.Log.Infof("deleted %d expired bouncers (api auth)", deletionCount) 148 } 149 } 150 } 151 152 func (c *Client) flushAgents(agentsCfg *csconfig.AuthGCCfg) { 153 if agentsCfg == nil { 154 return 155 } 156 157 if agentsCfg.CertDuration != nil { 158 log.Debug("trying to delete old agents from cert") 159 160 deletionCount, err := c.Ent.Machine.Delete().Where( 161 machine.LastHeartbeatLTE(time.Now().UTC().Add(-*agentsCfg.CertDuration)), 162 ).Where( 163 machine.Not(machine.HasAlerts()), 164 ).Where( 165 machine.AuthTypeEQ(types.TlsAuthType), 166 ).Exec(c.CTX) 167 log.Debugf("deleted %d entries", deletionCount) 168 if err != nil { 169 c.Log.Errorf("while auto-deleting expired machine (cert): %s", err) 170 } else if deletionCount > 0 { 171 c.Log.Infof("deleted %d expired machine (cert auth)", deletionCount) 172 } 173 } 174 175 if agentsCfg.LoginPasswordDuration != nil { 176 log.Debug("trying to delete old agents from password") 177 178 deletionCount, err := c.Ent.Machine.Delete().Where( 179 machine.LastHeartbeatLTE(time.Now().UTC().Add(-*agentsCfg.LoginPasswordDuration)), 180 ).Where( 181 machine.Not(machine.HasAlerts()), 182 ).Where( 183 machine.AuthTypeEQ(types.PasswordAuthType), 184 ).Exec(c.CTX) 185 log.Debugf("deleted %d entries", deletionCount) 186 if err != nil { 187 c.Log.Errorf("while auto-deleting expired machine (password): %s", err) 188 } else if deletionCount > 0 { 189 c.Log.Infof("deleted %d expired machine (password auth)", deletionCount) 190 } 191 } 192 } 193 194 func (c *Client) FlushAgentsAndBouncers(agentsCfg *csconfig.AuthGCCfg, bouncersCfg *csconfig.AuthGCCfg) error { 195 log.Debug("starting FlushAgentsAndBouncers") 196 197 c.flushBouncers(bouncersCfg) 198 c.flushAgents(agentsCfg) 199 200 return nil 201 } 202 203 func (c *Client) FlushAlerts(MaxAge string, MaxItems int) error { 204 var deletedByAge int 205 var deletedByNbItem int 206 var totalAlerts int 207 var err error 208 209 if !c.CanFlush { 210 c.Log.Debug("a list is being imported, flushing later") 211 return nil 212 } 213 214 c.Log.Debug("Flushing orphan alerts") 215 c.FlushOrphans() 216 c.Log.Debug("Done flushing orphan alerts") 217 totalAlerts, err = c.TotalAlerts() 218 if err != nil { 219 c.Log.Warningf("FlushAlerts (max items count): %s", err) 220 return fmt.Errorf("unable to get alerts count: %w", err) 221 } 222 223 c.Log.Debugf("FlushAlerts (Total alerts): %d", totalAlerts) 224 if MaxAge != "" { 225 filter := map[string][]string{ 226 "created_before": {MaxAge}, 227 } 228 nbDeleted, err := c.DeleteAlertWithFilter(filter) 229 if err != nil { 230 c.Log.Warningf("FlushAlerts (max age): %s", err) 231 return fmt.Errorf("unable to flush alerts with filter until=%s: %w", MaxAge, err) 232 } 233 234 c.Log.Debugf("FlushAlerts (deleted max age alerts): %d", nbDeleted) 235 deletedByAge = nbDeleted 236 } 237 if MaxItems > 0 { 238 //We get the highest id for the alerts 239 //We subtract MaxItems to avoid deleting alerts that are not old enough 240 //This gives us the oldest alert that we want to keep 241 //We then delete all the alerts with an id lower than this one 242 //We can do this because the id is auto-increment, and the database won't reuse the same id twice 243 lastAlert, err := c.QueryAlertWithFilter(map[string][]string{ 244 "sort": {"DESC"}, 245 "limit": {"1"}, 246 //we do not care about fetching the edges, we just want the id 247 "with_decisions": {"false"}, 248 }) 249 c.Log.Debugf("FlushAlerts (last alert): %+v", lastAlert) 250 if err != nil { 251 c.Log.Errorf("FlushAlerts: could not get last alert: %s", err) 252 return fmt.Errorf("could not get last alert: %w", err) 253 } 254 255 if len(lastAlert) != 0 { 256 maxid := lastAlert[0].ID - MaxItems 257 258 c.Log.Debugf("FlushAlerts (max id): %d", maxid) 259 260 if maxid > 0 { 261 //This may lead to orphan alerts (at least on MySQL), but the next time the flush job will run, they will be deleted 262 deletedByNbItem, err = c.Ent.Alert.Delete().Where(alert.IDLT(maxid)).Exec(c.CTX) 263 264 if err != nil { 265 c.Log.Errorf("FlushAlerts: Could not delete alerts: %s", err) 266 return fmt.Errorf("could not delete alerts: %w", err) 267 } 268 } 269 } 270 } 271 if deletedByNbItem > 0 { 272 c.Log.Infof("flushed %d/%d alerts because the max number of alerts has been reached (%d max)", deletedByNbItem, totalAlerts, MaxItems) 273 } 274 if deletedByAge > 0 { 275 c.Log.Infof("flushed %d/%d alerts because they were created %s ago or more", deletedByAge, totalAlerts, MaxAge) 276 } 277 return nil 278 }