Compare commits

..

No commits in common. "master" and "Python-v0.1.0-Release" have entirely different histories.

20 changed files with 83 additions and 891 deletions

41
.github/workflows/deploy-docs.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: build and deploy docs
on:
push:
branches: [ "master" ]
paths: ['Writerside/**']
pull_request:
branches: [ "master" ]
paths: ['Writerside/**']
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v3
- name: Docker login
run: docker login docker.dariusklein.nl -u Darius -p ${{ secrets.DOCKER_PASSWORD }}
- name: Build the Docker image
run: docker build . --file Writerside/Dockerfile --tag docker.dariusklein.nl/wazuh-notifier-docs
- name: Docker push
run: docker push docker.dariusklein.nl/wazuh-notifier-docs
publish:
needs: build
runs-on: self-hosted
steps:
- name: Docker stop
run: docker stop WazuhNotifier || true
- name: Docker login
run: docker login docker.dariusklein.nl -u Darius -p ${{ secrets.DOCKER_PASSWORD }}
- name: Docker pull
run: docker pull docker.dariusklein.nl/wazuh-notifier-docs
- name: Docker run
run: docker run --rm -dit -p 9091:80 --name WazuhNotifier docker.dariusklein.nl/wazuh-notifier-docs

View File

@ -1,3 +1,6 @@
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go name: Go
on: on:
@ -7,37 +10,49 @@ on:
branches: [ "master" ] branches: [ "master" ]
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v4
with: with:
go-version: '1.25' go-version: '1.23'
- name: Build wazuh-notify-go - name: Build
run: | run: |
cd wazuh-notify-go cd wazuh-notify-go
go build -o wazuh-notifier-go . go build -v .
- name: Upload wazuh-notify-go artifact - name: Set go variables
uses: christopherhx/gitea-upload-artifact@v4
with:
name: wazuh-notifier-go-binary
path: wazuh-notify-go/wazuh-notifier-go
- name: Build wazuh-notify-go-v2
run: | run: |
cd wazuh-notify-go-v2 cd wazuh-notify-go && GO_VER=$(cat VERSION)
go build -o wazuh-notifier-go-v2 . echo "GO_VERSION=$GO_VER" >> $GITHUB_ENV
- name: Upload wazuh-notify-go-v2 artifact - name: Release Go
uses: christopherhx/gitea-upload-artifact@v4 uses: softprops/action-gh-release@v2
with: with:
name: wazuh-notifier-go-v2-binary token: ${{ secrets.RELEASE_TOKEN }}
path: wazuh-notify-go-v2/wazuh-notifier-go-v2 tag_name: Golang-v${{ env.GO_VERSION }}
files: |
wazuh-notify-go/wazuh-notify
wazuh-notify-go/wazuh-notify-config.toml
licence.MD
- name: Set python variables
run: |
cd wazuh-notify-python && PY_VER=$(cat VERSION)
echo "PY_VERSION=$PY_VER" >> $GITHUB_ENV
- name: zip Python
run: zip -r wazuh-notify-python.zip wazuh-notify-python wazuh-notify-go/wazuh-notify-config.toml
- name: Release python
uses: softprops/action-gh-release@v2
with:
token: ${{ secrets.RELEASE_TOKEN }}
tag_name: Python-v${{ env.PY_VERSION }}
files: |
wazuh-notify-python.zip
licence.MD

View File

