decisions.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. package main
  2. import (
  3. "context"
  4. "encoding/csv"
  5. "encoding/json"
  6. "fmt"
  7. "net/url"
  8. "os"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "github.com/crowdsecurity/crowdsec/pkg/apiclient"
  14. "github.com/crowdsecurity/crowdsec/pkg/cwversion"
  15. "github.com/crowdsecurity/crowdsec/pkg/models"
  16. "github.com/crowdsecurity/crowdsec/pkg/types"
  17. "github.com/go-openapi/strfmt"
  18. "github.com/jszwec/csvutil"
  19. "github.com/olekukonko/tablewriter"
  20. log "github.com/sirupsen/logrus"
  21. "github.com/spf13/cobra"
  22. )
  23. var Client *apiclient.ApiClient
  24. var (
  25. defaultDuration = "4h"
  26. defaultScope = "ip"
  27. defaultType = "ban"
  28. defaultReason = "manual"
  29. )
  30. func DecisionsToTable(alerts *models.GetAlertsResponse, printMachine bool) error {
  31. /*here we cheat a bit : to make it more readable for the user, we dedup some entries*/
  32. var spamLimit map[string]bool = make(map[string]bool)
  33. var skipped = 0
  34. for aIdx := 0; aIdx < len(*alerts); aIdx++ {
  35. alertItem := (*alerts)[aIdx]
  36. newDecisions := make([]*models.Decision, 0)
  37. for _, decisionItem := range alertItem.Decisions {
  38. spamKey := fmt.Sprintf("%t:%s:%s:%s", *decisionItem.Simulated, *decisionItem.Type, *decisionItem.Scope, *decisionItem.Value)
  39. if _, ok := spamLimit[spamKey]; ok {
  40. skipped++
  41. continue
  42. }
  43. spamLimit[spamKey] = true
  44. newDecisions = append(newDecisions, decisionItem)
  45. }
  46. alertItem.Decisions = newDecisions
  47. }
  48. if csConfig.Cscli.Output == "raw" {
  49. csvwriter := csv.NewWriter(os.Stdout)
  50. header := []string{"id", "source", "ip", "reason", "action", "country", "as", "events_count", "expiration", "simulated", "alert_id"}
  51. if printMachine {
  52. header = append(header, "machine")
  53. }
  54. err := csvwriter.Write(header)
  55. if err != nil {
  56. return err
  57. }
  58. for _, alertItem := range *alerts {
  59. for _, decisionItem := range alertItem.Decisions {
  60. raw := []string{
  61. fmt.Sprintf("%d", decisionItem.ID),
  62. *decisionItem.Origin,
  63. *decisionItem.Scope + ":" + *decisionItem.Value,
  64. *decisionItem.Scenario,
  65. *decisionItem.Type,
  66. alertItem.Source.Cn,
  67. alertItem.Source.AsNumber + " " + alertItem.Source.AsName,
  68. fmt.Sprintf("%d", *alertItem.EventsCount),
  69. *decisionItem.Duration,
  70. fmt.Sprintf("%t", *decisionItem.Simulated),
  71. fmt.Sprintf("%d", alertItem.ID),
  72. }
  73. if printMachine {
  74. raw = append(raw, alertItem.MachineID)
  75. }
  76. err := csvwriter.Write(raw)
  77. if err != nil {
  78. return err
  79. }
  80. }
  81. }
  82. csvwriter.Flush()
  83. } else if csConfig.Cscli.Output == "json" {
  84. x, _ := json.MarshalIndent(alerts, "", " ")
  85. fmt.Printf("%s", string(x))
  86. } else if csConfig.Cscli.Output == "human" {
  87. table := tablewriter.NewWriter(os.Stdout)
  88. header := []string{"ID", "Source", "Scope:Value", "Reason", "Action", "Country", "AS", "Events", "expiration", "Alert ID"}
  89. if printMachine {
  90. header = append(header, "Machine")
  91. }
  92. table.SetHeader(header)
  93. if len(*alerts) == 0 {
  94. fmt.Println("No active decisions")
  95. return nil
  96. }
  97. for _, alertItem := range *alerts {
  98. for _, decisionItem := range alertItem.Decisions {
  99. if *alertItem.Simulated {
  100. *decisionItem.Type = fmt.Sprintf("(simul)%s", *decisionItem.Type)
  101. }
  102. raw := []string{
  103. strconv.Itoa(int(decisionItem.ID)),
  104. *decisionItem.Origin,
  105. *decisionItem.Scope + ":" + *decisionItem.Value,
  106. *decisionItem.Scenario,
  107. *decisionItem.Type,
  108. alertItem.Source.Cn,
  109. alertItem.Source.AsNumber + " " + alertItem.Source.AsName,
  110. strconv.Itoa(int(*alertItem.EventsCount)),
  111. *decisionItem.Duration,
  112. strconv.Itoa(int(alertItem.ID)),
  113. }
  114. if printMachine {
  115. raw = append(raw, alertItem.MachineID)
  116. }
  117. table.Append(raw)
  118. }
  119. }
  120. table.Render() // Send output
  121. if skipped > 0 {
  122. fmt.Printf("%d duplicated entries skipped\n", skipped)
  123. }
  124. }
  125. return nil
  126. }
  127. func NewDecisionsCmd() *cobra.Command {
  128. /* ---- DECISIONS COMMAND */
  129. var cmdDecisions = &cobra.Command{
  130. Use: "decisions [action]",
  131. Short: "Manage decisions",
  132. Long: `Add/List/Delete/Import decisions from LAPI`,
  133. Example: `cscli decisions [action] [filter]`,
  134. Aliases: []string{"decision"},
  135. /*TBD example*/
  136. Args: cobra.MinimumNArgs(1),
  137. DisableAutoGenTag: true,
  138. PersistentPreRun: func(cmd *cobra.Command, args []string) {
  139. if err := csConfig.LoadAPIClient(); err != nil {
  140. log.Fatalf(err.Error())
  141. }
  142. if csConfig.API.Client == nil {
  143. log.Fatalln("There is no configuration on 'api_client:'")
  144. }
  145. if csConfig.API.Client.Credentials == nil {
  146. log.Fatalf("Please provide credentials for the API in '%s'", csConfig.API.Client.CredentialsFilePath)
  147. }
  148. password := strfmt.Password(csConfig.API.Client.Credentials.Password)
  149. apiurl, err := url.Parse(csConfig.API.Client.Credentials.URL)
  150. if err != nil {
  151. log.Fatalf("parsing api url ('%s'): %s", csConfig.API.Client.Credentials.URL, err)
  152. }
  153. Client, err = apiclient.NewClient(&apiclient.Config{
  154. MachineID: csConfig.API.Client.Credentials.Login,
  155. Password: password,
  156. UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
  157. URL: apiurl,
  158. VersionPrefix: "v1",
  159. })
  160. if err != nil {
  161. log.Fatalf("creating api client : %s", err)
  162. }
  163. },
  164. }
  165. var filter = apiclient.AlertsListOpts{
  166. ValueEquals: new(string),
  167. ScopeEquals: new(string),
  168. ScenarioEquals: new(string),
  169. OriginEquals: new(string),
  170. IPEquals: new(string),
  171. RangeEquals: new(string),
  172. Since: new(string),
  173. Until: new(string),
  174. TypeEquals: new(string),
  175. IncludeCAPI: new(bool),
  176. Limit: new(int),
  177. }
  178. NoSimu := new(bool)
  179. contained := new(bool)
  180. var printMachine bool
  181. var cmdDecisionsList = &cobra.Command{
  182. Use: "list [options]",
  183. Short: "List decisions from LAPI",
  184. Example: `cscli decisions list -i 1.2.3.4
  185. cscli decisions list -r 1.2.3.0/24
  186. cscli decisions list -s crowdsecurity/ssh-bf
  187. cscli decisions list -t ban
  188. `,
  189. Args: cobra.ExactArgs(0),
  190. DisableAutoGenTag: true,
  191. Run: func(cmd *cobra.Command, args []string) {
  192. var err error
  193. /*take care of shorthand options*/
  194. if err := manageCliDecisionAlerts(filter.IPEquals, filter.RangeEquals, filter.ScopeEquals, filter.ValueEquals); err != nil {
  195. log.Fatalf("%s", err)
  196. }
  197. filter.ActiveDecisionEquals = new(bool)
  198. *filter.ActiveDecisionEquals = true
  199. if NoSimu != nil && *NoSimu {
  200. filter.IncludeSimulated = new(bool)
  201. }
  202. /* nullify the empty entries to avoid bad filter */
  203. if *filter.Until == "" {
  204. filter.Until = nil
  205. } else {
  206. /*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
  207. if strings.HasSuffix(*filter.Until, "d") {
  208. realDuration := strings.TrimSuffix(*filter.Until, "d")
  209. days, err := strconv.Atoi(realDuration)
  210. if err != nil {
  211. printHelp(cmd)
  212. log.Fatalf("Can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *filter.Until)
  213. }
  214. *filter.Until = fmt.Sprintf("%d%s", days*24, "h")
  215. }
  216. }
  217. if *filter.Since == "" {
  218. filter.Since = nil
  219. } else {
  220. /*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
  221. if strings.HasSuffix(*filter.Since, "d") {
  222. realDuration := strings.TrimSuffix(*filter.Since, "d")
  223. days, err := strconv.Atoi(realDuration)
  224. if err != nil {
  225. printHelp(cmd)
  226. log.Fatalf("Can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *filter.Until)
  227. }
  228. *filter.Since = fmt.Sprintf("%d%s", days*24, "h")
  229. }
  230. }
  231. if *filter.IncludeCAPI {
  232. *filter.Limit = 0
  233. }
  234. if *filter.TypeEquals == "" {
  235. filter.TypeEquals = nil
  236. }
  237. if *filter.ValueEquals == "" {
  238. filter.ValueEquals = nil
  239. }
  240. if *filter.ScopeEquals == "" {
  241. filter.ScopeEquals = nil
  242. }
  243. if *filter.ScenarioEquals == "" {
  244. filter.ScenarioEquals = nil
  245. }
  246. if *filter.IPEquals == "" {
  247. filter.IPEquals = nil
  248. }
  249. if *filter.RangeEquals == "" {
  250. filter.RangeEquals = nil
  251. }
  252. if *filter.OriginEquals == "" {
  253. filter.OriginEquals = nil
  254. }
  255. if contained != nil && *contained {
  256. filter.Contains = new(bool)
  257. }
  258. alerts, _, err := Client.Alerts.List(context.Background(), filter)
  259. if err != nil {
  260. log.Fatalf("Unable to list decisions : %v", err.Error())
  261. }
  262. err = DecisionsToTable(alerts, printMachine)
  263. if err != nil {
  264. log.Fatalf("unable to list decisions : %v", err.Error())
  265. }
  266. },
  267. }
  268. cmdDecisionsList.Flags().SortFlags = false
  269. cmdDecisionsList.Flags().BoolVarP(filter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
  270. cmdDecisionsList.Flags().StringVar(filter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
  271. cmdDecisionsList.Flags().StringVar(filter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
  272. cmdDecisionsList.Flags().StringVarP(filter.TypeEquals, "type", "t", "", "restrict to this decision type (ie. ban,captcha)")
  273. cmdDecisionsList.Flags().StringVar(filter.ScopeEquals, "scope", "", "restrict to this scope (ie. ip,range,session)")
  274. cmdDecisionsList.Flags().StringVar(filter.OriginEquals, "origin", "", "restrict to this origin (ie. lists,CAPI,cscli)")
  275. cmdDecisionsList.Flags().StringVarP(filter.ValueEquals, "value", "v", "", "restrict to this value (ie. 1.2.3.4,userName)")
  276. cmdDecisionsList.Flags().StringVarP(filter.ScenarioEquals, "scenario", "s", "", "restrict to this scenario (ie. crowdsecurity/ssh-bf)")
  277. cmdDecisionsList.Flags().StringVarP(filter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
  278. cmdDecisionsList.Flags().StringVarP(filter.RangeEquals, "range", "r", "", "restrict to alerts from this source range (shorthand for --scope range --value <RANGE>)")
  279. cmdDecisionsList.Flags().IntVarP(filter.Limit, "limit", "l", 100, "number of alerts to get (use 0 to remove the limit)")
  280. cmdDecisionsList.Flags().BoolVar(NoSimu, "no-simu", false, "exclude decisions in simulation mode")
  281. cmdDecisionsList.Flags().BoolVarP(&printMachine, "machine", "m", false, "print machines that triggered decisions")
  282. cmdDecisionsList.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
  283. cmdDecisions.AddCommand(cmdDecisionsList)
  284. var (
  285. addIP string
  286. addRange string
  287. addDuration string
  288. addValue string
  289. addScope string
  290. addReason string
  291. addType string
  292. )
  293. var cmdDecisionsAdd = &cobra.Command{
  294. Use: "add [options]",
  295. Short: "Add decision to LAPI",
  296. Example: `cscli decisions add --ip 1.2.3.4
  297. cscli decisions add --range 1.2.3.0/24
  298. cscli decisions add --ip 1.2.3.4 --duration 24h --type captcha
  299. cscli decisions add --scope username --value foobar
  300. `,
  301. /*TBD : fix long and example*/
  302. Args: cobra.ExactArgs(0),
  303. DisableAutoGenTag: true,
  304. Run: func(cmd *cobra.Command, args []string) {
  305. var err error
  306. var ipRange string
  307. alerts := models.AddAlertsRequest{}
  308. origin := "cscli"
  309. capacity := int32(0)
  310. leakSpeed := "0"
  311. eventsCount := int32(1)
  312. empty := ""
  313. simulated := false
  314. startAt := time.Now().UTC().Format(time.RFC3339)
  315. stopAt := time.Now().UTC().Format(time.RFC3339)
  316. createdAt := time.Now().UTC().Format(time.RFC3339)
  317. /*take care of shorthand options*/
  318. if err := manageCliDecisionAlerts(&addIP, &addRange, &addScope, &addValue); err != nil {
  319. log.Fatalf("%s", err)
  320. }
  321. if addIP != "" {
  322. addValue = addIP
  323. addScope = types.Ip
  324. } else if addRange != "" {
  325. addValue = addRange
  326. addScope = types.Range
  327. } else if addValue == "" {
  328. printHelp(cmd)
  329. log.Fatalf("Missing arguments, a value is required (--ip, --range or --scope and --value)")
  330. }
  331. if addReason == "" {
  332. addReason = fmt.Sprintf("manual '%s' from '%s'", addType, csConfig.API.Client.Credentials.Login)
  333. }
  334. decision := models.Decision{
  335. Duration: &addDuration,
  336. Scope: &addScope,
  337. Value: &addValue,
  338. Type: &addType,
  339. Scenario: &addReason,
  340. Origin: &origin,
  341. }
  342. alert := models.Alert{
  343. Capacity: &capacity,
  344. Decisions: []*models.Decision{&decision},
  345. Events: []*models.Event{},
  346. EventsCount: &eventsCount,
  347. Leakspeed: &leakSpeed,
  348. Message: &addReason,
  349. ScenarioHash: &empty,
  350. Scenario: &addReason,
  351. ScenarioVersion: &empty,
  352. Simulated: &simulated,
  353. Source: &models.Source{
  354. AsName: empty,
  355. AsNumber: empty,
  356. Cn: empty,
  357. IP: addValue,
  358. Range: ipRange,
  359. Scope: &addScope,
  360. Value: &addValue,
  361. },
  362. StartAt: &startAt,
  363. StopAt: &stopAt,
  364. CreatedAt: createdAt,
  365. }
  366. alerts = append(alerts, &alert)
  367. _, _, err = Client.Alerts.Add(context.Background(), alerts)
  368. if err != nil {
  369. log.Fatalf(err.Error())
  370. }
  371. log.Info("Decision successfully added")
  372. },
  373. }
  374. cmdDecisionsAdd.Flags().SortFlags = false
  375. cmdDecisionsAdd.Flags().StringVarP(&addIP, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
  376. cmdDecisionsAdd.Flags().StringVarP(&addRange, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
  377. cmdDecisionsAdd.Flags().StringVarP(&addDuration, "duration", "d", "4h", "Decision duration (ie. 1h,4h,30m)")
  378. cmdDecisionsAdd.Flags().StringVarP(&addValue, "value", "v", "", "The value (ie. --scope username --value foobar)")
  379. cmdDecisionsAdd.Flags().StringVar(&addScope, "scope", types.Ip, "Decision scope (ie. ip,range,username)")
  380. cmdDecisionsAdd.Flags().StringVarP(&addReason, "reason", "R", "", "Decision reason (ie. scenario-name)")
  381. cmdDecisionsAdd.Flags().StringVarP(&addType, "type", "t", "ban", "Decision type (ie. ban,captcha,throttle)")
  382. cmdDecisions.AddCommand(cmdDecisionsAdd)
  383. var delFilter = apiclient.DecisionsDeleteOpts{
  384. ScopeEquals: new(string),
  385. ValueEquals: new(string),
  386. TypeEquals: new(string),
  387. IPEquals: new(string),
  388. RangeEquals: new(string),
  389. }
  390. var delDecisionId string
  391. var delDecisionAll bool
  392. var cmdDecisionsDelete = &cobra.Command{
  393. Use: "delete [options]",
  394. Short: "Delete decisions",
  395. DisableAutoGenTag: true,
  396. Aliases: []string{"remove"},
  397. Example: `cscli decisions delete -r 1.2.3.0/24
  398. cscli decisions delete -i 1.2.3.4
  399. cscli decisions delete -s crowdsecurity/ssh-bf
  400. cscli decisions delete --id 42
  401. cscli decisions delete --type captcha
  402. `,
  403. /*TBD : refaire le Long/Example*/
  404. PreRun: func(cmd *cobra.Command, args []string) {
  405. if delDecisionAll {
  406. return
  407. }
  408. if *delFilter.ScopeEquals == "" && *delFilter.ValueEquals == "" &&
  409. *delFilter.TypeEquals == "" && *delFilter.IPEquals == "" &&
  410. *delFilter.RangeEquals == "" && delDecisionId == "" {
  411. cmd.Usage()
  412. log.Fatalln("At least one filter or --all must be specified")
  413. }
  414. },
  415. Run: func(cmd *cobra.Command, args []string) {
  416. var err error
  417. var decisions *models.DeleteDecisionResponse
  418. /*take care of shorthand options*/
  419. if err := manageCliDecisionAlerts(delFilter.IPEquals, delFilter.RangeEquals, delFilter.ScopeEquals, delFilter.ValueEquals); err != nil {
  420. log.Fatalf("%s", err)
  421. }
  422. if *delFilter.ScopeEquals == "" {
  423. delFilter.ScopeEquals = nil
  424. }
  425. if *delFilter.ValueEquals == "" {
  426. delFilter.ValueEquals = nil
  427. }
  428. if *delFilter.TypeEquals == "" {
  429. delFilter.TypeEquals = nil
  430. }
  431. if *delFilter.IPEquals == "" {
  432. delFilter.IPEquals = nil
  433. }
  434. if *delFilter.RangeEquals == "" {
  435. delFilter.RangeEquals = nil
  436. }
  437. if contained != nil && *contained {
  438. delFilter.Contains = new(bool)
  439. }
  440. if delDecisionId == "" {
  441. decisions, _, err = Client.Decisions.Delete(context.Background(), delFilter)
  442. if err != nil {
  443. log.Fatalf("Unable to delete decisions : %v", err.Error())
  444. }
  445. } else {
  446. decisions, _, err = Client.Decisions.DeleteOne(context.Background(), delDecisionId)
  447. if err != nil {
  448. log.Fatalf("Unable to delete decision : %v", err.Error())
  449. }
  450. }
  451. log.Infof("%s decision(s) deleted", decisions.NbDeleted)
  452. },
  453. }
  454. cmdDecisionsDelete.Flags().SortFlags = false
  455. cmdDecisionsDelete.Flags().StringVarP(delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
  456. cmdDecisionsDelete.Flags().StringVarP(delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
  457. cmdDecisionsDelete.Flags().StringVar(&delDecisionId, "id", "", "decision id")
  458. cmdDecisionsDelete.Flags().StringVarP(delFilter.TypeEquals, "type", "t", "", "the decision type (ie. ban,captcha)")
  459. cmdDecisionsDelete.Flags().StringVarP(delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
  460. cmdDecisionsDelete.Flags().BoolVar(&delDecisionAll, "all", false, "delete all decisions")
  461. cmdDecisionsDelete.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
  462. cmdDecisions.AddCommand(cmdDecisionsDelete)
  463. var (
  464. importDuration string
  465. importScope string
  466. importReason string
  467. importType string
  468. importFile string
  469. )
  470. var cmdDecisionImport = &cobra.Command{
  471. Use: "import [options]",
  472. Short: "Import decisions from json or csv file",
  473. Long: "expected format :\n" +
  474. "csv : any of duration,origin,reason,scope,type,value, with a header line\n" +
  475. `json : {"duration" : "24h", "origin" : "my-list", "reason" : "my_scenario", "scope" : "ip", "type" : "ban", "value" : "x.y.z.z"}`,
  476. DisableAutoGenTag: true,
  477. Example: `decisions.csv :
  478. duration,scope,value
  479. 24h,ip,1.2.3.4
  480. cscsli decisions import -i decisions.csv
  481. decisions.json :
  482. [{"duration" : "4h", "scope" : "ip", "type" : "ban", "value" : "1.2.3.4"}]
  483. `,
  484. Run: func(cmd *cobra.Command, args []string) {
  485. if importFile == "" {
  486. log.Fatalf("Please provide a input file containing decisions with -i flag")
  487. }
  488. csvData, err := os.ReadFile(importFile)
  489. if err != nil {
  490. log.Fatalf("unable to open '%s': %s", importFile, err)
  491. }
  492. type decisionRaw struct {
  493. Duration string `csv:"duration,omitempty" json:"duration,omitempty"`
  494. Origin string `csv:"origin,omitempty" json:"origin,omitempty"`
  495. Scenario string `csv:"reason,omitempty" json:"reason,omitempty"`
  496. Scope string `csv:"scope,omitempty" json:"scope,omitempty"`
  497. Type string `csv:"type,omitempty" json:"type,omitempty"`
  498. Value string `csv:"value" json:"value"`
  499. }
  500. var decisionsListRaw []decisionRaw
  501. switch fileFormat := filepath.Ext(importFile); fileFormat {
  502. case ".json":
  503. if err := json.Unmarshal(csvData, &decisionsListRaw); err != nil {
  504. log.Fatalf("unable to unmarshall json: '%s'", err)
  505. }
  506. case ".csv":
  507. if err := csvutil.Unmarshal(csvData, &decisionsListRaw); err != nil {
  508. log.Fatalf("unable to unmarshall csv: '%s'", err)
  509. }
  510. default:
  511. log.Fatalf("file format not supported for '%s'. supported format are 'json' and 'csv'", importFile)
  512. }
  513. decisionsList := make([]*models.Decision, 0)
  514. for i, decisionLine := range decisionsListRaw {
  515. line := i + 2
  516. if decisionLine.Value == "" {
  517. log.Fatalf("please provide a 'value' in your csv line %d", line)
  518. }
  519. /*deal with defaults and cli-override*/
  520. if decisionLine.Duration == "" {
  521. decisionLine.Duration = defaultDuration
  522. log.Debugf("No 'duration' line %d, using default value: '%s'", line, defaultDuration)
  523. }
  524. if importDuration != "" {
  525. decisionLine.Duration = importDuration
  526. log.Debugf("'duration' line %d, using supplied value: '%s'", line, importDuration)
  527. }
  528. decisionLine.Origin = "cscli-import"
  529. if decisionLine.Scenario == "" {
  530. decisionLine.Scenario = defaultReason
  531. log.Debugf("No 'reason' line %d, using value: '%s'", line, decisionLine.Scenario)
  532. }
  533. if importReason != "" {
  534. decisionLine.Scenario = importReason
  535. log.Debugf("No 'reason' line %d, using supplied value: '%s'", line, importReason)
  536. }
  537. if decisionLine.Type == "" {
  538. decisionLine.Type = defaultType
  539. log.Debugf("No 'type' line %d, using default value: '%s'", line, decisionLine.Type)
  540. }
  541. if importType != "" {
  542. decisionLine.Type = importType
  543. log.Debugf("'type' line %d, using supplied value: '%s'", line, importType)
  544. }
  545. if decisionLine.Scope == "" {
  546. decisionLine.Scope = defaultScope
  547. log.Debugf("No 'scope' line %d, using default value: '%s'", line, decisionLine.Scope)
  548. }
  549. if importScope != "" {
  550. decisionLine.Scope = importScope
  551. log.Debugf("'scope' line %d, using supplied value: '%s'", line, importScope)
  552. }
  553. decision := models.Decision{
  554. Value: types.StrPtr(decisionLine.Value),
  555. Duration: types.StrPtr(decisionLine.Duration),
  556. Origin: types.StrPtr(decisionLine.Origin),
  557. Scenario: types.StrPtr(decisionLine.Scenario),
  558. Type: types.StrPtr(decisionLine.Type),
  559. Scope: types.StrPtr(decisionLine.Scope),
  560. Simulated: new(bool),
  561. }
  562. decisionsList = append(decisionsList, &decision)
  563. }
  564. alerts := models.AddAlertsRequest{}
  565. importAlert := models.Alert{
  566. CreatedAt: time.Now().UTC().Format(time.RFC3339),
  567. Scenario: types.StrPtr(fmt.Sprintf("add: %d IPs", len(decisionsList))),
  568. Message: types.StrPtr(""),
  569. Events: []*models.Event{},
  570. Source: &models.Source{
  571. Scope: types.StrPtr("cscli/manual-import"),
  572. Value: types.StrPtr(""),
  573. },
  574. StartAt: types.StrPtr(time.Now().UTC().Format(time.RFC3339)),
  575. StopAt: types.StrPtr(time.Now().UTC().Format(time.RFC3339)),
  576. Capacity: types.Int32Ptr(0),
  577. Simulated: types.BoolPtr(false),
  578. EventsCount: types.Int32Ptr(int32(len(decisionsList))),
  579. Leakspeed: types.StrPtr(""),
  580. ScenarioHash: types.StrPtr(""),
  581. ScenarioVersion: types.StrPtr(""),
  582. Decisions: decisionsList,
  583. }
  584. alerts = append(alerts, &importAlert)
  585. if len(decisionsList) > 1000 {
  586. log.Infof("You are about to add %d decisions, this may take a while", len(decisionsList))
  587. }
  588. _, _, err = Client.Alerts.Add(context.Background(), alerts)
  589. if err != nil {
  590. log.Fatalf(err.Error())
  591. }
  592. log.Infof("%d decisions successfully imported", len(decisionsList))
  593. },
  594. }
  595. cmdDecisionImport.Flags().SortFlags = false
  596. cmdDecisionImport.Flags().StringVarP(&importFile, "input", "i", "", "Input file")
  597. cmdDecisionImport.Flags().StringVarP(&importDuration, "duration", "d", "", "Decision duration (ie. 1h,4h,30m)")
  598. cmdDecisionImport.Flags().StringVar(&importScope, "scope", types.Ip, "Decision scope (ie. ip,range,username)")
  599. cmdDecisionImport.Flags().StringVarP(&importReason, "reason", "R", "", "Decision reason (ie. scenario-name)")
  600. cmdDecisionImport.Flags().StringVarP(&importType, "type", "t", "", "Decision type (ie. ban,captcha,throttle)")
  601. cmdDecisions.AddCommand(cmdDecisionImport)
  602. return cmdDecisions
  603. }