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 }