2022-05-18 14:13:33 +00:00
package main
import (
2022-08-30 13:45:52 +00:00
"context"
2022-05-18 14:13:33 +00:00
"encoding/csv"
"encoding/json"
"fmt"
"io/fs"
2022-08-30 13:45:52 +00:00
"net/url"
2022-05-18 14:13:33 +00:00
"os"
"path/filepath"
2022-08-30 13:45:52 +00:00
"strconv"
2022-05-18 14:13:33 +00:00
"strings"
2022-08-30 13:45:52 +00:00
"time"
2022-05-18 14:13:33 +00:00
2022-10-13 10:28:24 +00:00
"github.com/fatih/color"
2022-08-30 13:45:52 +00:00
"github.com/go-openapi/strfmt"
2022-05-18 14:13:33 +00:00
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
2022-08-30 13:45:52 +00:00
"gopkg.in/tomb.v2"
2022-10-07 09:05:35 +00:00
"github.com/crowdsecurity/crowdsec/pkg/apiclient"
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/csplugin"
"github.com/crowdsecurity/crowdsec/pkg/csprofiles"
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
2022-05-18 14:13:33 +00:00
)
type NotificationsCfg struct {
Config csplugin . PluginConfig ` json:"plugin_config" `
Profiles [ ] * csconfig . ProfileCfg ` json:"associated_profiles" `
2022-08-30 13:45:52 +00:00
ids [ ] uint
2022-05-18 14:13:33 +00:00
}
func NewNotificationsCmd ( ) * cobra . Command {
var cmdNotifications = & cobra . Command {
Use : "notifications [action]" ,
Short : "Helper for notification plugin configuration" ,
Long : "To list/inspect/test notification template" ,
Args : cobra . MinimumNArgs ( 1 ) ,
Aliases : [ ] string { "notifications" , "notification" } ,
DisableAutoGenTag : true ,
PersistentPreRun : func ( cmd * cobra . Command , args [ ] string ) {
var (
err error
)
if err = csConfig . API . Server . LoadProfiles ( ) ; err != nil {
2022-11-29 08:16:07 +00:00
log . Fatal ( err )
2022-05-18 14:13:33 +00:00
}
2022-08-02 18:44:19 +00:00
if csConfig . ConfigPaths . NotificationDir == "" {
log . Fatalf ( "config_paths.notification_dir is not set in crowdsec config" )
}
2022-05-18 14:13:33 +00:00
} ,
}
var cmdNotificationsList = & cobra . Command {
Use : "list" ,
Short : "List active notifications plugins" ,
Long : ` List active notifications plugins ` ,
Example : ` cscli notifications list ` ,
Args : cobra . ExactArgs ( 0 ) ,
DisableAutoGenTag : true ,
2022-08-30 13:45:52 +00:00
RunE : func ( cmd * cobra . Command , arg [ ] string ) error {
ncfgs , err := getNotificationsConfiguration ( )
if err != nil {
return errors . Wrap ( err , "Can't build profiles configuration" )
}
2022-05-18 14:13:33 +00:00
if csConfig . Cscli . Output == "human" {
2022-10-13 10:28:24 +00:00
notificationListTable ( color . Output , ncfgs )
2022-05-18 14:13:33 +00:00
} else if csConfig . Cscli . Output == "json" {
x , err := json . MarshalIndent ( ncfgs , "" , " " )
if err != nil {
2022-08-30 13:45:52 +00:00
return errors . New ( "failed to marshal notification configuration" )
2022-05-18 14:13:33 +00:00
}
fmt . Printf ( "%s" , string ( x ) )
} else if csConfig . Cscli . Output == "raw" {
csvwriter := csv . NewWriter ( os . Stdout )
err := csvwriter . Write ( [ ] string { "Name" , "Type" , "Profile name" } )
if err != nil {
2022-08-30 13:45:52 +00:00
return errors . Wrap ( err , "failed to write raw header" )
2022-05-18 14:13:33 +00:00
}
for _ , b := range ncfgs {
profilesList := [ ] string { }
for _ , p := range b . Profiles {
profilesList = append ( profilesList , p . Name )
}
err := csvwriter . Write ( [ ] string { b . Config . Name , b . Config . Type , strings . Join ( profilesList , ", " ) } )
if err != nil {
2022-08-30 13:45:52 +00:00
return errors . Wrap ( err , "failed to write raw content" )
2022-05-18 14:13:33 +00:00
}
}
csvwriter . Flush ( )
}
2022-08-30 13:45:52 +00:00
return nil
2022-05-18 14:13:33 +00:00
} ,
}
cmdNotifications . AddCommand ( cmdNotificationsList )
var cmdNotificationsInspect = & cobra . Command {
Use : "inspect" ,
Short : "Inspect active notifications plugin configuration" ,
Long : ` Inspect active notifications plugin and show configuration ` ,
Example : ` cscli notifications inspect <plugin_name> ` ,
Args : cobra . ExactArgs ( 1 ) ,
DisableAutoGenTag : true ,
2022-08-30 13:45:52 +00:00
RunE : func ( cmd * cobra . Command , arg [ ] string ) error {
2022-05-18 14:13:33 +00:00
var (
cfg NotificationsCfg
ok bool
)
pluginName := arg [ 0 ]
if pluginName == "" {
2022-08-30 13:45:52 +00:00
errors . New ( "Please provide a plugin name to inspect" )
}
ncfgs , err := getNotificationsConfiguration ( )
if err != nil {
return errors . Wrap ( err , "Can't build profiles configuration" )
2022-05-18 14:13:33 +00:00
}
if cfg , ok = ncfgs [ pluginName ] ; ! ok {
2022-08-30 13:45:52 +00:00
return errors . New ( "The provided plugin name doesn't exist or isn't active" )
2022-05-18 14:13:33 +00:00
}
if csConfig . Cscli . Output == "human" || csConfig . Cscli . Output == "raw" {
fmt . Printf ( " - %15s: %15s\n" , "Type" , cfg . Config . Type )
fmt . Printf ( " - %15s: %15s\n" , "Name" , cfg . Config . Name )
fmt . Printf ( " - %15s: %15s\n" , "Timeout" , cfg . Config . TimeOut )
fmt . Printf ( " - %15s: %15s\n" , "Format" , cfg . Config . Format )
for k , v := range cfg . Config . Config {
fmt . Printf ( " - %15s: %15v\n" , k , v )
}
} else if csConfig . Cscli . Output == "json" {
x , err := json . MarshalIndent ( cfg , "" , " " )
if err != nil {
2022-08-30 13:45:52 +00:00
return errors . New ( "failed to marshal notification configuration" )
2022-05-18 14:13:33 +00:00
}
fmt . Printf ( "%s" , string ( x ) )
}
2022-08-30 13:45:52 +00:00
return nil
2022-05-18 14:13:33 +00:00
} ,
}
cmdNotifications . AddCommand ( cmdNotificationsInspect )
2022-08-30 13:45:52 +00:00
var remediation bool
var alertOverride string
var cmdNotificationsReinject = & cobra . Command {
Use : "reinject" ,
Short : "reinject alert into notifications system" ,
Long : ` Reinject alert into notifications system ` ,
Example : `
cscli notifications reinject < alert_id >
cscli notifications reinject < alert_id > -- remediation
cscli notifications reinject < alert_id > - a ' { "remediation" : true , "scenario" : "notification/test" } '
` ,
Args : cobra . ExactArgs ( 1 ) ,
DisableAutoGenTag : true ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
var (
pluginBroker csplugin . PluginBroker
pluginTomb tomb . Tomb
)
if len ( args ) != 1 {
printHelp ( cmd )
return errors . New ( "Wrong number of argument: there should be one argument" )
}
//first: get the alert
id , err := strconv . Atoi ( args [ 0 ] )
if err != nil {
return errors . New ( fmt . Sprintf ( "bad alert id %s" , args [ 0 ] ) )
}
if err := csConfig . LoadAPIClient ( ) ; err != nil {
return errors . Wrapf ( err , "loading api client" )
}
if csConfig . API . Client == nil {
return errors . New ( "There is no configuration on 'api_client:'" )
}
if csConfig . API . Client . Credentials == nil {
return errors . New ( fmt . Sprintf ( "Please provide credentials for the API in '%s'" , csConfig . API . Client . CredentialsFilePath ) )
}
apiURL , err := url . Parse ( csConfig . API . Client . Credentials . URL )
if err != nil {
return errors . Wrapf ( err , "error parsing the URL of the API" )
}
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 {
return errors . Wrapf ( err , "error creating the client for the API" )
}
alert , _ , err := client . Alerts . GetByID ( context . Background ( ) , id )
if err != nil {
return errors . Wrapf ( err , fmt . Sprintf ( "can't find alert with id %s" , args [ 0 ] ) )
}
if alertOverride != "" {
if err = json . Unmarshal ( [ ] byte ( alertOverride ) , alert ) ; err != nil {
return errors . Wrapf ( err , "Can't unmarshal the data given in the alert flag" )
}
}
if ! remediation {
alert . Remediation = true
}
// second we start plugins
err = pluginBroker . Init ( csConfig . PluginConfig , csConfig . API . Server . Profiles , csConfig . ConfigPaths )
if err != nil {
return errors . Wrapf ( err , "Can't initialize plugins" )
}
pluginTomb . Go ( func ( ) error {
pluginBroker . Run ( & pluginTomb )
return nil
} )
//third: get the profile(s), and process the whole stuff
profiles , err := csprofiles . NewProfile ( csConfig . API . Server . Profiles )
if err != nil {
return errors . Wrap ( err , "Cannot extract profiles from configuration" )
}
for id , profile := range profiles {
_ , matched , err := profile . EvaluateProfile ( alert )
if err != nil {
return errors . Wrapf ( err , "can't evaluate profile %s" , profile . Cfg . Name )
}
if ! matched {
log . Infof ( "The profile %s didn't match" , profile . Cfg . Name )
continue
}
log . Infof ( "The profile %s matched, sending to its configured notification plugins" , profile . Cfg . Name )
loop :
for {
select {
case pluginBroker . PluginChannel <- csplugin . ProfileAlert {
ProfileID : uint ( id ) ,
Alert : alert ,
} :
break loop
default :
time . Sleep ( 50 * time . Millisecond )
log . Info ( "sleeping\n" )
}
}
if profile . Cfg . OnSuccess == "break" {
log . Infof ( "The profile %s contains a 'on_success: break' so bailing out" , profile . Cfg . Name )
break
}
}
// time.Sleep(2 * time.Second) // There's no mechanism to ensure notification has been sent
pluginTomb . Kill ( errors . New ( "terminating" ) )
pluginTomb . Wait ( )
return nil
} ,
}
cmdNotificationsReinject . Flags ( ) . BoolVarP ( & remediation , "remediation" , "r" , false , "Set Alert.Remediation to false in the reinjected alert (see your profile filter configuration)" )
cmdNotificationsReinject . Flags ( ) . StringVarP ( & alertOverride , "alert" , "a" , "" , "JSON string used to override alert fields in the reinjected alert (see crowdsec/pkg/models/alert.go in the source tree for the full definition of the object)" )
cmdNotifications . AddCommand ( cmdNotificationsReinject )
2022-05-18 14:13:33 +00:00
return cmdNotifications
}
2022-08-30 13:45:52 +00:00
func getNotificationsConfiguration ( ) ( map [ string ] NotificationsCfg , error ) {
2022-05-18 14:13:33 +00:00
pcfgs := map [ string ] csplugin . PluginConfig { }
wf := func ( path string , info fs . FileInfo , err error ) error {
2022-08-02 18:44:19 +00:00
if info == nil {
return errors . Wrapf ( err , "error while traversing directory %s" , path )
}
2022-05-18 14:13:33 +00:00
name := filepath . Join ( csConfig . ConfigPaths . NotificationDir , info . Name ( ) ) //Avoid calling info.Name() twice
if ( strings . HasSuffix ( name , "yaml" ) || strings . HasSuffix ( name , "yml" ) ) && ! ( info . IsDir ( ) ) {
ts , err := csplugin . ParsePluginConfigFile ( name )
if err != nil {
return errors . Wrapf ( err , "Loading notifification plugin configuration with %s" , name )
}
for _ , t := range ts {
pcfgs [ t . Name ] = t
}
}
return nil
}
2022-08-02 18:44:19 +00:00
2022-05-18 14:13:33 +00:00
if err := filepath . Walk ( csConfig . ConfigPaths . NotificationDir , wf ) ; err != nil {
2022-08-30 13:45:52 +00:00
return nil , errors . Wrap ( err , "Loading notifification plugin configuration" )
2022-05-18 14:13:33 +00:00
}
// A bit of a tricky stuf now: reconcile profiles and notification plugins
ncfgs := map [ string ] NotificationsCfg { }
2022-08-30 13:45:52 +00:00
profiles , err := csprofiles . NewProfile ( csConfig . API . Server . Profiles )
if err != nil {
return nil , errors . Wrap ( err , "Cannot extract profiles from configuration" )
}
for profileID , profile := range profiles {
2022-05-18 14:13:33 +00:00
loop :
2022-08-30 13:45:52 +00:00
for _ , notif := range profile . Cfg . Notifications {
2022-05-18 14:13:33 +00:00
for name , pc := range pcfgs {
if notif == name {
if _ , ok := ncfgs [ pc . Name ] ; ! ok {
ncfgs [ pc . Name ] = NotificationsCfg {
Config : pc ,
2022-08-30 13:45:52 +00:00
Profiles : [ ] * csconfig . ProfileCfg { profile . Cfg } ,
ids : [ ] uint { uint ( profileID ) } ,
2022-05-18 14:13:33 +00:00
}
continue loop
}
tmp := ncfgs [ pc . Name ]
for _ , pr := range tmp . Profiles {
var profiles [ ] * csconfig . ProfileCfg
2022-08-30 13:45:52 +00:00
if pr . Name == profile . Cfg . Name {
2022-05-18 14:13:33 +00:00
continue
}
2022-08-30 13:45:52 +00:00
profiles = append ( tmp . Profiles , profile . Cfg )
ids := append ( tmp . ids , uint ( profileID ) )
2022-05-18 14:13:33 +00:00
ncfgs [ pc . Name ] = NotificationsCfg {
Config : tmp . Config ,
Profiles : profiles ,
2022-08-30 13:45:52 +00:00
ids : ids ,
2022-05-18 14:13:33 +00:00
}
}
}
}
}
}
2022-08-30 13:45:52 +00:00
return ncfgs , nil
2022-05-18 14:13:33 +00:00
}