github.com/crowdsecurity/crowdsec@v1.6.1/cmd/crowdsec/serve.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/signal"
     7  	"runtime/pprof"
     8  	"syscall"
     9  	"time"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  	"gopkg.in/tomb.v2"
    13  
    14  	"github.com/crowdsecurity/go-cs-lib/csdaemon"
    15  	"github.com/crowdsecurity/go-cs-lib/trace"
    16  
    17  	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
    18  	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
    19  	"github.com/crowdsecurity/crowdsec/pkg/database"
    20  	"github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
    21  	leaky "github.com/crowdsecurity/crowdsec/pkg/leakybucket"
    22  	"github.com/crowdsecurity/crowdsec/pkg/types"
    23  )
    24  
    25  //nolint:deadcode,unused // debugHandler is kept as a dev convenience: it shuts down and serialize internal state
    26  func debugHandler(sig os.Signal, cConfig *csconfig.Config) error {
    27  	var (
    28  		tmpFile string
    29  		err     error
    30  	)
    31  
    32  	// stop goroutines
    33  	if err = ShutdownCrowdsecRoutines(); err != nil {
    34  		log.Warningf("Failed to shut down routines: %s", err)
    35  	}
    36  
    37  	// todo: properly stop acquis with the tail readers
    38  	if tmpFile, err = leaky.DumpBucketsStateAt(time.Now().UTC(), cConfig.Crowdsec.BucketStateDumpDir, buckets); err != nil {
    39  		log.Warningf("Failed to dump bucket state : %s", err)
    40  	}
    41  
    42  	if err := leaky.ShutdownAllBuckets(buckets); err != nil {
    43  		log.Warningf("Failed to shut down routines : %s", err)
    44  	}
    45  
    46  	log.Printf("Shutdown is finished, buckets are in %s", tmpFile)
    47  
    48  	return nil
    49  }
    50  
    51  func reloadHandler(sig os.Signal) (*csconfig.Config, error) {
    52  	var tmpFile string
    53  
    54  	// re-initialize tombs
    55  	acquisTomb = tomb.Tomb{}
    56  	parsersTomb = tomb.Tomb{}
    57  	bucketsTomb = tomb.Tomb{}
    58  	outputsTomb = tomb.Tomb{}
    59  	apiTomb = tomb.Tomb{}
    60  	crowdsecTomb = tomb.Tomb{}
    61  	pluginTomb = tomb.Tomb{}
    62  
    63  	cConfig, err := LoadConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI, false)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	if !cConfig.DisableAPI {
    69  		if flags.DisableCAPI {
    70  			log.Warningf("Communication with CrowdSec Central API disabled from args")
    71  
    72  			cConfig.API.Server.OnlineClient = nil
    73  		}
    74  
    75  		apiServer, err := initAPIServer(cConfig)
    76  		if err != nil {
    77  			return nil, fmt.Errorf("unable to init api server: %w", err)
    78  		}
    79  
    80  		serveAPIServer(apiServer)
    81  	}
    82  
    83  	if !cConfig.DisableAgent {
    84  		hub, err := cwhub.NewHub(cConfig.Hub, nil, false, log.StandardLogger())
    85  		if err != nil {
    86  			return nil, fmt.Errorf("while loading hub index: %w", err)
    87  		}
    88  
    89  		csParsers, datasources, err := initCrowdsec(cConfig, hub)
    90  		if err != nil {
    91  			return nil, fmt.Errorf("unable to init crowdsec: %w", err)
    92  		}
    93  
    94  		// restore bucket state
    95  		if tmpFile != "" {
    96  			log.Warningf("we are now using %s as a state file", tmpFile)
    97  			cConfig.Crowdsec.BucketStateFile = tmpFile
    98  		}
    99  
   100  		// reload the simulation state
   101  		if err := cConfig.LoadSimulation(); err != nil {
   102  			log.Errorf("reload error (simulation) : %s", err)
   103  		}
   104  
   105  		agentReady := make(chan bool, 1)
   106  		serveCrowdsec(csParsers, cConfig, hub, datasources, agentReady)
   107  	}
   108  
   109  	log.Printf("Reload is finished")
   110  	// delete the tmp file, it's safe now :)
   111  	if tmpFile != "" {
   112  		if err := os.Remove(tmpFile); err != nil {
   113  			log.Warningf("Failed to delete temp file (%s) : %s", tmpFile, err)
   114  		}
   115  	}
   116  
   117  	return cConfig, nil
   118  }
   119  
   120  func ShutdownCrowdsecRoutines() error {
   121  	var reterr error
   122  
   123  	log.Debugf("Shutting down crowdsec sub-routines")
   124  
   125  	if len(dataSources) > 0 {
   126  		acquisTomb.Kill(nil)
   127  		log.Debugf("waiting for acquisition to finish")
   128  		drainChan(inputLineChan)
   129  
   130  		if err := acquisTomb.Wait(); err != nil {
   131  			log.Warningf("Acquisition returned error : %s", err)
   132  			reterr = err
   133  		}
   134  	}
   135  
   136  	log.Debugf("acquisition is finished, wait for parser/bucket/ouputs.")
   137  	parsersTomb.Kill(nil)
   138  	drainChan(inputEventChan)
   139  
   140  	if err := parsersTomb.Wait(); err != nil {
   141  		log.Warningf("Parsers returned error : %s", err)
   142  		reterr = err
   143  	}
   144  
   145  	log.Debugf("parsers is done")
   146  	time.Sleep(1 * time.Second) // ugly workaround for now to ensure PourItemtoholders are finished
   147  	bucketsTomb.Kill(nil)
   148  
   149  	if err := bucketsTomb.Wait(); err != nil {
   150  		log.Warningf("Buckets returned error : %s", err)
   151  		reterr = err
   152  	}
   153  
   154  	log.Debugf("buckets is done")
   155  	time.Sleep(1 * time.Second) // ugly workaround for now
   156  	outputsTomb.Kill(nil)
   157  
   158  	done := make(chan error, 1)
   159  	go func() {
   160  		done <- outputsTomb.Wait()
   161  	}()
   162  
   163  	// wait for outputs to finish, max 3 seconds
   164  	select {
   165  	case err := <-done:
   166  		if err != nil {
   167  			log.Warningf("Outputs returned error : %s", err)
   168  			reterr = err
   169  		}
   170  
   171  		log.Debugf("outputs are done")
   172  	case <-time.After(3 * time.Second):
   173  		// this can happen if outputs are stuck in a http retry loop
   174  		log.Warningf("Outputs didn't finish in time, some events may have not been flushed")
   175  	}
   176  
   177  	// He's dead, Jim.
   178  	crowdsecTomb.Kill(nil)
   179  
   180  	return reterr
   181  }
   182  
   183  func shutdownAPI() error {
   184  	log.Debugf("shutting down api via Tomb")
   185  	apiTomb.Kill(nil)
   186  
   187  	if err := apiTomb.Wait(); err != nil {
   188  		return err
   189  	}
   190  
   191  	log.Debugf("done")
   192  
   193  	return nil
   194  }
   195  
   196  func shutdownCrowdsec() error {
   197  	log.Debugf("shutting down crowdsec via Tomb")
   198  	crowdsecTomb.Kill(nil)
   199  
   200  	if err := crowdsecTomb.Wait(); err != nil {
   201  		return err
   202  	}
   203  
   204  	log.Debugf("done")
   205  
   206  	return nil
   207  }
   208  
   209  func shutdown(sig os.Signal, cConfig *csconfig.Config) error {
   210  	if !cConfig.DisableAgent {
   211  		if err := shutdownCrowdsec(); err != nil {
   212  			return fmt.Errorf("failed to shut down crowdsec: %w", err)
   213  		}
   214  	}
   215  
   216  	if !cConfig.DisableAPI {
   217  		if err := shutdownAPI(); err != nil {
   218  			return fmt.Errorf("failed to shut down api routines: %w", err)
   219  		}
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func drainChan(c chan types.Event) {
   226  	time.Sleep(500 * time.Millisecond)
   227  	// delay to avoid draining chan before the acquisition/parser
   228  	// get a chance to push its event
   229  	// We should close the chan on the writer side rather than this
   230  	for {
   231  		select {
   232  		case _, ok := <-c:
   233  			if !ok { // closed
   234  				return
   235  			}
   236  		default:
   237  			return
   238  		}
   239  	}
   240  }
   241  
   242  func HandleSignals(cConfig *csconfig.Config) error {
   243  	var (
   244  		newConfig *csconfig.Config
   245  		err       error
   246  	)
   247  
   248  	signalChan := make(chan os.Signal, 1)
   249  
   250  	// We add os.Interrupt mostly to ease windows development,
   251  	// it allows to simulate a clean shutdown when running in the console
   252  	signal.Notify(signalChan,
   253  		syscall.SIGHUP,
   254  		syscall.SIGTERM,
   255  		os.Interrupt)
   256  
   257  	exitChan := make(chan error)
   258  
   259  	// Always try to stop CPU profiling to avoid passing flags around
   260  	// It's a noop if profiling is not enabled
   261  	defer pprof.StopCPUProfile()
   262  
   263  	go func() {
   264  		defer trace.CatchPanic("crowdsec/HandleSignals")
   265  	Loop:
   266  		for {
   267  			s := <-signalChan
   268  			switch s {
   269  			// kill -SIGHUP XXXX
   270  			case syscall.SIGHUP:
   271  				log.Warning("SIGHUP received, reloading")
   272  
   273  				if err = shutdown(s, cConfig); err != nil {
   274  					exitChan <- fmt.Errorf("failed shutdown: %w", err)
   275  
   276  					break Loop
   277  				}
   278  
   279  				if newConfig, err = reloadHandler(s); err != nil {
   280  					exitChan <- fmt.Errorf("reload handler failure: %w", err)
   281  
   282  					break Loop
   283  				}
   284  
   285  				if newConfig != nil {
   286  					cConfig = newConfig
   287  				}
   288  			// ctrl+C, kill -SIGINT XXXX, kill -SIGTERM XXXX
   289  			case os.Interrupt, syscall.SIGTERM:
   290  				log.Warning("SIGTERM received, shutting down")
   291  				if err = shutdown(s, cConfig); err != nil {
   292  					exitChan <- fmt.Errorf("failed shutdown: %w", err)
   293  
   294  					break Loop
   295  				}
   296  				exitChan <- nil
   297  			}
   298  		}
   299  	}()
   300  
   301  	err = <-exitChan
   302  	if err == nil {
   303  		log.Warning("Crowdsec service shutting down")
   304  	}
   305  
   306  	return err
   307  }
   308  
   309  func Serve(cConfig *csconfig.Config, agentReady chan bool) error {
   310  	acquisTomb = tomb.Tomb{}
   311  	parsersTomb = tomb.Tomb{}
   312  	bucketsTomb = tomb.Tomb{}
   313  	outputsTomb = tomb.Tomb{}
   314  	apiTomb = tomb.Tomb{}
   315  	crowdsecTomb = tomb.Tomb{}
   316  	pluginTomb = tomb.Tomb{}
   317  
   318  	if cConfig.API.Server != nil && cConfig.API.Server.DbConfig != nil {
   319  		dbClient, err := database.NewClient(cConfig.API.Server.DbConfig)
   320  		if err != nil {
   321  			return fmt.Errorf("failed to get database client: %w", err)
   322  		}
   323  
   324  		err = exprhelpers.Init(dbClient)
   325  		if err != nil {
   326  			return fmt.Errorf("failed to init expr helpers: %w", err)
   327  		}
   328  	} else {
   329  		err := exprhelpers.Init(nil)
   330  		if err != nil {
   331  			return fmt.Errorf("failed to init expr helpers: %w", err)
   332  		}
   333  
   334  		log.Warningln("Exprhelpers loaded without database client.")
   335  	}
   336  
   337  	if cConfig.API.CTI != nil && *cConfig.API.CTI.Enabled {
   338  		log.Infof("Crowdsec CTI helper enabled")
   339  
   340  		if err := exprhelpers.InitCrowdsecCTI(cConfig.API.CTI.Key, cConfig.API.CTI.CacheTimeout, cConfig.API.CTI.CacheSize, cConfig.API.CTI.LogLevel); err != nil {
   341  			return fmt.Errorf("failed to init crowdsec cti: %w", err)
   342  		}
   343  	}
   344  
   345  	if !cConfig.DisableAPI {
   346  		if cConfig.API.Server.OnlineClient == nil || cConfig.API.Server.OnlineClient.Credentials == nil {
   347  			log.Warningf("Communication with CrowdSec Central API disabled from configuration file")
   348  		}
   349  
   350  		if flags.DisableCAPI {
   351  			log.Warningf("Communication with CrowdSec Central API disabled from args")
   352  
   353  			cConfig.API.Server.OnlineClient = nil
   354  		}
   355  
   356  		apiServer, err := initAPIServer(cConfig)
   357  		if err != nil {
   358  			return fmt.Errorf("api server init: %w", err)
   359  		}
   360  
   361  		if !flags.TestMode {
   362  			serveAPIServer(apiServer)
   363  		}
   364  	}
   365  
   366  	if !cConfig.DisableAgent {
   367  		hub, err := cwhub.NewHub(cConfig.Hub, nil, false, log.StandardLogger())
   368  		if err != nil {
   369  			return fmt.Errorf("while loading hub index: %w", err)
   370  		}
   371  
   372  		csParsers, datasources, err := initCrowdsec(cConfig, hub)
   373  		if err != nil {
   374  			return fmt.Errorf("crowdsec init: %w", err)
   375  		}
   376  
   377  		// if it's just linting, we're done
   378  		if !flags.TestMode {
   379  			serveCrowdsec(csParsers, cConfig, hub, datasources, agentReady)
   380  		} else {
   381  			agentReady <- true
   382  		}
   383  	} else {
   384  		agentReady <- true
   385  	}
   386  
   387  	if flags.TestMode {
   388  		log.Infof("Configuration test done")
   389  		pluginBroker.Kill()
   390  		os.Exit(0)
   391  	}
   392  
   393  	if cConfig.Common != nil && cConfig.Common.Daemonize {
   394  		csdaemon.NotifySystemd(log.StandardLogger())
   395  		// wait for signals
   396  		return HandleSignals(cConfig)
   397  	}
   398  
   399  	waitChans := make([]<-chan struct{}, 0)
   400  
   401  	if !cConfig.DisableAgent {
   402  		waitChans = append(waitChans, crowdsecTomb.Dead())
   403  	}
   404  
   405  	if !cConfig.DisableAPI {
   406  		waitChans = append(waitChans, apiTomb.Dead())
   407  	}
   408  
   409  	for _, ch := range waitChans {
   410  		<-ch
   411  
   412  		switch ch {
   413  		case apiTomb.Dead():
   414  			log.Infof("api shutdown")
   415  		case crowdsecTomb.Dead():
   416  			log.Infof("crowdsec shutdown")
   417  		}
   418  	}
   419  
   420  	return nil
   421  }