Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cdb96036f | |||
| 5b763dc7b4 | |||
| bfc763c0c0 | |||
| 2117fcc783 | |||
| 5205c2fb22 | |||
| cf155a951c | |||
| 26e6b08371 | |||
| 033b36d1d1 | |||
| 8f5663981a | |||
| c307c8c7a4 | |||
| 63e28bc5e9 | |||
| 38b6c997db | |||
| 0e5b20f0dd | |||
| ac1476b703 | |||
| ffc8bd66ee | |||
| d5b2bda535 | |||
| 2377469461 | |||
| e9d82ba5d9 | |||
| da325de501 | |||
| 6131edcdd5 | |||
|
|
58852326ea | ||
|
|
e914886efd | ||
|
|
c998b4a43f | ||
|
|
6c68447ef4 | ||
|
|
ac5d2babbd | ||
| bb4d4cf76f | |||
| a330c855e2 | |||
|
|
84d8b348d8 | ||
|
|
4f9a49e1f3 | ||
|
|
77b45dce9a | ||
|
|
83321c5910 | ||
|
|
b5982d348d | ||
|
|
656a96ab0f |
41
.github/workflows/deploy-docs.yml
vendored
41
.github/workflows/deploy-docs.yml
vendored
@ -1,41 +0,0 @@
|
|||||||
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
|
|
||||||
35
.github/workflows/golang.yml
vendored
35
.github/workflows/golang.yml
vendored
@ -1,35 +0,0 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: '1.22'
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
cd wazuh-notify-go
|
|
||||||
go build -v .
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.RELEASE_TOKEN }}
|
|
||||||
tag_name: test
|
|
||||||
files: |
|
|
||||||
wazuh-notify-go/wazuh-notify
|
|
||||||
43
.github/workflows/release.yml
vendored
Normal file
43
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
name: Go
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.25'
|
||||||
|
|
||||||
|
- name: Build wazuh-notify-go
|
||||||
|
run: |
|
||||||
|
cd wazuh-notify-go
|
||||||
|
go build -o wazuh-notifier-go .
|
||||||
|
|
||||||
|
- name: Upload wazuh-notify-go artifact
|
||||||
|
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: |
|
||||||
|
cd wazuh-notify-go-v2
|
||||||
|
go build -o wazuh-notifier-go-v2 .
|
||||||
|
|
||||||
|
- name: Upload wazuh-notify-go-v2 artifact
|
||||||
|
uses: christopherhx/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wazuh-notifier-go-v2-binary
|
||||||
|
path: wazuh-notify-go-v2/wazuh-notifier-go-v2
|
||||||
321
README.md
321
README.md
@ -1,23 +1,6 @@
|
|||||||
# Wazuh notify
|
# Wazuh notify
|
||||||
*version 1.0*
|
*version 1.0*
|
||||||
|
|
||||||
## Table of Contents
|
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
|
||||||
- [Installation](#installation)
|
|
||||||
- [Step 1: download](#step-1-download)
|
|
||||||
- [Step 2: copy files](#step-2-copy-files)
|
|
||||||
- [Python](#python_1)
|
|
||||||
- [Golang](#golang_1)
|
|
||||||
- [Step 3: copy the TOML file](#step-3-copy-the-toml-configuration-file)
|
|
||||||
- [Step 4: create .env file](#step-4-create-env-file)
|
|
||||||
- [Wazuh configuration](#wazuh-configuration)
|
|
||||||
- [Golang](#golang_2)
|
|
||||||
- [Python](#python_2)
|
|
||||||
- [Note](#note)
|
|
||||||
- [The TOML configuration file](#the-toml-configuration)
|
|
||||||
- [Setting up the platforms](#setting-up-the-platforms-receiving-the-notifications)
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Wazuh notifier enables the Wazuh manager to be notified when Wazuh selected events occur, using 3 messaging platforms:
|
Wazuh notifier enables the Wazuh manager to be notified when Wazuh selected events occur, using 3 messaging platforms:
|
||||||
@ -30,306 +13,4 @@ Wazuh notify is a stateless implementation and only notifies: triggered by speci
|
|||||||
|
|
||||||
Wazuh notify is executed by configuring the **ossec.conf** and adding an **active response configuration**.
|
Wazuh notify is executed by configuring the **ossec.conf** and adding an **active response configuration**.
|
||||||
|
|
||||||
## Installation
|
### Please refer to https://docs.notifier.kleinsense.nl/wazuh-notifier.html for the full documentation.
|
||||||
|
|
||||||
### Step 1: download
|
|
||||||
|
|
||||||
Download the files from https://github.com/kleinprojects/wazuh-notify to your server.
|
|
||||||
|
|
||||||
### Step 2: copy files
|
|
||||||
|
|
||||||
#### _Python_ {id="python_1"}
|
|
||||||
|
|
||||||
Copy the 2 Python scripts to the /var/ossec/active-response/bin/ folder
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo cp <download folder>/wazuh-*.py /var/ossec/active-response/bin/
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the correct ownership {id="set-the-correct-ownership_1"}
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo chown root:wazuh /var/ossec/active-response/bin/wazuh-notify.py
|
|
||||||
$ sudo chown root:wazuh /var/ossec/active-response/bin/wazuh_notify_module.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the correct permissions {id="set-the-correct-permissions_1"}
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo chmod uog+rx /var/ossec/active-response/bin/wazuh-notify.py
|
|
||||||
$ sudo chmod uog+rx /var/ossec/active-response/bin/wazuh_notify_module.py
|
|
||||||
```
|
|
||||||
|
|
||||||
#### _Golang_ {id="golang_1"}
|
|
||||||
|
|
||||||
Copy the Go executable to the /var/ossec/active-response/bin/ folder
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo cp <download folder>/wazuh-notify /var/ossec/active-response/bin/
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the correct ownership {id="set-the-correct-ownership_2"}
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo chown root:wazuh /var/ossec/active-response/bin/wazuh-notify
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the correct permissions {id="set-the-correct-permissions_2"}
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo chmod uog+rx /var/ossec/active-response/bin/wazuh-notify
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: copy the TOML configuration file
|
|
||||||
|
|
||||||
Copy the TOML file to /var/ossec/etc/
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo cp <download folder>/wazuh-notify-config.toml /var/ossec/etc/
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the correct ownership {id="set-the-correct-ownership_3"}
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo chown root:wazuh /var/ossec/etc/wazuh-notify-config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the correct permissions {id="set-the-correct-permissions_3"}
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo chmod uog+r /var/ossec/etc/wazuh-notify-config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: create .env file
|
|
||||||
|
|
||||||
Create an .env file in /var/ossec/etc/
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo touch /var/ossec/etc/.env
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the correct ownership {id="set-the-correct-ownership_4"}
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo chown root:wazuh /var/ossec/etc/wazuh-notify-config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
Set the correct permissions {id="set-the-correct-permissions_4"}
|
|
||||||
|
|
||||||
```
|
|
||||||
$ sudo chmod uog+r /var/ossec/etc/wazuh-notify-config.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Wazuh configuration
|
|
||||||
|
|
||||||
#### _Golang_ {id="golang_2"}
|
|
||||||
|
|
||||||
Modify the /var/ossec/etc/ossec.conf configuration file and add the following:<br/>
|
|
||||||
|
|
||||||
*Command section*
|
|
||||||
|
|
||||||
```
|
|
||||||
<command>
|
|
||||||
<name>wazuh-notify-go</name>
|
|
||||||
<executable>wazuh-notify</executable>
|
|
||||||
<timeout_allowed>yes</timeout_allowed>
|
|
||||||
</command>
|
|
||||||
```
|
|
||||||
|
|
||||||
*Active response section*
|
|
||||||
|
|
||||||
```
|
|
||||||
<active-response>
|
|
||||||
<command>wazuh-notify-go</command>
|
|
||||||
<location>server</location>
|
|
||||||
<level></level>
|
|
||||||
<rules_id></rules_id>
|
|
||||||
</active-response>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### _Python_ {id="python_2"}
|
|
||||||
|
|
||||||
*Command section*
|
|
||||||
|
|
||||||
```
|
|
||||||
<command>
|
|
||||||
<name>wazuh-notify-py</name>
|
|
||||||
<executable>wazuh-notify.py</executable>
|
|
||||||
<timeout_allowed>yes</timeout_allowed>
|
|
||||||
</command>
|
|
||||||
```
|
|
||||||
|
|
||||||
*Active response section*
|
|
||||||
|
|
||||||
```
|
|
||||||
<active-response>
|
|
||||||
<command>wazuh-notify-py</command>
|
|
||||||
<location>server</location>
|
|
||||||
<level></level>
|
|
||||||
<rules_id></rules_id>
|
|
||||||
</active-response>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### NOTE: <format color="OrangeRed">!</format>
|
|
||||||
The ```<name>``` in the ```<command>``` section needs to be the same as the ```<command>``` in
|
|
||||||
the ```<active-response>``` section.
|
|
||||||
The ```<command>``` section describes the program that is executed. The ```<active-response>``` section describes the
|
|
||||||
trigger that runs the ```<command>```.
|
|
||||||
|
|
||||||
Add the rules you want to be informed about between the ```<rules_id></rules_id>```, with the rules id's separated by
|
|
||||||
comma's.
|
|
||||||
Example: ```<rules_id>5402, 3461, 8777</rules_id>```.
|
|
||||||
|
|
||||||
Please refer to
|
|
||||||
the [Wazuh online documentation](https://documentation.wazuh.com/current/user-manual/capabilities/active-response/index.html)
|
|
||||||
for more information.
|
|
||||||
|
|
||||||
## The TOML configuration
|
|
||||||
|
|
||||||
This is the toml configuration file for wazuh-notify (for both the Python and Golang version).
|
|
||||||
|
|
||||||
The targets setting defines the platforms where notifications will be sent to.
|
|
||||||
Platforms in this comma-separated string will receive notifications, if and when they are set up.
|
|
||||||
Refer to [setting up the platforms](#setting-up-the-platforms-receiving-the-notifications).
|
|
||||||
|
|
||||||
```
|
|
||||||
targets: "slack, ntfy, discord"
|
|
||||||
```
|
|
||||||
|
|
||||||
Platforms in this comma-separated string will receive the full event information.
|
|
||||||
|
|
||||||
```
|
|
||||||
full_alert: ""
|
|
||||||
```
|
|
||||||
|
|
||||||
Exclude_rules and excluded_agents will disable notification for these particular events or agents that are enabled in
|
|
||||||
the ossec.conf active response definition.
|
|
||||||
These settings provide an easier way to disable event notifications from firing. No need to restart Wazuh-manager.
|
|
||||||
|
|
||||||
Enter rule numbers as a string with comma-separated values.
|
|
||||||
Enter numeric agent id's as a string with comma-separated values.
|
|
||||||
|
|
||||||
```
|
|
||||||
excluded_rules: "99999, 00000"
|
|
||||||
excluded_agents: "99999"
|
|
||||||
```
|
|
||||||
|
|
||||||
[The threat levels used in Wazuh](https://documentation.wazuh.com/current/user-manual/ruleset/rules-classification.html)
|
|
||||||
(0-15) are mapped to notification priority levels (1-5), and their respective colors (Discord only).
|
|
||||||
The Wazuh threat level scale runs from 0-15, where 15 is the most severe threat. It corresponds to the
|
|
||||||
[HSAS](https://en.wikipedia.org/wiki/Homeland_Security_Advisory_System) threat scale that runs from 5-1, whereby 1 is
|
|
||||||
the highest threat level. The configuration allows for customized mapping: in some use cases the mapping could be different.
|
|
||||||
|
|
||||||
The mention threshold defines when Discord users receive a DM, next to the common messages they receive in their channel.
|
|
||||||
Often these common channels are muted and DM's will draw more attention. 1 means that for every notification a DM will be sent.
|
|
||||||
A mention threshold of 5 means that for every 5th occurrence of this specific event, a DM will be sent also.
|
|
||||||
|
|
||||||
The notify threshold is somewhat similar to the mention threshold. A notify threshold of 1 will send each notification,
|
|
||||||
a notify threshold of 4 will only send each 4th notification triggered by a specific event. This will reduce high amounts
|
|
||||||
of notifications for the same event. The fired_times value in the message will show the actual number of the times this
|
|
||||||
specific event was generated.
|
|
||||||
|
|
||||||
Enter a threat_map as a list of integers,
|
|
||||||
color as a hex RGB color values,
|
|
||||||
mention/notify_threshold as integers.
|
|
||||||
```
|
|
||||||
[[priority_map]] # Priority 1 on the HSAS scale
|
|
||||||
threat_map = [15, 14, 13, 12] # Wazuh threat levels -> priority 2
|
|
||||||
color = 0xec3e40 # Red, SEVERE on the HSAS scale
|
|
||||||
mention_threshold = 1
|
|
||||||
notify_threshold = 1
|
|
||||||
|
|
||||||
[[priority_map]] # Priority 2 on the HSAS scale
|
|
||||||
threat_map = [11, 10, 9] # Wazuh threat levels -> priority 2
|
|
||||||
color = 0xff9b2b # Orange, HIGH on the HSAS scale
|
|
||||||
mention_threshold = 1
|
|
||||||
notify_threshold = 1
|
|
||||||
|
|
||||||
[[priority_map]] # Priority 3 on the HSAS scale
|
|
||||||
threat_map = [8, 7, 6] # Wazuh threat levels -> priority 3
|
|
||||||
color = 0xf5d800 # Yellow, ELEVATED on the HSAS scale
|
|
||||||
mention_threshold = 5
|
|
||||||
notify_threshold = 5
|
|
||||||
|
|
||||||
[[priority_map]] # Priority 4 on the HSAS scale
|
|
||||||
threat_map = [5, 4] # Wazuh threat levels -> priority 4
|
|
||||||
color = 0x377fc7 # Blue, GUARDED on the HSAS scale
|
|
||||||
mention_threshold = 20
|
|
||||||
notify_threshold = 5
|
|
||||||
|
|
||||||
[[priority_map]] # Priority 5 on the HSAS scale
|
|
||||||
threat_map = [3, 2, 1, 0] # Wazuh threat levels -> priority 5
|
|
||||||
color = 0x01a465 # Green, LOW on the HSAS scale
|
|
||||||
mention_threshold = 20
|
|
||||||
notify_threshold = 1
|
|
||||||
```
|
|
||||||
|
|
||||||
The next settings are used to add information to the messages.
|
|
||||||
```Sender``` translate to the ``` username ``` field in Discord and Slack and to the ```title``` field in ntfy.sh.
|
|
||||||
The ```click``` parameter adds an arbitrary URL to the message.
|
|
||||||
|
|
||||||
```
|
|
||||||
sender: "Wazuh (IDS)"
|
|
||||||
click: "https://documentation.wazuh.com/"
|
|
||||||
```
|
|
||||||
|
|
||||||
### From here on the settings are ONLY used by the Python version of wazuh-notify.
|
|
||||||
|
|
||||||
Below settings provide for a window that enable/disables events from firing the notifiers.
|
|
||||||
|
|
||||||
Enter ```excluded_days``` as a string with comma separated values. Be aware of your regional settings.
|
|
||||||
|
|
||||||
```
|
|
||||||
excluded_days: ""
|
|
||||||
```
|
|
||||||
|
|
||||||
Enter ```excluded_hours``` as a tuple of string values.
|
|
||||||
|
|
||||||
```
|
|
||||||
excluded_hours: [ "23:59", "00:00" ]
|
|
||||||
```
|
|
||||||
|
|
||||||
The following parameters define the markdown characters used to emphasise the parameter names in the notification
|
|
||||||
messages (Markdown style). This is a dictionary notation.
|
|
||||||
|
|
||||||
```
|
|
||||||
markdown_emphasis:
|
|
||||||
slack: "*"
|
|
||||||
ntfy: "**"
|
|
||||||
discord: "**"
|
|
||||||
```
|
|
||||||
|
|
||||||
The next settings are used for testing purposes.
|
|
||||||
|
|
||||||
```Test mode``` will add an example event (```wazuh-notify-test-event.json```) instead of the message received through Wazuh.
|
|
||||||
This enables customization for testing of a particular event.
|
|
||||||
|
|
||||||
```
|
|
||||||
test_mode: False
|
|
||||||
```
|
|
||||||
|
|
||||||
Setting the ```extended_logging``` and ```extended_print``` parameters provides more logging to the wazuh-notifier log
|
|
||||||
and console. The possible values are:
|
|
||||||
|
|
||||||
0-> limited logging
|
|
||||||
1-> basic logging
|
|
||||||
2-> verbose logging
|
|
||||||
|
|
||||||
```
|
|
||||||
extended_logging: 2
|
|
||||||
extended_print: 0
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting up the platforms receiving the notifications
|
|
||||||
|
|
||||||
Each of the 3 platforms make use of webhooks or similar API's. In order to have the right information in the ```.env```
|
|
||||||
file, please refer to the platform's documentation.
|
|
||||||
|
|
||||||
[Slack](https://api.slack.com/) API documentation
|
|
||||||
|
|
||||||
[ntfy.sh](https://docs.ntfy.sh/subscribe/api/) API documentation
|
|
||||||
|
|
||||||
[ntfy.sh](https://docs.ntfy.sh/examples/) examples
|
|
||||||
|
|
||||||
[Discord](https://discord.com/developers/docs/intro) developers documentation
|
|
||||||
|
|
||||||
|
|||||||
78
wazuh-notify-go-v2/common/activeResponse.go
Normal file
78
wazuh-notify-go-v2/common/activeResponse.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
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, ",")
|
||||||
|
}
|
||||||
73
wazuh-notify-go-v2/common/common.go
Normal file
73
wazuh-notify-go-v2/common/common.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
110
wazuh-notify-go-v2/config/config.go
Normal file
110
wazuh-notify-go-v2/config/config.go
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
67
wazuh-notify-go-v2/config/default-config.toml
Normal file
67
wazuh-notify-go-v2/config/default-config.toml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#############################################################################################################
|
||||||
|
# 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
|
||||||
14
wazuh-notify-go-v2/constants/constants.go
Normal file
14
wazuh-notify-go-v2/constants/constants.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
Discord = "discord"
|
||||||
|
Ntfy = "ntfy"
|
||||||
|
Slack = "slack"
|
||||||
|
Targets = "targets"
|
||||||
|
Click = "click"
|
||||||
|
DiscordMention = "@here"
|
||||||
|
Sender = "sender"
|
||||||
|
Source = "source"
|
||||||
|
EmphasisDouble = "**"
|
||||||
|
EmphasisSingle = "*"
|
||||||
|
)
|
||||||
67
wazuh-notify-go-v2/default-config.toml
Normal file
67
wazuh-notify-go-v2/default-config.toml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#############################################################################################################
|
||||||
|
# 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
|
||||||
14
wazuh-notify-go-v2/discord/message.go
Normal file
14
wazuh-notify-go-v2/discord/message.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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"`
|
||||||
|
}
|
||||||
40
wazuh-notify-go-v2/discord/send.go
Normal file
40
wazuh-notify-go-v2/discord/send.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
52
wazuh-notify-go-v2/example.json
Normal file
52
wazuh-notify-go-v2/example.json
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
wazuh-notify-go-v2/go.mod
Normal file
9
wazuh-notify-go-v2/go.mod
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
14
wazuh-notify-go-v2/go.sum
Normal file
14
wazuh-notify-go-v2/go.sum
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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=
|
||||||
46
wazuh-notify-go-v2/log/log.go
Normal file
46
wazuh-notify-go-v2/log/log.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
55
wazuh-notify-go-v2/main.go
Normal file
55
wazuh-notify-go-v2/main.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
153
wazuh-notify-go-v2/notify.go
Normal file
153
wazuh-notify-go-v2/notify.go
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
35
wazuh-notify-go-v2/ntfy/send.go
Normal file
35
wazuh-notify-go-v2/ntfy/send.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
5
wazuh-notify-go-v2/slack/message.go
Normal file
5
wazuh-notify-go-v2/slack/message.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package slack
|
||||||
|
|
||||||
|
type SlackMessage struct {
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
}
|
||||||
32
wazuh-notify-go-v2/slack/send.go
Normal file
32
wazuh-notify-go-v2/slack/send.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
1
wazuh-notify-go/VERSION
Normal file
1
wazuh-notify-go/VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.1.1-Release
|
||||||
@ -1,6 +1,6 @@
|
|||||||
module wazuh-notify
|
module wazuh-notify
|
||||||
|
|
||||||
go 1.22
|
go 1.25.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.4.0
|
github.com/BurntSushi/toml v1.4.0
|
||||||
|
|||||||
@ -22,4 +22,11 @@ func Filter(params types.Params) {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, description := range params.General.ExcludedDescription {
|
||||||
|
if strings.Contains(params.WazuhMessage.Parameters.Alert.FullLog, description) {
|
||||||
|
log.Log("excluded based on description")
|
||||||
|
log.CloseLogFile()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,12 +13,13 @@ type Params struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type General struct {
|
type General struct {
|
||||||
Targets string `toml:"targets"`
|
Targets string `toml:"targets"`
|
||||||
FullAlert string `toml:"full_alert"`
|
FullAlert string `toml:"full_alert"`
|
||||||
ExcludedRules string `toml:"excluded_rules"`
|
ExcludedRules string `toml:"excluded_rules"`
|
||||||
ExcludedAgents string `toml:"excluded_agents"`
|
ExcludedAgents string `toml:"excluded_agents"`
|
||||||
Sender string `toml:"sender"`
|
Sender string `toml:"sender"`
|
||||||
Click string `toml:"click"`
|
Click string `toml:"click"`
|
||||||
|
ExcludedDescription []string `toml:"exclude_descriptions"`
|
||||||
}
|
}
|
||||||
type PriorityMap struct {
|
type PriorityMap struct {
|
||||||
ThreatMap []int `toml:"threat_map"`
|
ThreatMap []int `toml:"threat_map"`
|
||||||
|
|||||||
@ -14,6 +14,12 @@ full_alert = ""
|
|||||||
excluded_rules = "99999, 00000"
|
excluded_rules = "99999, 00000"
|
||||||
excluded_agents = "99999"
|
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.
|
# The next 2 settings are used to add information to the messages.
|
||||||
sender = "Wazuh (IDS)"
|
sender = "Wazuh (IDS)"
|
||||||
click = "https://documentation.wazuh.com/"
|
click = "https://documentation.wazuh.com/"
|
||||||
|
|||||||
1
wazuh-notify-python/VERSION
Normal file
1
wazuh-notify-python/VERSION
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.1.0-Release
|
||||||
Loading…
x
Reference in New Issue
Block a user