143 lines
3.3 KiB
Go
Raw Normal View History

2025-11-15 23:00:03 +01:00
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
}
if slices.Contains(targets, constants.Discord) {
discord.Send(ar, color, mention, priority)
}
if slices.Contains(targets, constants.Ntfy) {
ntfy.Send(ar, priority)
}
if slices.Contains(targets, constants.Slack) {
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
}