Skip to content

Commit 655dc27

Browse files
committed
fake: add new mode random_single_value
1 parent 806d81e commit 655dc27

File tree

6 files changed

+86
-18
lines changed

6 files changed

+86
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Please refer to the [NEWS](NEWS.md) for a list of changes which have an affect o
2828

2929
#### Experts
3030
- `intelmq.bots.experts.asn_lookup.expert`: Print URLs to stdout only in verbose mode (PR#2591 by Sebastian Wagner).
31+
- `intelmq.bots.experts.fake.expert`: Add new mode `random_single_value` (PR#2601 by Sebastian Wagner).
3132

3233
#### Outputs
3334

docs/user/bots.md

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,35 +2684,56 @@ is `$portal_url + '/api/1.0/ripe/contact?cidr=%s'`.
26842684

26852685
### Fake <div id="intelmq.bots.experts.fake.expert" />
26862686

2687-
Adds fake data to events. Currently supports setting the IP address and network.
2687+
Adds fake data to events. It currently supports two operation methods:
26882688

2689-
For each incoming event, the bots chooses one random IP network range from the configured data file.
2690-
It set's the first IP address of the range as `source.ip` and the network itself as `source.network`.
2691-
To adapt the `source.asn` field accordingly, use the [ASN Lookup Expert](#asn-lookup).
2689+
* Setting the IP address and network
2690+
* For any Event event field, set the value to a random item of a used-defined list (mode `random_single_value`)
2691+
2692+
For a detailed description of the modes, see below.
26922693

26932694
**Module:** `intelmq.bots.experts.fake.expert`
26942695

26952696
**Parameters:**
26962697

26972698
**`database`**
26982699

2699-
(required, string) Path to a JSON file in the following format:
2700+
(required, string) Path to a JSON file in the following format (example):
27002701
```
27012702
{
27022703
"ip_network": [
27032704
"10.0.0.0/8",
2705+
"192.168.0.0/16",
27042706
...
2705-
]
2707+
],
2708+
"event_fields": {
2709+
"extra.severity": {
2710+
"mode": "random_single_value",
2711+
"values": ["critical", "high", "medium", "low", "info", "undefined"]
2712+
},
2713+
...
2714+
}
27062715
}
27072716
```
2717+
Any of the two modes can be ommitted
27082718

27092719
**`overwrite`**
27102720

27112721
(optional, boolean) Whether to overwrite existing fields. Defaults to false.
27122722

2723+
### Modes
2724+
2725+
#### IP Network
2726+
For each incoming event, the bots chooses one random IP network range (IPv4 or IPv6) from the configured data file.
2727+
It set's the first IP address of the range as `source.ip` and the network itself as `source.network`.
2728+
To adapt the `source.asn` field accordingly, use the [ASN Lookup Expert](#asn-lookup).
2729+
27132730
For data consistency `source.network` will only be set if `source.ip` was set or overridden.
27142731
If overwrite is false, `source.ip` was did not exist before but `source.network` existed before, `source.network` will still be overridden.
27152732

2733+
#### Event fields
2734+
##### Mode `random_single_value`
2735+
For any possible event field, the bot chooses a random value of the values in the `values` property.
2736+
27162737
---
27172738

27182739
### Field Reducer <div id="intelmq.bots.experts.field_reducer.expert" />

intelmq/bots/experts/fake/expert.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from json import load as json_load
88

99
from intelmq.lib.bot import ExpertBot
10+
from intelmq.lib.message import Event
1011

1112

1213
class FakeExpertBot(ExpertBot):
@@ -17,30 +18,58 @@ class FakeExpertBot(ExpertBot):
1718

1819
def init(self):
1920
with open(self.database) as database:
20-
self.networks = json_load(database)['ip_network']
21+
database = json_load(database)
22+
self.ip_networks = database.get('ip_network', [])
23+
self.event_fields = database.get('event_fields', {})
2124

2225
def process(self):
2326
event = self.receive_message()
24-
network = choice(self.networks)
27+
if self.ip_networks:
28+
network = choice(self.ip_networks)
2529

26-
updated = False
27-
try:
28-
updated = event.add('source.ip', ip_network(network)[1], overwrite=self.overwrite)
29-
except IndexError:
30-
updated = event.add('source.ip', ip_network(network)[0], overwrite=self.overwrite)
31-
# For consistency, only set the network if the source.ip was set or overwritten, but then always overwrite it
32-
if updated:
33-
event.add('source.network', network, overwrite=True)
30+
updated = False
31+
try:
32+
updated = event.add('source.ip', ip_network(network)[1], overwrite=self.overwrite)
33+
except IndexError:
34+
updated = event.add('source.ip', ip_network(network)[0], overwrite=self.overwrite)
35+
# For consistency, only set the network if the source.ip was set or overwritten, but then always overwrite it
36+
if updated:
37+
event.add('source.network', network, overwrite=True)
38+
39+
for fieldname, field in self.event_fields.items():
40+
if field['mode'] == 'random_single_value':
41+
event.add(fieldname, choice(field['values']), overwrite=self.overwrite)
42+
else:
43+
raise ValueError(f"Mode {field['mode']} not supported in field {fieldname}.")
3444

3545
self.send_message(event)
3646
self.acknowledge_message()
3747

3848
def check(parameters: dict):
3949
try:
4050
with open(parameters['database']) as database:
41-
json_load(database)['ip_network']
51+
database = json_load(database)
4252
except Exception as exc:
43-
return [['error', exc]]
53+
return [['error', f"Could not load database: {exc}"]]
54+
errors = []
55+
if not isinstance(database.get('ip_network', []), list):
56+
errors.append(['error', 'ip_network is not of type list'])
57+
if not isinstance(database.get('event_fields', {}), dict):
58+
errors.append(['error', 'event_fields is not of type dict'])
59+
else:
60+
test_event = Event()
61+
for fieldname, field in database.get('event_fields', {}).items():
62+
fieldname_check = test_event._Message__is_valid_key(fieldname)
63+
if not fieldname_check[0]:
64+
errors.append(['error', f"Field name {fieldname} is not valid: {fieldname_check[1]}."])
65+
mode = field.get('mode')
66+
if mode not in ('random_single_value', ):
67+
errors.append(['error', f"Mode {mode} not supported in field {fieldname}."])
68+
if 'values' not in field:
69+
errors.append(['error', f"No values defined in field {fieldname}."])
70+
elif not isinstance(field['values'], list):
71+
errors.append(['error', f"Values is not a list in field {fieldname}."])
72+
return errors if errors else None
4473

4574

4675
BOT = FakeExpertBot
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"event_fields": {
3+
"extra.severity": {
4+
"mode": "random_single_value",
5+
"values": ["critical", "high", "medium", "low", "info", "undefined"]
6+
}
7+
}
8+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
SPDX-FileCopyrightText: 2025 Institute for Common Good Technology, Sebastian Wagner
2+
SPDX-License-Identifier: AGPL-3.0-or-later

intelmq/tests/bots/experts/fake/test_expert.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from intelmq.bots.experts.fake.expert import FakeExpertBot
1313

1414
FAKE_DB = pkg_resources.resource_filename('intelmq', 'tests/bots/experts/fake/data.json')
15+
SEVERITY_DB = pkg_resources.resource_filename('intelmq', 'tests/bots/experts/fake/severity.json')
1516
EXAMPLE_INPUT = {"__type": "Event",
1617
"source.ip": "93.184.216.34", # example.com
1718
}
@@ -45,6 +46,12 @@ def test_network_exists(self):
4546
self.assertIn(ip_address(msg['source.ip']), ip_network("10.0.0.0/8"))
4647
self.assertEqual(msg['source.network'], "10.0.0.0/8")
4748

49+
def test_random_single_value(self):
50+
self.input_message = {"__type": "Event"}
51+
self.run_bot(parameters={'database': SEVERITY_DB})
52+
msg = json_loads(self.get_output_queue()[0])
53+
self.assertIn(msg['extra.severity'], ["critical", "high", "medium", "low", "info", "undefined"])
54+
4855

4956
if __name__ == '__main__': # pragma: no cover
5057
unittest.main()

0 commit comments

Comments
 (0)