bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/cmd/synsec-cli/decisions.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  	"bitbucket.org/Aishee/synsec/pkg/types"
    17  	"github.com/go-openapi/strfmt"
    18  	"github.com/olekukonko/tablewriter"
    19  	log "github.com/sirupsen/logrus"
    20  	"github.com/spf13/cobra"
    21  )
    22  
    23  var Client *apiclient.ApiClient
    24  
    25  func DecisionsToTable(alerts *models.GetAlertsResponse) error {
    26  	/*here we cheat a bit : to make it more readable for the user, we dedup some entries*/
    27  	var spamLimit map[string]bool = make(map[string]bool)
    28  
    29  	/*process in reverse order to keep the latest item only*/
    30  	for aIdx := len(*alerts) - 1; aIdx >= 0; aIdx-- {
    31  		alertItem := (*alerts)[aIdx]
    32  		newDecisions := make([]*models.Decision, 0)
    33  		for _, decisionItem := range alertItem.Decisions {
    34  			spamKey := fmt.Sprintf("%t:%s:%s:%s", *decisionItem.Simulated, *decisionItem.Type, *decisionItem.Scope, *decisionItem.Value)
    35  			if _, ok := spamLimit[spamKey]; ok {
    36  				continue
    37  			}
    38  			spamLimit[spamKey] = true
    39  			newDecisions = append(newDecisions, decisionItem)
    40  		}
    41  		alertItem.Decisions = newDecisions
    42  	}
    43  	if csConfig.Cscli.Output == "raw" {
    44  		fmt.Printf("id,source,ip,reason,action,country,as,events_count,expiration,simulated,alert_id\n")
    45  		for _, alertItem := range *alerts {
    46  			for _, decisionItem := range alertItem.Decisions {
    47  				fmt.Printf("%v,%v,%v,%v,%v,%v,%v,%v,%v,%v,%v\n",
    48  					decisionItem.ID,
    49  					*decisionItem.Origin,
    50  					*decisionItem.Scope+":"+*decisionItem.Value,
    51  					*decisionItem.Scenario,
    52  					*decisionItem.Type,
    53  					alertItem.Source.Cn,
    54  					alertItem.Source.AsNumber+" "+alertItem.Source.AsName,
    55  					*alertItem.EventsCount,
    56  					*decisionItem.Duration,
    57  					*decisionItem.Simulated,
    58  					alertItem.ID)
    59  			}
    60  		}
    61  	} else if csConfig.Cscli.Output == "json" {
    62  		x, _ := json.MarshalIndent(alerts, "", " ")
    63  		fmt.Printf("%s", string(x))
    64  	} else if csConfig.Cscli.Output == "human" {
    65  
    66  		table := tablewriter.NewWriter(os.Stdout)
    67  		table.SetHeader([]string{"ID", "Source", "Scope:Value", "Reason", "Action", "Country", "AS", "Events", "expiration", "Alert ID"})
    68  
    69  		if len(*alerts) == 0 {
    70  			fmt.Println("No active decisions")
    71  			return nil
    72  		}
    73  
    74  		for _, alertItem := range *alerts {
    75  			for _, decisionItem := range alertItem.Decisions {
    76  				if *alertItem.Simulated {
    77  					*decisionItem.Type = fmt.Sprintf("(simul)%s", *decisionItem.Type)
    78  				}
    79  				table.Append([]string{
    80  					strconv.Itoa(int(decisionItem.ID)),
    81  					*decisionItem.Origin,
    82  					*decisionItem.Scope + ":" + *decisionItem.Value,
    83  					*decisionItem.Scenario,
    84  					*decisionItem.Type,
    85  					alertItem.Source.Cn,
    86  					alertItem.Source.AsNumber + " " + alertItem.Source.AsName,
    87  					strconv.Itoa(int(*alertItem.EventsCount)),
    88  					*decisionItem.Duration,
    89  					strconv.Itoa(int(alertItem.ID)),
    90  				})
    91  			}
    92  		}
    93  		table.Render() // Send output
    94  	}
    95  	return nil
    96  }
    97  
    98  func NewDecisionsCmd() *cobra.Command {
    99  	/* ---- DECISIONS COMMAND */
   100  	var cmdDecisions = &cobra.Command{
   101  		Use:     "decisions [action]",
   102  		Short:   "Manage decisions",
   103  		Long:    `Add/List/Delete decisions from LAPI`,
   104  		Example: `ccscli decisions [action] [filter]`,
   105  		/*TBD example*/
   106  		Args: cobra.MinimumNArgs(1),
   107  		PersistentPreRun: func(cmd *cobra.Command, args []string) {
   108  			if err := csConfig.LoadAPIClient(); err != nil {
   109  				log.Fatalf(err.Error())
   110  			}
   111  			if csConfig.API.Client == nil {
   112  				log.Fatalln("There is no configuration on 'api_client:'")
   113  			}
   114  			if csConfig.API.Client.Credentials == nil {
   115  				log.Fatalf("Please provide credentials for the API in '%s'", csConfig.API.Client.CredentialsFilePath)
   116  			}
   117  			password := strfmt.Password(csConfig.API.Client.Credentials.Password)
   118  			apiurl, err := url.Parse(csConfig.API.Client.Credentials.URL)
   119  			if err != nil {
   120  				log.Fatalf("parsing api url ('%s'): %s", csConfig.API.Client.Credentials.URL, err)
   121  			}
   122  			Client, err = apiclient.NewClient(&apiclient.Config{
   123  				MachineID:     csConfig.API.Client.Credentials.Login,
   124  				Password:      password,
   125  				UserAgent:     fmt.Sprintf("synsec/%s", cwversion.VersionStr()),
   126  				URL:           apiurl,
   127  				VersionPrefix: "v1",
   128  			})
   129  			if err != nil {
   130  				log.Fatalf("creating api client : %s", err)
   131  			}
   132  		},
   133  	}
   134  
   135  	var filter = apiclient.AlertsListOpts{
   136  		ValueEquals:    new(string),
   137  		ScopeEquals:    new(string),
   138  		ScenarioEquals: new(string),
   139  		IPEquals:       new(string),
   140  		RangeEquals:    new(string),
   141  		Since:          new(string),
   142  		Until:          new(string),
   143  		TypeEquals:     new(string),
   144  	}
   145  	NoSimu := new(bool)
   146  	contained := new(bool)
   147  	var cmdDecisionsList = &cobra.Command{
   148  		Use:   "list [options]",
   149  		Short: "List decisions from LAPI",
   150  		Example: `ccscli decisions list -i 1.2.3.4
   151  ccscli decisions list -r 1.2.3.0/24
   152  ccscli decisions list -s breakteam/ssh-bf
   153  ccscli decisions list -t ban
   154  `,
   155  		Args: cobra.ExactArgs(0),
   156  		Run: func(cmd *cobra.Command, args []string) {
   157  			var err error
   158  			/*take care of shorthand options*/
   159  			if err := manageCliDecisionAlerts(filter.IPEquals, filter.RangeEquals, filter.ScopeEquals, filter.ValueEquals); err != nil {
   160  				log.Fatalf("%s", err)
   161  			}
   162  			filter.ActiveDecisionEquals = new(bool)
   163  			*filter.ActiveDecisionEquals = true
   164  			if NoSimu != nil && *NoSimu {
   165  				filter.IncludeSimulated = new(bool)
   166  			}
   167  			/*nulify the empty entries to avoid bad filter*/
   168  			if *filter.Until == "" {
   169  				filter.Until = nil
   170  			} else {
   171  				/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
   172  				if strings.HasSuffix(*filter.Until, "d") {
   173  					realDuration := strings.TrimSuffix(*filter.Until, "d")
   174  					days, err := strconv.Atoi(realDuration)
   175  					if err != nil {
   176  						cmd.Help()
   177  						log.Fatalf("Can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *filter.Until)
   178  					}
   179  					*filter.Until = fmt.Sprintf("%d%s", days*24, "h")
   180  				}
   181  			}
   182  			if *filter.Since == "" {
   183  				filter.Since = nil
   184  			} else {
   185  				/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
   186  				if strings.HasSuffix(*filter.Since, "d") {
   187  					realDuration := strings.TrimSuffix(*filter.Since, "d")
   188  					days, err := strconv.Atoi(realDuration)
   189  					if err != nil {
   190  						cmd.Help()
   191  						log.Fatalf("Can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *filter.Until)
   192  					}
   193  					*filter.Since = fmt.Sprintf("%d%s", days*24, "h")
   194  				}
   195  			}
   196  			if *filter.TypeEquals == "" {
   197  				filter.TypeEquals = nil
   198  			}
   199  			if *filter.ValueEquals == "" {
   200  				filter.ValueEquals = nil
   201  			}
   202  			if *filter.ScopeEquals == "" {
   203  				filter.ScopeEquals = nil
   204  			}
   205  			if *filter.ScenarioEquals == "" {
   206  				filter.ScenarioEquals = nil
   207  			}
   208  			if *filter.IPEquals == "" {
   209  				filter.IPEquals = nil
   210  			}
   211  			if *filter.RangeEquals == "" {
   212  				filter.RangeEquals = nil
   213  			}
   214  
   215  			if contained != nil && *contained {
   216  				filter.Contains = new(bool)
   217  			}
   218  
   219  			alerts, _, err := Client.Alerts.List(context.Background(), filter)
   220  			if err != nil {
   221  				log.Fatalf("Unable to list decisions : %v", err.Error())
   222  			}
   223  
   224  			err = DecisionsToTable(alerts)
   225  			if err != nil {
   226  				log.Fatalf("unable to list decisions : %v", err.Error())
   227  			}
   228  		},
   229  	}
   230  	cmdDecisionsList.Flags().SortFlags = false
   231  	cmdDecisionsList.Flags().StringVar(filter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
   232  	cmdDecisionsList.Flags().StringVar(filter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
   233  	cmdDecisionsList.Flags().StringVarP(filter.TypeEquals, "type", "t", "", "restrict to this decision type (ie. ban,captcha)")
   234  	cmdDecisionsList.Flags().StringVar(filter.ScopeEquals, "scope", "", "restrict to this scope (ie. ip,range,session)")
   235  	cmdDecisionsList.Flags().StringVarP(filter.ValueEquals, "value", "v", "", "restrict to this value (ie. 1.2.3.4,userName)")
   236  	cmdDecisionsList.Flags().StringVarP(filter.ScenarioEquals, "scenario", "s", "", "restrict to this scenario (ie. breakteam/ssh-bf)")
   237  	cmdDecisionsList.Flags().StringVarP(filter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
   238  	cmdDecisionsList.Flags().StringVarP(filter.RangeEquals, "range", "r", "", "restrict to alerts from this source range (shorthand for --scope range --value <RANGE>)")
   239  	cmdDecisionsList.Flags().BoolVar(NoSimu, "no-simu", false, "exclude decisions in simulation mode")
   240  	cmdDecisionsList.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
   241  
   242  	cmdDecisions.AddCommand(cmdDecisionsList)
   243  
   244  	var (
   245  		addIP       string
   246  		addRange    string
   247  		addDuration string
   248  		addValue    string
   249  		addScope    string
   250  		addReason   string
   251  		addType     string
   252  	)
   253  
   254  	var cmdDecisionsAdd = &cobra.Command{
   255  		Use:   "add [options]",
   256  		Short: "Add decision to LAPI",
   257  		Example: `ccscli decisions add --ip 1.2.3.4
   258  ccscli decisions add --range 1.2.3.0/24
   259  ccscli decisions add --ip 1.2.3.4 --duration 24h --type captcha
   260  ccscli decisions add --scope username --value foobar
   261  `,
   262  		/*TBD : fix long and example*/
   263  		Args: cobra.ExactArgs(0),
   264  		Run: func(cmd *cobra.Command, args []string) {
   265  			var err error
   266  			var ip, ipRange string
   267  			alerts := models.AddAlertsRequest{}
   268  			origin := "ccscli"
   269  			capacity := int32(0)
   270  			leakSpeed := "0"
   271  			eventsCount := int32(1)
   272  			empty := ""
   273  			simulated := false
   274  			startAt := time.Now().Format(time.RFC3339)
   275  			stopAt := time.Now().Format(time.RFC3339)
   276  			createdAt := time.Now().Format(time.RFC3339)
   277  
   278  			/*take care of shorthand options*/
   279  			if err := manageCliDecisionAlerts(&addIP, &addRange, &addScope, &addValue); err != nil {
   280  				log.Fatalf("%s", err)
   281  			}
   282  
   283  			if addIP != "" {
   284  				addValue = addIP
   285  				addScope = types.Ip
   286  			} else if addRange != "" {
   287  				addValue = addRange
   288  				addScope = types.Range
   289  			} else if addValue == "" {
   290  				cmd.Help()
   291  				log.Errorf("Missing arguments, a value is required (--ip, --range or --scope and --value)")
   292  				return
   293  			}
   294  
   295  			if addReason == "" {
   296  				addReason = fmt.Sprintf("manual '%s' from '%s'", addType, csConfig.API.Client.Credentials.Login)
   297  			}
   298  
   299  			decision := models.Decision{
   300  				Duration: &addDuration,
   301  				Scope:    &addScope,
   302  				Value:    &addValue,
   303  				Type:     &addType,
   304  				Scenario: &addReason,
   305  				Origin:   &origin,
   306  			}
   307  			alert := models.Alert{
   308  				Capacity:        &capacity,
   309  				Decisions:       []*models.Decision{&decision},
   310  				Events:          []*models.Event{},
   311  				EventsCount:     &eventsCount,
   312  				Leakspeed:       &leakSpeed,
   313  				Message:         &addReason,
   314  				ScenarioHash:    &empty,
   315  				Scenario:        &addReason,
   316  				ScenarioVersion: &empty,
   317  				Simulated:       &simulated,
   318  				Source: &models.Source{
   319  					AsName:   empty,
   320  					AsNumber: empty,
   321  					Cn:       empty,
   322  					IP:       ip,
   323  					Range:    ipRange,
   324  					Scope:    &addScope,
   325  					Value:    &addValue,
   326  				},
   327  				StartAt:   &startAt,
   328  				StopAt:    &stopAt,
   329  				CreatedAt: createdAt,
   330  			}
   331  			alerts = append(alerts, &alert)
   332  
   333  			_, _, err = Client.Alerts.Add(context.Background(), alerts)
   334  			if err != nil {
   335  				log.Fatalf(err.Error())
   336  			}
   337  
   338  			log.Info("Decision successfully added")
   339  		},
   340  	}
   341  
   342  	cmdDecisionsAdd.Flags().SortFlags = false
   343  	cmdDecisionsAdd.Flags().StringVarP(&addIP, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
   344  	cmdDecisionsAdd.Flags().StringVarP(&addRange, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
   345  	cmdDecisionsAdd.Flags().StringVarP(&addDuration, "duration", "d", "4h", "Decision duration (ie. 1h,4h,30m)")
   346  	cmdDecisionsAdd.Flags().StringVarP(&addValue, "value", "v", "", "The value (ie. --scope username --value foobar)")
   347  	cmdDecisionsAdd.Flags().StringVar(&addScope, "scope", types.Ip, "Decision scope (ie. ip,range,username)")
   348  	cmdDecisionsAdd.Flags().StringVarP(&addReason, "reason", "R", "", "Decision reason (ie. scenario-name)")
   349  	cmdDecisionsAdd.Flags().StringVarP(&addType, "type", "t", "ban", "Decision type (ie. ban,captcha,throttle)")
   350  	cmdDecisions.AddCommand(cmdDecisionsAdd)
   351  
   352  	var delFilter = apiclient.DecisionsDeleteOpts{
   353  		ScopeEquals: new(string),
   354  		ValueEquals: new(string),
   355  		TypeEquals:  new(string),
   356  		IPEquals:    new(string),
   357  		RangeEquals: new(string),
   358  	}
   359  	var delDecisionId string
   360  	var delDecisionAll bool
   361  	var cmdDecisionsDelete = &cobra.Command{
   362  		Use:   "delete [options]",
   363  		Short: "Delete decisions",
   364  		Example: `ccscli decisions delete -r 1.2.3.0/24
   365  ccscli decisions delete -i 1.2.3.4
   366  ccscli decisions delete -s breakteam/ssh-bf
   367  ccscli decisions delete --id 42
   368  ccscli decisions delete --type captcha
   369  `,
   370  		/*TBD : refaire le Long/Example*/
   371  		PreRun: func(cmd *cobra.Command, args []string) {
   372  			if delDecisionAll {
   373  				return
   374  			}
   375  			if *delFilter.ScopeEquals == "" && *delFilter.ValueEquals == "" &&
   376  				*delFilter.TypeEquals == "" && *delFilter.IPEquals == "" &&
   377  				*delFilter.RangeEquals == "" && delDecisionId == "" {
   378  				cmd.Usage()
   379  				log.Fatalln("At least one filter or --all must be specified")
   380  			}
   381  		},
   382  		Run: func(cmd *cobra.Command, args []string) {
   383  			var err error
   384  			var decisions *models.DeleteDecisionResponse
   385  
   386  			/*take care of shorthand options*/
   387  			if err := manageCliDecisionAlerts(delFilter.IPEquals, delFilter.RangeEquals, delFilter.ScopeEquals, delFilter.ValueEquals); err != nil {
   388  				log.Fatalf("%s", err)
   389  			}
   390  			if *delFilter.ScopeEquals == "" {
   391  				delFilter.ScopeEquals = nil
   392  			}
   393  			if *delFilter.ValueEquals == "" {
   394  				delFilter.ValueEquals = nil
   395  			}
   396  
   397  			if *delFilter.TypeEquals == "" {
   398  				delFilter.TypeEquals = nil
   399  			}
   400  
   401  			if *delFilter.IPEquals == "" {
   402  				delFilter.IPEquals = nil
   403  			}
   404  
   405  			if *delFilter.RangeEquals == "" {
   406  				delFilter.RangeEquals = nil
   407  			}
   408  			if contained != nil && *contained {
   409  				delFilter.Contains = new(bool)
   410  			}
   411  
   412  			if delDecisionId == "" {
   413  				decisions, _, err = Client.Decisions.Delete(context.Background(), delFilter)
   414  				if err != nil {
   415  					log.Fatalf("Unable to delete decisions : %v", err.Error())
   416  				}
   417  			} else {
   418  				decisions, _, err = Client.Decisions.DeleteOne(context.Background(), delDecisionId)
   419  				if err != nil {
   420  					log.Fatalf("Unable to delete decision : %v", err.Error())
   421  				}
   422  			}
   423  			log.Infof("%s decision(s) deleted", decisions.NbDeleted)
   424  		},
   425  	}
   426  
   427  	cmdDecisionsDelete.Flags().SortFlags = false
   428  	cmdDecisionsDelete.Flags().StringVarP(delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
   429  	cmdDecisionsDelete.Flags().StringVarP(delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
   430  	cmdDecisionsDelete.Flags().StringVar(&delDecisionId, "id", "", "decision id")
   431  	cmdDecisionsDelete.Flags().StringVarP(delFilter.TypeEquals, "type", "t", "", "the decision type (ie. ban,captcha)")
   432  	cmdDecisionsDelete.Flags().StringVarP(delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
   433  	cmdDecisionsDelete.Flags().BoolVar(&delDecisionAll, "all", false, "delete all decisions")
   434  	cmdDecisionsDelete.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
   435  
   436  	cmdDecisions.AddCommand(cmdDecisionsDelete)
   437  
   438  	return cmdDecisions
   439  }