@ -1,78 +0,0 @@
package common
import "strings"
type ActiveResponse struct {
Version int `json:"version"`
Origin Origin `json:"origin"`
Command string `json:"command"`
Parameters Parameters `json:"parameters"`
}
type Origin struct {
Name string `json:"name"`
Module string `json:"module"`
}
type Parameters struct {
ExtraArgs []string `json:"extra_args"`
Alert Alert `json:"alert"`
Program string `json:"program"`
}
type Alert struct {
Timestamp string `json:"timestamp"`
Rule Rule `json:"rule"`
Agent Agent `json:"agent"`
Manager Manager `json:"manager"`
ID string `json:"id"`
FullLog string `json:"full_log"`
Decoder Decoder `json:"decoder"`
Data Data `json:"data"`
Location string `json:"location"`
}
type Rule struct {
Level int `json:"level"`
Description string `json:"description"`
ID string `json:"id"`
Mitre Mitre `json:"mitre"`
Info string `json:"info"`
FiredTimes int `json:"firedtimes"`
Mail bool `json:"mail"`
Groups []string `json:"groups"`
PciDss []string `json:"pci_dss"`
Gdpr []string `json:"gdpr"`
Nist80053 []string `json:"nist_800_53"`
Tsc []string `json:"tsc"`
}
type Mitre struct {
ID []string `json:"id"`
Tactic []string `json:"tactic"`
Technique []string `json:"technique"`
}
type Agent struct {
ID string `json:"id"`
Name string `json:"name"`
}
type Manager struct {
Name string `json:"name"`
}
type Decoder struct {
Name string `json:"name"`
}
type Data struct {
Protocol string `json:"protocol"`
SrcIP string `json:"srcip"`
ID string `json:"id"`
URL string `json:"url"`
}
func (a *ActiveResponse) Tags() string {
return strings.Join(a.Parameters.Alert.Rule.Groups, ",")
}

View File

@ -1,73 +0,0 @@
package common
import (
"encoding/json"
"fmt"
"os"
"slices"
"strconv"
"strings"
"time"
"wazuh-notify/config"
logger "wazuh-notify/log"
)
func ReadFile(path string) (*os.File, error) {
fmt.Printf("Reading from file: %s\n", path)
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open file %s: %w", path, err)
}
return file, nil
}
func BuildMessage(ar ActiveResponse, target string, emphasis string, priority int) string {
if slices.Contains(strings.Split(config.File.General.FullAlert, ","), target) {
fullAlert, _ := json.MarshalIndent(ar, "", " ")
fullAlertString := strings.ReplaceAll(string(fullAlert), `"`, "")
fullAlertString = strings.ReplaceAll(fullAlertString, "{", "")
fullAlertString = strings.ReplaceAll(fullAlertString, "}", "")
fullAlertString = strings.ReplaceAll(fullAlertString, "[", "")
fullAlertString = strings.ReplaceAll(fullAlertString, "]", "")
fullAlertString = strings.ReplaceAll(fullAlertString, " ,", "")
return "\n\n ```" +
fullAlertString +
"```\n\n"
} else {
return "\n\n" +
fmt.Sprintf("%sTimestamp:%s ", emphasis, emphasis) + time.Now().Format(time.DateTime) + "\n" +
fmt.Sprintf("%sAgent:%s ", emphasis, emphasis) + ar.Parameters.Alert.Agent.Name + "\n" +
fmt.Sprintf("%sEvent id:%s ", emphasis, emphasis) + ar.Parameters.Alert.Rule.ID + "\n" +
fmt.Sprintf("%sRule:%s ", emphasis, emphasis) + ar.Parameters.Alert.Rule.Description + "\n" +
fmt.Sprintf("%sDescription:%s ", emphasis, emphasis) + ar.Parameters.Alert.FullLog + "\n" +
fmt.Sprintf("%sThreat level:%s ", emphasis, emphasis) + strconv.Itoa(ar.Parameters.Alert.Rule.Level) + "\n" +
fmt.Sprintf("%sTimes fired:%s ", emphasis, emphasis) + strconv.Itoa(ar.Parameters.Alert.Rule.FiredTimes) +
"\n\n" +
fmt.Sprintf("%sPriority:%s ", emphasis, emphasis) + strconv.Itoa(priority) + "\n"
}
}
func Ignored(ar ActiveResponse) bool {
for _, rule := range strings.Split(config.File.General.ExcludedRules, ",") {
if rule == ar.Parameters.Alert.Rule.ID {
logger.Log("rule excluded")
return true
}
}
for _, agent := range strings.Split(config.File.General.ExcludedAgents, ",") {
if agent == ar.Parameters.Alert.Agent.ID {
logger.Log("agent excluded")
return true
}
}
for _, description := range config.File.General.ExcludeDescriptions {
if description != "" && strings.Contains(ar.Parameters.Alert.FullLog, description) {
logger.Log("excluded based on description")
return true
}
}
return false
}

