123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- package main
- import (
- "os"
- "os/signal"
- "syscall"
- "time"
- "github.com/coreos/go-systemd/daemon"
- "github.com/pkg/errors"
- log "github.com/sirupsen/logrus"
- "gopkg.in/tomb.v2"
- "github.com/crowdsecurity/crowdsec/pkg/csconfig"
- "github.com/crowdsecurity/crowdsec/pkg/database"
- "github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
- leaky "github.com/crowdsecurity/crowdsec/pkg/leakybucket"
- "github.com/crowdsecurity/crowdsec/pkg/types"
- )
- //nolint: deadcode,unused // debugHandler is kept as a dev convenience: it shuts down and serialize internal state
- func debugHandler(sig os.Signal, cConfig *csconfig.Config) error {
- var (
- tmpFile string
- err error
- )
- // stop goroutines
- if err = ShutdownCrowdsecRoutines(); err != nil {
- log.Warningf("Failed to shut down routines: %s", err)
- }
- // todo: properly stop acquis with the tail readers
- if tmpFile, err = leaky.DumpBucketsStateAt(time.Now().UTC(), cConfig.Crowdsec.BucketStateDumpDir, buckets); err != nil {
- log.Warningf("Failed to dump bucket state : %s", err)
- }
- if err := leaky.ShutdownAllBuckets(buckets); err != nil {
- log.Warningf("Failed to shut down routines : %s", err)
- }
- log.Printf("Shutdown is finished, buckets are in %s", tmpFile)
- return nil
- }
- func reloadHandler(sig os.Signal) (*csconfig.Config, error) {
- var tmpFile string
- // re-initialize tombs
- acquisTomb = tomb.Tomb{}
- parsersTomb = tomb.Tomb{}
- bucketsTomb = tomb.Tomb{}
- outputsTomb = tomb.Tomb{}
- apiTomb = tomb.Tomb{}
- crowdsecTomb = tomb.Tomb{}
- pluginTomb = tomb.Tomb{}
- cConfig, err := csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI)
- if err != nil {
- return nil, err
- }
- if err = LoadConfig(cConfig); err != nil {
- return nil, err
- }
- if !cConfig.DisableAPI {
- if flags.DisableCAPI {
- log.Warningf("Communication with CrowdSec Central API disabled from args")
- cConfig.API.Server.OnlineClient = nil
- }
- apiServer, err := initAPIServer(cConfig)
- if err != nil {
- return nil, errors.Wrap(err, "unable to init api server")
- }
- apiReady := make(chan bool, 1)
- serveAPIServer(apiServer, apiReady)
- }
- if !cConfig.DisableAgent {
- csParsers, err := initCrowdsec(cConfig)
- if err != nil {
- return nil, errors.Wrap(err, "unable to init crowdsec")
- }
- // restore bucket state
- if tmpFile != "" {
- log.Warningf("we are now using %s as a state file", tmpFile)
- cConfig.Crowdsec.BucketStateFile = tmpFile
- }
- // reload the simulation state
- if err := cConfig.LoadSimulation(); err != nil {
- log.Errorf("reload error (simulation) : %s", err)
- }
- agentReady := make(chan bool, 1)
- serveCrowdsec(csParsers, cConfig, agentReady)
- }
- log.Printf("Reload is finished")
- // delete the tmp file, it's safe now :)
- if tmpFile != "" {
- if err := os.Remove(tmpFile); err != nil {
- log.Warningf("Failed to delete temp file (%s) : %s", tmpFile, err)
- }
- }
- return cConfig, nil
- }
- func ShutdownCrowdsecRoutines() error {
- var reterr error
- log.Debugf("Shutting down crowdsec sub-routines")
- if len(dataSources) > 0 {
- acquisTomb.Kill(nil)
- log.Debugf("waiting for acquisition to finish")
- if err := acquisTomb.Wait(); err != nil {
- log.Warningf("Acquisition returned error : %s", err)
- reterr = err
- }
- }
- log.Debugf("acquisition is finished, wait for parser/bucket/ouputs.")
- parsersTomb.Kill(nil)
- if err := parsersTomb.Wait(); err != nil {
- log.Warningf("Parsers returned error : %s", err)
- reterr = err
- }
- log.Debugf("parsers is done")
- time.Sleep(1 * time.Second) // ugly workaround for now to ensure PourItemtoholders are finished
- bucketsTomb.Kill(nil)
- if err := bucketsTomb.Wait(); err != nil {
- log.Warningf("Buckets returned error : %s", err)
- reterr = err
- }
- log.Debugf("buckets is done")
- time.Sleep(1 * time.Second) // ugly workaround for now
- outputsTomb.Kill(nil)
- if err := outputsTomb.Wait(); err != nil {
- log.Warningf("Ouputs returned error : %s", err)
- reterr = err
- }
- log.Debugf("outputs are done")
- // He's dead, Jim.
- crowdsecTomb.Kill(nil)
- return reterr
- }
- func shutdownAPI() error {
- log.Debugf("shutting down api via Tomb")
- apiTomb.Kill(nil)
- if err := apiTomb.Wait(); err != nil {
- return err
- }
- log.Debugf("done")
- return nil
- }
- func shutdownCrowdsec() error {
- log.Debugf("shutting down crowdsec via Tomb")
- crowdsecTomb.Kill(nil)
- if err := crowdsecTomb.Wait(); err != nil {
- return err
- }
- log.Debugf("done")
- return nil
- }
- func shutdown(sig os.Signal, cConfig *csconfig.Config) error {
- if !cConfig.DisableAgent {
- if err := shutdownCrowdsec(); err != nil {
- return errors.Wrap(err, "failed to shut down crowdsec")
- }
- }
- if !cConfig.DisableAPI {
- if err := shutdownAPI(); err != nil {
- return errors.Wrap(err, "failed to shut down api routines")
- }
- }
- return nil
- }
- func HandleSignals(cConfig *csconfig.Config) error {
- var (
- newConfig *csconfig.Config
- err error
- )
- signalChan := make(chan os.Signal, 1)
- // We add os.Interrupt mostly to ease windows development,
- // it allows to simulate a clean shutdown when running in the console
- signal.Notify(signalChan,
- syscall.SIGHUP,
- syscall.SIGTERM,
- os.Interrupt)
- exitChan := make(chan error)
- go func() {
- defer types.CatchPanic("crowdsec/HandleSignals")
- Loop:
- for {
- s := <-signalChan
- switch s {
- // kill -SIGHUP XXXX
- case syscall.SIGHUP:
- log.Warning("SIGHUP received, reloading")
- if err = shutdown(s, cConfig); err != nil {
- exitChan <- errors.Wrap(err, "failed shutdown")
- break Loop
- }
- if newConfig, err = reloadHandler(s); err != nil {
- exitChan <- errors.Wrap(err, "reload handler failure")
- break Loop
- }
- if newConfig != nil {
- cConfig = newConfig
- }
- // ctrl+C, kill -SIGINT XXXX, kill -SIGTERM XXXX
- case os.Interrupt, syscall.SIGTERM:
- log.Warning("SIGTERM received, shutting down")
- if err = shutdown(s, cConfig); err != nil {
- exitChan <- errors.Wrap(err, "failed shutdown")
- break Loop
- }
- exitChan <- nil
- }
- }
- }()
- err = <-exitChan
- if err == nil {
- log.Warning("Crowdsec service shutting down")
- }
- return err
- }
- func Serve(cConfig *csconfig.Config, apiReady chan bool, agentReady chan bool) error {
- acquisTomb = tomb.Tomb{}
- parsersTomb = tomb.Tomb{}
- bucketsTomb = tomb.Tomb{}
- outputsTomb = tomb.Tomb{}
- apiTomb = tomb.Tomb{}
- crowdsecTomb = tomb.Tomb{}
- pluginTomb = tomb.Tomb{}
- if cConfig.API.Server != nil && cConfig.API.Server.DbConfig != nil {
- dbClient, err := database.NewClient(cConfig.API.Server.DbConfig)
- if err != nil {
- return errors.Wrap(err, "failed to get database client")
- }
- err = exprhelpers.Init(dbClient)
- if err != nil {
- return errors.Wrap(err, "failed to init expr helpers")
- }
- } else {
- err := exprhelpers.Init(nil)
- if err != nil {
- return errors.Wrap(err, "failed to init expr helpers")
- }
- log.Warningln("Exprhelpers loaded without database client.")
- }
- if !cConfig.DisableAPI {
- if cConfig.API.Server.OnlineClient == nil || cConfig.API.Server.OnlineClient.Credentials == nil {
- log.Warningf("Communication with CrowdSec Central API disabled from configuration file")
- }
- if flags.DisableCAPI {
- log.Warningf("Communication with CrowdSec Central API disabled from args")
- cConfig.API.Server.OnlineClient = nil
- }
- apiServer, err := initAPIServer(cConfig)
- if err != nil {
- return errors.Wrap(err, "api server init")
- }
- if !flags.TestMode {
- serveAPIServer(apiServer, apiReady)
- }
- } else {
- apiReady <- true
- }
- if !cConfig.DisableAgent {
- csParsers, err := initCrowdsec(cConfig)
- if err != nil {
- return errors.Wrap(err, "crowdsec init")
- }
- // if it's just linting, we're done
- if !flags.TestMode {
- serveCrowdsec(csParsers, cConfig, agentReady)
- }
- } else {
- agentReady <- true
- }
- if flags.TestMode {
- log.Infof("test done")
- pluginBroker.Kill()
- os.Exit(0)
- }
- if cConfig.Common != nil && cConfig.Common.Daemonize {
- sent, err := daemon.SdNotify(false, daemon.SdNotifyReady)
- if !sent || err != nil {
- log.Errorf("Failed to notify(sent: %v): %v", sent, err)
- }
- // wait for signals
- return HandleSignals(cConfig)
- }
- for {
- select {
- case <-apiTomb.Dead():
- log.Infof("api shutdown")
- return nil
- case <-crowdsecTomb.Dead():
- log.Infof("crowdsec shutdown")
- return nil
- }
- }
- }
|