intelmq-extensions 1.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. intelmq_extensions/__init__.py +0 -0
  2. intelmq_extensions/bots/__init__.py +0 -0
  3. intelmq_extensions/bots/collectors/blackkite/__init__.py +0 -0
  4. intelmq_extensions/bots/collectors/blackkite/_client.py +167 -0
  5. intelmq_extensions/bots/collectors/blackkite/collector.py +182 -0
  6. intelmq_extensions/bots/collectors/disp/__init__.py +0 -0
  7. intelmq_extensions/bots/collectors/disp/_client.py +121 -0
  8. intelmq_extensions/bots/collectors/disp/collector.py +104 -0
  9. intelmq_extensions/bots/collectors/xmpp/__init__.py +0 -0
  10. intelmq_extensions/bots/collectors/xmpp/collector.py +210 -0
  11. intelmq_extensions/bots/experts/__init__.py +0 -0
  12. intelmq_extensions/bots/experts/certat_contact_intern/__init__.py +0 -0
  13. intelmq_extensions/bots/experts/certat_contact_intern/expert.py +139 -0
  14. intelmq_extensions/bots/experts/copy_extra/__init__.py +0 -0
  15. intelmq_extensions/bots/experts/copy_extra/expert.py +27 -0
  16. intelmq_extensions/bots/experts/event_group_splitter/__init__.py +0 -0
  17. intelmq_extensions/bots/experts/event_group_splitter/expert.py +117 -0
  18. intelmq_extensions/bots/experts/event_splitter/__init__.py +0 -0
  19. intelmq_extensions/bots/experts/event_splitter/expert.py +41 -0
  20. intelmq_extensions/bots/experts/squelcher/__init__.py +0 -0
  21. intelmq_extensions/bots/experts/squelcher/expert.py +316 -0
  22. intelmq_extensions/bots/experts/vulnerability_lookup/__init__.py +0 -0
  23. intelmq_extensions/bots/experts/vulnerability_lookup/expert.py +136 -0
  24. intelmq_extensions/bots/outputs/__init__.py +0 -0
  25. intelmq_extensions/bots/outputs/mattermost/__init__.py +0 -0
  26. intelmq_extensions/bots/outputs/mattermost/output.py +113 -0
  27. intelmq_extensions/bots/outputs/to_logs/__init__.py +0 -0
  28. intelmq_extensions/bots/outputs/to_logs/output.py +12 -0
  29. intelmq_extensions/bots/outputs/xmpp/__init__.py +0 -0
  30. intelmq_extensions/bots/outputs/xmpp/output.py +180 -0
  31. intelmq_extensions/bots/parsers/__init__.py +0 -0
  32. intelmq_extensions/bots/parsers/blackkite/__init__.py +0 -0
  33. intelmq_extensions/bots/parsers/blackkite/_transformers.py +202 -0
  34. intelmq_extensions/bots/parsers/blackkite/parser.py +65 -0
  35. intelmq_extensions/bots/parsers/disp/__init__.py +0 -0
  36. intelmq_extensions/bots/parsers/disp/parser.py +125 -0
  37. intelmq_extensions/bots/parsers/malwaredomains/__init__.py +0 -0
  38. intelmq_extensions/bots/parsers/malwaredomains/parser.py +63 -0
  39. intelmq_extensions/cli/__init__.py +0 -0
  40. intelmq_extensions/cli/create_reports.py +161 -0
  41. intelmq_extensions/cli/intelmqcli.py +657 -0
  42. intelmq_extensions/cli/lib.py +670 -0
  43. intelmq_extensions/cli/utils.py +12 -0
  44. intelmq_extensions/etc/harmonization.conf +434 -0
  45. intelmq_extensions/etc/squelcher.conf +52 -0
  46. intelmq_extensions/lib/__init__.py +0 -0
  47. intelmq_extensions/lib/api_helpers.py +105 -0
  48. intelmq_extensions/lib/blackkite.py +29 -0
  49. intelmq_extensions/tests/__init__.py +0 -0
  50. intelmq_extensions/tests/base.py +336 -0
  51. intelmq_extensions/tests/bots/__init__.py +0 -0
  52. intelmq_extensions/tests/bots/collectors/__init__.py +0 -0
  53. intelmq_extensions/tests/bots/collectors/blackkite/__init__.py +0 -0
  54. intelmq_extensions/tests/bots/collectors/blackkite/base.py +45 -0
  55. intelmq_extensions/tests/bots/collectors/blackkite/test_client.py +154 -0
  56. intelmq_extensions/tests/bots/collectors/blackkite/test_collector.py +287 -0
  57. intelmq_extensions/tests/bots/collectors/disp/__init__.py +0 -0
  58. intelmq_extensions/tests/bots/collectors/disp/base.py +147 -0
  59. intelmq_extensions/tests/bots/collectors/disp/test_client.py +134 -0
  60. intelmq_extensions/tests/bots/collectors/disp/test_collector.py +137 -0
  61. intelmq_extensions/tests/bots/collectors/xmpp/__init__.py +0 -0
  62. intelmq_extensions/tests/bots/collectors/xmpp/test_collector.py +10 -0
  63. intelmq_extensions/tests/bots/experts/__init__.py +0 -0
  64. intelmq_extensions/tests/bots/experts/certat_contact_intern/__init__.py +0 -0
  65. intelmq_extensions/tests/bots/experts/certat_contact_intern/test_expert.py +176 -0
  66. intelmq_extensions/tests/bots/experts/copy_extra/__init__.py +0 -0
  67. intelmq_extensions/tests/bots/experts/copy_extra/test_expert.py +42 -0
  68. intelmq_extensions/tests/bots/experts/event_group_splitter/__init__.py +0 -0
  69. intelmq_extensions/tests/bots/experts/event_group_splitter/test_expert.py +302 -0
  70. intelmq_extensions/tests/bots/experts/event_splitter/__init__.py +0 -0
  71. intelmq_extensions/tests/bots/experts/event_splitter/test_expert.py +101 -0
  72. intelmq_extensions/tests/bots/experts/squelcher/__init__.py +0 -0
  73. intelmq_extensions/tests/bots/experts/squelcher/test_expert.py +548 -0
  74. intelmq_extensions/tests/bots/experts/vulnerability_lookup/__init__.py +0 -0
  75. intelmq_extensions/tests/bots/experts/vulnerability_lookup/test_expert.py +203 -0
  76. intelmq_extensions/tests/bots/outputs/__init__.py +0 -0
  77. intelmq_extensions/tests/bots/outputs/mattermost/__init__.py +0 -0
  78. intelmq_extensions/tests/bots/outputs/mattermost/test_output.py +138 -0
  79. intelmq_extensions/tests/bots/outputs/xmpp/__init__.py +0 -0
  80. intelmq_extensions/tests/bots/outputs/xmpp/test_output.py +10 -0
  81. intelmq_extensions/tests/bots/parsers/__init__.py +0 -0
  82. intelmq_extensions/tests/bots/parsers/blackkite/__init__.py +0 -0
  83. intelmq_extensions/tests/bots/parsers/blackkite/data.py +69 -0
  84. intelmq_extensions/tests/bots/parsers/blackkite/test_parser.py +197 -0
  85. intelmq_extensions/tests/bots/parsers/disp/__init__.py +0 -0
  86. intelmq_extensions/tests/bots/parsers/disp/test_parser.py +282 -0
  87. intelmq_extensions/tests/bots/parsers/malwaredomains/__init__.py +0 -0
  88. intelmq_extensions/tests/bots/parsers/malwaredomains/test_parser.py +62 -0
  89. intelmq_extensions/tests/cli/__init__.py +0 -0
  90. intelmq_extensions/tests/cli/test_create_reports.py +97 -0
  91. intelmq_extensions/tests/cli/test_intelmqcli.py +158 -0
  92. intelmq_extensions/tests/lib/__init__.py +0 -0
  93. intelmq_extensions/tests/lib/base.py +81 -0
  94. intelmq_extensions/tests/lib/test_api_helpers.py +126 -0
  95. intelmq_extensions-1.8.1.dist-info/METADATA +60 -0
  96. intelmq_extensions-1.8.1.dist-info/RECORD +100 -0
  97. intelmq_extensions-1.8.1.dist-info/WHEEL +5 -0
  98. intelmq_extensions-1.8.1.dist-info/entry_points.txt +33 -0
  99. intelmq_extensions-1.8.1.dist-info/licenses/LICENSE +661 -0
  100. intelmq_extensions-1.8.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,210 @@
