intelmq-extensions 1.8.1__py3-none-any.whl → 1.10.0__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.
File without changes
@@ -0,0 +1,87 @@
1
+ """Collector of data from Modat API
2
+
3
+ SPDX-FileCopyrightText: 2025 CERT.at GmbH <https://cert.at/>
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+
6
+ https://api.magnify.modat.io/docs
7
+
8
+ Parameters:
9
+
10
+ api_key
11
+ # TODO: multiple queries in one bot?
12
+ query
13
+ type [service|host]
14
+
15
+ # with defaults
16
+ url = "https://api.magnify.modat.io/"
17
+ page_size = 10
18
+ max_results = 100
19
+
20
+ # Standard Collector parameters
21
+ name: Optional[str] = None
22
+ accuracy: int = 100
23
+ code: Optional[str] = None
24
+ provider: Optional[str] = None
25
+ documentation: Optional[str] = None
26
+ """
27
+
28
+ import json
29
+ from urllib.parse import urljoin
30
+
31
+ from intelmq.lib.bot import CollectorBot
32
+ from intelmq.lib.utils import create_request_session
33
+
34
+
35
+ class ModatCollectorBot(CollectorBot):
36
+ url: str = "https://api.magnify.modat.io/"
37
+ page_size = 10
38
+ max_results = 100
39
+
40
+ api_key: str
41
+ query: str
42
+ type: str # service|host
43
+
44
+ def init(self):
45
+ self.set_request_parameters()
46
+ self.session = create_request_session(self)
47
+ self.session.headers = {"Authorization": f"Bearer {self.api_key}"}
48
+
49
+ def process(self):
50
+ page = 1
51
+ collected_results = 0
52
+ total_available = self.page_size
53
+
54
+ if self.type == "host":
55
+ url = urljoin(self.url, "/host/search/v1")
56
+ else:
57
+ url = urljoin(self.url, "/service/search/v1")
58
+
59
+ while (
60
+ total_available > collected_results and collected_results < self.max_results
61
+ ):
62
+ result = self.session.post(
63
+ url,
64
+ json={"page": page, "page_size": self.page_size, "query": self.query},
65
+ )
66
+ if result.status_code != 200:
67
+ self.logger.error("Modat responded with error %d.", result.status_code)
68
+ self.logger.debug("Modat response: %s.", result.text)
69
+ raise RuntimeError(
70
+ "Cannot retrieve data from Modat.", detail=result.text
71
+ )
72
+
73
+ data = result.json()
74
+ report = self.new_report()
75
+ report.add("raw", json.dumps(data["page"]))
76
+ self.send_message(report)
77
+
78
+ page += 1
79
+ collected_results += len(data["page"])
80
+ total_available = data["total_records"]
81
+
82
+ # @staticmethod
83
+ # def check(parameters: dict) -> list[list[str]] or None:
84
+ # pass
85
+
86
+
87
+ BOT = ModatCollectorBot
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ ReplaceInDict allow replacing pattern in any text field in a dict field(s)
4
+ """
5
+
6
+ from intelmq.lib.bot import ExpertBot
7
+ from intelmq.lib.exceptions import ConfigurationError, KeyNotExists
8
+
9
+
10
+ class ReplaceInDictExpertBot(ExpertBot):
11
+ old_value: str = None
12
+ new_value: str = None
13
+ fields: str = None # actually str | list on newer Python
14
+
15
+ def init(self):
16
+ if isinstance(self.fields, str):
17
+ self.fields = self.fields.split(",")
18
+ for field in self.fields:
19
+ definition = self.harmonization["event"][field]
20
+ if definition["type"] != "JSONDict":
21
+ raise ConfigurationError("Field is not a JSONDict", field)
22
+
23
+ def process(self):
24
+ event = self.receive_message()
25
+
26
+ for field in self.fields:
27
+ for name, value in event.finditems(f"{field}."):
28
+ if isinstance(value, str):
29
+ try:
30
+ event.change(
31
+ name, value.replace(self.old_value, self.new_value)
32
+ )
33
+ except KeyNotExists:
34
+ # Safeguard for an edge case if we would get default value
35
+ # of an non-existing field
36
+ pass
37
+
38
+ self.send_message(event)
39
+ self.acknowledge_message()
40
+
41
+
42
+ BOT = ReplaceInDictExpertBot
File without changes
@@ -0,0 +1,92 @@
1
+ """Parsing Modat search requests
2
+
3
+ Currently supporting only host search results
4
+
5
+ SPDX-FileCopyrightText: 2025 CERT.at GmbH <https://cert.at/>
6
+ SPDX-License-Identifier: AGPL-3.0-or-later
7
+ """
8
+
9
+ import json
10
+ from datetime import datetime, timezone
11
+
12
+ import intelmq.lib.message as message
13
+ from intelmq.lib import utils
14
+ from intelmq.lib.bot import ParserBot
15
+
16
+
17
+ class ModatParserBot(ParserBot):
18
+ def parse(self, report: message.Report):
19
+ raw_report = utils.base64_decode(report.get("raw"))
20
+ report_data = json.loads(raw_report)
21
+
22
+ for entry in report_data:
23
+ self._current_line = json.dumps(entry)
24
+ yield entry
25
+
26
+ def parse_line(self, line: dict, report: message.Report):
27
+ event = self.new_event(report)
28
+ event.add("raw", self._current_line)
29
+
30
+ event.add("source.ip", line.get("ip"), raise_failure=False)
31
+ event.add(
32
+ "source.geolocation.cc",
33
+ line.get("geo", {}).get("country_iso_code"),
34
+ raise_failure=False,
35
+ )
36
+ event.add(
37
+ "source.geolocation.country",
38
+ line.get("geo", {}).get("country_name"),
39
+ raise_failure=False,
40
+ )
41
+ event.add(
42
+ "source.geolocation.city",
43
+ line.get("geo", {}).get("city_name"),
44
+ raise_failure=False,
45
+ )
46
+
47
+ event.add("source.asn", line.get("asn", {}).get("number"), raise_failure=False)
48
+ event.add("source.as_name", line.get("asn", {}).get("org"), raise_failure=False)
49
+ fqdns = line.get("fqdns", [])
50
+ if fqdns:
51
+ event.add("source.fqdn", fqdns[0], raise_failure=False)
52
+ if len(fqdns) > 1:
53
+ event.add("extra.fqdns", ";".join(fqdns), raise_failure=False)
54
+ event.add("extra.tag", ";".join(line.get("tags", [])), raise_failure=False)
55
+
56
+ cves = ";".join(cve.get("id", "").lower() for cve in line.get("cves", []))
57
+ if cves:
58
+ event.add("product.vulnerabilities", cves, raise_failure=False)
59
+
60
+ services = line.get("services", [])
61
+ last_scanned = datetime.now(tz=timezone.utc)
62
+ if services:
63
+ last_scanned = max(s["scanned_at"] for s in services)
64
+ event.add("extra.services", services, raise_failure=False)
65
+
66
+ for service in services:
67
+ # This is what we were looking for
68
+ if service["is_match"]:
69
+ event.add(
70
+ "protocol.application",
71
+ service.get("protocol"),
72
+ ignore=(None, "unknown"),
73
+ raise_failure=False,
74
+ )
75
+ event.add(
76
+ "protocol.transport",
77
+ service.get("transport"),
78
+ raise_failure=False,
79
+ )
80
+ event.add("source.port", service.get("port"), raise_failure=False)
81
+
82
+ last_scanned = service.get("scanned_at") or last_scanned
83
+
84
+ # TODO: emit more events?
85
+ break
86
+
87
+ event.add("time.source", last_scanned, raise_failure=False)
88
+
89
+ return event
90
+
91
+
92
+ BOT = ModatParserBot
@@ -1,5 +1,33 @@
1
1
  {
2
2
  "event": {
3
+ "destination_visible": {
4
+ "description": "If destination fields are visible. CERT.at-specific",
5
+ "type": "Boolean"
6
+ },
7
+ "notify": {
8
+ "description": "If mail will be sent out to affected or responsible contact. CERT.at-specific",
9
+ "type": "Boolean"
10
+ },
11
+ "rtir_incident_id": {
12
+ "description": "Request Tracker Incident Response incident id. CERT.at-specific",
13
+ "type": "Integer"
14
+ },
15
+ "rtir_investigation_id": {
16
+ "description": "Request Tracker Incident Response investigation id. CERT.at-specific",
17
+ "type": "Integer"
18
+ },
19
+ "rtir_report_id": {
20
+ "description": "Request Tracker Incident Response incident report id. CERT.at-specific",
21
+ "type": "Integer"
22
+ },
23
+ "sent_at": {
24
+ "description": "Time when the report has been sent to the responsible recipient. CERT.at-specific",
25
+ "type": "DateTime"
26
+ },
27
+ "shareable_extra_info": {
28
+ "description": "Fields from extra which can be shared. CERT.at-specific",
29
+ "type": "JSON"
30
+ },
3
31
  "classification.identifier": {
4
32
  "description": "The lowercase identifier defines the actual software or service (e.g. ``heartbleed`` or ``ntp_version``) or standardized malware name (e.g. ``zeus``). Note that you MAY overwrite this field during processing for your individual setup. This field is not standardized across IntelMQ setups/users.",
5
33
  "type": "String"
@@ -7,7 +35,7 @@
7
35
  "classification.taxonomy": {
8
36
  "description": "We recognize the need for the CSIRT teams to apply a static (incident) taxonomy to abuse data. With this goal in mind the type IOC will serve as a basis for this activity. Each value of the dynamic type mapping translates to a an element in the static taxonomy. The European CSIRT teams for example have decided to apply the eCSIRT.net incident classification. The value of the taxonomy key is thus a derivative of the dynamic type above. For more information about check `ENISA taxonomies <http://www.enisa.europa.eu/activities/cert/support/incident-management/browsable/incident-handling-process/incident-taxonomy/existing-taxonomies>`_.",
9
37
  "length": 100,
10
- "type": "LowercaseString"
38
+ "type": "ClassificationTaxonomy"
11
39
  },
12
40
  "classification.type": {
13
41
  "description": "The abuse type IOC is one of the most crucial pieces of information for any given abuse event. The main idea of dynamic typing is to keep our ontology flexible, since we need to evolve with the evolving threatscape of abuse data. In contrast with the static taxonomy below, the dynamic typing is used to perform business decisions in the abuse handling pipeline. Furthermore, the value data set should be kept as minimal as possible to avoid *type explosion*, which in turn dilutes the business value of the dynamic typing. In general, we normally have two types of abuse type IOC: ones referring to a compromised resource or ones referring to pieces of the criminal infrastructure, such as a command and control servers for example.",
@@ -17,6 +45,10 @@
17
45
  "description": "Free text commentary about the abuse event inserted by an analyst.",
18
46
  "type": "String"
19
47
  },
48
+ "constituency": {
49
+ "description": "Internal identifier for multi-constituency setup",
50
+ "type": "String"
51
+ },
20
52
  "destination.abuse_contact": {
21
53
  "description": "Abuse contact for destination address. A comma separated list.",
22
54
  "type": "LowercaseString"
@@ -81,11 +113,11 @@
81
113
  "type": "IPAddress"
82
114
  },
83
115
  "destination.local_hostname": {
84
- "description": "Some sources report a internal hostname within a NAT related to the name configured for a compromized system",
116
+ "description": "Some sources report an internal hostname within a NAT related to the name configured for a compromised system",
85
117
  "type": "String"
86
118
  },
87
119
  "destination.local_ip": {
88
- "description": "Some sources report a internal (NATed) IP address related a compromized system. N.B. RFC1918 IPs are OK here.",
120
+ "description": "Some sources report an internal (NATed) IP address related a compromised system. N.B. RFC1918 IPs are OK here.",
89
121
  "type": "IPAddress"
90
122
  },
91
123
  "destination.network": {
@@ -118,10 +150,6 @@
118
150
  "description": "The path portion of an HTTP or related network request.",
119
151
  "type": "String"
120
152
  },
121
- "destination_visible": {
122
- "description": "If destination fields are visible.",
123
- "type": "Boolean"
124
- },
125
153
  "event_description.target": {
126
154
  "description": "Some sources denominate the target (organization) of a an attack.",
127
155
  "type": "String"
@@ -144,6 +172,10 @@
144
172
  "description": "All anecdotal information, which cannot be parsed into the data harmonization elements. E.g. os.name, os.version, etc. **Note**: this is only intended for mapping any fields which can not map naturally into the data harmonization. It is not intended for extending the data harmonization with your own fields.",
145
173
  "type": "JSONDict"
146
174
  },
175
+ "extra.min_amplification": {
176
+ "description": "Temporal definition to make sieve work properly",
177
+ "type": "Float"
178
+ },
147
179
  "feed.accuracy": {
148
180
  "description": "A float between 0 and 100 that represents how accurate the data in the feed is",
149
181
  "type": "Accuracy"
@@ -209,14 +241,30 @@
209
241
  "regex": "^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[0-9a-z]{12}$",
210
242
  "type": "LowercaseString"
211
243
  },
212
- "notify": {
213
- "description": "If mail will be sent out to affected or responsible contact.",
214
- "type": "Boolean"
215
- },
216
244
  "output": {
217
245
  "description": "Event data converted into foreign format, intended to be exported by output plugin.",
218
246
  "type": "JSON"
219
247
  },
248
+ "product.full_name": {
249
+ "description": "A human readable product name. If a machine-readable format isn't available, this field should be used. It can directly use the version identification strings presented by the product. If not given, a good enough value can usually be constructed by concatenating product.product and product.version, or by consulting external sources such as the CPE Product Dictionary. Example: openssh_/8.9",
250
+ "type": "String"
251
+ },
252
+ "product.name": {
253
+ "description": "Product name, recommended being as the product in the CPE format. Example: openssh",
254
+ "type": "LowercaseString"
255
+ },
256
+ "product.vendor": {
257
+ "description": "Vendor name, recommended being as vendor in the CPE format. Example: openbsd",
258
+ "type": "LowercaseString"
259
+ },
260
+ "product.version": {
261
+ "description": "Product version, recommended being as version in the CPE format. Example: 8.9",
262
+ "type": "LowercaseString"
263
+ },
264
+ "product.vulnerabilities": {
265
+ "description": "List of vulnerability IDs, separated by semicolons. It's recommended to use a CVE ID where available, and other easily retrievable IDs in other cases, e.g. Github Advisory Database ID. Each vulnerability should only be listed once, and multiple values should be used if there are several different vulnerabilities. However, it's not necessary for a source to list all possible vulnerabilities for a given piece of software. Example: cve-2023-38408;cve-2023-28531;cve-2008-3844;cve-2007-2768",
266
+ "type": "LowercaseString"
267
+ },
220
268
  "protocol.application": {
221
269
  "description": "e.g. vnc, ssh, sip, irc, http or smtp.",
222
270
  "length": 100,
@@ -233,29 +281,19 @@
233
281
  "description": "The original line of the event from encoded in base64.",
234
282
  "type": "Base64"
235
283
  },
236
- "rtir_incident_id": {
237
- "description": "Request Tracker Incident Response incident id.",
238
- "type": "Integer"
239
- },
240
- "rtir_investigation_id": {
241
- "description": "Request Tracker Incident Response investigation id.",
242
- "type": "Integer"
243
- },
244
- "rtir_report_id": {
245
- "description": "Request Tracker Incident Response incident report id.",
284
+ "rtir_id": {
285
+ "description": "Request Tracker Incident Response ticket id.",
246
286
  "type": "Integer"
247
287
  },
248
288
  "screenshot_url": {
249
289
  "description": "Some source may report URLs related to a an image generated of a resource without any metadata. Or an URL pointing to resource, which has been rendered into a webshot, e.g. a PNG image and the relevant metadata related to its retrieval/generation.",
250
290
  "type": "URL"
251
291
  },
252
- "sent_at": {
253
- "description": "Time when the report has been sent to the responsible recipient.",
254
- "type": "DateTime"
255
- },
256
- "shareable_extra_info": {
257
- "description": "Fields from extra which can be shared.",
258
- "type": "JSON"
292
+ "severity": {
293
+ "description": "Severity of the event, based on the information from the source, and eventually modified by IntelMQ during processing. Meaning of the levels may differ based on the event source. Allowed values: critical (highly critical vulnerabilities being actively exploited and pose a very high likelihood of compromise. For example, RCEs, sensitive data access), high (end of life systems, accessible internal systems that should not be exposed, risk of data leaks, malware drone and sinkhole events), medium (DDoS-amplifiers, unencrypted services requiring login, vulnerabilities requiring MITM to exploit, attacks need prior knowledge), low (deviation from best practice, little to no practical way to exploit, but setup is not ideal), info (informational only, no known risk), undefined (unknown or undetermined)",
294
+ "length": 10,
295
+ "regex": "^(critical|high|medium|low|info|undefined)$",
296
+ "type": "LowercaseString"
259
297
  },
260
298
  "source.abuse_contact": {
261
299
  "description": "Abuse contact for source address. A comma separated list.",
@@ -389,6 +427,10 @@
389
427
  }
390
428
  },
391
429
  "report": {
430
+ "rtir_report_id": {
431
+ "description": "Request Tracker Incident Response incident report id. CERT.at-specific",
432
+ "type": "Integer"
433
+ },
392
434
  "extra": {
393
435
  "description": "All anecdotal information of the report, which cannot be parsed into the data harmonization elements. E.g. subject of mails, etc. This is data is not automatically propagated to the events.",
394
436
  "type": "JSONDict"
@@ -422,8 +464,8 @@
422
464
  "description": "The original raw and unparsed data encoded in base64.",
423
465
  "type": "Base64"
424
466
  },
425
- "rtir_report_id": {
426
- "description": "Request Tracker Incident Response incident report id.",
467
+ "rtir_id": {
468
+ "description": "Request Tracker Incident Response ticket id.",
427
469
  "type": "Integer"
428
470
  },
429
471
  "time.observation": {
@@ -431,4 +473,4 @@
431
473
  "type": "DateTime"
432
474
  }
433
475
  }
434
- }
476
+ }
@@ -0,0 +1,112 @@
1
+ """Tests for Modat Collector
2
+
3
+ SPDX-FileCopyrightText: 2025 CERT.at GmbH <https://cert.at/>
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+ """
6
+
7
+ import json
8
+ import unittest
9
+ from unittest import mock
10
+
11
+ import intelmq.lib.message as message
12
+ import requests
13
+ from requests_mock import MockerCore
14
+
15
+ from intelmq_extensions.bots.collectors.modat.collector import ModatCollectorBot
16
+
17
+ from ....base import BotTestCase
18
+
19
+
20
+ class TestModatCollectorBot(BotTestCase, unittest.TestCase):
21
+ @classmethod
22
+ def set_bot(cls):
23
+ cls.bot_reference = ModatCollectorBot
24
+ cls.sysconfig = {
25
+ "query": "test-query",
26
+ "type": "host",
27
+ "api_key": "secure-api-key",
28
+ "code": "feed-code",
29
+ }
30
+
31
+ def mock_http_session(self):
32
+ session = requests.Session()
33
+ session_mock = mock.patch(
34
+ "intelmq_extensions.bots.collectors.modat.collector.create_request_session",
35
+ return_value=session,
36
+ )
37
+ session_mock.start()
38
+ self.addCleanup(session_mock.stop)
39
+
40
+ self.requests = MockerCore(session=session)
41
+ self.requests.start()
42
+ self.addCleanup(self.requests.stop)
43
+
44
+ def setUp(self):
45
+ super().setUp()
46
+ self.mock_http_session()
47
+
48
+ def mock_request(
49
+ self,
50
+ path: str,
51
+ expected_query: str = "test-query",
52
+ expected_page: int = 1,
53
+ expected_page_size: int = 10,
54
+ **kwargs,
55
+ ):
56
+ def check_request(request):
57
+ if request.headers.get("Authorization", "") != "Bearer secure-api-key":
58
+ return False
59
+ if request.json()["query"] != expected_query:
60
+ return False
61
+ if request.json()["page"] != expected_page:
62
+ return False
63
+ if request.json()["page_size"] != expected_page_size:
64
+ return False
65
+ return True
66
+
67
+ self.requests.post(
68
+ f"https://api.magnify.modat.io/{path}",
69
+ status_code=200,
70
+ additional_matcher=check_request,
71
+ **kwargs,
72
+ )
73
+
74
+ def _create_report_dict(self, data, **kwargs):
75
+ report = message.Report(kwargs, harmonization=self.harmonization)
76
+ report.add("feed.name", "Test Bot")
77
+ report.add("feed.accuracy", 100.0)
78
+ report.add("feed.code", "feed-code")
79
+ report.add("raw", json.dumps(data))
80
+ return report.to_dict(with_type=True)
81
+
82
+ def test_query_hosts(self):
83
+ self.mock_request(
84
+ "host/search/v1",
85
+ json={
86
+ "page_nr": 1,
87
+ "total_pages": 1,
88
+ "total_records": 4,
89
+ "page": [{"ip": "ip1"}, {"ip": "ip2"}],
90
+ },
91
+ )
92
+ self.mock_request(
93
+ "host/search/v1",
94
+ json={
95
+ "page_nr": 2,
96
+ "total_pages": 1,
97
+ "total_records": 4,
98
+ "page": [{"ip": "ip3"}, {"ip": "ip4"}],
99
+ },
100
+ expected_page=2,
101
+ )
102
+
103
+ self.run_bot()
104
+
105
+ self.assertOutputQueueLen(2)
106
+ self.assertMessageEqual(
107
+ 0, self._create_report_dict([{"ip": "ip1"}, {"ip": "ip2"}])
108
+ )
109
+ self.assertMessageEqual(
110
+ 1, self._create_report_dict([{"ip": "ip3"}, {"ip": "ip4"}])
111
+ )
112
+ self.assertEqual(2, self.requests.call_count)
@@ -0,0 +1,92 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Testing ReplaceInDictExpertBot.
4
+ """
5
+
6
+ import copy
7
+ import unittest
8
+
9
+ from intelmq.lib.exceptions import ConfigurationError
10
+
11
+ from intelmq_extensions.bots.experts.replace_in_dict.expert import (
12
+ ReplaceInDictExpertBot,
13
+ )
14
+
15
+ from ....base import BotTestCase
16
+
17
+
18
+ class TestReplaceInDictExpertBot(BotTestCase, unittest.TestCase):
19
+ """
20
+ A TestCase for ReplaceInDictExpertBot.
21
+ """
22
+
23
+ @classmethod
24
+ def set_bot(cls):
25
+ cls.bot_reference = ReplaceInDictExpertBot
26
+ cls.sysconfig = {
27
+ "old_value": "\\u0000",
28
+ "new_value": "[nullbyte]",
29
+ "fields": "extra",
30
+ }
31
+ cls.default_input_message = {"__type": "Event"}
32
+
33
+ def test_event_no_changes(self):
34
+ message = {
35
+ "__type": "Event",
36
+ "time.observation": "2015-01-01T00:00:00+00:00",
37
+ "extra.payload": "foo",
38
+ "extra.name": "bar",
39
+ "extra.firmwarerev": 1,
40
+ }
41
+ self.input_message = copy.deepcopy(message)
42
+ self.run_bot()
43
+ self.assertMessageEqual(0, message)
44
+
45
+ def test_event_no_extra(self):
46
+ message = {
47
+ "__type": "Event",
48
+ "time.observation": "2015-01-01T00:00:00+00:00",
49
+ "feed.code": "foo",
50
+ }
51
+ self.input_message = copy.deepcopy(message)
52
+ self.run_bot()
53
+ self.assertMessageEqual(0, message)
54
+
55
+ def test_event_changes_one_dict(self):
56
+ message = {
57
+ "__type": "Event",
58
+ "time.observation": "2015-01-01T00:00:00+00:00",
59
+ "extra.payload": "foo\\u0000bar\\u0000",
60
+ "extra.name": "bar ok \\u0001 and not ok \\\\u0000",
61
+ "extra.firmwarerev": 1,
62
+ "feed.code": "foo",
63
+ }
64
+ self.input_message = copy.deepcopy(message)
65
+ self.run_bot()
66
+
67
+ message["extra.payload"] = "foo[nullbyte]bar[nullbyte]"
68
+ message["extra.name"] = "bar ok \\u0001 and not ok \\[nullbyte]"
69
+ self.assertMessageEqual(0, message)
70
+
71
+ def test_event_multiple_dict_fail_if_not_jsondict(self):
72
+ with self.assertRaises(ConfigurationError):
73
+ self.run_bot(
74
+ parameters={
75
+ "fields": "extra,output",
76
+ }
77
+ )
78
+
79
+ def test_event_other_fields_not_modified(self):
80
+ message = {
81
+ "__type": "Event",
82
+ "time.observation": "2015-01-01T00:00:00+00:00",
83
+ "feed.code": "foo\\u0000",
84
+ }
85
+ self.input_message = copy.deepcopy(message)
86
+ self.run_bot()
87
+
88
+ self.assertMessageEqual(0, message)
89
+
90
+
91
+ if __name__ == "__main__":
92
+ unittest.main()
@@ -0,0 +1,135 @@
1
+ """Data for tests for ModatParserBot
2
+
3
+ SPDX-FileCopyrightText: 2025 CERT.at GmbH <https://cert.at/>
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+ """
6
+
7
+ RESPONSE_1 = [
8
+ {
9
+ "ip": "127.0.0.1",
10
+ "geo": {
11
+ "city_name": "Vienna",
12
+ "country_name": "Austria",
13
+ "country_iso_code": "AT",
14
+ },
15
+ "asn": {"number": 8412, "org": "T-Mobile Austria GmbH"},
16
+ "fqdns": ["domain.example.at", "device-123.example.at"],
17
+ "is_anycast": False,
18
+ "tags": [],
19
+ "cves": [
20
+ {"id": "CVE-2020-11985", "cvss": 5.3, "is_kev": False},
21
+ {"id": "CVE-2025-3891", "cvss": 7.5, "is_kev": False},
22
+ ],
23
+ "services": [
24
+ {
25
+ "transport": "tcp",
26
+ "port": 8443,
27
+ "ports": [8443],
28
+ "last_scanned_port": 8443,
29
+ "protocol": "http",
30
+ "scanned_at": "2025-12-17T03:57:49Z",
31
+ "is_match": False,
32
+ },
33
+ {
34
+ "transport": "tcp",
35
+ "port": 49592,
36
+ "ports": [49592],
37
+ "last_scanned_port": 49592,
38
+ "protocol": "http",
39
+ "scanned_at": "2025-05-29T20:24:10.308508Z",
40
+ "is_match": True,
41
+ },
42
+ {
43
+ "transport": "tcp",
44
+ "port": 39895,
45
+ "ports": [39895],
46
+ "last_scanned_port": 39895,
47
+ "protocol": "unknown",
48
+ "scanned_at": "2025-11-22T12:22:14Z",
49
+ "is_match": True,
50
+ },
51
+ ],
52
+ },
53
+ {
54
+ "ip": "127.0.0.2",
55
+ "geo": {
56
+ "city_name": "Feldkirch",
57
+ "country_name": "Austria",
58
+ "country_iso_code": "AT",
59
+ },
60
+ "asn": {"number": 5385, "org": "Russmedia IT GmbH"},
61
+ "fqdns": [],
62
+ "is_anycast": False,
63
+ "tags": [],
64
+ "cves": [
65
+ {"id": "CVE-2016-1247", "cvss": 7.8, "is_kev": False},
66
+ {"id": "CVE-2017-20005", "cvss": 9.8, "is_kev": False},
67
+ ],
68
+ "services": [
69
+ {
70
+ "transport": "tcp",
71
+ "port": 44302,
72
+ "ports": [44302],
73
+ "last_scanned_port": 44302,
74
+ "protocol": "unknown",
75
+ "scanned_at": "2025-11-24T00:30:40Z",
76
+ "is_match": True,
77
+ },
78
+ {
79
+ "transport": "tcp",
80
+ "port": 443,
81
+ "ports": [443],
82
+ "last_scanned_port": 443,
83
+ "protocol": "http",
84
+ "scanned_at": "2025-12-15T15:54:24Z",
85
+ "is_match": False,
86
+ },
87
+ {
88
+ "transport": "tcp",
89
+ "port": 8443,
90
+ "ports": [8443],
91
+ "last_scanned_port": 8443,
92
+ "protocol": "http",
93
+ "scanned_at": "2025-12-16T22:49:47Z",
94
+ "is_match": False,
95
+ },
96
+ ],
97
+ },
98
+ ]
99
+
100
+ EVENT_1 = {
101
+ # "raw": RESPONSE1[0],
102
+ "source.ip": "127.0.0.1",
103
+ "source.geolocation.cc": "AT",
104
+ "source.geolocation.country": "Austria",
105
+ "source.geolocation.city": "Vienna",
106
+ "extra": {
107
+ "services": RESPONSE_1[0]["services"],
108
+ "fqdns": "domain.example.at;device-123.example.at",
109
+ },
110
+ "product.vulnerabilities": "cve-2020-11985;cve-2025-3891",
111
+ "protocol.application": "http",
112
+ "protocol.transport": "tcp",
113
+ "source.as_name": "T-Mobile Austria GmbH",
114
+ "source.asn": 8412,
115
+ "source.fqdn": "domain.example.at",
116
+ "source.port": 49592,
117
+ "time.source": "2025-05-29T20:24:10.308508+00:00",
118
+ }
119
+
120
+ EVENT_2 = {
121
+ # "raw": RESPONSE1[1],
122
+ "source.ip": "127.0.0.2",
123
+ "source.geolocation.cc": "AT",
124
+ "source.geolocation.country": "Austria",
125
+ "source.geolocation.city": "Feldkirch",
126
+ "extra": {
127
+ "services": RESPONSE_1[1]["services"],
128
+ },
129
+ "product.vulnerabilities": "cve-2016-1247;cve-2017-20005",
130
+ "protocol.transport": "tcp",
131
+ "source.as_name": "Russmedia IT GmbH",
132
+ "source.asn": 5385,
133
+ "source.port": 44302,
134
+ "time.source": "2025-11-24T00:30:40+00:00",
135
+ }
@@ -0,0 +1,83 @@
1
+ """Tests for ModatParserBot
2
+
3
+ SPDX-FileCopyrightText: 2025 CERT.at GmbH <https://cert.at/>
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+ """
6
+
7
+ import json
8
+ import unittest
9
+ from datetime import datetime
10
+
11
+ import intelmq.lib.message as message
12
+ from dateutil.tz import UTC
13
+
14
+ from intelmq_extensions.bots.parsers.modat.parser import ModatParserBot
15
+
16
+ from ....base import BotTestCase
17
+ from . import data
18
+
19
+
20
+ class TestModatParserBot(BotTestCase, unittest.TestCase):
21
+ @classmethod
22
+ def set_bot(cls):
23
+ cls.bot_reference = ModatParserBot
24
+ cls.sysconfig = {
25
+ "default_fields": {
26
+ "classification.taxonomy": "other",
27
+ "classification.type": "other",
28
+ "classification.identifier": "something",
29
+ }
30
+ }
31
+
32
+ def _create_report_dict(self, raw, **kwargs):
33
+ data = {
34
+ "feed.name": "Test Bot",
35
+ "feed.accuracy": 100.0,
36
+ "feed.code": "feed-code",
37
+ "time.observation": datetime(2023, 1, 1, tzinfo=UTC).isoformat(),
38
+ }
39
+ data.update(kwargs)
40
+ report = message.Report(data, harmonization=self.harmonization)
41
+ report.add("raw", json.dumps(raw))
42
+ return report.to_dict(with_type=True)
43
+
44
+ def _create_event_dict(
45
+ self,
46
+ extra: dict,
47
+ # raw: dict,
48
+ **kwargs,
49
+ ):
50
+ data = {
51
+ "classification.identifier": "something",
52
+ "classification.taxonomy": "other",
53
+ "classification.type": "other",
54
+ "time.observation": datetime(2023, 1, 1, tzinfo=UTC).isoformat(),
55
+ "feed.accuracy": 100.0,
56
+ "feed.code": "feed-code",
57
+ "feed.name": "Test Bot",
58
+ }
59
+ data.update(kwargs)
60
+ event = message.Event(data, harmonization=self.harmonization)
61
+ event.add("extra", extra)
62
+ # event.add(
63
+ # "raw", json.dumps(raw)
64
+ # )
65
+ return event.to_dict(with_type=True)
66
+
67
+ def test_parsing_report_default(self):
68
+ self.input_message = self._create_report_dict(data.RESPONSE_1)
69
+
70
+ self.run_bot()
71
+
72
+ self.assertMessageEqual(
73
+ 0,
74
+ self._create_event_dict(
75
+ **data.EVENT_1,
76
+ ),
77
+ compare_raw=False,
78
+ )
79
+ self.assertMessageEqual(
80
+ 1,
81
+ self._create_event_dict(**data.EVENT_2),
82
+ compare_raw=False,
83
+ )
@@ -1,22 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: intelmq_extensions
3
- Version: 1.8.1
3
+ Version: 1.10.0
4
4
  Summary: Additional bots for IntelMQ
