ban.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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. var all bool
  20. func simpleBanToSignal(targetIP string, reason string, expirationStr string, action string, asName string, asNum string, country string, banSource string) (types.SignalOccurence, error) {
  21. var signalOcc types.SignalOccurence
  22. expiration, err := time.ParseDuration(expirationStr)
  23. if err != nil {
  24. return signalOcc, err
  25. }
  26. asOrgInt := 0
  27. if asNum != "" {
  28. asOrgInt, err = strconv.Atoi(asNum)
  29. if err != nil {
  30. log.Infof("Invalid as value %s : %s", asNum, err)
  31. }
  32. }
  33. banApp := types.BanApplication{
  34. MeasureSource: banSource,
  35. MeasureType: action,
  36. Until: time.Now().Add(expiration),
  37. IpText: targetIP,
  38. TargetCN: country,
  39. TargetAS: asOrgInt,
  40. TargetASName: asName,
  41. Reason: reason,
  42. }
  43. var parsedIP net.IP
  44. var parsedRange *net.IPNet
  45. if strings.Contains(targetIP, "/") {
  46. if _, parsedRange, err = net.ParseCIDR(targetIP); err != nil {
  47. return signalOcc, fmt.Errorf("'%s' is not a valid CIDR", targetIP)
  48. }
  49. if parsedRange == nil {
  50. return signalOcc, fmt.Errorf("unable to parse network : %s", err)
  51. }
  52. banApp.StartIp = types.IP2Int(parsedRange.IP)
  53. banApp.EndIp = types.IP2Int(types.LastAddress(parsedRange))
  54. } else {
  55. parsedIP = net.ParseIP(targetIP)
  56. if parsedIP == nil {
  57. return signalOcc, fmt.Errorf("'%s' is not a valid IP", targetIP)
  58. }
  59. banApp.StartIp = types.IP2Int(parsedIP)
  60. banApp.EndIp = types.IP2Int(parsedIP)
  61. }
  62. var banApps = make([]types.BanApplication, 0)
  63. banApps = append(banApps, banApp)
  64. signalOcc = types.SignalOccurence{
  65. Scenario: reason,
  66. Events_count: 1,
  67. Start_at: time.Now(),
  68. Stop_at: time.Now(),
  69. BanApplications: banApps,
  70. Source_ip: targetIP,
  71. Source_AutonomousSystemNumber: asNum,
  72. Source_AutonomousSystemOrganization: asName,
  73. Source_Country: country,
  74. }
  75. return signalOcc, nil
  76. }
  77. func BanList() error {
  78. at := time.Now()
  79. if atTime != "" {
  80. _, at = parser.GenDateParse(atTime)
  81. if at.IsZero() {
  82. return fmt.Errorf("unable to parse date '%s'", atTime)
  83. }
  84. }
  85. ret, err := outputCTX.ReadAT(at)
  86. if err != nil {
  87. return fmt.Errorf("unable to get records from Database : %v", err)
  88. }
  89. if config.output == "raw" {
  90. fmt.Printf("source,ip,reason,bans,action,country,as,events_count,expiration\n")
  91. for _, rm := range ret {
  92. 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"])
  93. }
  94. } else if config.output == "json" {
  95. x, _ := json.MarshalIndent(ret, "", " ")
  96. fmt.Printf("%s", string(x))
  97. } else if config.output == "human" {
  98. uniqAS := map[string]bool{}
  99. uniqCN := map[string]bool{}
  100. table := tablewriter.NewWriter(os.Stdout)
  101. table.SetHeader([]string{"Source", "Ip", "Reason", "Bans", "Action", "Country", "AS", "Events", "Expiration"})
  102. dispcount := 0
  103. totcount := 0
  104. apicount := 0
  105. for _, rm := range ret {
  106. if !all && rm["source"] == "api" {
  107. apicount++
  108. if _, ok := uniqAS[rm["as"]]; !ok {
  109. uniqAS[rm["as"]] = true
  110. }
  111. if _, ok := uniqCN[rm["cn"]]; !ok {
  112. uniqCN[rm["cn"]] = true
  113. }
  114. continue
  115. }
  116. if dispcount < 20 {
  117. table.Append([]string{rm["source"], rm["iptext"], rm["reason"], rm["bancount"], rm["action"], rm["cn"], rm["as"], rm["events_count"], rm["until"]})
  118. }
  119. totcount++
  120. dispcount++
  121. }
  122. if dispcount > 0 {
  123. if !all {
  124. fmt.Printf("%d local decisions:\n", totcount)
  125. }
  126. table.Render() // Send output
  127. if dispcount > 20 {
  128. fmt.Printf("Additional records stripped.\n")
  129. }
  130. } else {
  131. fmt.Printf("No local decisions.\n")
  132. }
  133. if !all {
  134. fmt.Printf("And %d records from API, %d distinct AS, %d distinct countries\n", apicount, len(uniqAS), len(uniqCN))
  135. }
  136. }
  137. return nil
  138. }
  139. func BanAdd(target string, duration string, reason string, action string) error {
  140. var signalOcc types.SignalOccurence
  141. var err error
  142. signalOcc, err = simpleBanToSignal(target, reason, duration, action, "", "", "", "cli")
  143. if err != nil {
  144. return fmt.Errorf("unable to insert ban : %v", err)
  145. }
  146. err = outputCTX.Insert(signalOcc)
  147. if err != nil {
  148. return err
  149. }
  150. err = outputCTX.Flush()
  151. if err != nil {
  152. return err
  153. }
  154. log.Infof("Wrote ban to database.")
  155. return nil
  156. }
  157. func NewBanCmds() *cobra.Command {
  158. /*TODO : add a remediation type*/
  159. var cmdBan = &cobra.Command{
  160. Use: "ban [command] <target> <duration> <reason>",
  161. Short: "Manage bans/mitigations",
  162. Long: `This is the main interaction point with local ban database for humans.
  163. You can add/delete/list or flush current bans in your local ban DB.`,
  164. Args: cobra.MinimumNArgs(1),
  165. PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
  166. var err error
  167. if !config.configured {
  168. return fmt.Errorf("you must configure cli before using bans")
  169. }
  170. outputConfig := outputs.OutputFactory{
  171. BackendFolder: config.BackendPluginFolder,
  172. Flush: false,
  173. }
  174. outputCTX, err = outputs.NewOutput(&outputConfig)
  175. if err != nil {
  176. return fmt.Errorf(err.Error())
  177. }
  178. return nil
  179. },
  180. }
  181. cmdBan.PersistentFlags().StringVar(&remediationType, "remediation", "ban", "Set specific remediation type : ban|slow|captcha")
  182. cmdBan.Flags().SortFlags = false
  183. cmdBan.PersistentFlags().SortFlags = false
  184. var cmdBanAdd = &cobra.Command{
  185. Use: "add [ip|range] <target> <duration> <reason>",
  186. Short: "Adds a ban against a given ip/range for the provided duration",
  187. Long: `
  188. Allows to add a ban against a specific ip or range target for a specific duration.
  189. The duration argument can be expressed in seconds(s), minutes(m) or hours (h).
  190. See [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) for more informations.`,
  191. Example: `cscli ban add ip 1.2.3.4 24h "scan"
  192. cscli ban add range 1.2.3.0/24 24h "the whole range"`,
  193. Args: cobra.MinimumNArgs(4),
  194. }
  195. cmdBan.AddCommand(cmdBanAdd)
  196. var cmdBanAddIp = &cobra.Command{
  197. Use: "ip <target> <duration> <reason>",
  198. Short: "Adds the specific ip to the ban db",
  199. Long: `Duration must be [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration), expressed in s/m/h.`,
  200. Example: `cscli ban add ip 1.2.3.4 12h "the scan"`,
  201. Args: cobra.MinimumNArgs(3),
  202. Run: func(cmd *cobra.Command, args []string) {
  203. reason := strings.Join(args[2:], " ")
  204. if err := BanAdd(args[0], args[1], reason, remediationType); err != nil {
  205. log.Fatalf("failed to add ban to database : %v", err)
  206. }
  207. },
  208. }
  209. cmdBanAdd.AddCommand(cmdBanAddIp)
  210. var cmdBanAddRange = &cobra.Command{
  211. Use: "range <target> <duration> <reason>",
  212. Short: "Adds the specific ip to the ban db",
  213. Long: `Duration must be [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) compatible, expressed in s/m/h.`,
  214. Example: `cscli ban add range 1.2.3.0/24 12h "the whole range"`,
  215. Args: cobra.MinimumNArgs(3),
  216. Run: func(cmd *cobra.Command, args []string) {
  217. reason := strings.Join(args[2:], " ")
  218. if err := BanAdd(args[0], args[1], reason, remediationType); err != nil {
  219. log.Fatalf("failed to add ban to database : %v", err)
  220. }
  221. },
  222. }
  223. cmdBanAdd.AddCommand(cmdBanAddRange)
  224. var cmdBanDel = &cobra.Command{
  225. Use: "del [command] <target>",
  226. Short: "Delete bans from db",
  227. Long: "The removal of the bans can be applied on a single IP address or directly on a IP range.",
  228. Example: `cscli ban del ip 1.2.3.4
  229. cscli ban del range 1.2.3.0/24`,
  230. Args: cobra.MinimumNArgs(2),
  231. }
  232. cmdBan.AddCommand(cmdBanDel)
  233. var cmdBanFlush = &cobra.Command{
  234. Use: "flush",
  235. Short: "Fush ban DB",
  236. Example: `cscli ban flush`,
  237. Args: cobra.NoArgs,
  238. Run: func(cmd *cobra.Command, args []string) {
  239. if err := outputCTX.DeleteAll(); err != nil {
  240. log.Fatalf(err.Error())
  241. }
  242. log.Printf("Ban DB flushed")
  243. },
  244. }
  245. cmdBan.AddCommand(cmdBanFlush)
  246. var cmdBanDelIp = &cobra.Command{
  247. Use: "ip <target>",
  248. Short: "Delete bans for given ip from db",
  249. Example: `cscli ban del ip 1.2.3.4`,
  250. Args: cobra.ExactArgs(1),
  251. Run: func(cmd *cobra.Command, args []string) {
  252. count, err := outputCTX.Delete(args[0])
  253. if err != nil {
  254. log.Fatalf("failed to delete %s : %v", args[0], err)
  255. }
  256. log.Infof("Deleted %d entries", count)
  257. },
  258. }
  259. cmdBanDel.AddCommand(cmdBanDelIp)
  260. var cmdBanDelRange = &cobra.Command{
  261. Use: "range <target>",
  262. Short: "Delete bans for given ip from db",
  263. Example: `cscli ban del range 1.2.3.0/24`,
  264. Args: cobra.ExactArgs(1),
  265. Run: func(cmd *cobra.Command, args []string) {
  266. count, err := outputCTX.Delete(args[0])
  267. if err != nil {
  268. log.Fatalf("failed to delete %s : %v", args[0], err)
  269. }
  270. log.Infof("Deleted %d entries", count)
  271. },
  272. }
  273. cmdBanDel.AddCommand(cmdBanDelRange)
  274. var cmdBanList = &cobra.Command{
  275. Use: "list",
  276. Short: "List local or api bans/remediations",
  277. Long: `List the bans, by default only local decisions.
  278. If --all/-a is specified, api-provided bans will be displayed too.
  279. Time can be specified with --at and support a variety of date formats:
  280. - Jan 2 15:04:05
  281. - Mon Jan 02 15:04:05.000000 2006
  282. - 2006-01-02T15:04:05Z07:00
  283. - 2006/01/02
  284. - 2006/01/02 15:04
  285. - 2006-01-02
  286. - 2006-01-02 15:04
  287. `,
  288. Args: cobra.ExactArgs(0),
  289. Run: func(cmd *cobra.Command, args []string) {
  290. if err := BanList(); err != nil {
  291. log.Fatalf("failed to list bans : %v", err)
  292. }
  293. },
  294. }
  295. cmdBanList.PersistentFlags().StringVar(&atTime, "at", "", "List bans at given time")
  296. cmdBanList.PersistentFlags().BoolVarP(&all, "all", "a", false, "List as well bans received from API")
  297. cmdBan.AddCommand(cmdBanList)
  298. return cmdBan
  299. }