decisions.go 21 KB

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