5
5
  Author: CERT.at Data & Development Team
6
6
  License: AGPLv3
7
7
  Project-URL: Repository, https://github.com/certat/intelmq-extensions
8
8
  Classifier: Programming Language :: Python :: 3
9
- Requires-Python: >=3.7
9
+ Requires-Python: >=3.10
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: rt<3.0.0,>=1.0.9
13
- Requires-Dist: mergedeep
14
12
  Requires-Dist: intelmq
15
- Requires-Dist: tabulate>=0.7.5
13
+ Requires-Dist: importlib_metadata; python_version < "3.8"
16
14
  Requires-Dist: psycopg2-binary
17
15
  Requires-Dist: netaddr>=0.7.14
16
+ Requires-Dist: rt<3.0.0,>=1.0.9
17
+ Requires-Dist: tabulate>=0.7.5
18
18
  Requires-Dist: python-termstyle>=0.1.10
19
- Requires-Dist: importlib_metadata; python_version < "3.8"
19
+ Requires-Dist: mergedeep
20
20
  Provides-Extra: dev
21
21
  Requires-Dist: pytest; extra == "dev"
22
22
  Requires-Dist: tox>=4; extra == "dev"
@@ -30,14 +30,25 @@ Dynamic: requires-dist
30
30
 
31
31
  # IntelMQ Extensions
