bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/cmd/synsec-cli/alerts.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net/url"
     8  	"os"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"bitbucket.org/Aishee/synsec/pkg/apiclient"
    14  	"bitbucket.org/Aishee/synsec/pkg/cwversion"
    15  	"bitbucket.org/Aishee/synsec/pkg/models"
    16  	"github.com/go-openapi/strfmt"
    17  	"github.com/olekukonko/tablewriter"
    18  	log "github.com/sirupsen/logrus"
    19  	"github.com/spf13/cobra"
    20  	"gopkg.in/yaml.v2"
    21  )
    22  
    23  var printMachine bool
    24  var limit *int
    25  
    26  func DecisionsFromAlert(alert *models.Alert) string {
    27  	ret := ""
    28  	var decMap = make(map[string]int)
    29  	for _, decision := range alert.Decisions {
    30  		k := *decision.Type
    31  		if *decision.Simulated {
    32  			k = fmt.Sprintf("(simul)%s", k)
    33  		}
    34  		v := decMap[k]
    35  		decMap[k] = v + 1
    36  	}
    37  	for k, v := range decMap {
    38  		if len(ret) > 0 {
    39  			ret += " "
    40  		}
    41  		ret += fmt.Sprintf("%s:%d", k, v)
    42  	}
    43  	return ret
    44  }
    45  
    46  func AlertsToTable(alerts *models.GetAlertsResponse, printMachine bool) error {
    47  
    48  	if csConfig.Cscli.Output == "raw" {
    49  		if printMachine {
    50  			fmt.Printf("id,scope,value,reason,country,as,decisions,created_at,machine\n")
    51  		} else {
    52  			fmt.Printf("id,scope,value,reason,country,as,decisions,created_at\n")
    53  		}
    54  		for _, alertItem := range *alerts {
    55  			if printMachine {
    56  				fmt.Printf("%v,%v,%v,%v,%v,%v,%v,%v,%v\n",
    57  					alertItem.ID,
    58  					*alertItem.Source.Scope,
    59  					*alertItem.Source.Value,
    60  					*alertItem.Scenario,
    61  					alertItem.Source.Cn,
    62  					alertItem.Source.AsNumber+" "+alertItem.Source.AsName,
    63  					DecisionsFromAlert(alertItem),
    64  					*alertItem.StartAt,
    65  					alertItem.MachineID)
    66  			} else {
    67  				fmt.Printf("%v,%v,%v,%v,%v,%v,%v,%v\n",
    68  					alertItem.ID,
    69  					*alertItem.Source.Scope,
    70  					*alertItem.Source.Value,
    71  					*alertItem.Scenario,
    72  					alertItem.Source.Cn,
    73  					alertItem.Source.AsNumber+" "+alertItem.Source.AsName,
    74  					DecisionsFromAlert(alertItem),
    75  					*alertItem.StartAt)
    76  			}
    77  
    78  		}
    79  	} else if csConfig.Cscli.Output == "json" {
    80  		x, _ := json.MarshalIndent(alerts, "", " ")
    81  		fmt.Printf("%s", string(x))
    82  	} else if csConfig.Cscli.Output == "human" {
    83  
    84  		table := tablewriter.NewWriter(os.Stdout)
    85  		if printMachine {
    86  			table.SetHeader([]string{"ID", "value", "reason", "country", "as", "decisions", "created_at", "machine"})
    87  		} else {
    88  			table.SetHeader([]string{"ID", "value", "reason", "country", "as", "decisions", "created_at"})
    89  		}
    90  
    91  		if len(*alerts) == 0 {
    92  			fmt.Println("No active alerts")
    93  			return nil
    94  		}
    95  		for _, alertItem := range *alerts {
    96  
    97  			displayVal := *alertItem.Source.Scope
    98  			if *alertItem.Source.Value != "" {
    99  				displayVal += ":" + *alertItem.Source.Value
   100  			}
   101  			if printMachine {
   102  				table.Append([]string{
   103  					strconv.Itoa(int(alertItem.ID)),
   104  					displayVal,
   105  					*alertItem.Scenario,
   106  					alertItem.Source.Cn,
   107  					alertItem.Source.AsNumber + " " + alertItem.Source.AsName,
   108  					DecisionsFromAlert(alertItem),
   109  					*alertItem.StartAt,
   110  					alertItem.MachineID,
   111  				})
   112  			} else {
   113  				table.Append([]string{
   114  					strconv.Itoa(int(alertItem.ID)),
   115  					displayVal,
   116  					*alertItem.Scenario,
   117  					alertItem.Source.Cn,
   118  					alertItem.Source.AsNumber + " " + alertItem.Source.AsName,
   119  					DecisionsFromAlert(alertItem),
   120  					*alertItem.StartAt,
   121  				})
   122  			}
   123  		}
   124  		table.Render() // Send output
   125  	}
   126  	return nil
   127  }
   128  
   129  func DisplayOneAlert(alert *models.Alert, withDetail bool) error {
   130  	if csConfig.Cscli.Output == "human" {
   131  		fmt.Printf("\n################################################################################################\n\n")
   132  		scopeAndValue := *alert.Source.Scope
   133  		if *alert.Source.Value != "" {
   134  			scopeAndValue += ":" + *alert.Source.Value
   135  		}
   136  		fmt.Printf(" - ID         : %d\n", alert.ID)
   137  		fmt.Printf(" - Date       : %s\n", alert.CreatedAt)
   138  		fmt.Printf(" - Machine    : %s\n", alert.MachineID)
   139  		fmt.Printf(" - Simulation : %v\n", *alert.Simulated)
   140  		fmt.Printf(" - Reason     : %s\n", *alert.Scenario)
   141  		fmt.Printf(" - Events Count : %d\n", *alert.EventsCount)
   142  		fmt.Printf(" - Scope:Value: %s\n", scopeAndValue)
   143  		fmt.Printf(" - Country    : %s\n", alert.Source.Cn)
   144  		fmt.Printf(" - AS         : %s\n\n", alert.Source.AsName)
   145  		foundActive := false
   146  		table := tablewriter.NewWriter(os.Stdout)
   147  		table.SetHeader([]string{"ID", "scope:value", "action", "expiration", "created_at"})
   148  		for _, decision := range alert.Decisions {
   149  			parsedDuration, err := time.ParseDuration(*decision.Duration)
   150  			if err != nil {
   151  				log.Errorf(err.Error())
   152  			}
   153  			expire := time.Now().Add(parsedDuration)
   154  			if time.Now().After(expire) {
   155  				continue
   156  			}
   157  			foundActive = true
   158  			scopeAndValue := *decision.Scope
   159  			if *decision.Value != "" {
   160  				scopeAndValue += ":" + *decision.Value
   161  			}
   162  			table.Append([]string{
   163  				strconv.Itoa(int(decision.ID)),
   164  				scopeAndValue,
   165  				*decision.Type,
   166  				*decision.Duration,
   167  				alert.CreatedAt,
   168  			})
   169  		}
   170  		if foundActive {
   171  			fmt.Printf(" - Active Decisions  :\n")
   172  			table.Render() // Send output
   173  		}
   174  
   175  		if withDetail {
   176  			fmt.Printf("\n - Events  :\n")
   177  			for _, event := range alert.Events {
   178  				fmt.Printf("\n- Date: %s\n", *event.Timestamp)
   179  				table = tablewriter.NewWriter(os.Stdout)
   180  				table.SetHeader([]string{"Key", "Value"})
   181  				for _, meta := range event.Meta {
   182  					table.Append([]string{
   183  						meta.Key,
   184  						meta.Value,
   185  					})
   186  				}
   187  				table.Render() // Send output
   188  			}
   189  		}
   190  	}
   191  	return nil
   192  }
   193  
   194  func NewAlertsCmd() *cobra.Command {
   195  	/* ---- ALERTS COMMAND */
   196  	var cmdAlerts = &cobra.Command{
   197  		Use:   "alerts [action]",
   198  		Short: "Manage alerts",
   199  		Args:  cobra.MinimumNArgs(1),
   200  		PersistentPreRun: func(cmd *cobra.Command, args []string) {
   201  			var err error
   202  			if err := csConfig.LoadAPIClient(); err != nil {
   203  				log.Fatalf("loading api client: %s", err.Error())
   204  			}
   205  			if csConfig.API.Client == nil {
   206  				log.Fatalln("There is no configuration on 'api_client:'")
   207  			}
   208  			if csConfig.API.Client.Credentials == nil {
   209  				log.Fatalf("Please provide credentials for the API in '%s'", csConfig.API.Client.CredentialsFilePath)
   210  			}
   211  			apiURL, err := url.Parse(csConfig.API.Client.Credentials.URL)
   212  			if err != nil {
   213  				log.Fatalf("parsing api url: %s", apiURL)
   214  			}
   215  			Client, err = apiclient.NewClient(&apiclient.Config{
   216  				MachineID:     csConfig.API.Client.Credentials.Login,
   217  				Password:      strfmt.Password(csConfig.API.Client.Credentials.Password),
   218  				UserAgent:     fmt.Sprintf("synsec/%s", cwversion.VersionStr()),
   219  				URL:           apiURL,
   220  				VersionPrefix: "v1",
   221  			})
   222  
   223  			if err != nil {
   224  				log.Fatalf("new api client: %s", err.Error())
   225  			}
   226  		},
   227  	}
   228  
   229  	var alertListFilter = apiclient.AlertsListOpts{
   230  		ScopeEquals:    new(string),
   231  		ValueEquals:    new(string),
   232  		ScenarioEquals: new(string),
   233  		IPEquals:       new(string),
   234  		RangeEquals:    new(string),
   235  		Since:          new(string),
   236  		Until:          new(string),
   237  		TypeEquals:     new(string),
   238  	}
   239  	limit = new(int)
   240  	contained := new(bool)
   241  	var cmdAlertsList = &cobra.Command{
   242  		Use:   "list [filters]",
   243  		Short: "List alerts",
   244  		Example: `ccscli alerts list
   245  ccscli alerts list --ip 1.2.3.4
   246  ccscli alerts list --range 1.2.3.0/24
   247  ccscli alerts list -s breakteam/ssh-bf
   248  ccscli alerts list --type ban`,
   249  		Run: func(cmd *cobra.Command, args []string) {
   250  			var err error
   251  
   252  			if err := manageCliDecisionAlerts(alertListFilter.IPEquals, alertListFilter.RangeEquals,
   253  				alertListFilter.ScopeEquals, alertListFilter.ValueEquals); err != nil {
   254  				_ = cmd.Help()
   255  				log.Fatalf("%s", err)
   256  			}
   257  			if limit != nil {
   258  				alertListFilter.Limit = limit
   259  			}
   260  
   261  			if *alertListFilter.Until == "" {
   262  				alertListFilter.Until = nil
   263  			} else {
   264  				/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
   265  				if strings.HasSuffix(*alertListFilter.Until, "d") {
   266  					realDuration := strings.TrimSuffix(*alertListFilter.Until, "d")
   267  					days, err := strconv.Atoi(realDuration)
   268  					if err != nil {
   269  						cmd.Help()
   270  						log.Fatalf("Can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Until)
   271  					}
   272  					*alertListFilter.Until = fmt.Sprintf("%d%s", days*24, "h")
   273  				}
   274  			}
   275  			if *alertListFilter.Since == "" {
   276  				alertListFilter.Since = nil
   277  			} else {
   278  				/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
   279  				if strings.HasSuffix(*alertListFilter.Since, "d") {
   280  					realDuration := strings.TrimSuffix(*alertListFilter.Since, "d")
   281  					days, err := strconv.Atoi(realDuration)
   282  					if err != nil {
   283  						cmd.Help()
   284  						log.Fatalf("Can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Since)
   285  					}
   286  					*alertListFilter.Since = fmt.Sprintf("%d%s", days*24, "h")
   287  				}
   288  			}
   289  			if *alertListFilter.TypeEquals == "" {
   290  				alertListFilter.TypeEquals = nil
   291  			}
   292  			if *alertListFilter.ScopeEquals == "" {
   293  				alertListFilter.ScopeEquals = nil
   294  			}
   295  			if *alertListFilter.ValueEquals == "" {
   296  				alertListFilter.ValueEquals = nil
   297  			}
   298  			if *alertListFilter.ScenarioEquals == "" {
   299  				alertListFilter.ScenarioEquals = nil
   300  			}
   301  			if *alertListFilter.IPEquals == "" {
   302  				alertListFilter.IPEquals = nil
   303  			}
   304  			if *alertListFilter.RangeEquals == "" {
   305  				alertListFilter.RangeEquals = nil
   306  			}
   307  			if contained != nil && *contained {
   308  				alertListFilter.Contains = new(bool)
   309  			}
   310  			alerts, _, err := Client.Alerts.List(context.Background(), alertListFilter)
   311  			if err != nil {
   312  				log.Fatalf("Unable to list alerts : %v", err.Error())
   313  			}
   314  
   315  			err = AlertsToTable(alerts, printMachine)
   316  			if err != nil {
   317  				log.Fatalf("unable to list alerts : %v", err.Error())
   318  			}
   319  		},
   320  	}
   321  	cmdAlertsList.Flags().SortFlags = false
   322  	cmdAlertsList.Flags().StringVar(alertListFilter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
   323  	cmdAlertsList.Flags().StringVar(alertListFilter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
   324  	cmdAlertsList.Flags().StringVarP(alertListFilter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
   325  	cmdAlertsList.Flags().StringVarP(alertListFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. breakteam/ssh-bf)")
   326  	cmdAlertsList.Flags().StringVarP(alertListFilter.RangeEquals, "range", "r", "", "restrict to alerts from this range (shorthand for --scope range --value <RANGE/X>)")
   327  	cmdAlertsList.Flags().StringVar(alertListFilter.TypeEquals, "type", "", "restrict to alerts with given decision type (ie. ban, captcha)")
   328  	cmdAlertsList.Flags().StringVar(alertListFilter.ScopeEquals, "scope", "", "restrict to alerts of this scope (ie. ip,range)")
   329  	cmdAlertsList.Flags().StringVarP(alertListFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
   330  	cmdAlertsList.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
   331  	cmdAlertsList.Flags().BoolVarP(&printMachine, "machine", "m", false, "print machines that sended alerts")
   332  	cmdAlertsList.Flags().IntVarP(limit, "limit", "l", 50, "limit size of alerts list table (0 to view all alerts)")
   333  	cmdAlerts.AddCommand(cmdAlertsList)
   334  
   335  	var ActiveDecision *bool
   336  	var AlertDeleteAll bool
   337  	var alertDeleteFilter = apiclient.AlertsDeleteOpts{
   338  		ScopeEquals:    new(string),
   339  		ValueEquals:    new(string),
   340  		ScenarioEquals: new(string),
   341  		IPEquals:       new(string),
   342  		RangeEquals:    new(string),
   343  	}
   344  	var cmdAlertsDelete = &cobra.Command{
   345  		Use: "delete [filters] [--all]",
   346  		Short: `Delete alerts
   347  /!\ This command can be use only on the same machine than the local API.`,
   348  		Example: `ccscli alerts delete --ip 1.2.3.4
   349  ccscli alerts delete --range 1.2.3.0/24
   350  ccscli alerts delete -s breakteam/ssh-bf"`,
   351  		Args: cobra.ExactArgs(0),
   352  		PreRun: func(cmd *cobra.Command, args []string) {
   353  			if AlertDeleteAll {
   354  				return
   355  			}
   356  			if *alertDeleteFilter.ScopeEquals == "" && *alertDeleteFilter.ValueEquals == "" &&
   357  				*alertDeleteFilter.ScenarioEquals == "" && *alertDeleteFilter.IPEquals == "" &&
   358  				*alertDeleteFilter.RangeEquals == "" {
   359  				_ = cmd.Usage()
   360  				log.Fatalln("At least one filter or --all must be specified")
   361  			}
   362  		},
   363  		Run: func(cmd *cobra.Command, args []string) {
   364  			var err error
   365  
   366  			if !AlertDeleteAll {
   367  				if err := manageCliDecisionAlerts(alertDeleteFilter.IPEquals, alertDeleteFilter.RangeEquals,
   368  					alertDeleteFilter.ScopeEquals, alertDeleteFilter.ValueEquals); err != nil {
   369  					_ = cmd.Help()
   370  					log.Fatalf("%s", err)
   371  				}
   372  				if ActiveDecision != nil {
   373  					alertDeleteFilter.ActiveDecisionEquals = ActiveDecision
   374  				}
   375  
   376  				if *alertDeleteFilter.ScopeEquals == "" {
   377  					alertDeleteFilter.ScopeEquals = nil
   378  				}
   379  				if *alertDeleteFilter.ValueEquals == "" {
   380  					alertDeleteFilter.ValueEquals = nil
   381  				}
   382  				if *alertDeleteFilter.ScenarioEquals == "" {
   383  					alertDeleteFilter.ScenarioEquals = nil
   384  				}
   385  				if *alertDeleteFilter.IPEquals == "" {
   386  					alertDeleteFilter.IPEquals = nil
   387  				}
   388  				if *alertDeleteFilter.RangeEquals == "" {
   389  					alertDeleteFilter.RangeEquals = nil
   390  				}
   391  				if contained != nil && *contained {
   392  					alertDeleteFilter.Contains = new(bool)
   393  				}
   394  			} else {
   395  				alertDeleteFilter = apiclient.AlertsDeleteOpts{}
   396  			}
   397  			alerts, _, err := Client.Alerts.Delete(context.Background(), alertDeleteFilter)
   398  			if err != nil {
   399  				log.Fatalf("Unable to delete alerts : %v", err.Error())
   400  			}
   401  			log.Infof("%s alert(s) deleted", alerts.NbDeleted)
   402  
   403  		},
   404  	}
   405  	cmdAlertsDelete.Flags().SortFlags = false
   406  	cmdAlertsDelete.Flags().StringVar(alertDeleteFilter.ScopeEquals, "scope", "", "the scope (ie. ip,range)")
   407  	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
   408  	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. breakteam/ssh-bf)")
   409  	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
   410  	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
   411  	cmdAlertsDelete.Flags().BoolVarP(&AlertDeleteAll, "all", "a", false, "delete all alerts")
   412  	cmdAlertsDelete.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
   413  
   414  	cmdAlerts.AddCommand(cmdAlertsDelete)
   415  
   416  	var details bool
   417  	var cmdAlertsInspect = &cobra.Command{
   418  		Use:     "inspect <alert_id>",
   419  		Short:   `Show info about an alert`,
   420  		Example: `ccscli alerts inspect 123`,
   421  		Run: func(cmd *cobra.Command, args []string) {
   422  			if len(args) == 0 {
   423  				_ = cmd.Help()
   424  				return
   425  			}
   426  			for _, alertID := range args {
   427  				id, err := strconv.Atoi(alertID)
   428  				if err != nil {
   429  					log.Fatalf("bad alert id %s", alertID)
   430  					continue
   431  				}
   432  				alert, _, err := Client.Alerts.GetByID(context.Background(), id)
   433  				if err != nil {
   434  					log.Fatalf("can't find alert with id %s: %s", alertID, err)
   435  				}
   436  				switch csConfig.Cscli.Output {
   437  				case "human":
   438  					if err := DisplayOneAlert(alert, details); err != nil {
   439  						continue
   440  					}
   441  				case "json":
   442  					data, err := json.MarshalIndent(alert, "", "  ")
   443  					if err != nil {
   444  						log.Fatalf("unable to marshal alert with id %s: %s", alertID, err)
   445  					}
   446  					fmt.Printf("%s\n", string(data))
   447  				case "raw":
   448  					data, err := yaml.Marshal(alert)
   449  					if err != nil {
   450  						log.Fatalf("unable to marshal alert with id %s: %s", alertID, err)
   451  					}
   452  					fmt.Printf("%s\n", string(data))
   453  				}
   454  			}
   455  		},
   456  	}
   457  	cmdAlertsInspect.Flags().SortFlags = false
   458  	cmdAlertsInspect.Flags().BoolVarP(&details, "details", "d", false, "show alerts with events")
   459  
   460  	cmdAlerts.AddCommand(cmdAlertsInspect)
   461  
   462  	return cmdAlerts
   463  }