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 }