lapi.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "net/url"
  6. "os"
  7. "sort"
  8. "strings"
  9. "github.com/go-openapi/strfmt"
  10. log "github.com/sirupsen/logrus"
  11. "github.com/spf13/cobra"
  12. "golang.org/x/exp/slices"
  13. "gopkg.in/yaml.v2"
  14. "github.com/crowdsecurity/go-cs-lib/version"
  15. "github.com/crowdsecurity/crowdsec/pkg/alertcontext"
  16. "github.com/crowdsecurity/crowdsec/pkg/apiclient"
  17. "github.com/crowdsecurity/crowdsec/pkg/csconfig"
  18. "github.com/crowdsecurity/crowdsec/pkg/cwhub"
  19. "github.com/crowdsecurity/crowdsec/pkg/exprhelpers"
  20. "github.com/crowdsecurity/crowdsec/pkg/models"
  21. "github.com/crowdsecurity/crowdsec/pkg/parser"
  22. )
  23. var LAPIURLPrefix string = "v1"
  24. func runLapiStatus(cmd *cobra.Command, args []string) error {
  25. var err error
  26. password := strfmt.Password(csConfig.API.Client.Credentials.Password)
  27. apiurl, err := url.Parse(csConfig.API.Client.Credentials.URL)
  28. login := csConfig.API.Client.Credentials.Login
  29. if err != nil {
  30. log.Fatalf("parsing api url ('%s'): %s", apiurl, err)
  31. }
  32. if err := csConfig.LoadHub(); err != nil {
  33. log.Fatal(err)
  34. }
  35. if err := cwhub.GetHubIdx(csConfig.Hub); err != nil {
  36. log.Info("Run 'sudo cscli hub update' to get the hub index")
  37. log.Fatalf("Failed to load hub index : %s", err)
  38. }
  39. scenarios, err := cwhub.GetInstalledScenariosAsString()
  40. if err != nil {
  41. log.Fatalf("failed to get scenarios : %s", err)
  42. }
  43. Client, err = apiclient.NewDefaultClient(apiurl,
  44. LAPIURLPrefix,
  45. fmt.Sprintf("crowdsec/%s", version.String()),
  46. nil)
  47. if err != nil {
  48. log.Fatalf("init default client: %s", err)
  49. }
  50. t := models.WatcherAuthRequest{
  51. MachineID: &login,
  52. Password: &password,
  53. Scenarios: scenarios,
  54. }
  55. log.Infof("Loaded credentials from %s", csConfig.API.Client.CredentialsFilePath)
  56. log.Infof("Trying to authenticate with username %s on %s", login, apiurl)
  57. _, _, err = Client.Auth.AuthenticateWatcher(context.Background(), t)
  58. if err != nil {
  59. log.Fatalf("Failed to authenticate to Local API (LAPI) : %s", err)
  60. } else {
  61. log.Infof("You can successfully interact with Local API (LAPI)")
  62. }
  63. return nil
  64. }
  65. func runLapiRegister(cmd *cobra.Command, args []string) error {
  66. var err error
  67. flags := cmd.Flags()
  68. apiURL, err := flags.GetString("url")
  69. if err != nil {
  70. return err
  71. }
  72. outputFile, err := flags.GetString("file")
  73. if err != nil {
  74. return err
  75. }
  76. lapiUser, err := flags.GetString("machine")
  77. if err != nil {
  78. return err
  79. }
  80. if lapiUser == "" {
  81. lapiUser, err = generateID("")
  82. if err != nil {
  83. log.Fatalf("unable to generate machine id: %s", err)
  84. }
  85. }
  86. password := strfmt.Password(generatePassword(passwordLength))
  87. if apiURL == "" {
  88. if csConfig.API.Client != nil && csConfig.API.Client.Credentials != nil && csConfig.API.Client.Credentials.URL != "" {
  89. apiURL = csConfig.API.Client.Credentials.URL
  90. } else {
  91. log.Fatalf("No Local API URL. Please provide it in your configuration or with the -u parameter")
  92. }
  93. }
  94. /*URL needs to end with /, but user doesn't care*/
  95. if !strings.HasSuffix(apiURL, "/") {
  96. apiURL += "/"
  97. }
  98. /*URL needs to start with http://, but user doesn't care*/
  99. if !strings.HasPrefix(apiURL, "http://") && !strings.HasPrefix(apiURL, "https://") {
  100. apiURL = "http://" + apiURL
  101. }
  102. apiurl, err := url.Parse(apiURL)
  103. if err != nil {
  104. log.Fatalf("parsing api url: %s", err)
  105. }
  106. _, err = apiclient.RegisterClient(&apiclient.Config{
  107. MachineID: lapiUser,
  108. Password: password,
  109. UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
  110. URL: apiurl,
  111. VersionPrefix: LAPIURLPrefix,
  112. }, nil)
  113. if err != nil {
  114. log.Fatalf("api client register: %s", err)
  115. }
  116. log.Printf("Successfully registered to Local API (LAPI)")
  117. var dumpFile string
  118. if outputFile != "" {
  119. dumpFile = outputFile
  120. } else if csConfig.API.Client.CredentialsFilePath != "" {
  121. dumpFile = csConfig.API.Client.CredentialsFilePath
  122. } else {
  123. dumpFile = ""
  124. }
  125. apiCfg := csconfig.ApiCredentialsCfg{
  126. Login: lapiUser,
  127. Password: password.String(),
  128. URL: apiURL,
  129. }
  130. apiConfigDump, err := yaml.Marshal(apiCfg)
  131. if err != nil {
  132. log.Fatalf("unable to marshal api credentials: %s", err)
  133. }
  134. if dumpFile != "" {
  135. err = os.WriteFile(dumpFile, apiConfigDump, 0644)
  136. if err != nil {
  137. log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err)
  138. }
  139. log.Printf("Local API credentials dumped to '%s'", dumpFile)
  140. } else {
  141. fmt.Printf("%s\n", string(apiConfigDump))
  142. }
  143. log.Warning(ReloadMessage())
  144. return nil
  145. }
  146. func NewLapiStatusCmd() *cobra.Command {
  147. cmdLapiStatus := &cobra.Command{
  148. Use: "status",
  149. Short: "Check authentication to Local API (LAPI)",
  150. Args: cobra.MinimumNArgs(0),
  151. DisableAutoGenTag: true,
  152. RunE: runLapiStatus,
  153. }
  154. return cmdLapiStatus
  155. }
  156. func NewLapiRegisterCmd() *cobra.Command {
  157. cmdLapiRegister := &cobra.Command{
  158. Use: "register",
  159. Short: "Register a machine to Local API (LAPI)",
  160. Long: `Register your machine to the Local API (LAPI).
  161. Keep in mind the machine needs to be validated by an administrator on LAPI side to be effective.`,
  162. Args: cobra.MinimumNArgs(0),
  163. DisableAutoGenTag: true,
  164. RunE: runLapiRegister,
  165. }
  166. flags := cmdLapiRegister.Flags()
  167. flags.StringP("url", "u", "", "URL of the API (ie. http://127.0.0.1)")
  168. flags.StringP("file", "f", "", "output file destination")
  169. flags.String("machine", "", "Name of the machine to register with")
  170. return cmdLapiRegister
  171. }
  172. func NewLapiCmd() *cobra.Command {
  173. var cmdLapi = &cobra.Command{
  174. Use: "lapi [action]",
  175. Short: "Manage interaction with Local API (LAPI)",
  176. Args: cobra.MinimumNArgs(1),
  177. DisableAutoGenTag: true,
  178. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  179. if err := csConfig.LoadAPIClient(); err != nil {
  180. return fmt.Errorf("loading api client: %w", err)
  181. }
  182. return nil
  183. },
  184. }
  185. cmdLapi.AddCommand(NewLapiRegisterCmd())
  186. cmdLapi.AddCommand(NewLapiStatusCmd())
  187. cmdLapi.AddCommand(NewLapiContextCmd())
  188. return cmdLapi
  189. }
  190. func AddContext(key string, values []string) error {
  191. if err := alertcontext.ValidateContextExpr(key, values); err != nil {
  192. return fmt.Errorf("invalid context configuration :%s", err)
  193. }
  194. if _, ok := csConfig.Crowdsec.ContextToSend[key]; !ok {
  195. csConfig.Crowdsec.ContextToSend[key] = make([]string, 0)
  196. log.Infof("key '%s' added", key)
  197. }
  198. data := csConfig.Crowdsec.ContextToSend[key]
  199. for _, val := range values {
  200. if !slices.Contains(data, val) {
  201. log.Infof("value '%s' added to key '%s'", val, key)
  202. data = append(data, val)
  203. }
  204. csConfig.Crowdsec.ContextToSend[key] = data
  205. }
  206. if err := csConfig.Crowdsec.DumpContextConfigFile(); err != nil {
  207. return err
  208. }
  209. return nil
  210. }
  211. func NewLapiContextCmd() *cobra.Command {
  212. cmdContext := &cobra.Command{
  213. Use: "context [command]",
  214. Short: "Manage context to send with alerts",
  215. DisableAutoGenTag: true,
  216. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  217. if err := csConfig.LoadCrowdsec(); err != nil {
  218. fileNotFoundMessage := fmt.Sprintf("failed to open context file: open %s: no such file or directory", csConfig.Crowdsec.ConsoleContextPath)
  219. if err.Error() != fileNotFoundMessage {
  220. log.Fatalf("Unable to load CrowdSec Agent: %s", err)
  221. }
  222. }
  223. if csConfig.DisableAgent {
  224. log.Fatalf("Agent is disabled and lapi context can only be used on the agent")
  225. }
  226. return nil
  227. },
  228. Run: func(cmd *cobra.Command, args []string) {
  229. printHelp(cmd)
  230. },
  231. }
  232. var keyToAdd string
  233. var valuesToAdd []string
  234. cmdContextAdd := &cobra.Command{
  235. Use: "add",
  236. Short: "Add context to send with alerts. You must specify the output key with the expr value you want",
  237. Example: `cscli lapi context add --key source_ip --value evt.Meta.source_ip
  238. cscli lapi context add --key file_source --value evt.Line.Src
  239. cscli lapi context add --value evt.Meta.source_ip --value evt.Meta.target_user
  240. `,
  241. DisableAutoGenTag: true,
  242. Run: func(cmd *cobra.Command, args []string) {
  243. if keyToAdd != "" {
  244. if err := AddContext(keyToAdd, valuesToAdd); err != nil {
  245. log.Fatalf(err.Error())
  246. }
  247. return
  248. }
  249. for _, v := range valuesToAdd {
  250. keySlice := strings.Split(v, ".")
  251. key := keySlice[len(keySlice)-1]
  252. value := []string{v}
  253. if err := AddContext(key, value); err != nil {
  254. log.Fatalf(err.Error())
  255. }
  256. }
  257. },
  258. }
  259. cmdContextAdd.Flags().StringVarP(&keyToAdd, "key", "k", "", "The key of the different values to send")
  260. cmdContextAdd.Flags().StringSliceVar(&valuesToAdd, "value", []string{}, "The expr fields to associate with the key")
  261. cmdContextAdd.MarkFlagRequired("value")
  262. cmdContext.AddCommand(cmdContextAdd)
  263. cmdContextStatus := &cobra.Command{
  264. Use: "status",
  265. Short: "List context to send with alerts",
  266. DisableAutoGenTag: true,
  267. Run: func(cmd *cobra.Command, args []string) {
  268. if len(csConfig.Crowdsec.ContextToSend) == 0 {
  269. fmt.Println("No context found on this agent. You can use 'cscli lapi context add' to add context to your alerts.")
  270. return
  271. }
  272. dump, err := yaml.Marshal(csConfig.Crowdsec.ContextToSend)
  273. if err != nil {
  274. log.Fatalf("unable to show context status: %s", err)
  275. }
  276. fmt.Println(string(dump))
  277. },
  278. }
  279. cmdContext.AddCommand(cmdContextStatus)
  280. var detectAll bool
  281. cmdContextDetect := &cobra.Command{
  282. Use: "detect",
  283. Short: "Detect available fields from the installed parsers",
  284. Example: `cscli lapi context detect --all
  285. cscli lapi context detect crowdsecurity/sshd-logs
  286. `,
  287. DisableAutoGenTag: true,
  288. Run: func(cmd *cobra.Command, args []string) {
  289. var err error
  290. if !detectAll && len(args) == 0 {
  291. log.Infof("Please provide parsers to detect or --all flag.")
  292. printHelp(cmd)
  293. }
  294. // to avoid all the log.Info from the loaders functions
  295. log.SetLevel(log.ErrorLevel)
  296. err = exprhelpers.Init(nil)
  297. if err != nil {
  298. log.Fatalf("Failed to init expr helpers : %s", err)
  299. }
  300. // Populate cwhub package tools
  301. if err := cwhub.GetHubIdx(csConfig.Hub); err != nil {
  302. log.Fatalf("Failed to load hub index : %s", err)
  303. }
  304. csParsers := parser.NewParsers()
  305. if csParsers, err = parser.LoadParsers(csConfig, csParsers); err != nil {
  306. log.Fatalf("unable to load parsers: %s", err)
  307. }
  308. fieldByParsers := make(map[string][]string)
  309. for _, node := range csParsers.Nodes {
  310. if !detectAll && !slices.Contains(args, node.Name) {
  311. continue
  312. }
  313. if !detectAll {
  314. args = removeFromSlice(node.Name, args)
  315. }
  316. fieldByParsers[node.Name] = make([]string, 0)
  317. fieldByParsers[node.Name] = detectNode(node, *csParsers.Ctx)
  318. subNodeFields := detectSubNode(node, *csParsers.Ctx)
  319. for _, field := range subNodeFields {
  320. if !slices.Contains(fieldByParsers[node.Name], field) {
  321. fieldByParsers[node.Name] = append(fieldByParsers[node.Name], field)
  322. }
  323. }
  324. }
  325. fmt.Printf("Acquisition :\n\n")
  326. fmt.Printf(" - evt.Line.Module\n")
  327. fmt.Printf(" - evt.Line.Raw\n")
  328. fmt.Printf(" - evt.Line.Src\n")
  329. fmt.Println()
  330. parsersKey := make([]string, 0)
  331. for k := range fieldByParsers {
  332. parsersKey = append(parsersKey, k)
  333. }
  334. sort.Strings(parsersKey)
  335. for _, k := range parsersKey {
  336. if len(fieldByParsers[k]) == 0 {
  337. continue
  338. }
  339. fmt.Printf("%s :\n\n", k)
  340. values := fieldByParsers[k]
  341. sort.Strings(values)
  342. for _, value := range values {
  343. fmt.Printf(" - %s\n", value)
  344. }
  345. fmt.Println()
  346. }
  347. if len(args) > 0 {
  348. for _, parserNotFound := range args {
  349. log.Errorf("parser '%s' not found, can't detect fields", parserNotFound)
  350. }
  351. }
  352. },
  353. }
  354. cmdContextDetect.Flags().BoolVarP(&detectAll, "all", "a", false, "Detect evt field for all installed parser")
  355. cmdContext.AddCommand(cmdContextDetect)
  356. var keysToDelete []string
  357. var valuesToDelete []string
  358. cmdContextDelete := &cobra.Command{
  359. Use: "delete",
  360. Short: "Delete context to send with alerts",
  361. Example: `cscli lapi context delete --key source_ip
  362. cscli lapi context delete --value evt.Line.Src
  363. `,
  364. DisableAutoGenTag: true,
  365. Run: func(cmd *cobra.Command, args []string) {
  366. if len(keysToDelete) == 0 && len(valuesToDelete) == 0 {
  367. log.Fatalf("please provide at least a key or a value to delete")
  368. }
  369. for _, key := range keysToDelete {
  370. if _, ok := csConfig.Crowdsec.ContextToSend[key]; ok {
  371. delete(csConfig.Crowdsec.ContextToSend, key)
  372. log.Infof("key '%s' has been removed", key)
  373. } else {
  374. log.Warningf("key '%s' doesn't exist", key)
  375. }
  376. }
  377. for _, value := range valuesToDelete {
  378. valueFound := false
  379. for key, context := range csConfig.Crowdsec.ContextToSend {
  380. if slices.Contains(context, value) {
  381. valueFound = true
  382. csConfig.Crowdsec.ContextToSend[key] = removeFromSlice(value, context)
  383. log.Infof("value '%s' has been removed from key '%s'", value, key)
  384. }
  385. if len(csConfig.Crowdsec.ContextToSend[key]) == 0 {
  386. delete(csConfig.Crowdsec.ContextToSend, key)
  387. }
  388. }
  389. if !valueFound {
  390. log.Warningf("value '%s' not found", value)
  391. }
  392. }
  393. if err := csConfig.Crowdsec.DumpContextConfigFile(); err != nil {
  394. log.Fatalf(err.Error())
  395. }
  396. },
  397. }
  398. cmdContextDelete.Flags().StringSliceVarP(&keysToDelete, "key", "k", []string{}, "The keys to delete")
  399. cmdContextDelete.Flags().StringSliceVar(&valuesToDelete, "value", []string{}, "The expr fields to delete")
  400. cmdContext.AddCommand(cmdContextDelete)
  401. return cmdContext
  402. }
  403. func detectStaticField(GrokStatics []parser.ExtraField) []string {
  404. ret := make([]string, 0)
  405. for _, static := range GrokStatics {
  406. if static.Parsed != "" {
  407. fieldName := fmt.Sprintf("evt.Parsed.%s", static.Parsed)
  408. if !slices.Contains(ret, fieldName) {
  409. ret = append(ret, fieldName)
  410. }
  411. }
  412. if static.Meta != "" {
  413. fieldName := fmt.Sprintf("evt.Meta.%s", static.Meta)
  414. if !slices.Contains(ret, fieldName) {
  415. ret = append(ret, fieldName)
  416. }
  417. }
  418. if static.TargetByName != "" {
  419. fieldName := static.TargetByName
  420. if !strings.HasPrefix(fieldName, "evt.") {
  421. fieldName = "evt." + fieldName
  422. }
  423. if !slices.Contains(ret, fieldName) {
  424. ret = append(ret, fieldName)
  425. }
  426. }
  427. }
  428. return ret
  429. }
  430. func detectNode(node parser.Node, parserCTX parser.UnixParserCtx) []string {
  431. var ret = make([]string, 0)
  432. if node.Grok.RunTimeRegexp != nil {
  433. for _, capturedField := range node.Grok.RunTimeRegexp.Names() {
  434. fieldName := fmt.Sprintf("evt.Parsed.%s", capturedField)
  435. if !slices.Contains(ret, fieldName) {
  436. ret = append(ret, fieldName)
  437. }
  438. }
  439. }
  440. if node.Grok.RegexpName != "" {
  441. grokCompiled, err := parserCTX.Grok.Get(node.Grok.RegexpName)
  442. if err != nil {
  443. log.Warningf("Can't get subgrok: %s", err)
  444. }
  445. for _, capturedField := range grokCompiled.Names() {
  446. fieldName := fmt.Sprintf("evt.Parsed.%s", capturedField)
  447. if !slices.Contains(ret, fieldName) {
  448. ret = append(ret, fieldName)
  449. }
  450. }
  451. }
  452. if len(node.Grok.Statics) > 0 {
  453. staticsField := detectStaticField(node.Grok.Statics)
  454. for _, staticField := range staticsField {
  455. if !slices.Contains(ret, staticField) {
  456. ret = append(ret, staticField)
  457. }
  458. }
  459. }
  460. if len(node.Statics) > 0 {
  461. staticsField := detectStaticField(node.Statics)
  462. for _, staticField := range staticsField {
  463. if !slices.Contains(ret, staticField) {
  464. ret = append(ret, staticField)
  465. }
  466. }
  467. }
  468. return ret
  469. }
  470. func detectSubNode(node parser.Node, parserCTX parser.UnixParserCtx) []string {
  471. var ret = make([]string, 0)
  472. for _, subnode := range node.LeavesNodes {
  473. if subnode.Grok.RunTimeRegexp != nil {
  474. for _, capturedField := range subnode.Grok.RunTimeRegexp.Names() {
  475. fieldName := fmt.Sprintf("evt.Parsed.%s", capturedField)
  476. if !slices.Contains(ret, fieldName) {
  477. ret = append(ret, fieldName)
  478. }
  479. }
  480. }
  481. if subnode.Grok.RegexpName != "" {
  482. grokCompiled, err := parserCTX.Grok.Get(subnode.Grok.RegexpName)
  483. if err != nil {
  484. log.Warningf("Can't get subgrok: %s", err)
  485. }
  486. for _, capturedField := range grokCompiled.Names() {
  487. fieldName := fmt.Sprintf("evt.Parsed.%s", capturedField)
  488. if !slices.Contains(ret, fieldName) {
  489. ret = append(ret, fieldName)
  490. }
  491. }
  492. }
  493. if len(subnode.Grok.Statics) > 0 {
  494. staticsField := detectStaticField(subnode.Grok.Statics)
  495. for _, staticField := range staticsField {
  496. if !slices.Contains(ret, staticField) {
  497. ret = append(ret, staticField)
  498. }
  499. }
  500. }
  501. if len(subnode.Statics) > 0 {
  502. staticsField := detectStaticField(subnode.Statics)
  503. for _, staticField := range staticsField {
  504. if !slices.Contains(ret, staticField) {
  505. ret = append(ret, staticField)
  506. }
  507. }
  508. }
  509. }
  510. return ret
  511. }