package main import ( "context" "encoding/json" "fmt" "io" "os" "slices" "strings" "wazuh-notify/common" "wazuh-notify/config" "wazuh-notify/constants" "wazuh-notify/discord" logger "wazuh-notify/log" "wazuh-notify/ntfy" "wazuh-notify/slack" "github.com/urfave/cli/v3" ) func Notify() *cli.Command { return &cli.Command{ Name: "notify", Usage: "notify services about security event", Action: action, Flags: flags(), ArgsUsage: "args usage", } } func flags() []cli.Flag { return []cli.Flag{ &cli.StringFlag{ Name: constants.Source, Aliases: []string{"s"}, Usage: "The file path to read from. Defaults to **standard in** if not set.", Value: "", DefaultText: "stdin", }, &cli.StringFlag{ Name: constants.Click, Usage: "is a link (URL) that can be followed by tapping/clicking inside the message. (Overrides TOML config)", Value: "", DefaultText: "Defined in config", }, &cli.StringFlag{ Name: constants.Sender, Usage: "is the sender of the message, either an app name or a person. (Overrides TOML config)", Value: "Wazuh (IDS) Golang", }, &cli.StringSliceFlag{ Name: constants.Targets, Aliases: []string{"t"}, Usage: "is a comma-separated list of targets (slack, ntfy, discord) to send notifications to. (Overrides TOML config)", Value: []string{}, DefaultText: "Defined in config", }, } } func action(context context.Context, c *cli.Command) error { sourcePath := c.String(constants.Source) var input io.Reader if sourcePath == "" { fmt.Println("Reading from standard input (stdin)...") input = os.Stdin } else { file, err := common.ReadFile(sourcePath) if err != nil { return err } defer file.Close() input = file } ar, err := readInput(input) if err != nil { return err } var color int var priority int var mention string = "" for i := range config.File.PriorityMaps { if slices.Contains(config.File.PriorityMaps[i].ThreatMap, ar.Parameters.Alert.Rule.Level) { //Check notify threshold if ar.Parameters.Alert.Rule.FiredTimes%config.File.PriorityMaps[i].NotifyThreshold != 0 { logger.Log("threshold not met") return nil } //Set color based on config map color = config.File.PriorityMaps[i].Color //Check mention threshold if ar.Parameters.Alert.Rule.FiredTimes >= config.File.PriorityMaps[i].MentionThreshold { mention = constants.DiscordMention } break } } targets := c.StringSlice(constants.Targets) if len(targets) == 0 { targets = strings.Split(config.File.General.Targets, ",") } if c.String(constants.Click) != "" { config.File.General.Click = c.String(constants.Click) } // Ignored out messages based on config if common.Ignored(ar) { return nil } logger.Log("Finished processing message. sending to enabled targets.") if slices.Contains(targets, constants.Discord) { logger.Log("Discord targets found.") discord.Send(ar, color, mention, priority) } if slices.Contains(targets, constants.Ntfy) { logger.Log("Ntfy targets found.") ntfy.Send(ar, priority) } if slices.Contains(targets, constants.Slack) { logger.Log("Slack targets found.") slack.Send(ar, priority) } return nil } func readInput(input io.Reader) (common.ActiveResponse, error) { var ar common.ActiveResponse decoder := json.NewDecoder(input) err := decoder.Decode(&ar) if err != nil { fmt.Fprintf(os.Stderr, "Error decoding JSON from stdin: %v\n", err) return ar, err } return ar, nil }