2020-11-30 09:37:17 +00:00
package main
import (
"context"
2021-12-29 13:08:47 +00:00
"encoding/csv"
2020-11-30 09:37:17 +00:00
"encoding/json"
"fmt"
"net/url"
"os"
2021-04-23 11:42:14 +00:00
"sort"
2020-11-30 09:37:17 +00:00
"strconv"
"strings"
"time"
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
2021-10-26 11:33:45 +00:00
"github.com/crowdsecurity/crowdsec/pkg/database"
2020-11-30 09:37:17 +00:00
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/go-openapi/strfmt"
"github.com/olekukonko/tablewriter"
2022-05-19 08:48:08 +00:00
"github.com/pkg/errors"
2020-11-30 09:37:17 +00:00
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)
var printMachine bool
var limit * int
func DecisionsFromAlert ( alert * models . Alert ) string {
ret := ""
var decMap = make ( map [ string ] int )
for _ , decision := range alert . Decisions {
k := * decision . Type
if * decision . Simulated {
k = fmt . Sprintf ( "(simul)%s" , k )
}
v := decMap [ k ]
decMap [ k ] = v + 1
}
for k , v := range decMap {
if len ( ret ) > 0 {
ret += " "
}
ret += fmt . Sprintf ( "%s:%d" , k , v )
}
return ret
}
func AlertsToTable ( alerts * models . GetAlertsResponse , printMachine bool ) error {
if csConfig . Cscli . Output == "raw" {
2021-12-29 13:08:47 +00:00
csvwriter := csv . NewWriter ( os . Stdout )
2022-03-09 15:15:18 +00:00
header := [ ] string { "id" , "scope" , "value" , "reason" , "country" , "as" , "decisions" , "created_at" }
2020-11-30 09:37:17 +00:00
if printMachine {
2022-03-09 15:15:18 +00:00
header = append ( header , "machine" )
}
err := csvwriter . Write ( header )
if err != nil {
return err
2020-11-30 09:37:17 +00:00
}
for _ , alertItem := range * alerts {
2021-12-29 13:08:47 +00:00
row := [ ] string {
fmt . Sprintf ( "%d" , alertItem . ID ) ,
* alertItem . Source . Scope ,
* alertItem . Source . Value ,
* alertItem . Scenario ,
alertItem . Source . Cn ,
alertItem . Source . AsNumber + " " + alertItem . Source . AsName ,
DecisionsFromAlert ( alertItem ) ,
* alertItem . StartAt ,
}
2020-11-30 09:37:17 +00:00
if printMachine {
2021-12-29 13:08:47 +00:00
row = append ( row , alertItem . MachineID )
}
err := csvwriter . Write ( row )
if err != nil {
return err
2020-11-30 09:37:17 +00:00
}
}
2021-12-29 13:08:47 +00:00
csvwriter . Flush ( )
2020-11-30 09:37:17 +00:00
} else if csConfig . Cscli . Output == "json" {
x , _ := json . MarshalIndent ( alerts , "" , " " )
fmt . Printf ( "%s" , string ( x ) )
} else if csConfig . Cscli . Output == "human" {
table := tablewriter . NewWriter ( os . Stdout )
2022-03-09 15:15:18 +00:00
header := [ ] string { "ID" , "value" , "reason" , "country" , "as" , "decisions" , "created_at" }
2020-11-30 09:37:17 +00:00
if printMachine {
2022-03-09 15:15:18 +00:00
header = append ( header , "machine" )
2020-11-30 09:37:17 +00:00
}
2022-03-09 15:15:18 +00:00
table . SetHeader ( header )
2020-11-30 09:37:17 +00:00
if len ( * alerts ) == 0 {
fmt . Println ( "No active alerts" )
return nil
}
for _ , alertItem := range * alerts {
displayVal := * alertItem . Source . Scope
if * alertItem . Source . Value != "" {
displayVal += ":" + * alertItem . Source . Value
}
2022-03-09 15:15:18 +00:00
row := [ ] string {
strconv . Itoa ( int ( alertItem . ID ) ) ,
displayVal ,
* alertItem . Scenario ,
alertItem . Source . Cn ,
alertItem . Source . AsNumber + " " + alertItem . Source . AsName ,
DecisionsFromAlert ( alertItem ) ,
* alertItem . StartAt ,
}
2020-11-30 09:37:17 +00:00
if printMachine {
2022-03-09 15:15:18 +00:00
row = append ( row , alertItem . MachineID )
2020-11-30 09:37:17 +00:00
}
2022-03-09 15:15:18 +00:00
table . Append ( row )
2020-11-30 09:37:17 +00:00
}
table . Render ( ) // Send output
}
return nil
}
func DisplayOneAlert ( alert * models . Alert , withDetail bool ) error {
if csConfig . Cscli . Output == "human" {
fmt . Printf ( "\n################################################################################################\n\n" )
scopeAndValue := * alert . Source . Scope
if * alert . Source . Value != "" {
scopeAndValue += ":" + * alert . Source . Value
}
fmt . Printf ( " - ID : %d\n" , alert . ID )
fmt . Printf ( " - Date : %s\n" , alert . CreatedAt )
fmt . Printf ( " - Machine : %s\n" , alert . MachineID )
fmt . Printf ( " - Simulation : %v\n" , * alert . Simulated )
fmt . Printf ( " - Reason : %s\n" , * alert . Scenario )
fmt . Printf ( " - Events Count : %d\n" , * alert . EventsCount )
fmt . Printf ( " - Scope:Value: %s\n" , scopeAndValue )
fmt . Printf ( " - Country : %s\n" , alert . Source . Cn )
2021-12-29 13:08:47 +00:00
fmt . Printf ( " - AS : %s\n" , alert . Source . AsName )
fmt . Printf ( " - Begin : %s\n" , * alert . StartAt )
fmt . Printf ( " - End : %s\n\n" , * alert . StopAt )
2020-11-30 09:37:17 +00:00
foundActive := false
table := tablewriter . NewWriter ( os . Stdout )
table . SetHeader ( [ ] string { "ID" , "scope:value" , "action" , "expiration" , "created_at" } )
for _ , decision := range alert . Decisions {
parsedDuration , err := time . ParseDuration ( * decision . Duration )
if err != nil {
log . Errorf ( err . Error ( ) )
}
2022-01-19 13:56:05 +00:00
expire := time . Now ( ) . UTC ( ) . Add ( parsedDuration )
if time . Now ( ) . UTC ( ) . After ( expire ) {
2020-11-30 09:37:17 +00:00
continue
}
foundActive = true
scopeAndValue := * decision . Scope
if * decision . Value != "" {
scopeAndValue += ":" + * decision . Value
}
table . Append ( [ ] string {
strconv . Itoa ( int ( decision . ID ) ) ,
scopeAndValue ,
* decision . Type ,
* decision . Duration ,
alert . CreatedAt ,
} )
}
if foundActive {
fmt . Printf ( " - Active Decisions :\n" )
table . Render ( ) // Send output
}
if withDetail {
fmt . Printf ( "\n - Events :\n" )
for _ , event := range alert . Events {
fmt . Printf ( "\n- Date: %s\n" , * event . Timestamp )
table = tablewriter . NewWriter ( os . Stdout )
table . SetHeader ( [ ] string { "Key" , "Value" } )
2021-04-23 11:42:14 +00:00
sort . Slice ( event . Meta , func ( i , j int ) bool {
return event . Meta [ i ] . Key < event . Meta [ j ] . Key
} )
2020-11-30 09:37:17 +00:00
for _ , meta := range event . Meta {
table . Append ( [ ] string {
meta . Key ,
meta . Value ,
} )
}
2021-04-23 11:42:14 +00:00
2020-11-30 09:37:17 +00:00
table . Render ( ) // Send output
}
}
}
return nil
}
func NewAlertsCmd ( ) * cobra . Command {
/* ---- ALERTS COMMAND */
var cmdAlerts = & cobra . Command {
2021-09-03 10:56:17 +00:00
Use : "alerts [action]" ,
Short : "Manage alerts" ,
Args : cobra . MinimumNArgs ( 1 ) ,
DisableAutoGenTag : true ,
2022-05-19 08:48:08 +00:00
PersistentPreRunE : func ( cmd * cobra . Command , args [ ] string ) error {
2020-11-30 09:37:17 +00:00
var err error
2021-03-24 17:16:17 +00:00
if err := csConfig . LoadAPIClient ( ) ; err != nil {
2022-05-19 08:48:08 +00:00
return errors . Wrap ( err , "loading api client" )
2020-11-30 09:37:17 +00:00
}
apiURL , err := url . Parse ( csConfig . API . Client . Credentials . URL )
if err != nil {
2022-05-19 08:48:08 +00:00
return errors . Wrapf ( err , "parsing api url %s" , apiURL )
2020-11-30 09:37:17 +00:00
}
Client , err = apiclient . NewClient ( & apiclient . Config {
MachineID : csConfig . API . Client . Credentials . Login ,
Password : strfmt . Password ( csConfig . API . Client . Credentials . Password ) ,
UserAgent : fmt . Sprintf ( "crowdsec/%s" , cwversion . VersionStr ( ) ) ,
URL : apiURL ,
VersionPrefix : "v1" ,
} )
if err != nil {
2022-05-19 08:48:08 +00:00
return errors . Wrap ( err , "new api client" )
2020-11-30 09:37:17 +00:00
}
2022-05-19 08:48:08 +00:00
return nil
2020-11-30 09:37:17 +00:00
} ,
}
var alertListFilter = apiclient . AlertsListOpts {
ScopeEquals : new ( string ) ,
ValueEquals : new ( string ) ,
ScenarioEquals : new ( string ) ,
IPEquals : new ( string ) ,
RangeEquals : new ( string ) ,
Since : new ( string ) ,
Until : new ( string ) ,
TypeEquals : new ( string ) ,
2022-07-28 15:31:53 +00:00
IncludeCAPI : new ( bool ) ,
2020-11-30 09:37:17 +00:00
}
limit = new ( int )
2021-01-15 08:48:39 +00:00
contained := new ( bool )
2020-11-30 09:37:17 +00:00
var cmdAlertsList = & cobra . Command {
Use : "list [filters]" ,
Short : "List alerts" ,
Example : ` cscli alerts list
cscli alerts list -- ip 1.2 .3 .4
cscli alerts list -- range 1.2 .3 .0 / 24
cscli alerts list - s crowdsecurity / ssh - bf
cscli alerts list -- type ban ` ,
2021-08-31 13:03:47 +00:00
DisableAutoGenTag : true ,
2020-11-30 09:37:17 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
var err error
if err := manageCliDecisionAlerts ( alertListFilter . IPEquals , alertListFilter . RangeEquals ,
alertListFilter . ScopeEquals , alertListFilter . ValueEquals ) ; err != nil {
2022-03-10 12:55:25 +00:00
printHelp ( cmd )
2020-11-30 09:37:17 +00:00
log . Fatalf ( "%s" , err )
}
if limit != nil {
alertListFilter . Limit = limit
}
if * alertListFilter . Until == "" {
alertListFilter . Until = nil
} else {
/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
if strings . HasSuffix ( * alertListFilter . Until , "d" ) {
realDuration := strings . TrimSuffix ( * alertListFilter . Until , "d" )
days , err := strconv . Atoi ( realDuration )
if err != nil {
2022-03-10 12:55:25 +00:00
printHelp ( cmd )
2020-11-30 09:37:17 +00:00
log . Fatalf ( "Can't parse duration %s, valid durations format: 1d, 4h, 4h15m" , * alertListFilter . Until )
}
* alertListFilter . Until = fmt . Sprintf ( "%d%s" , days * 24 , "h" )
}
}
if * alertListFilter . Since == "" {
alertListFilter . Since = nil
} else {
/*time.ParseDuration support hours 'h' as bigger unit, let's make the user's life easier*/
if strings . HasSuffix ( * alertListFilter . Since , "d" ) {
realDuration := strings . TrimSuffix ( * alertListFilter . Since , "d" )
days , err := strconv . Atoi ( realDuration )
if err != nil {
2022-03-10 12:55:25 +00:00
printHelp ( cmd )
2020-11-30 09:37:17 +00:00
log . Fatalf ( "Can't parse duration %s, valid durations format: 1d, 4h, 4h15m" , * alertListFilter . Since )
}
* alertListFilter . Since = fmt . Sprintf ( "%d%s" , days * 24 , "h" )
}
}
2022-07-28 15:31:53 +00:00
if * alertListFilter . IncludeCAPI {
* alertListFilter . Limit = 0
}
2020-11-30 09:37:17 +00:00
if * alertListFilter . TypeEquals == "" {
alertListFilter . TypeEquals = nil
}
if * alertListFilter . ScopeEquals == "" {
alertListFilter . ScopeEquals = nil
}
if * alertListFilter . ValueEquals == "" {
alertListFilter . ValueEquals = nil
}
if * alertListFilter . ScenarioEquals == "" {
alertListFilter . ScenarioEquals = nil
}
if * alertListFilter . IPEquals == "" {
alertListFilter . IPEquals = nil
}
if * alertListFilter . RangeEquals == "" {
alertListFilter . RangeEquals = nil
}
2021-01-15 08:48:39 +00:00
if contained != nil && * contained {
alertListFilter . Contains = new ( bool )
}
2020-11-30 09:37:17 +00:00
alerts , _ , err := Client . Alerts . List ( context . Background ( ) , alertListFilter )
if err != nil {
2022-06-22 13:53:53 +00:00
log . Fatalf ( "Unable to list alerts : %v" , err )
2020-11-30 09:37:17 +00:00
}
err = AlertsToTable ( alerts , printMachine )
if err != nil {
2022-06-22 13:53:53 +00:00
log . Fatalf ( "unable to list alerts : %v" , err )
2020-11-30 09:37:17 +00:00
}
} ,
}
cmdAlertsList . Flags ( ) . SortFlags = false
2022-07-28 15:31:53 +00:00
cmdAlertsList . Flags ( ) . BoolVarP ( alertListFilter . IncludeCAPI , "all" , "a" , false , "Include decisions from Central API" )
2020-11-30 09:37:17 +00:00
cmdAlertsList . Flags ( ) . StringVar ( alertListFilter . Until , "until" , "" , "restrict to alerts older than until (ie. 4h, 30d)" )
cmdAlertsList . Flags ( ) . StringVar ( alertListFilter . Since , "since" , "" , "restrict to alerts newer than since (ie. 4h, 30d)" )
cmdAlertsList . Flags ( ) . StringVarP ( alertListFilter . IPEquals , "ip" , "i" , "" , "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)" )
cmdAlertsList . Flags ( ) . StringVarP ( alertListFilter . ScenarioEquals , "scenario" , "s" , "" , "the scenario (ie. crowdsecurity/ssh-bf)" )
cmdAlertsList . Flags ( ) . StringVarP ( alertListFilter . RangeEquals , "range" , "r" , "" , "restrict to alerts from this range (shorthand for --scope range --value <RANGE/X>)" )
cmdAlertsList . Flags ( ) . StringVar ( alertListFilter . TypeEquals , "type" , "" , "restrict to alerts with given decision type (ie. ban, captcha)" )
cmdAlertsList . Flags ( ) . StringVar ( alertListFilter . ScopeEquals , "scope" , "" , "restrict to alerts of this scope (ie. ip,range)" )
cmdAlertsList . Flags ( ) . StringVarP ( alertListFilter . ValueEquals , "value" , "v" , "" , "the value to match for in the specified scope" )
2021-01-15 08:48:39 +00:00
cmdAlertsList . Flags ( ) . BoolVar ( contained , "contained" , false , "query decisions contained by range" )
2022-03-16 16:29:31 +00:00
cmdAlertsList . Flags ( ) . BoolVarP ( & printMachine , "machine" , "m" , false , "print machines that sent alerts" )
2020-11-30 09:37:17 +00:00
cmdAlertsList . Flags ( ) . IntVarP ( limit , "limit" , "l" , 50 , "limit size of alerts list table (0 to view all alerts)" )
cmdAlerts . AddCommand ( cmdAlertsList )
var ActiveDecision * bool
var AlertDeleteAll bool
var alertDeleteFilter = apiclient . AlertsDeleteOpts {
ScopeEquals : new ( string ) ,
ValueEquals : new ( string ) ,
ScenarioEquals : new ( string ) ,
IPEquals : new ( string ) ,
RangeEquals : new ( string ) ,
}
var cmdAlertsDelete = & cobra . Command {
Use : "delete [filters] [--all]" ,
Short : ` Delete alerts
/ ! \ This command can be use only on the same machine than the local API . ` ,
Example : ` cscli alerts delete -- ip 1.2 .3 .4
cscli alerts delete -- range 1.2 .3 .0 / 24
cscli alerts delete - s crowdsecurity / ssh - bf " ` ,
2021-08-31 13:03:47 +00:00
DisableAutoGenTag : true ,
2022-04-20 13:44:48 +00:00
Aliases : [ ] string { "remove" } ,
2021-08-31 13:03:47 +00:00
Args : cobra . ExactArgs ( 0 ) ,
2020-11-30 09:37:17 +00:00
PreRun : func ( cmd * cobra . Command , args [ ] string ) {
if AlertDeleteAll {
return
}
if * alertDeleteFilter . ScopeEquals == "" && * alertDeleteFilter . ValueEquals == "" &&
* alertDeleteFilter . ScenarioEquals == "" && * alertDeleteFilter . IPEquals == "" &&
* alertDeleteFilter . RangeEquals == "" {
_ = cmd . Usage ( )
log . Fatalln ( "At least one filter or --all must be specified" )
}
} ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
var err error
if ! AlertDeleteAll {
if err := manageCliDecisionAlerts ( alertDeleteFilter . IPEquals , alertDeleteFilter . RangeEquals ,
alertDeleteFilter . ScopeEquals , alertDeleteFilter . ValueEquals ) ; err != nil {
2022-03-10 12:55:25 +00:00
printHelp ( cmd )
2020-11-30 09:37:17 +00:00
log . Fatalf ( "%s" , err )
}
if ActiveDecision != nil {
alertDeleteFilter . ActiveDecisionEquals = ActiveDecision
}
if * alertDeleteFilter . ScopeEquals == "" {
alertDeleteFilter . ScopeEquals = nil
}
if * alertDeleteFilter . ValueEquals == "" {
alertDeleteFilter . ValueEquals = nil
}
if * alertDeleteFilter . ScenarioEquals == "" {
alertDeleteFilter . ScenarioEquals = nil
}
if * alertDeleteFilter . IPEquals == "" {
alertDeleteFilter . IPEquals = nil
}
if * alertDeleteFilter . RangeEquals == "" {
alertDeleteFilter . RangeEquals = nil
}
2021-01-15 08:48:39 +00:00
if contained != nil && * contained {
alertDeleteFilter . Contains = new ( bool )
}
2020-11-30 09:37:17 +00:00
} else {
2021-04-27 09:59:18 +00:00
limit := 0
alertDeleteFilter = apiclient . AlertsDeleteOpts { Limit : & limit }
2020-11-30 09:37:17 +00:00
}
alerts , _ , err := Client . Alerts . Delete ( context . Background ( ) , alertDeleteFilter )
if err != nil {
2022-06-22 13:53:53 +00:00
log . Fatalf ( "Unable to delete alerts : %v" , err )
2020-11-30 09:37:17 +00:00
}
log . Infof ( "%s alert(s) deleted" , alerts . NbDeleted )
} ,
}
cmdAlertsDelete . Flags ( ) . SortFlags = false
cmdAlertsDelete . Flags ( ) . StringVar ( alertDeleteFilter . ScopeEquals , "scope" , "" , "the scope (ie. ip,range)" )
cmdAlertsDelete . Flags ( ) . StringVarP ( alertDeleteFilter . ValueEquals , "value" , "v" , "" , "the value to match for in the specified scope" )
cmdAlertsDelete . Flags ( ) . StringVarP ( alertDeleteFilter . ScenarioEquals , "scenario" , "s" , "" , "the scenario (ie. crowdsecurity/ssh-bf)" )
cmdAlertsDelete . Flags ( ) . StringVarP ( alertDeleteFilter . IPEquals , "ip" , "i" , "" , "Source ip (shorthand for --scope ip --value <IP>)" )
cmdAlertsDelete . Flags ( ) . StringVarP ( alertDeleteFilter . RangeEquals , "range" , "r" , "" , "Range source ip (shorthand for --scope range --value <RANGE>)" )
cmdAlertsDelete . Flags ( ) . BoolVarP ( & AlertDeleteAll , "all" , "a" , false , "delete all alerts" )
2021-01-15 08:48:39 +00:00
cmdAlertsDelete . Flags ( ) . BoolVar ( contained , "contained" , false , "query decisions contained by range" )
2020-11-30 09:37:17 +00:00
cmdAlerts . AddCommand ( cmdAlertsDelete )
var details bool
var cmdAlertsInspect = & cobra . Command {
2021-08-31 13:03:47 +00:00
Use : ` inspect "alert_id" ` ,
Short : ` Show info about an alert ` ,
Example : ` cscli alerts inspect 123 ` ,
DisableAutoGenTag : true ,
2020-11-30 09:37:17 +00:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
if len ( args ) == 0 {
2022-03-10 12:55:25 +00:00
printHelp ( cmd )
2020-11-30 09:37:17 +00:00
return
}
for _ , alertID := range args {
id , err := strconv . Atoi ( alertID )
if err != nil {
log . Fatalf ( "bad alert id %s" , alertID )
continue
}
alert , _ , err := Client . Alerts . GetByID ( context . Background ( ) , id )
if err != nil {
log . Fatalf ( "can't find alert with id %s: %s" , alertID , err )
}
switch csConfig . Cscli . Output {
case "human" :
if err := DisplayOneAlert ( alert , details ) ; err != nil {
continue
}
case "json" :
data , err := json . MarshalIndent ( alert , "" , " " )
if err != nil {
log . Fatalf ( "unable to marshal alert with id %s: %s" , alertID , err )
}
fmt . Printf ( "%s\n" , string ( data ) )
case "raw" :
data , err := yaml . Marshal ( alert )
if err != nil {
log . Fatalf ( "unable to marshal alert with id %s: %s" , alertID , err )
}
fmt . Printf ( "%s\n" , string ( data ) )
}
}
} ,
}
cmdAlertsInspect . Flags ( ) . SortFlags = false
cmdAlertsInspect . Flags ( ) . BoolVarP ( & details , "details" , "d" , false , "show alerts with events" )
cmdAlerts . AddCommand ( cmdAlertsInspect )
2021-10-26 11:33:45 +00:00
var maxItems int
var maxAge string
var cmdAlertsFlush = & cobra . Command {
Use : ` flush ` ,
Short : ` Flush alerts
/ ! \ This command can be used only on the same machine than the local API ` ,
Example : ` cscli alerts flush --max-items 1000 --max-age 7d ` ,
DisableAutoGenTag : true ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
var err error
if err := csConfig . LoadAPIServer ( ) ; err != nil || csConfig . DisableAPI {
log . Fatal ( "Local API is disabled, please run this command on the local API machine" )
}
if err := csConfig . LoadDBConfig ( ) ; err != nil {
log . Fatalf ( err . Error ( ) )
}
dbClient , err = database . NewClient ( csConfig . DbConfig )
if err != nil {
log . Fatalf ( "unable to create new database client: %s" , err )
}
log . Info ( "Flushing alerts. !! This may take a long time !!" )
err = dbClient . FlushAlerts ( maxAge , maxItems )
if err != nil {
log . Fatalf ( "unable to flush alerts: %s" , err )
}
log . Info ( "Alerts flushed" )
} ,
}
cmdAlertsFlush . Flags ( ) . SortFlags = false
cmdAlertsFlush . Flags ( ) . IntVar ( & maxItems , "max-items" , 5000 , "Maximum number of alert items to keep in the database" )
cmdAlertsFlush . Flags ( ) . StringVar ( & maxAge , "max-age" , "7d" , "Maximum age of alert items to keep in the database" )
cmdAlerts . AddCommand ( cmdAlertsFlush )
2020-11-30 09:37:17 +00:00
return cmdAlerts
}