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  }