View File

@ -1,110 +0,0 @@
package config
import (
_ "embed"
"fmt"
"os"
"path"
"wazuh-notify/log"
"github.com/BurntSushi/toml"
)
//go:embed default-config.toml
var DefaultConfigFile []byte
var File Config
// WARNING: this code is ai generated
func Read() error {
const SystemConfigPath = "/etc/wazuh-notify/wazuh-notify-config.toml"
execPath, _ := os.Executable()
LocalConfigPath := path.Join(path.Dir(execPath), "wazuh-notify-config.toml")
err := toml.Unmarshal(DefaultConfigFile, &File)
if err != nil {
log.Log(fmt.Sprintf("CRITICAL: Failed to parse embedded default config: %v", err))
return err
}
var userTomlFile []byte
var readErr error
var configPath string
userTomlFile, readErr = os.ReadFile(SystemConfigPath)
if readErr == nil {
configPath = SystemConfigPath
} else {
userTomlFile, readErr = os.ReadFile(LocalConfigPath)
if readErr == nil {
configPath = LocalConfigPath
}
}
if readErr != nil {
log.Log("No user config file found. Attempting to create default on disk.")
errMkdir := os.MkdirAll(path.Dir(SystemConfigPath), os.ModePerm)
errWrite := os.WriteFile(SystemConfigPath, DefaultConfigFile, 0600)
if errMkdir != nil || errWrite != nil {
log.Log(fmt.Sprintf("Warning: Could not write default config to %s (%v).", SystemConfigPath, errWrite))
log.Log("Using embedded default configuration only.")
} else {
log.Log(fmt.Sprintf("Successfully created default config at %s.", SystemConfigPath))
}
log.Log("TOML configuration loaded successfully from Embedded Default")
return nil
}
overrideErr := toml.Unmarshal(userTomlFile, &File)
if overrideErr != nil {
log.Log(fmt.Sprintf("Error parsing user configuration from %s: %v", configPath, overrideErr))
return overrideErr
}
log.Log(fmt.Sprintf("TOML configuration loaded successfully. Defaults merged with %s", configPath))
return nil
}
// Config holds the entire configuration structure defined in the TOML file.
type Config struct {
General General `toml:"general"`
PriorityMaps []PriorityMap `toml:"priority_map"`
Discord Discord `toml:"discord"`
Ntfy Ntfy `toml:"ntfy"`
Slack Slack `toml:"slack"`
}
// General maps the values within the [general] TOML table.
type General struct {
Targets string `toml:"targets"`
FullAlert string `toml:"full_alert"`
ExcludedRules string `toml:"excluded_rules"`
ExcludedAgents string `toml:"excluded_agents"`
ExcludeDescriptions []string `toml:"exclude_descriptions"`
Sender string `toml:"sender"`
Click string `toml:"click"`
}
// PriorityMap maps the values within a single [[priority_map]] TOML table entry.
type PriorityMap struct {
ThreatMap []int `toml:"threat_map"`
MentionThreshold int `toml:"mention_threshold"`
NotifyThreshold int `toml:"notify_threshold"`
Color int `toml:"color"`
}
type Discord struct {
Webhook string `toml:"webhook"`
}
// Ntfy maps the values within the [ntfy] TOML table.
type Ntfy struct {
Webhook string `toml:"webhook"`
}
// Slack maps the values within the [slack] TOML table.
type Slack struct {
Webhook string `toml:"webhook"`
}

View File

