Skip to content

Commit efb761d

Browse files
committed
Add documentation. Fix code compatibility
1 parent 5caa3e0 commit efb761d

File tree

5 files changed

+66
-42
lines changed

5 files changed

+66
-42
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
- Treat value `false` for parameter `filter_regex` as false (PR#2499 by Sebastian Wagner).
4141

4242
#### Outputs
43-
- `intelmq.bots.outputs.misp.output_feed`: Handle failures if saved current event wasn't saved or is incorrect (PR by Kamil Mankowski).
43+
- `intelmq.bots.outputs.misp.output_feed`:
44+
- Handle failures if saved current event wasn't saved or is incorrect (PR by Kamil Mankowski).
45+
- Allow saving messages in bulks instead of refreshing the feed immediately (PR#2505 by Kamil Mankowski).
4446
- `intelmq.bots.outputs.smtp_batch.output`: Documentation on multiple recipients added (PR#2501 by Edvard Rejthar).
4547

4648
### Documentation

docs/user/bots.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4609,6 +4609,13 @@ The PyMISP library >= 2.4.119.1 is required, see
46094609
() The output bot creates one event per each interval, all data in this time frame is part of this event. Default "1
46104610
hour", string.
46114611

4612+
**`bulk_save_count`**
4613+
4614+
(optional, int) If set to a non-0 value, the bot won't refresh the MISP feed immeadiately, but will cache
4615+
incoming messages until the given number of them. Use it if your bot proceeds a high number of messages
4616+
and constant saving to the disk is a problem. Reloading or restarting bot as well as generating
4617+
a new MISP event based on `interval_event` triggers saving regardless of the cache size.
4618+
46124619
**Usage in MISP**
46134620

46144621
Configure the destination directory of this feed as feed in MISP, either as local location, or served via a web server.

intelmq/bots/outputs/misp/output_feed.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class MISPFeedOutputBot(OutputBot, CacheMixin):
2727
"""Generate an output in the MISP Feed format"""
2828

2929
interval_event: str = "1 hour"
30-
delay_save_event_count: int = None
30+
bulk_save_count: int = None
3131
misp_org_name = None
3232
misp_org_uuid = None
3333
output_dir: str = "/opt/intelmq/var/lib/bots/mispfeed-output" # TODO: should be path
@@ -115,12 +115,12 @@ def process(self):
115115
event = self.receive_message().to_dict(jsondict_as_string=True)
116116

117117
cache_size = None
118-
if self.delay_save_event_count:
118+
if self.bulk_save_count:
119119
cache_size = self.cache_put(event)
120120

121121
if cache_size is None:
122122
self._generate_feed(event)
123-
elif cache_size >= self.delay_save_event_count:
123+
elif cache_size >= self.bulk_save_count:
124124
self._generate_feed()
125125

126126
self.acknowledge_message()
@@ -138,8 +138,10 @@ def _generate_feed(self, message: dict = None):
138138
if message:
139139
self._add_message_to_feed(message)
140140

141-
while message := self.cache_pop():
141+
message = self.cache_pop()
142+
while message:
142143
self._add_message_to_feed(message)
144+
message = self.cache_pop()
143145

144146
feed_output = self.current_event.to_feed(with_meta=False)
145147
with self.current_file.open("w") as f:

intelmq/lib/mixins/cache.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@
1313

1414

1515
class CacheMixin:
16+
"""Provides caching possibilities for bots
17+
18+
For key-value cache, use methods:
19+
cache_exists
20+
cache_get
21+
cache_set
22+
23+
To store dict elements in a cache queue named after bot id, use methods:
24+
cache_put
25+
cache_pop
26+
"""
1627
__redis: redis.Redis = None
1728
redis_cache_host: str = "127.0.0.1"
1829
redis_cache_port: int = 6379

intelmq/tests/bots/outputs/misp/test_output_feed.py

Lines changed: 39 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,30 @@
1111
import intelmq.lib.test as test
1212
from intelmq.bots.outputs.misp.output_feed import MISPFeedOutputBot
1313

14-
EXAMPLE_EVENT = {"classification.type": "infected-system",
15-
"destination.port": 9796,
16-
"feed.accuracy": 100.0,
17-
"destination.ip": "52.18.196.169",
18-
"malware.name": "salityp2p",
19-
"event_description.text": "Sinkhole attempted connection",
20-
"time.source": "2016-04-19T23:16:08+00:00",
21-
"source.ip": "152.166.119.2",
22-
"feed.url": "http://alerts.bitsighttech.com:8080/stream?",
23-
"source.geolocation.country": "Dominican Republic",
24-
"time.observation": "2016-04-19T23:16:08+00:00",
25-
"source.port": 65118,
26-
"__type": "Event",
27-
"feed.name": "BitSight",
28-
"extra.non_ascii": "ççãããã\x80\ua000 \164 \x80\x80 abcd \165\166",
29-
"raw": "eyJ0cm9qYW5mYW1pbHkiOiJTYWxpdHlwMnAiLCJlbnYiOnsic"
30-
"mVtb3RlX2FkZHIiOiIxNTIuMTY2LjExOS4yIiwicmVtb3RlX3"
31-
"BvcnQiOiI2NTExOCIsInNlcnZlcl9hZGRyIjoiNTIuMTguMTk"
32-
"2LjE2OSIsInNlcnZlcl9wb3J0IjoiOTc5NiJ9LCJfdHMiOjE0"
33-
"NjExMDc3NjgsIl9nZW9fZW52X3JlbW90ZV9hZGRyIjp7ImNvd"
34-
"W50cnlfbmFtZSI6IkRvbWluaWNhbiBSZXB1YmxpYyJ9fQ==",
35-
"__type": "Event",
36-
}
14+
EXAMPLE_EVENT = {
15+
"classification.type": "infected-system",
16+
"destination.port": 9796,
17+
"feed.accuracy": 100.0,
18+
"destination.ip": "52.18.196.169",
19+
"malware.name": "salityp2p",
20+
"event_description.text": "Sinkhole attempted connection",
21+
"time.source": "2016-04-19T23:16:08+00:00",
22+
"source.ip": "152.166.119.2",
23+
"feed.url": "http://alerts.bitsighttech.com:8080/stream?",
24+
"source.geolocation.country": "Dominican Republic",
25+
"time.observation": "2016-04-19T23:16:08+00:00",
26+
"source.port": 65118,
27+
"__type": "Event",
28+
"feed.name": "BitSight",
29+
"extra.non_ascii": "ççãããã\x80\ua000 \164 \x80\x80 abcd \165\166",
30+
"raw": "eyJ0cm9qYW5mYW1pbHkiOiJTYWxpdHlwMnAiLCJlbnYiOnsic"
31+
"mVtb3RlX2FkZHIiOiIxNTIuMTY2LjExOS4yIiwicmVtb3RlX3"
32+
"BvcnQiOiI2NTExOCIsInNlcnZlcl9hZGRyIjoiNTIuMTguMTk"
33+
"2LjE2OSIsInNlcnZlcl9wb3J0IjoiOTc5NiJ9LCJfdHMiOjE0"
34+
"NjExMDc3NjgsIl9nZW9fZW52X3JlbW90ZV9hZGRyIjp7ImNvd"
35+
"W50cnlfbmFtZSI6IkRvbWluaWNhbiBSZXB1YmxpYyJ9fQ==",
36+
"__type": "Event",
37+
}
3738

3839

3940
@test.skip_exotic()
@@ -43,11 +44,16 @@ def set_bot(cls):
4344
cls.use_cache = True
4445
cls.bot_reference = MISPFeedOutputBot
4546
cls.default_input_message = EXAMPLE_EVENT
46-
cls.directory = TemporaryDirectory()
47-
cls.sysconfig = {"misp_org_name": 'IntelMQTestOrg',
48-
"misp_org_uuid": "b89da4c2-0f74-11ea-96a1-6fa873a0eb4d",
49-
"output_dir": cls.directory.name,
50-
"interval_event": '1 hour'}
47+
cls.sysconfig = {
48+
"misp_org_name": "IntelMQTestOrg",
49+
"misp_org_uuid": "b89da4c2-0f74-11ea-96a1-6fa873a0eb4d",
50+
"interval_event": "1 hour",
51+
}
52+
53+
def setUp(self) -> None:
54+
super().setUp()
55+
self.directory = TemporaryDirectory()
56+
self.sysconfig["output_dir"] = self.directory.name
5157

5258
def test_event(self):
5359
self.run_bot()
@@ -59,7 +65,7 @@ def test_event(self):
5965

6066
def test_accumulating_events(self):
6167
self.input_message = [EXAMPLE_EVENT, EXAMPLE_EVENT]
62-
self.run_bot(iterations=2, parameters={"delay_save_event_count": 3})
68+
self.run_bot(iterations=2, parameters={"bulk_save_count": 3})
6369

6470
current_event = open(f"{self.directory.name}/.current").read()
6571

@@ -69,15 +75,15 @@ def test_accumulating_events(self):
6975
assert len(objects) == 0
7076

7177
self.input_message = [EXAMPLE_EVENT]
72-
self.run_bot(parameters={"delay_save_event_count": 3})
78+
self.run_bot(parameters={"bulk_save_count": 3})
7379

7480
# When enough events were collected, save them
7581
with open(current_event) as f:
7682
objects = json.load(f)["Event"]["Object"]
7783
assert len(objects) == 3
7884

7985
self.input_message = [EXAMPLE_EVENT, EXAMPLE_EVENT, EXAMPLE_EVENT]
80-
self.run_bot(iterations=3, parameters={"delay_save_event_count": 3})
86+
self.run_bot(iterations=3, parameters={"bulk_save_count": 3})
8187

8288
# We continue saving to the same file until interval timeout
8389
with open(current_event) as f:
@@ -87,22 +93,18 @@ def test_accumulating_events(self):
8793
# Simulating leftovers in the queue when it's time to generate new event
8894
Path(f"{self.directory.name}/.current").unlink()
8995
self.bot.cache_put(EXAMPLE_EVENT)
90-
self.run_bot(parameters={"delay_save_event_count": 3})
96+
self.run_bot(parameters={"bulk_save_count": 3})
9197

9298
new_event = open(f"{self.directory.name}/.current").read()
9399
with open(new_event) as f:
94100
objects = json.load(f)["Event"]["Object"]
95101
assert len(objects) == 1
96102

97-
98103
def tearDown(self):
99104
self.cache.delete(self.bot_id)
105+
self.directory.cleanup()
100106
super().tearDown()
101107

102-
@classmethod
103-
def tearDownClass(cls):
104-
cls.directory.cleanup()
105-
106108

107109
if __name__ == "__main__": # pragma: no cover
108110
unittest.main()

0 commit comments

Comments
 (0)