32
32
 
33
- This project collects customized bots used primary by CERT.at.
33
+ [![Running tests](https://github.com/certat/intelmq-extensions/actions/workflows/ci.yml/badge.svg)](https://github.com/certat/intelmq-extensions/actions/workflows/ci.yml)
34
+
35
+ This project collects customized bots and some helper scripts for
36
+ [IntelMQ](https://github.com/certtools/intelmq) used primary by CERT.at.
37
+
38
+ It's a combination of customization previously available in [certat/intelmq](https://github.com/certat/intelmq)
39
+ as well as newer solutions.
34
40
 
35
41
  ## Usage
36
42
 
37
- Install the package on the machine. Then, it's enough to just declare the bot's module
38
- pointing to this package, e.g. `intelmq_extensions.bots.collectors.xmpp`
43
+ Install the package on the machine or virtualenv, where you have the IntelMQ, using
44
+ `pip install intelmq-extensions`. Then, the bots will be available as any other IntelMQ
45
+ bot in the Manager as well to import using `intelmq.bots.*.certat` namespace, e.g.
46
+ `intelmq.bots.experts.certat.vulnerability_lookup.expert`
39
47
 
48
+ ## Documentation
40
49
 
50
+ There is a limited documentation available. Consult bot Python code to see information
51
+ about the usage and available configuration.
41
52
 
42
53
  ## Running tests
43
54
 
@@ -58,3 +69,9 @@ This package comes with test runners configured using `tox`. To use them:
58
69
  tox -efull-with-docker -- intelmq_extensions/tests/bots/experts/squelcher/test_expert.py::TestSquelcherExpertBot::test_address_match1
59
70
 
60
71
  ```
72
+
73
+ ---
74
+
75
+ Part of the development was financed by the European Union.
76
+
77
+ ![CEF-Logo](https://github.com/certat/intelmq-extensions/blob/main/docs/cef_logo.png?raw=true)
@@ -6,6 +6,8 @@ intelmq_extensions/bots/collectors/blackkite/collector.py,sha256=ZpAgJOOMKRh-q2T
6
6
  intelmq_extensions/bots/collectors/disp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  intelmq_extensions/bots/collectors/disp/_client.py,sha256=iLHDV6gu2uBwUPUe8x_rIVfnTrrjgIUdk9khFigXYX8,3929
8
8
  intelmq_extensions/bots/collectors/disp/collector.py,sha256=j1EEQx8awDxLs-K1vl7tjBQ2GaRRGhsaZ_mICeUdjrw,3374
9
+ intelmq_extensions/bots/collectors/modat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ intelmq_extensions/bots/collectors/modat/collector.py,sha256=PGtbHkgvay2vtzl9n8iugh1mb7peXOIEf8fVZnRP4KA,2343
9
11
  intelmq_extensions/bots/collectors/xmpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
12
  intelmq_extensions/bots/collectors/xmpp/collector.py,sha256=6jOSwjkHCuPQShG_2gwczrxIevUA83-Go18mrlvFmzw,7415
11
13
  intelmq_extensions/bots/experts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -17,6 +19,8 @@ intelmq_extensions/bots/experts/event_group_splitter/__init__.py,sha256=47DEQpj8
17
19
  intelmq_extensions/bots/experts/event_group_splitter/expert.py,sha256=Jq9v1p94dwbiFKeaulf7pg5XA15hlb967Wwvjp-fZ3g,4095
18
20
  intelmq_extensions/bots/experts/event_splitter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
21
  intelmq_extensions/bots/experts/event_splitter/expert.py,sha256=DcAXwapXMmP-IP7sPwDBWJ9cMu9UCcKh3nJPSDJbGfU,1204
22
+ intelmq_extensions/bots/experts/replace_in_dict/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ intelmq_extensions/bots/experts/replace_in_dict/expert.py,sha256=GyevseD5n5E5cNO_6PZqnK8gZhILplPvLcwyS6iXNks,1375
20
24
  intelmq_extensions/bots/experts/squelcher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
25
  intelmq_extensions/bots/experts/squelcher/expert.py,sha256=cvmA4N5MjC_QazcEmCgW_uffn0P-4jPIPMikjtOcEd0,11022
22
26
  intelmq_extensions/bots/experts/vulnerability_lookup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -36,12 +40,14 @@ intelmq_extensions/bots/parsers/disp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeu
36
40
  intelmq_extensions/bots/parsers/disp/parser.py,sha256=co-eOx8aR3-z2BYe50Rlyr9dYWlX1w0ZoBAFZVgCdbg,4401
37
41
  intelmq_extensions/bots/parsers/malwaredomains/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
42
  intelmq_extensions/bots/parsers/malwaredomains/parser.py,sha256=FEp0YxZ44bD5KoeO162nRols48Xtq_8haAA8zA7BgZg,1996
43
+ intelmq_extensions/bots/parsers/modat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
+ intelmq_extensions/bots/parsers/modat/parser.py,sha256=VanWdV8RhTcqlwGlbrtJ848GG9s0DVSAPE93nGZhLpk,3165
39
45
  intelmq_extensions/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
46
  intelmq_extensions/cli/create_reports.py,sha256=aPANN6VeHQhTSl6XT0SKulo9WhEuR1lFZVfWJ3TaOYk,5591
41
47
  intelmq_extensions/cli/intelmqcli.py,sha256=1DhGm_wAyyUEA45CEFkbLN-k29O9o6XPcSZFvjOc_Ls,24085
42
48
  intelmq_extensions/cli/lib.py,sha256=mrMuRVvd-nKd1ASVBhTZn28B13VIK6VVSv-MZO2FFx4,20767
43
49
  intelmq_extensions/cli/utils.py,sha256=CpK4rvYtJDYiW5CDhxtBwK7VIY-MOV6FSSEQpwf4HmE,410
44
- intelmq_extensions/etc/harmonization.conf,sha256=n6PXTYYD5LQRIBgNmDIeco1dy5gwxu77_g_qj6P8Qz8,22018
50
+ intelmq_extensions/etc/harmonization.conf,sha256=8kxVSdZEFE7ik1XL1np_vHd3CvTasQ8N4w5hUdMW3YU,25393
45
51
  intelmq_extensions/etc/squelcher.conf,sha256=36hrMfzBL2C36tFExvDc4OxmIA_Z1x8UEUDfiY1vH5A,989
46
52
  intelmq_extensions/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
53
  intelmq_extensions/lib/api_helpers.py,sha256=wi4Jau_9MThoborMhAi4Qpq3w2sviHNpxSJDce1JXfw,3453
@@ -58,6 +64,8 @@ intelmq_extensions/tests/bots/collectors/disp/__init__.py,sha256=47DEQpj8HBSa-_T
58
64
  intelmq_extensions/tests/bots/collectors/disp/base.py,sha256=BDFdvkWs4hQNtYU35CaK_yLvIvk1DJnmclmrxpzNfBU,4655
59
65
  intelmq_extensions/tests/bots/collectors/disp/test_client.py,sha256=FsEUyhgWtv9OaPADhVv-FbG9lH5Vqk7hJ_mHTXYp0rA,4366
60
66
  intelmq_extensions/tests/bots/collectors/disp/test_collector.py,sha256=-yeRJPS1fqbbZhFAFMbivCEIsfn6Sr3nT77kyY_t7S4,4385
67
+ intelmq_extensions/tests/bots/collectors/modat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
68
+ intelmq_extensions/tests/bots/collectors/modat/test_collector.py,sha256=aLaOnk0dbskygFNPmVNKGUAKm04DzFxkiQPf4tkd6V4,3332
61
69
  intelmq_extensions/tests/bots/collectors/xmpp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
62
70
  intelmq_extensions/tests/bots/collectors/xmpp/test_collector.py,sha256=KyzENq4kYkFvANy19IWhHSGGW9ed7vbFk_dDS-sag0I,187
63
71
  intelmq_extensions/tests/bots/experts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -69,6 +77,8 @@ intelmq_extensions/tests/bots/experts/event_group_splitter/__init__.py,sha256=47
69
77
  intelmq_extensions/tests/bots/experts/event_group_splitter/test_expert.py,sha256=FcuFOzOGctWQ0whG5e7t9_K2Vf7a_doDEl5WoWhDqq8,9124
70
78
  intelmq_extensions/tests/bots/experts/event_splitter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
79
  intelmq_extensions/tests/bots/experts/event_splitter/test_expert.py,sha256=KGjh86BacNiUcJGg8kxggHoJY3ORZPmPFmVPYJK0J6A,2752
80
+ intelmq_extensions/tests/bots/experts/replace_in_dict/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
81
+ intelmq_extensions/tests/bots/experts/replace_in_dict/test_expert.py,sha256=aGJ0zGjU4qEKP4HLIF4LnFyze7I__DMrMJz6ynf5bko,2630
72
82
  intelmq_extensions/tests/bots/experts/squelcher/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
83
  intelmq_extensions/tests/bots/experts/squelcher/test_expert.py,sha256=oHE3E9q2fMwPVavgZM4Bs8kQCGehpMFEv3biJQs_JJk,17731
74
84
  intelmq_extensions/tests/bots/experts/vulnerability_lookup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -86,15 +96,18 @@ intelmq_extensions/tests/bots/parsers/disp/__init__.py,sha256=47DEQpj8HBSa-_TImW
86
96
  intelmq_extensions/tests/bots/parsers/disp/test_parser.py,sha256=geUXWaKpOUW_k9-Qb_VAtp99cr6vDTcnSBmT9LfDHec,9714
87
97
  intelmq_extensions/tests/bots/parsers/malwaredomains/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
98
  intelmq_extensions/tests/bots/parsers/malwaredomains/test_parser.py,sha256=zkfjcyniiPRcpABVurdRrRpZyZ2zV_PQ84xiU1eUJv0,1882
99
+ intelmq_extensions/tests/bots/parsers/modat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
+ intelmq_extensions/tests/bots/parsers/modat/data.py,sha256=K4BVUxqm7zGHYittNmFC1FuLevCz6XapJAdhFY1EyOQ,4171
101
+ intelmq_extensions/tests/bots/parsers/modat/test_parser.py,sha256=5Du2KG18F3BnvXrSckUojHoFSYyzWkyO24dMUqLlUMs,2401
89
102
  intelmq_extensions/tests/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
103
  intelmq_extensions/tests/cli/test_create_reports.py,sha256=hZ-3lhNJJPhi9xetyMAG7k-G5crZ9kf0bbvQj-nMSrg,3343
91
104
  intelmq_extensions/tests/cli/test_intelmqcli.py,sha256=Izy0FHVkj44DCgp4Zm8f1vYTdl5wDyCP1sZV0xgMrgY,5009
92
105
  intelmq_extensions/tests/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
106
  intelmq_extensions/tests/lib/base.py,sha256=iD4MuDq0Dh-CqFDgyXf4CfaJgT4AL7XAcNjPGxUlkPM,2560
94
107
  intelmq_extensions/tests/lib/test_api_helpers.py,sha256=cO6KNfVjwpYgevyyxxokOAmk7VXo-8T8OCH8hQhf980,3807
95
- intelmq_extensions-1.8.1.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
96
- intelmq_extensions-1.8.1.dist-info/METADATA,sha256=FjXDB14UgCsC5FRaLxnsnGThgiC6p9k7ztXhi9zop5o,1988
97
- intelmq_extensions-1.8.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
98
- intelmq_extensions-1.8.1.dist-info/entry_points.txt,sha256=dW_TN7JbkRT4zV7cJoFGIVzAcuqEL9OA1k7fI9Qtipk,3489
99
- intelmq_extensions-1.8.1.dist-info/top_level.txt,sha256=YVqZnmAiBfQPNdJPAv64Afvc41p3B24Akx2v2-3BFyc,19
100
- intelmq_extensions-1.8.1.dist-info/RECORD,,
108
+ intelmq_extensions-1.10.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
109
+ intelmq_extensions-1.10.0.dist-info/METADATA,sha256=mfhiBm-fykPWoGvl9UGMaiuGdVjbpole3WHd9UG4V7c,2843
110
+ intelmq_extensions-1.10.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
111
+ intelmq_extensions-1.10.0.dist-info/entry_points.txt,sha256=ZnDurNdOqqIbPjKHzsmxrczs5aE9d_KLOlz1mQx2C5I,4141
112
+ intelmq_extensions-1.10.0.dist-info/top_level.txt,sha256=YVqZnmAiBfQPNdJPAv64Afvc41p3B24Akx2v2-3BFyc,19
113
+ intelmq_extensions-1.10.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,11 +1,13 @@
1
1
  [console_scripts]
2
2
  intelmq.bots.collectors.certat.blackkite.collector = intelmq_extensions.bots.collectors.blackkite.collector:BOT.run
3
3
  intelmq.bots.collectors.certat.disp.collector = intelmq_extensions.bots.collectors.disp.collector:BOT.run
4
+ intelmq.bots.collectors.certat.modat.collector = intelmq_extensions.bots.collectors.modat.collector:BOT.run
4
5
  intelmq.bots.collectors.certat.xmpp.collector = intelmq_extensions.bots.collectors.xmpp.collector:BOT.run
5
6
  intelmq.bots.experts.certat.certat_contact_intern.expert = intelmq_extensions.bots.experts.certat_contact_intern.expert:BOT.run
6
7
  intelmq.bots.experts.certat.copy_extra.expert = intelmq_extensions.bots.experts.copy_extra.expert:BOT.run
7
8
  intelmq.bots.experts.certat.event_group_splitter.expert = intelmq_extensions.bots.experts.event_group_splitter.expert:BOT.run
8
9
  intelmq.bots.experts.certat.event_splitter.expert = intelmq_extensions.bots.experts.event_splitter.expert:BOT.run
10
+ intelmq.bots.experts.certat.replace_in_dict.expert = intelmq_extensions.bots.experts.replace_in_dict.expert:BOT.run
9
11
  intelmq.bots.experts.certat.squelcher.expert = intelmq_extensions.bots.experts.squelcher.expert:BOT.run
10
12
  intelmq.bots.experts.certat.vulnerability_lookup.expert = intelmq_extensions.bots.experts.vulnerability_lookup.expert:BOT.run
11
13
  intelmq.bots.outputs.certat.mattermost.output = intelmq_extensions.bots.outputs.mattermost.output:BOT.run
@@ -14,13 +16,16 @@ intelmq.bots.outputs.certat.xmpp.output = intelmq_extensions.bots.outputs.xmpp.o
14
16
  intelmq.bots.parsers.certat.blackkite.parser = intelmq_extensions.bots.parsers.blackkite.parser:BOT.run
15
17
  intelmq.bots.parsers.certat.disp.parser = intelmq_extensions.bots.parsers.disp.parser:BOT.run
16
18
  intelmq.bots.parsers.certat.malwaredomains.parser = intelmq_extensions.bots.parsers.malwaredomains.parser:BOT.run
19
+ intelmq.bots.parsers.certat.modat.parser = intelmq_extensions.bots.parsers.modat.parser:BOT.run
17
20
  intelmq_extensions.bots.collectors.blackkite.collector = intelmq_extensions.bots.collectors.blackkite.collector:BOT.run
18
21
  intelmq_extensions.bots.collectors.disp.collector = intelmq_extensions.bots.collectors.disp.collector:BOT.run
22
+ intelmq_extensions.bots.collectors.modat.collector = intelmq_extensions.bots.collectors.modat.collector:BOT.run
19
23
  intelmq_extensions.bots.collectors.xmpp.collector = intelmq_extensions.bots.collectors.xmpp.collector:BOT.run
20
24
  intelmq_extensions.bots.experts.certat_contact_intern.expert = intelmq_extensions.bots.experts.certat_contact_intern.expert:BOT.run
21
25
  intelmq_extensions.bots.experts.copy_extra.expert = intelmq_extensions.bots.experts.copy_extra.expert:BOT.run
22
26
  intelmq_extensions.bots.experts.event_group_splitter.expert = intelmq_extensions.bots.experts.event_group_splitter.expert:BOT.run
23
27
  intelmq_extensions.bots.experts.event_splitter.expert = intelmq_extensions.bots.experts.event_splitter.expert:BOT.run
28
+ intelmq_extensions.bots.experts.replace_in_dict.expert = intelmq_extensions.bots.experts.replace_in_dict.expert:BOT.run
24
29
  intelmq_extensions.bots.experts.squelcher.expert = intelmq_extensions.bots.experts.squelcher.expert:BOT.run
25
30
  intelmq_extensions.bots.experts.vulnerability_lookup.expert = intelmq_extensions.bots.experts.vulnerability_lookup.expert:BOT.run
26
31
  intelmq_extensions.bots.outputs.mattermost.output = intelmq_extensions.bots.outputs.mattermost.output:BOT.run
@@ -29,5 +34,6 @@ intelmq_extensions.bots.outputs.xmpp.output = intelmq_extensions.bots.outputs.xm
29
34
  intelmq_extensions.bots.parsers.blackkite.parser = intelmq_extensions.bots.parsers.blackkite.parser:BOT.run
30
35
  intelmq_extensions.bots.parsers.disp.parser = intelmq_extensions.bots.parsers.disp.parser:BOT.run
31
36
  intelmq_extensions.bots.parsers.malwaredomains.parser = intelmq_extensions.bots.parsers.malwaredomains.parser:BOT.run
37
+ intelmq_extensions.bots.parsers.modat.parser = intelmq_extensions.bots.parsers.modat.parser:BOT.run
32
38
  intelmqcli = intelmq_extensions.cli.intelmqcli:main
33
39
  intelmqcli_create_reports = intelmq_extensions.cli.create_reports:main