@ -1,67 +0,0 @@
#############################################################################################################
# This is the TOML config file for wazuh-notify (active response) for both the Python and Go implementation #
#############################################################################################################
[general]
# Platforms in this string with comma seperated values are triggered.
targets = "slack, ntfy, discord"
# Platforms in this string will enable sending the full event information.
full_alert = ""
# Exclude rule events that are enabled in the ossec.conf active response definition.
# These settings provide an easier way to disable events from firing the notifiers.
excluded_rules = "99999, 00000"
excluded_agents = "99999"
# Exclude specific rules by string contained in description
# These settings provide an easier way to disable events from firing the notifiers.
exclude_descriptions = [
""
]
# The next 2 settings are used to add information to the messages.
sender = "Wazuh (IDS)"
click = "https://documentation.wazuh.com/"
[discord]
webhook = "https://discord.com/api/webhooks/XXX"
[ntfy]
webhook = "https://ntfy.sh/XXX"
[slack]
webhook = "https://hooks.slack.com/services/XXX"
# Priority mapping from 0-15 (Wazuh threat levels) to 1-5 (in notifications) and their respective colors (Discord)
# https://documentation.wazuh.com/current/user-manual/ruleset/rules-classification.html
# Enter threat_map as lists of integers, mention/notify_threshold as integer and color as Hex integer
[[priority_map]]
threat_map = [15, 14, 13, 12]
mention_threshold = 1
notify_threshold = 1
color = 0xec3e40 # Red, SEVERE
[[priority_map]]
threat_map = [11, 10, 9]
mention_threshold = 1
notify_threshold = 1
color = 0xff9b2b # Orange, HIGH
[[priority_map]]
threat_map = [8, 7, 6]
mention_threshold = 5
notify_threshold = 5
color = 0xf5d800 # Yellow, ELEVATED
[[priority_map]]
threat_map = [5, 4]
mention_threshold = 20
notify_threshold = 5
color = 0x377fc7 # Blue, GUARDED
[[priority_map]]
threat_map = [3, 2, 1, 0]
mention_threshold = 20
notify_threshold = 1
color = 0x01a465 # Green, LOW

View File

@ -1,14 +0,0 @@
package constants
const (
Discord = "discord"
Ntfy = "ntfy"
Slack = "slack"
Targets = "targets"
Click = "click"
DiscordMention = "@here"
Sender = "sender"
Source = "source"
EmphasisDouble = "**"
EmphasisSingle = "*"
)

View File

@ -1,67 +0,0 @@
#############################################################################################################
# This is the TOML config file for wazuh-notify (active response) for both the Python and Go implementation #
#############################################################################################################
[general]
# Platforms in this string with comma seperated values are triggered.
targets = "slack, ntfy, discord"
# Platforms in this string will enable sending the full event information.
full_alert = ""
# Exclude rule events that are enabled in the ossec.conf active response definition.
# These settings provide an easier way to disable events from firing the notifiers.
excluded_rules = "99999, 00000"
excluded_agents = "99999"
# Exclude specific rules by string contained in description
# These settings provide an easier way to disable events from firing the notifiers.
exclude_descriptions = [
""
]
# The next 2 settings are used to add information to the messages.
sender = "Wazuh (IDS)"
click = "https://documentation.wazuh.com/"
[discord]
webhook = "https://discord.com/api/webhooks/XXX"
[ntfy]
webhook = "https://ntfy.sh/XXX"
[slack]
webhook = "https://hooks.slack.com/services/XXX"
# Priority mapping from 0-15 (Wazuh threat levels) to 1-5 (in notifications) and their respective colors (Discord)
# https://documentation.wazuh.com/current/user-manual/ruleset/rules-classification.html
# Enter threat_map as lists of integers, mention/notify_threshold as integer and color as Hex integer
[[priority_map]]
threat_map = [15, 14, 13, 12]
mention_threshold = 1
notify_threshold = 1
color = 0xec3e40 # Red, SEVERE
[[priority_map]]
threat_map = [11, 10, 9]
mention_threshold = 1
notify_threshold = 1
color = 0xff9b2b # Orange, HIGH
[[priority_map]]
threat_map = [8, 7, 6]
mention_threshold = 5
notify_threshold = 5
color = 0xf5d800 # Yellow, ELEVATED
[[priority_map]]
threat_map = [5, 4]
mention_threshold = 20
notify_threshold = 5
color = 0x377fc7 # Blue, GUARDED
[[priority_map]]
threat_map = [3, 2, 1, 0]
mention_threshold = 20
notify_threshold = 1
color = 0x01a465 # Green, LOW

View File

