serve.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. package main
  2. import (
  3. "os"
  4. "os/signal"
  5. "syscall"
  6. "time"
  7. "github.com/coreos/go-systemd/daemon"
  8. "github.com/pkg/errors"
  9. log "github.com/sirupsen/logrus"
  10. "gopkg.in/tomb.v2"
  11. "github.com/crowdsecurity/crowdsec/pkg/csconfig"
  12. "github.com/crowdsecurity/crowdsec/pkg/database"
  13. "github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
  14. leaky "github.com/crowdsecurity/crowdsec/pkg/leakybucket"
  15. "github.com/crowdsecurity/crowdsec/pkg/types"
  16. )
  17. //nolint: deadcode,unused // debugHandler is kept as a dev convenience: it shuts down and serialize internal state
  18. func debugHandler(sig os.Signal, cConfig *csconfig.Config) error {
  19. var (
  20. tmpFile string
  21. err error
  22. )
  23. // stop goroutines
  24. if err = ShutdownCrowdsecRoutines(); err != nil {
  25. log.Warningf("Failed to shut down routines: %s", err)
  26. }
  27. // todo: properly stop acquis with the tail readers
  28. if tmpFile, err = leaky.DumpBucketsStateAt(time.Now().UTC(), cConfig.Crowdsec.BucketStateDumpDir, buckets); err != nil {
  29. log.Warningf("Failed to dump bucket state : %s", err)
  30. }
  31. if err := leaky.ShutdownAllBuckets(buckets); err != nil {
  32. log.Warningf("Failed to shut down routines : %s", err)
  33. }
  34. log.Printf("Shutdown is finished, buckets are in %s", tmpFile)
  35. return nil
  36. }
  37. func reloadHandler(sig os.Signal) (*csconfig.Config, error) {
  38. var tmpFile string
  39. // re-initialize tombs
  40. acquisTomb = tomb.Tomb{}
  41. parsersTomb = tomb.Tomb{}
  42. bucketsTomb = tomb.Tomb{}
  43. outputsTomb = tomb.Tomb{}
  44. apiTomb = tomb.Tomb{}
  45. crowdsecTomb = tomb.Tomb{}
  46. pluginTomb = tomb.Tomb{}
  47. cConfig, err := csconfig.NewConfig(flags.ConfigFile, flags.DisableAgent, flags.DisableAPI)
  48. if err != nil {
  49. return nil, err
  50. }
  51. if err = LoadConfig(cConfig); err != nil {
  52. return nil, err
  53. }
  54. if !cConfig.DisableAPI {
  55. if flags.DisableCAPI {
  56. log.Warningf("Communication with CrowdSec Central API disabled from args")
  57. cConfig.API.Server.OnlineClient = nil
  58. }
  59. apiServer, err := initAPIServer(cConfig)
  60. if err != nil {
  61. return nil, errors.Wrap(err, "unable to init api server")
  62. }
  63. apiReady := make(chan bool, 1)
  64. serveAPIServer(apiServer, apiReady)
  65. }
  66. if !cConfig.DisableAgent {
  67. csParsers, err := initCrowdsec(cConfig)
  68. if err != nil {
  69. return nil, errors.Wrap(err, "unable to init crowdsec")
  70. }
  71. // restore bucket state
  72. if tmpFile != "" {
  73. log.Warningf("we are now using %s as a state file", tmpFile)
  74. cConfig.Crowdsec.BucketStateFile = tmpFile
  75. }
  76. // reload the simulation state
  77. if err := cConfig.LoadSimulation(); err != nil {
  78. log.Errorf("reload error (simulation) : %s", err)
  79. }
  80. agentReady := make(chan bool, 1)
  81. serveCrowdsec(csParsers, cConfig, agentReady)
  82. }
  83. log.Printf("Reload is finished")
  84. // delete the tmp file, it's safe now :)
  85. if tmpFile != "" {
  86. if err := os.Remove(tmpFile); err != nil {
  87. log.Warningf("Failed to delete temp file (%s) : %s", tmpFile, err)
  88. }
  89. }
  90. return cConfig, nil
  91. }
  92. func ShutdownCrowdsecRoutines() error {
  93. var reterr error
  94. log.Debugf("Shutting down crowdsec sub-routines")
  95. if len(dataSources) > 0 {
  96. acquisTomb.Kill(nil)
  97. log.Debugf("waiting for acquisition to finish")
  98. if err := acquisTomb.Wait(); err != nil {
  99. log.Warningf("Acquisition returned error : %s", err)
  100. reterr = err
  101. }
  102. }
  103. log.Debugf("acquisition is finished, wait for parser/bucket/ouputs.")
  104. parsersTomb.Kill(nil)
  105. if err := parsersTomb.Wait(); err != nil {
  106. log.Warningf("Parsers returned error : %s", err)
  107. reterr = err
  108. }
  109. log.Debugf("parsers is done")
  110. time.Sleep(1 * time.Second) // ugly workaround for now to ensure PourItemtoholders are finished
  111. bucketsTomb.Kill(nil)
  112. if err := bucketsTomb.Wait(); err != nil {
  113. log.Warningf("Buckets returned error : %s", err)
  114. reterr = err
  115. }
  116. log.Debugf("buckets is done")
  117. time.Sleep(1 * time.Second) // ugly workaround for now
  118. outputsTomb.Kill(nil)
  119. if err := outputsTomb.Wait(); err != nil {
  120. log.Warningf("Ouputs returned error : %s", err)
  121. reterr = err
  122. }
  123. log.Debugf("outputs are done")
  124. // He's dead, Jim.
  125. crowdsecTomb.Kill(nil)
  126. return reterr
  127. }
  128. func shutdownAPI() error {
  129. log.Debugf("shutting down api via Tomb")
  130. apiTomb.Kill(nil)
  131. if err := apiTomb.Wait(); err != nil {
  132. return err
  133. }
  134. log.Debugf("done")
  135. return nil
  136. }
  137. func shutdownCrowdsec() error {
  138. log.Debugf("shutting down crowdsec via Tomb")
  139. crowdsecTomb.Kill(nil)
  140. if err := crowdsecTomb.Wait(); err != nil {
  141. return err
  142. }
  143. log.Debugf("done")
  144. return nil
  145. }
  146. func shutdown(sig os.Signal, cConfig *csconfig.Config) error {
  147. if !cConfig.DisableAgent {
  148. if err := shutdownCrowdsec(); err != nil {
  149. return errors.Wrap(err, "failed to shut down crowdsec")
  150. }
  151. }
  152. if !cConfig.DisableAPI {
  153. if err := shutdownAPI(); err != nil {
  154. return errors.Wrap(err, "failed to shut down api routines")
  155. }
  156. }
  157. return nil
  158. }
  159. func HandleSignals(cConfig *csconfig.Config) error {
  160. var (
  161. newConfig *csconfig.Config
  162. err error
  163. )
  164. signalChan := make(chan os.Signal, 1)
  165. // We add os.Interrupt mostly to ease windows development,
  166. // it allows to simulate a clean shutdown when running in the console
  167. signal.Notify(signalChan,
  168. syscall.SIGHUP,
  169. syscall.SIGTERM,
  170. os.Interrupt)
  171. exitChan := make(chan error)
  172. go func() {
  173. defer types.CatchPanic("crowdsec/HandleSignals")
  174. Loop:
  175. for {
  176. s := <-signalChan
  177. switch s {
  178. // kill -SIGHUP XXXX
  179. case syscall.SIGHUP:
  180. log.Warning("SIGHUP received, reloading")
  181. if err = shutdown(s, cConfig); err != nil {
  182. exitChan <- errors.Wrap(err, "failed shutdown")
  183. break Loop
  184. }
  185. if newConfig, err = reloadHandler(s); err != nil {
  186. exitChan <- errors.Wrap(err, "reload handler failure")
  187. break Loop
  188. }
  189. if newConfig != nil {
  190. cConfig = newConfig
  191. }
  192. // ctrl+C, kill -SIGINT XXXX, kill -SIGTERM XXXX
  193. case os.Interrupt, syscall.SIGTERM:
  194. log.Warning("SIGTERM received, shutting down")
  195. if err = shutdown(s, cConfig); err != nil {
  196. exitChan <- errors.Wrap(err, "failed shutdown")
  197. break Loop
  198. }
  199. exitChan <- nil
  200. }
  201. }
  202. }()
  203. err = <-exitChan
  204. if err == nil {
  205. log.Warning("Crowdsec service shutting down")
  206. }
  207. return err
  208. }
  209. func Serve(cConfig *csconfig.Config, apiReady chan bool, agentReady chan bool) error {
  210. acquisTomb = tomb.Tomb{}
  211. parsersTomb = tomb.Tomb{}
  212. bucketsTomb = tomb.Tomb{}
  213. outputsTomb = tomb.Tomb{}
  214. apiTomb = tomb.Tomb{}
  215. crowdsecTomb = tomb.Tomb{}
  216. pluginTomb = tomb.Tomb{}
  217. if cConfig.API.Server != nil && cConfig.API.Server.DbConfig != nil {
  218. dbClient, err := database.NewClient(cConfig.API.Server.DbConfig)
  219. if err != nil {
  220. return errors.Wrap(err, "failed to get database client")
  221. }
  222. err = exprhelpers.Init(dbClient)
  223. if err != nil {
  224. return errors.Wrap(err, "failed to init expr helpers")
  225. }
  226. } else {
  227. err := exprhelpers.Init(nil)
  228. if err != nil {
  229. return errors.Wrap(err, "failed to init expr helpers")
  230. }
  231. log.Warningln("Exprhelpers loaded without database client.")
  232. }
  233. if !cConfig.DisableAPI {
  234. if cConfig.API.Server.OnlineClient == nil || cConfig.API.Server.OnlineClient.Credentials == nil {
  235. log.Warningf("Communication with CrowdSec Central API disabled from configuration file")
  236. }
  237. if flags.DisableCAPI {
  238. log.Warningf("Communication with CrowdSec Central API disabled from args")
  239. cConfig.API.Server.OnlineClient = nil
  240. }
  241. apiServer, err := initAPIServer(cConfig)
  242. if err != nil {
  243. return errors.Wrap(err, "api server init")
  244. }
  245. if !flags.TestMode {
  246. serveAPIServer(apiServer, apiReady)
  247. }
  248. } else {
  249. apiReady <- true
  250. }
  251. if !cConfig.DisableAgent {
  252. csParsers, err := initCrowdsec(cConfig)
  253. if err != nil {
  254. return errors.Wrap(err, "crowdsec init")
  255. }
  256. // if it's just linting, we're done
  257. if !flags.TestMode {
  258. serveCrowdsec(csParsers, cConfig, agentReady)
  259. }
  260. } else {
  261. agentReady <- true
  262. }
  263. if flags.TestMode {
  264. log.Infof("test done")
  265. pluginBroker.Kill()
  266. os.Exit(0)
  267. }
  268. if cConfig.Common != nil && cConfig.Common.Daemonize {
  269. sent, err := daemon.SdNotify(false, daemon.SdNotifyReady)
  270. if !sent || err != nil {
  271. log.Errorf("Failed to notify(sent: %v): %v", sent, err)
  272. }
  273. // wait for signals
  274. return HandleSignals(cConfig)
  275. }
  276. for {
  277. select {
  278. case <-apiTomb.Dead():
  279. log.Infof("api shutdown")
  280. return nil
  281. case <-crowdsecTomb.Dead():
  282. log.Infof("crowdsec shutdown")
  283. return nil
  284. }
  285. }
  286. }