Skip to content

Commit c00a878

Browse files
AlexRuiz7f-galland
andcommitted
Add events generator tool for wazuh-alerts (#152)
* Add events generator tool for wazuh-alerts * Fix typo in README.md Signed-off-by: Álex Ruiz <[email protected]> * Make timestamps timezone aware --------- Signed-off-by: Álex Ruiz <[email protected]> Co-authored-by: Fede Tux <[email protected]>
1 parent a8d7f77 commit c00a878

File tree

5 files changed

+1257
-0
lines changed

5 files changed

+1257
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.venv
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
### Events generator tool
2+
3+
This python tool provides functionality to generate and index sample events for Wazuh's indices.
4+
5+
#### Getting started
6+
7+
Create a virtual environment to install the dependencies of the project.
8+
9+
```console
10+
python -m venv .venv
11+
source .venv/bin/activate
12+
pip install -r requirements.txt
13+
```
14+
15+
Start the events' generator with `./run.py` or `python run.py`. The program takes no required
16+
arguments, as it's configured with default values that will work in most cases during development.
17+
To know more about its capabilities and arguments, display the help menu with `-h`.
18+
19+
As for now, this tool generates events for the `wazuh-alerts-4.x-*` and `wazuh-archives-4.x-*` indices.
20+
Since 4.8.0, these indices are aliased to `wazuh-alerts` and `wazuh-archives`. If you need to, run the
21+
[indexer-ism-init.sh](../../../distribution/src/bin/indexer-ism-init.sh) script to create them. This is important as, by default, the tool will write to
22+
the `wazuh-alerts` alias. You may also need to create an **index pattern** in _dashboards_ in order to perform
23+
queries to the index from the UI. To do that, go to Dashboards Management > Index Patterns > Create index pattern > wazuh-alerts-4.x-* > timestamp as Time field
24+
25+
Newer indices, like `wazuh-states-vulnerabilities`, are ECS compliant and use a dedicated events' generator.
26+
You can find it in the [ecs](../../../ecs/) folder.
27+
28+
29+
```console
30+
python run.py -o indexer -c 5 -t 1
31+
INFO:event_generator:Inventory created
32+
INFO:event_generator:Publisher created
33+
INFO:event_generator:Event created
34+
{'_index': 'wazuh-alerts-4.x-2024.02.13-000001', '_id': 'dRWno40BZRXLJU5t0u6Z', '_version': 1, 'result': 'created', '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 168, '_primary_term': 1}
35+
INFO:event_generator:Event created
36+
{'_index': 'wazuh-alerts-4.x-2024.02.13-000001', '_id': 'dhWno40BZRXLJU5t1u6Y', '_version': 1, 'result': 'created', '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 169, '_primary_term': 1}
37+
INFO:event_generator:Event created
38+
{'_index': 'wazuh-alerts-4.x-2024.02.13-000001', '_id': 'dxWno40BZRXLJU5t2u6i', '_version': 1, 'result': 'created', '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 170, '_primary_term': 1}
39+
INFO:event_generator:Event created
40+
{'_index': 'wazuh-alerts-4.x-2024.02.13-000001', '_id': 'eBWno40BZRXLJU5t3u6v', '_version': 1, 'result': 'created', '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 171, '_primary_term': 1}
41+
INFO:event_generator:Event created
42+
{'_index': 'wazuh-alerts-4.x-2024.02.13-000001', '_id': 'eRWno40BZRXLJU5t4u66', '_version': 1, 'result': 'created', '_shards': {'total': 2, 'successful': 2, 'failed': 0}, '_seq_no': 172, '_primary_term': 1}
43+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
requests>=2.31.0
+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#!/usr/bin/pyton
2+
3+
# Events generator tool for Wazuh's indices.
4+
# Chooses a random element from <index>/alerts.json to index
5+
# (indexer, filebeat). Required. Destination of the events. Default: indexer.
6+
# -c: Number of elements to push. Use 0 to run indefinitely. Default: 0
7+
# -i: index name prefix or module (e.g: wazuh-alerts, wazuh-states-vulnerabilities)
8+
# -t: interval between events in seconds. Default: 5
9+
# when output is "indexer", the following parameters can be provided:
10+
# -a: indexer's API IP address or hostname.
11+
# -P: indexer's API port number.
12+
# -u: username
13+
# -p: password
14+
15+
16+
from abc import ABC, abstractmethod
17+
import argparse
18+
import datetime
19+
import logging
20+
import random
21+
import requests
22+
import time
23+
import json
24+
import urllib3
25+
# import OpenSearch.opensearchpy
26+
27+
logging.basicConfig(level=logging.NOTSET)
28+
# Combination to supress certificates validation warning when verify=False
29+
# https://github.com/influxdata/influxdb-python/issues/240#issuecomment-341313420
30+
logging.getLogger("urllib3").setLevel(logging.ERROR)
31+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
32+
33+
logger = logging.getLogger("event_generator")
34+
35+
# ================================================== #
36+
37+
38+
class Inventory:
39+
def __init__(self, path: str):
40+
with open(path, "r") as fd:
41+
self.elements = fd.readlines()
42+
self.size = len(self.elements)
43+
44+
def get_random(self) -> str:
45+
random.shuffle(self.elements)
46+
return self.elements.pop()
47+
# return self.elements[random.randint(0, self.size)]
48+
49+
# ================================================== #
50+
51+
52+
class Publisher(ABC):
53+
@abstractmethod
54+
def publish(self, event: str):
55+
pass
56+
57+
# ================================================== #
58+
59+
60+
class PublisherClient(Publisher):
61+
def __init__(self):
62+
# self.client = OpenSearch(
63+
# hosts...
64+
# )
65+
pass
66+
67+
# ================================================== #
68+
69+
70+
class PublisherHttp(Publisher):
71+
def __init__(self, address: str, port: int, path: str, user: str, password: str):
72+
super()
73+
self.address = address
74+
self.port = port
75+
self.path = path
76+
self.username = user
77+
self.password = password
78+
79+
def url(self) -> str:
80+
return f"https://{self.address}:{self.port}/{self.path}/_doc"
81+
82+
def publish(self, event: str):
83+
try:
84+
result = requests.post(
85+
self.url(),
86+
auth=(self.username, self.password),
87+
json=json.loads(event),
88+
verify=False
89+
)
90+
print(result.json())
91+
except json.JSONDecodeError as e:
92+
logger.error("Error encoding event " +
93+
event + "\n Caused by: " + e.msg)
94+
95+
# ================================================== #
96+
97+
98+
class PublisherFilebeat(Publisher):
99+
def __init__(self):
100+
super()
101+
self.path = "/var/ossec/logs/alerts/alerts.json"
102+
103+
def publish(self, event: str):
104+
with open(self.path, "a") as fd:
105+
fd.write(event)
106+
107+
# ================================================== #
108+
109+
110+
class PublisherCreator:
111+
@staticmethod
112+
def create(publisher: str, args) -> Publisher:
113+
if publisher == "indexer":
114+
address = args["address"]
115+
port = args["port"]
116+
path = args["index"]
117+
username = args["username"]
118+
password = args["password"]
119+
120+
return PublisherHttp(address, port, path, username, password)
121+
elif publisher == "filebeat":
122+
return PublisherFilebeat()
123+
else:
124+
raise ValueError("Unsupported publisher type")
125+
126+
# ================================================== #
127+
128+
129+
def date_now() -> str:
130+
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3]+'+0000'
131+
132+
# ================================================== #
133+
134+
135+
def parse_args():
136+
parser = argparse.ArgumentParser(
137+
description="Events generator tool for Wazuh's indices. Indexes a random element from <index>/alerts.json",
138+
)
139+
parser.add_argument(
140+
'-o', '--output',
141+
choices=['indexer', 'filebeat'],
142+
default="indexer",
143+
help="Destination of the events. Default: indexer."
144+
)
145+
parser.add_argument(
146+
'-i', '--index',
147+
default="wazuh-alerts",
148+
help="Index name or module (e.g: wazuh-alerts, wazuh-states-vulnerabilities)"
149+
)
150+
# Infinite loop by default
151+
parser.add_argument(
152+
'-c', '--count',
153+
default=0,
154+
type=int,
155+
help="Number of elements to push. Use 0 to run indefinitely. Default: 0"
156+
)
157+
# Interval of time between events
158+
parser.add_argument(
159+
'-t', '--time',
160+
default=5,
161+
type=int,
162+
help="Interval between events in seconds. Default: 5"
163+
)
164+
parser.add_argument(
165+
'-a', '--address',
166+
default="localhost",
167+
help="Indexer's API IP address or hostname."
168+
)
169+
parser.add_argument(
170+
'-P', '--port',
171+
default=9200,
172+
type=int,
173+
help="Indexer's API port number."
174+
)
175+
parser.add_argument(
176+
'-u', '--username',
177+
default="admin",
178+
help="Indexer's username"
179+
)
180+
parser.add_argument(
181+
'-p', '--password',
182+
default="admin",
183+
help="Indexer's password"
184+
)
185+
return parser.parse_args()
186+
187+
188+
# ================================================== #
189+
190+
191+
def main(args: dict):
192+
inventory = Inventory(f"{args['index']}/alerts.json")
193+
logger.info("Inventory created")
194+
publisher = PublisherCreator.create(args["output"], args)
195+
logger.info("Publisher created")
196+
197+
count = 0
198+
max_iter = args["count"]
199+
time_interval = args["time"]
200+
while (count < max_iter or max_iter == 0):
201+
chosen = inventory.get_random().replace("{timestamp}", date_now())
202+
logger.info("Event created")
203+
publisher.publish(chosen)
204+
205+
time.sleep(time_interval)
206+
count += 1
207+
208+
# ================================================== #
209+
210+
211+
if __name__ == '__main__':
212+
main(vars(parse_args()))

0 commit comments

Comments
 (0)