Skip to content

Commit c7f9f8b

Browse files
authored
Merge pull request #2601 from sebix/fake-random_single_value
fake: add new mode random_single_value
2 parents 2dddca9 + 24fa9b9 commit c7f9f8b

File tree

6 files changed

+87
-19
lines changed

6 files changed

+87
-19
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ Please refer to the [NEWS](NEWS.md) for a list of changes which have an affect o
3333
- Print URLs to stdout only in verbose mode (PR#2591 by Sebastian Wagner).
3434
- Check for database file existence and writability (fixes #2566).
3535
- Use database path matching to installation type (PR#2606 by Sebastian Wagner).
36-
- `intelmq.bots.experts.fake.expert`: Use database path matching to installation type (PR#2606 by Sebastian Wagner).
36+
- `intelmq.bots.experts.fake.expert`:
37+
- Use database path matching to installation type (PR#2606 by Sebastian Wagner).
38+
- Add new mode `random_single_value` (PR#2601 by Sebastian Wagner).
3739

3840
#### Outputs
3941

docs/user/bots.md

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

26872687
### Fake <div id="intelmq.bots.experts.fake.expert" />
26882688

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

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

26952696
**Module:** `intelmq.bots.experts.fake.expert`
26962697

26972698
**Parameters:**
26982699

26992700
**`database`**
27002701

2701-
(required, string) Path to a JSON file in the following format:
2702+
(required, string) Path to a JSON file in the following format (example):
27022703
```
27032704
{
27042705
"ip_network": [
27052706
"10.0.0.0/8",
2707+
"192.168.0.0/16",
27062708
...
2707-
]
2709+
],
2710+
"event_fields": {
2711+
"extra.severity": {
2712+
"mode": "random_single_value",
2713+
"values": ["critical", "high", "medium", "low", "info", "undefined"]
2714+
},
2715+
...
2716+
}
27082717
}
27092718
```
27102719

27112720
**`overwrite`**
27122721

27132722
(optional, boolean) Whether to overwrite existing fields. Defaults to false.
27142723

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

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

27202740
### 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
@@ -8,6 +8,7 @@
88

99
from intelmq.lib.bot import ExpertBot
1010
from intelmq import VAR_STATE_PATH
11+
from intelmq.lib.message import Event
1112

1213

1314
class FakeExpertBot(ExpertBot):
@@ -18,30 +19,58 @@ class FakeExpertBot(ExpertBot):
1819

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

2326
def process(self):
2427
event = self.receive_message()
25-
network = choice(self.networks)
28+
if self.ip_networks:
29+
network = choice(self.ip_networks)
2630

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

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

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

4675

4776
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)