1
+ """
2
+ XMPP Collector Bot
3
+ Connects to a XMPP Server and a Room and reads data from the room.
4
+ If no room is provided, which is equivalent to an empty string,
5
+ it only collects events which were sent to the xmpp user directly.
6
+
7
+ TLS is used by default.
8
+
9
+ Tested with Python >= 3.4
10
+ Tested with slixmpp >= 1.0.0-beta5
11
+
12
+ Copyright (C) 2016 by Bundesamt für Sicherheit in der Informationstechnik
13
+ Software engineering by Intevation GmbH
14
+
15
+ Parameters:
16
+ ca_certs: string to a CA-bundle file or false/empty string for no checks
17
+ strip_message: boolean
18
+ xmpp_user: string
19
+ xmpp_server: string
20
+ xmpp_password: boolean
21
+ xmpp_room: string
22
+ xmpp_room_password: string
23
+ xmpp_room_nick: string
24
+ pass_full_xml: boolean
25
+ strip_message: boolean
26
+ xmpp_userlist: array
27
+ xmpp_whitelist_mode: boolean
28
+ """
29
+
30
+ from intelmq.lib.bot import CollectorBot
31
+ from intelmq.lib.exceptions import MissingDependencyError
32
+
33
+ try:
34
+ import slixmpp
35
+
36
+ class XMPPClient(slixmpp.ClientXMPP):
37
+ def __init__(self, jid, password, room, room_nick, room_password, logger):
38
+ slixmpp.ClientXMPP.__init__(self, jid, password)
39
+
40
+ self.logger = logger
41
+ self.logger.info("Initiated.")
42
+ self.xmpp_room = room
43
+ self.xmpp_room_nick = room_nick
44
+ self.xmpp_room_password = room_password
45
+
46
+ self.add_event_handler("session_start", self.session_start)
47
+
48
+ def session_start(self, event):
49
+ self.send_presence()
50
+ self.logger.debug("Session started.")
51
+
52
+ try:
53
+ self.get_roster()
54
+ except slixmpp.exceptions.IqError as err:
55
+ self.logger.error("There was an error getting the roster.")
56
+ self.logger.error(err.iq["error"]["condition"])
57
+ self.disconnect()
58
+ except slixmpp.exceptions.IqTimeout:
59
+ self.logger.error("Server is taking too long to respond.")
60
+ self.disconnect()
61
+
62
+ if (
63
+ self.xmpp_room
64
+ ): # and self.plugin.get('xep_0045') # this check should also exist!
65
+ self.logger.debug("Joining room: %s.", self.xmpp_room)
66
+ pwd = self.xmpp_room_password if self.xmpp_room_password else ""
67
+ self.plugin["xep_0045"].joinMUC(
68
+ self.xmpp_room, self.xmpp_room_nick, password=pwd, wait=True
69
+ )
70
+
71
+ except ImportError:
72
+ slixmpp = None
73
+
74
+
75
+ class XMPPCollectorBot(CollectorBot):
76
+ name = "XMPP"
77
+ xmpp = None
78
+ collector_empty_process = True
79
+
80
+ def init(self):
81
+ self.logger.warning(
82
+ "The output bot 'intelmq.bots.collectors.xmpp.output' "
83
+ "is deprecated. It will be removed in version 3.0."
84
+ "Please see https://github.com/certtools/intelmq/blob/"
85
+ "develop/NEWS.md#xmpp-bots for more details."
86
+ )
87
+ if slixmpp is None:
88
+ raise MissingDependencyError("slixmpp")
89
+
90
+ # Retrieve Parameters from configuration
91
+ xmpp_user = getattr(self.parameters, "xmpp_user", None)
92
+ xmpp_server = getattr(self.parameters, "xmpp_server", None)
93
+ xmpp_password = getattr(self.parameters, "xmpp_password", None)
94
+
95
+ if None in (xmpp_user, xmpp_server, xmpp_password):
96
+ raise ValueError("No User / Password provided.")
97
+ else:
98
+ xmpp_login = xmpp_user + "@" + xmpp_server
99
+
100
+ self.userlist = getattr(self.parameters, "xmpp_userlist", [])
101
+ # When configured in manager this is most likely a ,-separated string, we'd like an array
102
+ if type(self.userlist) is str:
103
+ self.userlist = [u.strip() for u in self.userlist.split(",")]
104
+ elif self.userlist is None: # if value is unset, set to empty list
105
+ self.userlist = []
106
+
107
+ self.whitelist_mode = getattr(self.parameters, "xmpp_whitelist_mode", False)
108
+
109
+ self.muc = getattr(self.parameters, "use_muc", None)
110
+ xmpp_room = getattr(self.parameters, "xmpp_room", None) if self.muc else None
111
+ xmpp_room_nick = (
112
+ getattr(self.parameters, "xmpp_room_nick", None) if self.muc else None
113
+ )
114
+ xmpp_room_password = (
115
+ getattr(self.parameters, "xmpp_room_password", None) if self.muc else None
116
+ )
117
+
118
+ self.pass_full_xml = getattr(self.parameters, "pass_full_xml", None)
119
+ self.strip_message = getattr(self.parameters, "strip_message", None)
120
+
121
+ ca_certs = getattr(self.parameters, "ca_certs", None)
122
+
123
+ if self.muc and not xmpp_room:
124
+ raise ValueError("No room provided.")
125
+
126
+ if self.muc:
127
+ if not xmpp_room_nick:
128
+ # create the room_nick from user and server
129
+ xmpp_room_nick = xmpp_login
130
+
131
+ self.xmpp = XMPPClient(
132
+ xmpp_login,
133
+ xmpp_password,
134
+ xmpp_room,
135
+ xmpp_room_nick,
136
+ xmpp_room_password,
137
+ self.logger,
138
+ )
139
+
140
+ if ca_certs:
141
+ # Set CA-Certificates
142
+ self.xmpp.ca_certs = ca_certs
143
+
144
+ if self.xmpp.connect(reattempt=False):
145
+ self.xmpp.process()
146
+ # Add Handlers and register Plugins
147
+ self.xmpp.register_plugin("xep_0030") # Service Discovery
148
+ self.xmpp.register_plugin("xep_0045") # Multi-User Chat
149
+
150
+ self.xmpp.add_event_handler("message", self.log_message)
151
+
152
+ else:
153
+ raise ValueError("Could not connect to XMPP-Server.")
154
+
155
+ def process(self):
156
+ # Processing is done by function called from the eventhandler...
157
+ pass
158
+
159
+ def shutdown(self):
160
+ if self.xmpp:
161
+ if self.xmpp.disconnect():
162
+ self.logger.info("Disconnected from XMPP Server.")
163
+ else:
164
+ self.logger.error("Could not disconnect from XMPP Server.")
165
+ else:
166
+ self.logger.info("There was no XMPPClient I could stop.")
167
+
168
+ def log_message(self, msg):
169
+ # If some exception happens here, the bot would silently fail.
170
+ # We want to know the reason, so we will log the exception manually.
171
+ try:
172
+ # Check if the message was sent by a users that is on
173
+ # the white or blacklist, determine if the message shall
174
+ # be processed.
175
+ if self.muc:
176
+ if msg["mucnick"] not in self.userlist and self.whitelist_mode:
177
+ # Whitelist-Case
178
+ return
179
+ elif msg["mucnick"] in self.userlist and not self.whitelist_mode:
180
+ # Blacklist Case
181
+ return
182
+
183
+ if self.pass_full_xml:
184
+ body = str(msg)
185
+ else:
186
+ if self.strip_message:
187
+ body = msg["body"].strip()
188
+ else:
189
+ body = msg["body"]
190
+
191
+ if len(body) > 400:
192
+ tmp_body = body[:397] + "..."
193
+ else:
194
+ tmp_body = body
195
+
196
+ self.logger.debug("Received Stanza: %r from %r.", tmp_body, msg["from"])
197
+
198
+ raw_msg = body
199
+ # Read msg-body and add as raw to a new report.
200
+ # now it's up to a parser to do the interpretation of the message.
201
+ if raw_msg:
202
+ report = self.new_report()
203
+ report.add("raw", raw_msg)
204
+ self.send_message(report)
205
+ except Exception:
206
+ self.logger.exception("Error during message handling.")
207
+ raise
208
+
209
+
210
+ BOT = XMPPCollectorBot
File without changes
@@ -0,0 +1,139 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ CERT.at geolocate the national CERT abuse service
4
+ """
5
+ from intelmq.lib.bot import Bot
6
+
7
+ try:
8
+ import psycopg2
9
+ except ImportError:
10
+ psycopg2 = None
11
+
12
+
13
+ class CERTatContactExpertBot(Bot):
14
+ connect_timeout = 5
15
+ database: str = ""
16
+ user: str = ""
17
+ password: str = ""
18
+ host: str = ""
19
+ port: str = ""
20
+ sslmode: str = ""
21
+ autocommit: bool = True
22
+ table: str = "contacts"
23
+ column: str = ""
24
+ feed_code: str = ""
25
+ ascolumn: str = ""
26
+ overwrite: bool = False
27
+
28
+ def init(self):
29
+ self.logger.debug("Connecting to database.")
30
+ if psycopg2 is None:
31
+ self.logger.error("Could not import psycopg2. Please install it.")
32
+ self.stop()
33
+
34
+ try:
35
+ self.con = psycopg2.connect(
36
+ database=self.database,
37
+ user=self.user,
38
+ password=self.password,
39
+ host=self.host,
40
+ port=self.port,
41
+ sslmode=self.sslmode,
42
+ connect_timeout=self.connect_timeout,
43
+ )
44
+ self.cur = self.con.cursor()
45
+ self.con.autocommit = self.autocommit
46
+
47
+ except Exception:
48
+ self.logger.exception("Failed to connect to database.")
49
+ self.stop()
50
+ self.logger.info("Connected to PostgreSQL.")
51
+
52
+ self.query = (
53
+ 'SELECT "{column}", "can-see-tlp-amber_{feed_code}"'
54
+ ' FROM "{table}" WHERE "{ascolumn}" = %s'
55
+ "".format(
56
+ table=self.table,
57
+ column=self.column,
58
+ feed_code=self.feed_code,
59
+ ascolumn=self.ascolumn,
60
+ )
61
+ )
62
+
63
+ def process(self):
64
+ event = self.receive_message()
65
+ default_destination_visible = (
66
+ True if event.get("feed.code") != self.feed_code else False
67
+ )
68
+
69
+ if "source.asn" not in event:
70
+ self.logger.info("source.asn not present in event. Skipping event.")
71
+ event.add(
72
+ "destination_visible",
73
+ default_destination_visible,
74
+ overwrite=self.overwrite,
75
+ )
76
+ self.send_message(event)
77
+ self.acknowledge_message()
78
+ return
79
+
80
+ if "source.abuse_contact" in event and not self.overwrite:
81
+ event.add(
82
+ "destination_visible",
83
+ default_destination_visible,
84
+ overwrite=self.overwrite,
85
+ )
86
+ self.send_message(event)
87
+ self.acknowledge_message()
88
+ return
89
+
90
+ try:
91
+ self.logger.debug(
92
+ "Executing %r." % self.cur.mogrify(self.query, (event["source.asn"],))
93
+ )
94
+ self.cur.execute(self.query, (event["source.asn"],))
95
+ except (
96
+ psycopg2.InterfaceError,
97
+ psycopg2.InternalError,
98
+ psycopg2.OperationalError,
99
+ AttributeError,
100
+ ):
101
+ self.logger.exception("Database connection problem, connecting again.")
102
+ self.init()
103
+ else:
104
+ if self.cur.rowcount > 1:
105
+ raise ValueError(
106
+ "Lookup returned more than one result. Please inspect."
107
+ )
108
+ elif self.cur.rowcount == 1:
109
+ result = self.cur.fetchone()
110
+ self.logger.debug(
111
+ "Changing `source.abuse_contact` from %r to %r."
112
+ % (event.get("source.abuse_contact"), result[0])
113
+ )
114
+
115
+ event.add("source.abuse_contact", result[0], overwrite=self.overwrite)
116
+
117
+ if event["feed.code"] == self.feed_code:
118
+ if result[1]:
119
+ event.add("destination_visible", True, overwrite=self.overwrite)
120
+ else:
121
+ event.add(
122
+ "destination_visible", False, overwrite=self.overwrite
123
+ )
124
+ else:
125
+ event.add("destination_visible", True, overwrite=self.overwrite)
126
+
127
+ else:
128
+ self.logger.debug("No contact found.")
129
+ event.add(
130
+ "destination_visible",
131
+ default_destination_visible,
132
+ overwrite=self.overwrite,
133
+ )
134
+
135
+ self.send_message(event)
136
+ self.acknowledge_message()
137
+
138
+
139
+ BOT = CERTatContactExpertBot
File without changes
@@ -0,0 +1,27 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Modify Expert bot let's you manipulate all fields with a config file.
4
+ """
5
+ import json
6
+
7
+ from intelmq.lib.bot import Bot
8
+
9
+
10
+ class CopyExtraExpertBot(Bot):
11
+ keys: list = []
12
+
13
+ def process(self):
14
+ event = self.receive_message()
15
+
16
+ if "extra" in event:
17
+ extra = json.loads(event["extra"])
18
+
19
+ share = {key: extra[key] for key in self.keys if key in extra}
20
+ if share:
21
+ event.add("shareable_extra_info", share, overwrite=False)
22
+
23
+ self.send_message(event)
24
+ self.acknowledge_message()
25
+
26
+
27
+ BOT = CopyExtraExpertBot
@@ -0,0 +1,117 @@
1
+ """Event Group Splitter can split events based on tag groups,
2
+ and automatically recognize new groups.
3
+
4
+ The bot is designed with an intention to handle ShadowServer Compromise Website report.
5
+
6
+ SPDX-FileCopyrightText: 2024 CERT.at GmbH <https://cert.at/>
7
+ SPDX-License-Identifier: AGPL-3.0-or-later
8
+ """
9
+
10
+ import itertools
11
+ import json
12
+ import re
13
+
14
+ from intelmq.lib.bot import ExpertBot
15
+
16
+
17
+ class EventGroupSplitterExpertBot(ExpertBot):
18
+ look_in: str = "extra.tag"
19
+ copy_to: list[str] = ["classification.identifier", "extra.vulnerabilities"]
20
+ regex: str = "" # has to match one group meaning matched duplication value
21
+ ignore: list[str] = [] # tags to ignore
22
+ # path to a JSON file holding groups; the file will be updated with new groups
23
+ # the structure is a dict with keys as feed codes, and values as list of list of tags
24
+ # e.g.:
25
+ # {
26
+ # "feed-1": [["t1", "t2"], ["t1", "t3"]],
27
+ # "feed-2": [["t6"]]
28
+ # }
29
+ groups_file: str = None
30
+ # whether groups should be analysed separately for every feed or not;
31
+ # if not, the bot will not use "new group" path for events matching
32
+ # group already seen in any other feed
33
+ treat_feeds_separately: bool = True
34
+
35
+ def init(self):
36
+ self.matcher = re.compile(self.regex)
37
+
38
+ self._tag_groups: dict[str, list[list[str]]] = []
39
+ self._reload_groups()
40
+
41
+ def _reload_groups(self, dump_current=False):
42
+ if dump_current:
43
+ with open(self.groups_file, "w+") as f:
44
+ json.dump(
45
+ {
46
+ feed: [list(g) for g in groups]
47
+ for feed, groups in self._tag_groups.items()
48
+ },
49
+ f,
50
+ indent=4,
51
+ )
52
+
53
+ with open(self.groups_file) as f:
54
+ new_groups = json.load(f)
55
+
56
+ self._tag_groups = {
57
+ feed: sorted(
58
+ (set(group) for group in groups), key=lambda g: len(g), reverse=True
59
+ )
60
+ for feed, groups in new_groups.items()
61
+ }
62
+
63
+ def process(self):
64
+ event = self.receive_message()
65
+
66
+ lookup_data = event.get(self.look_in, "")
67
+ matches = self.matcher.findall(lookup_data)
68
+ if not matches:
69
+ self.send_message(event)
70
+ else:
71
+ matches = [m for m in matches if m not in self.ignore]
72
+ matches_set = set(matches)
73
+ feed = event.get("feed.code", "")
74
+ if self.treat_feeds_separately:
75
+ feed_groups = self._tag_groups.get(feed, [])
76
+ else:
77
+ feed_groups = itertools.chain(*self._tag_groups.values())
78
+
79
+ for group in feed_groups:
80
+ if not matches_set >= group:
81
+ # The group is not in event's tags
82
+ continue
83
+ self._generate_event(event, group)
84
+
85
+ # Tags can be duplicated in event. Respect it.
86
+ for tag in group:
87
+ matches.remove(tag)
88
+ matches_set = set(matches)
89
+
90
+ if not matches:
91
+ break
92
+
93
+ # Something is left - new group
94
+ if matches:
95
+ new_group = set(matches)
96
+ self.logger.info("New tag group was discovered: %s.", new_group)
97
+ self._generate_event(event, new_group, new_group=True)
98
+
99
+ if feed not in self._tag_groups:
100
+ self._tag_groups[feed] = list()
101
+ self._tag_groups[feed].append(new_group)
102
+ self._reload_groups(dump_current=True)
103
+
104
+ self.acknowledge_message()
105
+
106
+ def _generate_event(self, event, group: set, new_group: bool = False):
107
+ grouped_value = "-".join(sorted(group))
108
+ self.logger.debug("Found: %s.", group)
109
+ sub_event = self.new_event(event)
110
+ for key in self.copy_to:
111
+ sub_event.add(key, grouped_value, overwrite=True)
112
+ self.send_message(sub_event)
113
+ if new_group:
114
+ self.send_message(sub_event, path="new_tag_groups", path_permissive=True)
115
+
116
+
117
+ BOT = EventGroupSplitterExpertBot
@@ -0,0 +1,41 @@
1
+ """Event Splitter can produce multiple events from a one, based
2
+ on event's tag.
3
+
4
+ Currently implemented with intention of splitting CVE events.
5
+
6
+ SPDX-FileCopyrightText: 2023 CERT.at GmbH <https://cert.at/>
7
+ SPDX-License-Identifier: AGPL-3.0-or-later
8
+ """
9
+
10
+ import re
11
+
12
+ from intelmq.lib.bot import ExpertBot
13
+
14
+
15
+ class EventSplitterExpertBot(ExpertBot):
16
+ look_in: str = "extra.tag"
17
+ copy_to: list[str] = ["classification.identifier", "extra.vulnerabilities"]
18
+ regex: str = "" # has to match one group meaning matched duplication value
19
+
20
+ def init(self):
21
+ self.matcher = re.compile(self.regex)
22
+
23
+ def process(self):
24
+ event = self.receive_message()
25
+
26
+ lookup_data = event.get(self.look_in, "")
27
+ matches = self.matcher.findall(lookup_data)
28
+ if not matches:
29
+ self.send_message(event)
30
+ else:
31
+ for matched in matches:
32
+ self.logger.debug("Found: %s.", matched)
33
+ sub_event = self.new_event(event)
34
+ for key in self.copy_to:
35
+ sub_event.add(key, matched, overwrite=True)
36
+ self.send_message(sub_event)
37
+
38
+ self.acknowledge_message()
39
+
40
+
41
+ BOT = EventSplitterExpertBot
File without changes