@ -1,14 +0,0 @@
package discord
type DiscordMessage struct {
Username string `json:"username,omitempty"`
AvatarUrl string `json:"avatar_url,omitempty"`
Content string `json:"content,omitempty"`
Embeds []Embed `json:"embeds,omitempty"`
}
type Embed struct {
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Color int `json:"color,omitempty"`
}

View File

@ -1,40 +0,0 @@
package discord
import (
"bytes"
"encoding/json"
"log"
"net/http"
"wazuh-notify/common"
"wazuh-notify/config"
"wazuh-notify/constants"
)
func Send(ar common.ActiveResponse, color int, mention string, priority int) {
body := common.BuildMessage(ar, constants.Discord, constants.EmphasisDouble, priority)
message := DiscordMessage{
Username: config.File.General.Sender,
Content: mention,
Embeds: []Embed{
{
Title: config.File.General.Sender,
Description: body,
Color: color,
},
},
}
payload := new(bytes.Buffer)
//Parse message to json
err := json.NewEncoder(payload).Encode(message)
if err != nil {
return
}
//Send message to webhook
_, err = http.Post(config.File.Discord.Webhook, "application/json", payload)
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
}

View File

@ -1,52 +0,0 @@
{
"version":1,
"origin":{
"name":"worker01",
"module":"wazuh-execd"
},
"command":"add",
"parameters":{
"extra_args":[],
"alert":{
"timestamp":"2021-02-01T20:58:44.830+0000",
"rule":{
"level":15,
"description":"Shellshock attack detected",
"id":"31168",
"mitre":{
"id":["T1068","T1190"],
"tactic":["Privilege Escalation","Initial Access"],
"technique":["Exploitation for Privilege Escalation","Exploit Public-Facing Application"]
},
"info":"CVE-2014-6271https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6271",
"firedtimes":2,
"mail":true,
"groups":["web","accesslog","attack"],
"pci_dss":["11.4"],
"gdpr":["IV_35.7.d"],
"nist_800_53":["SI.4"],
"tsc":["CC6.1","CC6.8","CC7.2","CC7.3"]
},
"agent":{
"id":"000",
"name":"wazuh-server"
},
"manager":{
"name":"wazuh-server"
},
"id":"1612213124.6448363",
"full_log":"192.168.0.223 - - [01/Feb/2021:20:58:43 +0000] \"GET / HTTP/1.1\" 200 612 \"-\" \"() { :; }; /bin/cat /etc/passwd\"",
"decoder":{
"name":"web-accesslog"
},
"data":{
"protocol":"GET",
"srcip":"192.168.0.223",
"id":"200",
"url":"/"
},
"location":"/var/log/nginx/access.log"
},
"program":"/var/ossec/active-response/bin/firewall-drop"
}
}

View File

@ -1,9 +0,0 @@
module wazuh-notify
go 1.25.4
require (
github.com/BurntSushi/toml v1.4.0
github.com/joho/godotenv v1.5.1
github.com/urfave/cli/v3 v3.3.8
)

View File

