apiserver.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. package apiserver
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "crypto/x509"
  6. "fmt"
  7. "io"
  8. "net"
  9. "net/http"
  10. "os"
  11. "strings"
  12. "time"
  13. "github.com/crowdsecurity/crowdsec/pkg/apiclient"
  14. "github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers"
  15. v1 "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
  16. "github.com/crowdsecurity/crowdsec/pkg/csconfig"
  17. "github.com/crowdsecurity/crowdsec/pkg/csplugin"
  18. "github.com/crowdsecurity/crowdsec/pkg/database"
  19. "github.com/crowdsecurity/crowdsec/pkg/fflag"
  20. "github.com/crowdsecurity/crowdsec/pkg/types"
  21. "github.com/gin-gonic/gin"
  22. "github.com/go-co-op/gocron"
  23. "github.com/golang-jwt/jwt/v4"
  24. "github.com/pkg/errors"
  25. log "github.com/sirupsen/logrus"
  26. "gopkg.in/natefinch/lumberjack.v2"
  27. "gopkg.in/tomb.v2"
  28. )
  29. var (
  30. keyLength = 32
  31. )
  32. type APIServer struct {
  33. URL string
  34. TLS *csconfig.TLSCfg
  35. dbClient *database.Client
  36. logFile string
  37. controller *controllers.Controller
  38. flushScheduler *gocron.Scheduler
  39. router *gin.Engine
  40. httpServer *http.Server
  41. apic *apic
  42. papi *Papi
  43. httpServerTomb tomb.Tomb
  44. consoleConfig *csconfig.ConsoleConfig
  45. isEnrolled bool
  46. }
  47. // RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
  48. func CustomRecoveryWithWriter() gin.HandlerFunc {
  49. return func(c *gin.Context) {
  50. defer func() {
  51. if err := recover(); err != nil {
  52. // Check for a broken connection, as it is not really a
  53. // condition that warrants a panic stack trace.
  54. var brokenPipe bool
  55. if ne, ok := err.(*net.OpError); ok {
  56. if se, ok := ne.Err.(*os.SyscallError); ok {
  57. if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
  58. brokenPipe = true
  59. }
  60. }
  61. }
  62. // because of https://github.com/golang/net/blob/39120d07d75e76f0079fe5d27480bcb965a21e4c/http2/server.go
  63. // and because it seems gin doesn't handle those neither, we need to "hand define" some errors to properly catch them
  64. if strErr, ok := err.(error); ok {
  65. //stolen from http2/server.go in x/net
  66. var (
  67. errClientDisconnected = errors.New("client disconnected")
  68. errClosedBody = errors.New("body closed by handler")
  69. errHandlerComplete = errors.New("http2: request body closed due to handler exiting")
  70. errStreamClosed = errors.New("http2: stream closed")
  71. )
  72. if errors.Is(strErr, errClientDisconnected) ||
  73. errors.Is(strErr, errClosedBody) ||
  74. errors.Is(strErr, errHandlerComplete) ||
  75. errors.Is(strErr, errStreamClosed) {
  76. brokenPipe = true
  77. }
  78. }
  79. if brokenPipe {
  80. log.Warningf("client %s disconnected : %s", c.ClientIP(), err)
  81. c.Abort()
  82. } else {
  83. filename := types.WriteStackTrace(err)
  84. log.Warningf("client %s error : %s", c.ClientIP(), err)
  85. log.Warningf("stacktrace written to %s, please join to your issue", filename)
  86. c.AbortWithStatus(http.StatusInternalServerError)
  87. }
  88. }
  89. }()
  90. c.Next()
  91. }
  92. }
  93. func NewServer(config *csconfig.LocalApiServerCfg) (*APIServer, error) {
  94. var flushScheduler *gocron.Scheduler
  95. dbClient, err := database.NewClient(config.DbConfig)
  96. if err != nil {
  97. return &APIServer{}, errors.Wrap(err, "unable to init database client")
  98. }
  99. if config.DbConfig.Flush != nil {
  100. flushScheduler, err = dbClient.StartFlushScheduler(config.DbConfig.Flush)
  101. if err != nil {
  102. return &APIServer{}, err
  103. }
  104. }
  105. logFile := ""
  106. if config.LogMedia == "file" {
  107. logFile = fmt.Sprintf("%s/crowdsec_api.log", config.LogDir)
  108. }
  109. if log.GetLevel() < log.DebugLevel {
  110. gin.SetMode(gin.ReleaseMode)
  111. }
  112. log.Debugf("starting router, logging to %s", logFile)
  113. router := gin.New()
  114. if config.TrustedProxies != nil && config.UseForwardedForHeaders {
  115. if err := router.SetTrustedProxies(*config.TrustedProxies); err != nil {
  116. return &APIServer{}, errors.Wrap(err, "while setting trusted_proxies")
  117. }
  118. router.ForwardedByClientIP = true
  119. } else {
  120. router.ForwardedByClientIP = false
  121. }
  122. /*The logger that will be used by handlers*/
  123. clog := log.New()
  124. if err := types.ConfigureLogger(clog); err != nil {
  125. return nil, errors.Wrap(err, "while configuring gin logger")
  126. }
  127. if config.LogLevel != nil {
  128. clog.SetLevel(*config.LogLevel)
  129. }
  130. /*Configure logs*/
  131. if logFile != "" {
  132. _maxsize := 500
  133. if config.LogMaxSize != 0 {
  134. _maxsize = config.LogMaxSize
  135. }
  136. _maxfiles := 3
  137. if config.LogMaxFiles != 0 {
  138. _maxfiles = config.LogMaxFiles
  139. }
  140. _maxage := 28
  141. if config.LogMaxAge != 0 {
  142. _maxage = config.LogMaxAge
  143. }
  144. _compress := true
  145. if config.CompressLogs != nil {
  146. _compress = *config.CompressLogs
  147. }
  148. /*cf. https://github.com/natefinch/lumberjack/issues/82
  149. let's create the file beforehand w/ the right perms */
  150. // check if file exists
  151. _, err := os.Stat(logFile)
  152. // create file if not exists, purposefully ignore errors
  153. if os.IsNotExist(err) {
  154. file, _ := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE, 0600)
  155. file.Close()
  156. }
  157. LogOutput := &lumberjack.Logger{
  158. Filename: logFile,
  159. MaxSize: _maxsize, //megabytes
  160. MaxBackups: _maxfiles,
  161. MaxAge: _maxage, //days
  162. Compress: _compress, //disabled by default
  163. }
  164. clog.SetOutput(LogOutput)
  165. }
  166. gin.DefaultErrorWriter = clog.WriterLevel(log.ErrorLevel)
  167. gin.DefaultWriter = clog.Writer()
  168. router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
  169. return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
  170. param.ClientIP,
  171. param.TimeStamp.Format(time.RFC1123),
  172. param.Method,
  173. param.Path,
  174. param.Request.Proto,
  175. param.StatusCode,
  176. param.Latency,
  177. param.Request.UserAgent(),
  178. param.ErrorMessage,
  179. )
  180. }))
  181. router.NoRoute(func(c *gin.Context) {
  182. c.JSON(http.StatusNotFound, gin.H{"message": "Page or Method not found"})
  183. })
  184. router.Use(CustomRecoveryWithWriter())
  185. controller := &controllers.Controller{
  186. DBClient: dbClient,
  187. Ectx: context.Background(),
  188. Router: router,
  189. Profiles: config.Profiles,
  190. Log: clog,
  191. ConsoleConfig: config.ConsoleConfig,
  192. DisableRemoteLapiRegistration: config.DisableRemoteLapiRegistration,
  193. }
  194. var apiClient *apic
  195. var papiClient *Papi
  196. var isMachineEnrolled = false
  197. if config.OnlineClient != nil && config.OnlineClient.Credentials != nil {
  198. log.Printf("Loading CAPI manager")
  199. apiClient, err = NewAPIC(config.OnlineClient, dbClient, config.ConsoleConfig, config.CapiWhitelists)
  200. if err != nil {
  201. return &APIServer{}, err
  202. }
  203. log.Infof("CAPI manager configured successfully")
  204. isMachineEnrolled = isEnrolled(apiClient.apiClient)
  205. controller.AlertsAddChan = apiClient.AlertsAddChan
  206. if fflag.PapiClient.IsEnabled() {
  207. if isMachineEnrolled {
  208. log.Infof("Machine is enrolled in the console, Loading PAPI Client")
  209. papiClient, err = NewPAPI(apiClient, dbClient, config.ConsoleConfig, *config.PapiLogLevel)
  210. if err != nil {
  211. return &APIServer{}, err
  212. }
  213. controller.DecisionDeleteChan = papiClient.Channels.DeleteDecisionChannel
  214. } else {
  215. log.Errorf("Machine is not enrolled in the console, can't synchronize with the console")
  216. }
  217. }
  218. } else {
  219. apiClient = nil
  220. controller.AlertsAddChan = nil
  221. controller.DecisionDeleteChan = nil
  222. }
  223. if trustedIPs, err := config.GetTrustedIPs(); err == nil {
  224. controller.TrustedIPs = trustedIPs
  225. } else {
  226. return &APIServer{}, err
  227. }
  228. return &APIServer{
  229. URL: config.ListenURI,
  230. TLS: config.TLS,
  231. logFile: logFile,
  232. dbClient: dbClient,
  233. controller: controller,
  234. flushScheduler: flushScheduler,
  235. router: router,
  236. apic: apiClient,
  237. papi: papiClient,
  238. httpServerTomb: tomb.Tomb{},
  239. consoleConfig: config.ConsoleConfig,
  240. isEnrolled: isMachineEnrolled,
  241. }, nil
  242. }
  243. func isEnrolled(client *apiclient.ApiClient) bool {
  244. apiHTTPClient := client.GetClient()
  245. jwtTransport := apiHTTPClient.Transport.(*apiclient.JWTTransport)
  246. tokenStr := jwtTransport.Token
  247. token, _ := jwt.Parse(tokenStr, nil)
  248. if token == nil {
  249. return false
  250. }
  251. claims := token.Claims.(jwt.MapClaims)
  252. _, ok := claims["organization_id"]
  253. return ok
  254. }
  255. func (s *APIServer) Router() (*gin.Engine, error) {
  256. return s.router, nil
  257. }
  258. func (s *APIServer) GetTLSConfig() (*tls.Config, error) {
  259. var caCert []byte
  260. var err error
  261. var caCertPool *x509.CertPool
  262. var clientAuthType tls.ClientAuthType
  263. if s.TLS == nil {
  264. return &tls.Config{}, nil
  265. }
  266. if s.TLS.ClientVerification == "" {
  267. //sounds like a sane default : verify client cert if given, but don't make it mandatory
  268. clientAuthType = tls.VerifyClientCertIfGiven
  269. } else {
  270. clientAuthType, err = getTLSAuthType(s.TLS.ClientVerification)
  271. if err != nil {
  272. return nil, err
  273. }
  274. }
  275. if s.TLS.CACertPath != "" {
  276. if clientAuthType > tls.RequestClientCert {
  277. log.Infof("(tls) Client Auth Type set to %s", clientAuthType.String())
  278. caCert, err = os.ReadFile(s.TLS.CACertPath)
  279. if err != nil {
  280. return nil, errors.Wrap(err, "Error opening cert file")
  281. }
  282. caCertPool = x509.NewCertPool()
  283. caCertPool.AppendCertsFromPEM(caCert)
  284. }
  285. }
  286. return &tls.Config{
  287. ServerName: s.TLS.ServerName, //should it be removed ?
  288. ClientAuth: clientAuthType,
  289. ClientCAs: caCertPool,
  290. MinVersion: tls.VersionTLS12, // TLS versions below 1.2 are considered insecure - see https://www.rfc-editor.org/rfc/rfc7525.txt for details
  291. }, nil
  292. }
  293. func (s *APIServer) Run(apiReady chan bool) error {
  294. defer types.CatchPanic("lapi/runServer")
  295. tlsCfg, err := s.GetTLSConfig()
  296. if err != nil {
  297. return errors.Wrap(err, "while creating TLS config")
  298. }
  299. s.httpServer = &http.Server{
  300. Addr: s.URL,
  301. Handler: s.router,
  302. TLSConfig: tlsCfg,
  303. }
  304. if s.apic != nil {
  305. s.apic.pushTomb.Go(func() error {
  306. if err := s.apic.Push(); err != nil {
  307. log.Errorf("capi push: %s", err)
  308. return err
  309. }
  310. return nil
  311. })
  312. s.apic.pullTomb.Go(func() error {
  313. if err := s.apic.Pull(); err != nil {
  314. log.Errorf("capi pull: %s", err)
  315. return err
  316. }
  317. return nil
  318. })
  319. //csConfig.API.Server.ConsoleConfig.ShareCustomScenarios
  320. if s.isEnrolled {
  321. if fflag.PapiClient.IsEnabled() {
  322. if s.consoleConfig.ConsoleManagement != nil && *s.consoleConfig.ConsoleManagement {
  323. if s.papi.URL != "" {
  324. log.Infof("Starting PAPI decision receiver")
  325. s.papi.pullTomb.Go(func() error {
  326. if err := s.papi.Pull(); err != nil {
  327. log.Errorf("papi pull: %s", err)
  328. return err
  329. }
  330. return nil
  331. })
  332. s.papi.syncTomb.Go(func() error {
  333. if err := s.papi.SyncDecisions(); err != nil {
  334. log.Errorf("capi decisions sync: %s", err)
  335. return err
  336. }
  337. return nil
  338. })
  339. } else {
  340. log.Warnf("papi_url is not set in online_api_credentials.yaml, can't synchronize with the console. Run cscli console enable console_management to add it.")
  341. }
  342. } else {
  343. log.Warningf("Machine is not allowed to synchronize decisions, you can enable it with `cscli console enable console_management`")
  344. }
  345. }
  346. }
  347. s.apic.metricsTomb.Go(func() error {
  348. s.apic.SendMetrics(make(chan bool))
  349. return nil
  350. })
  351. }
  352. s.httpServerTomb.Go(func() error {
  353. go func() {
  354. apiReady <- true
  355. log.Infof("CrowdSec Local API listening on %s", s.URL)
  356. if s.TLS != nil && (s.TLS.CertFilePath != "" || s.TLS.KeyFilePath != "") {
  357. if s.TLS.KeyFilePath == "" {
  358. log.Fatalf("while serving local API: %v", errors.New("missing TLS key file"))
  359. } else if s.TLS.CertFilePath == "" {
  360. log.Fatalf("while serving local API: %v", errors.New("missing TLS cert file"))
  361. }
  362. if err := s.httpServer.ListenAndServeTLS(s.TLS.CertFilePath, s.TLS.KeyFilePath); err != nil {
  363. log.Fatalf("while serving local API: %v", err)
  364. }
  365. } else {
  366. if err := s.httpServer.ListenAndServe(); err != http.ErrServerClosed {
  367. log.Fatalf("while serving local API: %v", err)
  368. }
  369. }
  370. }()
  371. <-s.httpServerTomb.Dying()
  372. return nil
  373. })
  374. return nil
  375. }
  376. func (s *APIServer) Close() {
  377. if s.apic != nil {
  378. s.apic.Shutdown() // stop apic first since it use dbClient
  379. }
  380. s.dbClient.Ent.Close()
  381. if s.flushScheduler != nil {
  382. s.flushScheduler.Stop()
  383. }
  384. }
  385. func (s *APIServer) Shutdown() error {
  386. s.Close()
  387. if s.httpServer != nil {
  388. if err := s.httpServer.Shutdown(context.TODO()); err != nil {
  389. return err
  390. }
  391. }
  392. //close io.writer logger given to gin
  393. if pipe, ok := gin.DefaultErrorWriter.(*io.PipeWriter); ok {
  394. pipe.Close()
  395. }
  396. if pipe, ok := gin.DefaultWriter.(*io.PipeWriter); ok {
  397. pipe.Close()
  398. }
  399. s.httpServerTomb.Kill(nil)
  400. if err := s.httpServerTomb.Wait(); err != nil {
  401. return errors.Wrap(err, "while waiting on httpServerTomb")
  402. }
  403. return nil
  404. }
  405. func (s *APIServer) AttachPluginBroker(broker *csplugin.PluginBroker) {
  406. s.controller.PluginChannel = broker.PluginChannel
  407. }
  408. func (s *APIServer) InitController() error {
  409. err := s.controller.Init()
  410. if err != nil {
  411. return errors.Wrap(err, "controller init")
  412. }
  413. if s.TLS != nil {
  414. var cacheExpiration time.Duration
  415. if s.TLS.CacheExpiration != nil {
  416. cacheExpiration = *s.TLS.CacheExpiration
  417. } else {
  418. cacheExpiration = time.Hour
  419. }
  420. s.controller.HandlerV1.Middlewares.JWT.TlsAuth, err = v1.NewTLSAuth(s.TLS.AllowedAgentsOU, s.TLS.CRLPath,
  421. cacheExpiration,
  422. log.WithFields(log.Fields{
  423. "component": "tls-auth",
  424. "type": "agent",
  425. }))
  426. if err != nil {
  427. return errors.Wrap(err, "while creating TLS auth for agents")
  428. }
  429. s.controller.HandlerV1.Middlewares.APIKey.TlsAuth, err = v1.NewTLSAuth(s.TLS.AllowedBouncersOU, s.TLS.CRLPath,
  430. cacheExpiration,
  431. log.WithFields(log.Fields{
  432. "component": "tls-auth",
  433. "type": "bouncer",
  434. }))
  435. if err != nil {
  436. return errors.Wrap(err, "while creating TLS auth for bouncers")
  437. }
  438. }
  439. return err
  440. }