decisions.go 21 KB

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