@ -1,14 +0,0 @@
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v3 v3.3.8 h1:BzolUExliMdet9NlJ/u4m5vHSotJ3PzEqSAZ1oPMa/E=
github.com/urfave/cli/v3 v3.3.8/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,46 +0,0 @@
package log
import (
"log/slog"
"os"
"path"
"time"
)
var logFile *os.File
func OpenLogFile(BasePath string) {
logFile, _ = os.OpenFile(path.Join(BasePath, "active-responses.log"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777)
_, err := logFile.WriteString(
"\n#######################################\n## START ##" +
"\n" + time.Now().String() +
"\n#######################################\n",
)
if err != nil {
panic(err)
}
if logFile == nil {
panic("logFile is nil")
}
}
func CloseLogFile() {
if logFile != nil {
_, err := logFile.WriteString(
"\n\n#######################################\n## CLOSE ##" +
"\n" + time.Now().String() +
"\n#######################################\n",
)
if err != nil {
panic(err)
}
logFile.Close()
}
}
func Log(message string) {
slog.Info(message)
if _, err := logFile.WriteString("\n" + message + ": " + time.Now().String()); err != nil {
panic(err)
}
}

View File

@ -1,55 +0,0 @@
package main
import (
"context"
"fmt"
"log"
"net/mail"
"os"
"wazuh-notify/config"
logger "wazuh-notify/log"
"github.com/urfave/cli/v3"
)
func main() {
app := &cli.Command{
Name: "KleinCommand",
Usage: "CLI tool for internal use",
UsageText: "kleinCommand [category] [command] [arguments...]",
Version: "v0.1.0",
HideVersion: true,
Authors: []any{
mail.Address{
Name: "Darius",
Address: "darius.klein@dariusklein.nl",
},
},
DefaultCommand: "help",
Commands: []*cli.Command{
Notify(),
},
Before: func(context context.Context, c *cli.Command) (context.Context, error) {
const WazuhLogDir = "/var/ossec/logs/active-responses"
if err := os.MkdirAll(WazuhLogDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error creating log directory %s: %v\n", WazuhLogDir, err)
}
logger.OpenLogFile(WazuhLogDir)
config.Read()
return context, nil
},
After: func(context context.Context, c *cli.Command) error {
logger.Log("Starting cleanup")
logger.CloseLogFile()
return nil
},
}
if err := app.Run(context.Background(), os.Args); err != nil {
log.Fatal(err)
}
}

View File

@ -1,153 +0,0 @@
package main
import (
"bufio"
"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 {
logger.Log("Notify starting")
sourcePath := c.String(constants.Source)
var input io.Reader
if sourcePath == "" {
logger.Log("Reading from standard input (stdin)...")
input = bufio.NewReader(os.Stdin)
} else {
logger.Log("Reading from file (stdin)...")
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)
}
logger.Log("Finished sending messages")
return nil
}
func readInput(input io.Reader) (common.ActiveResponse, error) {
logger.Log("Parsing input")
var ar common.ActiveResponse
decoder := json.NewDecoder(input)
err := decoder.Decode(&ar)
if err != nil {
logger.Log(fmt.Sprintf("Error decoding JSON from stdin: %v\n", err))
return ar, err
}
return ar, nil
}

View File

@ -1,35 +0,0 @@
package ntfy
import (
"net/http"
"strconv"
"strings"
"wazuh-notify/common"
"wazuh-notify/config"
"wazuh-notify/constants"
)
func Send(params common.ActiveResponse, priority int) {
//Create request and build message
req, _ := http.NewRequest(
"POST",
config.File.Ntfy.Webhook,
strings.NewReader(" "+common.BuildMessage(params, constants.Ntfy, constants.EmphasisDouble, priority)))
req.Header.Set("Content-Type", "text/markdown")
//Set headers if not empty
if config.File.General.Sender != "" {
req.Header.Add("Title", config.File.General.Sender)
}
if params.Tags() != "" {
req.Header.Add("Tags", params.Tags())
}
if config.File.General.Click != "" {
req.Header.Add("Click", config.File.General.Click)
}
if priority != 0 {
req.Header.Add("Priority", strconv.Itoa(priority))
}
//Send request
http.DefaultClient.Do(req)
}

View File

@ -1,5 +0,0 @@
package slack
type SlackMessage struct {
Text string `json:"text,omitempty"`
}

View File

@ -1,32 +0,0 @@
package slack
import (
"bytes"
"encoding/json"
"log"
"net/http"
"wazuh-notify/common"
"wazuh-notify/config"
"wazuh-notify/constants"
)
func Send(params common.ActiveResponse, priority int) {
//Build message
message := SlackMessage{
Text: common.BuildMessage(params, constants.Slack, constants.EmphasisSingle, priority) +
"*Tags:* " + params.Tags() + "\n\n" +
config.File.General.Click,
}
payload := new(bytes.Buffer)
//Parse message to json
err := json.NewEncoder(payload).Encode(message)
if err != nil {
return
}
//Send message to webhook
_, err = http.Post(config.File.Slack.Webhook, "application/json", payload)
if err != nil {
log.Fatalf("An Error Occured %v", err)
}
}

View File

@ -1,6 +1,6 @@
module wazuh-notify module wazuh-notify
go 1.25.4 go 1.23
require ( require (
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.4.0