ban.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net"
  6. "os"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/crowdsecurity/crowdsec/pkg/outputs"
  11. "github.com/crowdsecurity/crowdsec/pkg/parser"
  12. "github.com/crowdsecurity/crowdsec/pkg/types"
  13. "github.com/olekukonko/tablewriter"
  14. log "github.com/sirupsen/logrus"
  15. "github.com/spf13/cobra"
  16. )
  17. var remediationType string
  18. var atTime string
  19. //user supplied filters
  20. var ipFilter, rangeFilter, reasonFilter, countryFilter, asFilter string
  21. var displayLimit int
  22. var displayAPI, displayALL bool
  23. func simpleBanToSignal(targetIP string, reason string, expirationStr string, action string, asName string, asNum string, country string, banSource string) (types.SignalOccurence, error) {
  24. var signalOcc types.SignalOccurence
  25. expiration, err := time.ParseDuration(expirationStr)
  26. if err != nil {
  27. return signalOcc, err
  28. }
  29. asOrgInt := 0
  30. if asNum != "" {
  31. asOrgInt, err = strconv.Atoi(asNum)
  32. if err != nil {
  33. log.Infof("Invalid as value %s : %s", asNum, err)
  34. }
  35. }
  36. banApp := types.BanApplication{
  37. MeasureSource: banSource,
  38. MeasureType: action,
  39. Until: time.Now().Add(expiration),
  40. IpText: targetIP,
  41. TargetCN: country,
  42. TargetAS: asOrgInt,
  43. TargetASName: asName,
  44. Reason: reason,
  45. }
  46. var parsedIP net.IP
  47. var parsedRange *net.IPNet
  48. if strings.Contains(targetIP, "/") {
  49. if _, parsedRange, err = net.ParseCIDR(targetIP); err != nil {
  50. return signalOcc, fmt.Errorf("'%s' is not a valid CIDR", targetIP)
  51. }
  52. if parsedRange == nil {
  53. return signalOcc, fmt.Errorf("unable to parse network : %s", err)
  54. }
  55. banApp.StartIp = types.IP2Int(parsedRange.IP)
  56. banApp.EndIp = types.IP2Int(types.LastAddress(parsedRange))
  57. } else {
  58. parsedIP = net.ParseIP(targetIP)
  59. if parsedIP == nil {
  60. return signalOcc, fmt.Errorf("'%s' is not a valid IP", targetIP)
  61. }
  62. banApp.StartIp = types.IP2Int(parsedIP)
  63. banApp.EndIp = types.IP2Int(parsedIP)
  64. }
  65. var banApps = make([]types.BanApplication, 1)
  66. banApps = append(banApps, banApp)
  67. signalOcc = types.SignalOccurence{
  68. Scenario: reason,
  69. Events_count: 1,
  70. Start_at: time.Now(),
  71. Stop_at: time.Now(),
  72. BanApplications: banApps,
  73. Source_ip: targetIP,
  74. Source_AutonomousSystemNumber: asNum,
  75. Source_AutonomousSystemOrganization: asName,
  76. Source_Country: country,
  77. }
  78. return signalOcc, nil
  79. }
  80. func filterBans(bans []map[string]string) ([]map[string]string, error) {
  81. var retBans []map[string]string
  82. for _, ban := range bans {
  83. var banIP net.IP
  84. var banRange *net.IPNet
  85. var keep bool = true
  86. var err error
  87. if ban["iptext"] != "" {
  88. if strings.Contains(ban["iptext"], "/") {
  89. log.Debugf("%s is a range", ban["iptext"])
  90. banIP, banRange, err = net.ParseCIDR(ban["iptext"])
  91. if err != nil {
  92. log.Warningf("failed to parse range '%s' from database : %s", ban["iptext"], err)
  93. }
  94. } else {
  95. log.Debugf("%s is IP", ban["iptext"])
  96. banIP = net.ParseIP(ban["iptext"])
  97. }
  98. }
  99. if ipFilter != "" {
  100. var filterBinIP net.IP = net.ParseIP(ipFilter)
  101. if banRange != nil {
  102. if banRange.Contains(filterBinIP) {
  103. log.Debugf("[keep] ip filter is set, and range contains ip")
  104. keep = true
  105. } else {
  106. log.Debugf("[discard] ip filter is set, and range doesn't contain ip")
  107. keep = false
  108. }
  109. } else {
  110. if ipFilter == ban["iptext"] {
  111. log.Debugf("[keep] (ip) %s == %s", ipFilter, ban["iptext"])
  112. keep = true
  113. } else {
  114. log.Debugf("[discard] (ip) %s == %s", ipFilter, ban["iptext"])
  115. keep = false
  116. }
  117. }
  118. }
  119. if rangeFilter != "" {
  120. _, filterBinRange, err := net.ParseCIDR(rangeFilter)
  121. if err != nil {
  122. return nil, fmt.Errorf("failed to parse range '%s' : %s", rangeFilter, err)
  123. }
  124. if filterBinRange.Contains(banIP) {
  125. log.Debugf("[keep] range filter %s contains %s", rangeFilter, banIP.String())
  126. keep = true
  127. } else {
  128. log.Debugf("[discard] range filter %s doesn't contain %s", rangeFilter, banIP.String())
  129. keep = false
  130. }
  131. }
  132. if reasonFilter != "" {
  133. if strings.Contains(ban["reason"], reasonFilter) {
  134. log.Debugf("[keep] reason filter %s matches %s", reasonFilter, ban["reason"])
  135. keep = true
  136. } else {
  137. log.Debugf("[discard] reason filter %s doesn't match %s", reasonFilter, ban["reason"])
  138. keep = false
  139. }
  140. }
  141. if countryFilter != "" {
  142. if ban["cn"] == countryFilter {
  143. log.Debugf("[keep] country filter %s matches %s", countryFilter, ban["cn"])
  144. keep = true
  145. } else {
  146. log.Debugf("[discard] country filter %s matches %s", countryFilter, ban["cn"])
  147. keep = false
  148. }
  149. }
  150. if asFilter != "" {
  151. if strings.Contains(ban["as"], asFilter) {
  152. log.Debugf("[keep] AS filter %s matches %s", asFilter, ban["as"])
  153. keep = true
  154. } else {
  155. log.Debugf("[discard] AS filter %s doesn't match %s", asFilter, ban["as"])
  156. keep = false
  157. }
  158. }
  159. if keep {
  160. retBans = append(retBans, ban)
  161. } else {
  162. log.Debugf("[discard] discard %v", ban)
  163. }
  164. }
  165. return retBans, nil
  166. }
  167. func BanList() error {
  168. at := time.Now()
  169. if atTime != "" {
  170. _, at = parser.GenDateParse(atTime)
  171. if at.IsZero() {
  172. return fmt.Errorf("unable to parse date '%s'", atTime)
  173. }
  174. }
  175. ret, err := outputCTX.ReadAT(at)
  176. if err != nil {
  177. return fmt.Errorf("unable to get records from sqlite : %v", err)
  178. }
  179. ret, err = filterBans(ret)
  180. if err != nil {
  181. log.Errorf("Error while filtering : %s", err)
  182. }
  183. if config.output == "raw" {
  184. fmt.Printf("source,ip,reason,bans,action,country,as,events_count,expiration\n")
  185. for _, rm := range ret {
  186. fmt.Printf("%s,%s,%s,%s,%s,%s,%s,%s,%s\n", rm["source"], rm["iptext"], rm["reason"], rm["bancount"], rm["action"], rm["cn"], rm["as"], rm["events_count"], rm["until"])
  187. }
  188. } else if config.output == "json" {
  189. x, _ := json.MarshalIndent(ret, "", " ")
  190. fmt.Printf("%s", string(x))
  191. } else if config.output == "human" {
  192. uniqAS := map[string]bool{}
  193. uniqCN := map[string]bool{}
  194. table := tablewriter.NewWriter(os.Stdout)
  195. table.SetHeader([]string{"Source", "Ip", "Reason", "Bans", "Action", "Country", "AS", "Events", "Expiration"})
  196. dispcount := 0
  197. apicount := 0
  198. for _, rm := range ret {
  199. if !displayAPI && rm["source"] == "api" {
  200. apicount++
  201. if _, ok := uniqAS[rm["as"]]; !ok {
  202. uniqAS[rm["as"]] = true
  203. }
  204. if _, ok := uniqCN[rm["cn"]]; !ok {
  205. uniqCN[rm["cn"]] = true
  206. }
  207. }
  208. if displayALL {
  209. if rm["source"] == "api" {
  210. if displayAPI {
  211. table.Append([]string{rm["source"], rm["iptext"], rm["reason"], rm["bancount"], rm["action"], rm["cn"], rm["as"], rm["events_count"], rm["until"]})
  212. dispcount++
  213. continue
  214. }
  215. } else {
  216. table.Append([]string{rm["source"], rm["iptext"], rm["reason"], rm["bancount"], rm["action"], rm["cn"], rm["as"], rm["events_count"], rm["until"]})
  217. dispcount++
  218. continue
  219. }
  220. } else if dispcount < displayLimit {
  221. if displayAPI {
  222. if rm["source"] == "api" {
  223. table.Append([]string{rm["source"], rm["iptext"], rm["reason"], rm["bancount"], rm["action"], rm["cn"], rm["as"], rm["events_count"], rm["until"]})
  224. dispcount++
  225. continue
  226. }
  227. } else {
  228. if rm["source"] != "api" {
  229. table.Append([]string{rm["source"], rm["iptext"], rm["reason"], rm["bancount"], rm["action"], rm["cn"], rm["as"], rm["events_count"], rm["until"]})
  230. dispcount++
  231. continue
  232. }
  233. }
  234. }
  235. }
  236. if dispcount > 0 {
  237. if !displayAPI {
  238. fmt.Printf("%d local decisions:\n", dispcount)
  239. } else if displayAPI && !displayALL {
  240. fmt.Printf("%d decision from API\n", dispcount)
  241. } else if displayALL && displayAPI {
  242. fmt.Printf("%d decision from crowdsec and API\n", dispcount)
  243. }
  244. table.Render() // Send output
  245. if dispcount > displayLimit && !displayALL {
  246. fmt.Printf("Additional records stripped.\n")
  247. }
  248. } else {
  249. fmt.Printf("No local decisions.\n")
  250. }
  251. if !displayAPI {
  252. fmt.Printf("And %d records from API, %d distinct AS, %d distinct countries\n", apicount, len(uniqAS), len(uniqCN))
  253. }
  254. }
  255. return nil
  256. }
  257. func BanAdd(target string, duration string, reason string, action string) error {
  258. var signalOcc types.SignalOccurence
  259. var err error
  260. signalOcc, err = simpleBanToSignal(target, reason, duration, action, "", "", "", "cli")
  261. if err != nil {
  262. return fmt.Errorf("unable to insert ban : %v", err)
  263. }
  264. err = outputCTX.Insert(signalOcc)
  265. if err != nil {
  266. return err
  267. }
  268. err = outputCTX.Flush()
  269. if err != nil {
  270. return err
  271. }
  272. log.Infof("%s %s for %s (%s)", action, target, duration, reason)
  273. return nil
  274. }
  275. func NewBanCmds() *cobra.Command {
  276. /*TODO : add a remediation type*/
  277. var cmdBan = &cobra.Command{
  278. Use: "ban [command] <target> <duration> <reason>",
  279. Short: "Manage bans/mitigations",
  280. Long: `This is the main interaction point with local ban database for humans.
  281. You can add/delete/list or flush current bans in your local ban DB.`,
  282. Args: cobra.MinimumNArgs(1),
  283. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  284. var err error
  285. if !config.configured {
  286. return fmt.Errorf("you must configure cli before using bans")
  287. }
  288. outputConfig := outputs.OutputFactory{
  289. BackendFolder: config.BackendPluginFolder,
  290. Flush: false,
  291. }
  292. outputCTX, err = outputs.NewOutput(&outputConfig)
  293. if err != nil {
  294. return fmt.Errorf(err.Error())
  295. }
  296. return nil
  297. },
  298. }
  299. cmdBan.PersistentFlags().StringVar(&remediationType, "remediation", "ban", "Set specific remediation type : ban|slow|captcha")
  300. cmdBan.Flags().SortFlags = false
  301. cmdBan.PersistentFlags().SortFlags = false
  302. var cmdBanAdd = &cobra.Command{
  303. Use: "add [ip|range] <target> <duration> <reason>",
  304. Short: "Adds a ban against a given ip/range for the provided duration",
  305. Long: `
  306. Allows to add a ban against a specific ip or range target for a specific duration.
  307. The duration argument can be expressed in seconds(s), minutes(m) or hours (h).
  308. See [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) for more informations.`,
  309. Example: `cscli ban add ip 1.2.3.4 24h "scan"
  310. cscli ban add range 1.2.3.0/24 24h "the whole range"`,
  311. Args: cobra.MinimumNArgs(4),
  312. }
  313. cmdBan.AddCommand(cmdBanAdd)
  314. var cmdBanAddIp = &cobra.Command{
  315. Use: "ip <target> <duration> <reason>",
  316. Short: "Adds the specific ip to the ban db",
  317. Long: `Duration must be [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration), expressed in s/m/h.`,
  318. Example: `cscli ban add ip 1.2.3.4 12h "the scan"`,
  319. Args: cobra.MinimumNArgs(3),
  320. Run: func(cmd *cobra.Command, args []string) {
  321. reason := strings.Join(args[2:], " ")
  322. if err := BanAdd(args[0], args[1], reason, remediationType); err != nil {
  323. log.Fatalf("failed to add ban to sqlite : %v", err)
  324. }
  325. },
  326. }
  327. cmdBanAdd.AddCommand(cmdBanAddIp)
  328. var cmdBanAddRange = &cobra.Command{
  329. Use: "range <target> <duration> <reason>",
  330. Short: "Adds the specific ip to the ban db",
  331. Long: `Duration must be [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) compatible, expressed in s/m/h.`,
  332. Example: `cscli ban add range 1.2.3.0/24 12h "the whole range"`,
  333. Args: cobra.MinimumNArgs(3),
  334. Run: func(cmd *cobra.Command, args []string) {
  335. reason := strings.Join(args[2:], " ")
  336. if err := BanAdd(args[0], args[1], reason, remediationType); err != nil {
  337. log.Fatalf("failed to add ban to sqlite : %v", err)
  338. }
  339. },
  340. }
  341. cmdBanAdd.AddCommand(cmdBanAddRange)
  342. var cmdBanDel = &cobra.Command{
  343. Use: "del [command] <target>",
  344. Short: "Delete bans from db",
  345. Long: "The removal of the bans can be applied on a single IP address or directly on a IP range.",
  346. Example: `cscli ban del ip 1.2.3.4
  347. cscli ban del range 1.2.3.0/24`,
  348. Args: cobra.MinimumNArgs(2),
  349. }
  350. cmdBan.AddCommand(cmdBanDel)
  351. var cmdBanFlush = &cobra.Command{
  352. Use: "flush",
  353. Short: "Fush ban DB",
  354. Example: `cscli ban flush`,
  355. Args: cobra.NoArgs,
  356. Run: func(cmd *cobra.Command, args []string) {
  357. if err := outputCTX.DeleteAll(); err != nil {
  358. log.Fatalf(err.Error())
  359. }
  360. log.Printf("Ban DB flushed")
  361. },
  362. }
  363. cmdBan.AddCommand(cmdBanFlush)
  364. var cmdBanDelIp = &cobra.Command{
  365. Use: "ip <target>",
  366. Short: "Delete bans for given ip from db",
  367. Example: `cscli ban del ip 1.2.3.4`,
  368. Args: cobra.ExactArgs(1),
  369. Run: func(cmd *cobra.Command, args []string) {
  370. count, err := outputCTX.Delete(args[0])
  371. if err != nil {
  372. log.Fatalf("failed to delete %s : %v", args[0], err)
  373. }
  374. log.Infof("Deleted %d entries", count)
  375. },
  376. }
  377. cmdBanDel.AddCommand(cmdBanDelIp)
  378. var cmdBanDelRange = &cobra.Command{
  379. Use: "range <target>",
  380. Short: "Delete bans for given ip from db",
  381. Example: `cscli ban del range 1.2.3.0/24`,
  382. Args: cobra.ExactArgs(1),
  383. Run: func(cmd *cobra.Command, args []string) {
  384. count, err := outputCTX.Delete(args[0])
  385. if err != nil {
  386. log.Fatalf("failed to delete %s : %v", args[0], err)
  387. }
  388. log.Infof("Deleted %d entries", count)
  389. },
  390. }
  391. cmdBanDel.AddCommand(cmdBanDelRange)
  392. var cmdBanList = &cobra.Command{
  393. Use: "list",
  394. Short: "List local or api bans/remediations",
  395. Long: `List the bans, by default only local decisions.
  396. If --all/-a is specified, bans will be displayed without limit (--limit).
  397. Default limit is 50.
  398. Time can be specified with --at and support a variety of date formats:
  399. - Jan 2 15:04:05
  400. - Mon Jan 02 15:04:05.000000 2006
  401. - 2006-01-02T15:04:05Z07:00
  402. - 2006/01/02
  403. - 2006/01/02 15:04
  404. - 2006-01-02
  405. - 2006-01-02 15:04
  406. `,
  407. Example: `ban list --range 0.0.0.0/0 : will list all
  408. ban list --country CN
  409. ban list --reason crowdsecurity/http-probing
  410. ban list --as OVH`,
  411. Args: cobra.ExactArgs(0),
  412. Run: func(cmd *cobra.Command, args []string) {
  413. if err := BanList(); err != nil {
  414. log.Fatalf("failed to list bans : %v", err)
  415. }
  416. },
  417. }
  418. cmdBanList.PersistentFlags().StringVar(&atTime, "at", "", "List bans at given time")
  419. cmdBanList.PersistentFlags().BoolVarP(&displayALL, "all", "a", false, "List bans without limit")
  420. cmdBanList.PersistentFlags().BoolVarP(&displayAPI, "api", "", false, "List as well bans received from API")
  421. cmdBanList.PersistentFlags().StringVar(&ipFilter, "ip", "", "List bans for given IP")
  422. cmdBanList.PersistentFlags().StringVar(&rangeFilter, "range", "", "List bans belonging to given range")
  423. cmdBanList.PersistentFlags().StringVar(&reasonFilter, "reason", "", "List bans containing given reason")
  424. cmdBanList.PersistentFlags().StringVar(&countryFilter, "country", "", "List bans belonging to given country code")
  425. cmdBanList.PersistentFlags().StringVar(&asFilter, "as", "", "List bans belonging to given AS name")
  426. cmdBanList.PersistentFlags().IntVar(&displayLimit, "limit", 50, "Limit of bans to display (default 50)")
  427. cmdBan.AddCommand(cmdBanList)
  428. return cmdBan
  429. }