intelmq-extensions 1.8.1__tar.gz → 1.10.0__tar.gz
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.
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/PKG-INFO +28 -11
- intelmq_extensions-1.10.0/README.md +47 -0
- intelmq_extensions-1.10.0/intelmq_extensions/bots/collectors/modat/collector.py +87 -0
- intelmq_extensions-1.10.0/intelmq_extensions/bots/experts/replace_in_dict/expert.py +42 -0
- intelmq_extensions-1.10.0/intelmq_extensions/bots/parsers/modat/parser.py +92 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/etc/harmonization.conf +73 -31
- intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/collectors/modat/test_collector.py +112 -0
- intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/experts/replace_in_dict/test_expert.py +92 -0
- intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/parsers/blackkite/__init__.py +0 -0
- intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/parsers/disp/__init__.py +0 -0
- intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/parsers/malwaredomains/__init__.py +0 -0
- intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/parsers/modat/__init__.py +0 -0
- intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/parsers/modat/data.py +135 -0
- intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/parsers/modat/test_parser.py +83 -0
- intelmq_extensions-1.10.0/intelmq_extensions/tests/cli/__init__.py +0 -0
- intelmq_extensions-1.10.0/intelmq_extensions/tests/lib/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions.egg-info/PKG-INFO +28 -11
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions.egg-info/SOURCES.txt +13 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions.egg-info/entry_points.txt +6 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/pyproject.toml +2 -2
- intelmq_extensions-1.8.1/README.md +0 -30
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/LICENSE +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/collectors/blackkite/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/collectors/blackkite/_client.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/collectors/blackkite/collector.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/collectors/disp/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/collectors/disp/_client.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/collectors/disp/collector.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/collectors/xmpp → intelmq_extensions-1.10.0/intelmq_extensions/bots/collectors/modat}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/experts → intelmq_extensions-1.10.0/intelmq_extensions/bots/collectors/xmpp}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/collectors/xmpp/collector.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/experts/certat_contact_intern → intelmq_extensions-1.10.0/intelmq_extensions/bots/experts}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/experts/copy_extra → intelmq_extensions-1.10.0/intelmq_extensions/bots/experts/certat_contact_intern}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/experts/certat_contact_intern/expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/experts/event_group_splitter → intelmq_extensions-1.10.0/intelmq_extensions/bots/experts/copy_extra}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/experts/copy_extra/expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/experts/event_splitter → intelmq_extensions-1.10.0/intelmq_extensions/bots/experts/event_group_splitter}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/experts/event_group_splitter/expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/experts/squelcher → intelmq_extensions-1.10.0/intelmq_extensions/bots/experts/event_splitter}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/experts/event_splitter/expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/experts/vulnerability_lookup → intelmq_extensions-1.10.0/intelmq_extensions/bots/experts/replace_in_dict}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/outputs → intelmq_extensions-1.10.0/intelmq_extensions/bots/experts/squelcher}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/experts/squelcher/expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/outputs/mattermost → intelmq_extensions-1.10.0/intelmq_extensions/bots/experts/vulnerability_lookup}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/experts/vulnerability_lookup/expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/outputs/to_logs → intelmq_extensions-1.10.0/intelmq_extensions/bots/outputs}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/outputs/xmpp → intelmq_extensions-1.10.0/intelmq_extensions/bots/outputs/mattermost}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/outputs/mattermost/output.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/parsers → intelmq_extensions-1.10.0/intelmq_extensions/bots/outputs/to_logs}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/outputs/to_logs/output.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/parsers/blackkite → intelmq_extensions-1.10.0/intelmq_extensions/bots/outputs/xmpp}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/outputs/xmpp/output.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/parsers/disp → intelmq_extensions-1.10.0/intelmq_extensions/bots/parsers}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/bots/parsers/malwaredomains → intelmq_extensions-1.10.0/intelmq_extensions/bots/parsers/blackkite}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/parsers/blackkite/_transformers.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/parsers/blackkite/parser.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/cli → intelmq_extensions-1.10.0/intelmq_extensions/bots/parsers/disp}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/parsers/disp/parser.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/lib → intelmq_extensions-1.10.0/intelmq_extensions/bots/parsers/malwaredomains}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/bots/parsers/malwaredomains/parser.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests → intelmq_extensions-1.10.0/intelmq_extensions/bots/parsers/modat}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots → intelmq_extensions-1.10.0/intelmq_extensions/cli}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/cli/create_reports.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/cli/intelmqcli.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/cli/lib.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/cli/utils.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/etc/squelcher.conf +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/collectors → intelmq_extensions-1.10.0/intelmq_extensions/lib}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/lib/api_helpers.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/lib/blackkite.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/collectors/blackkite → intelmq_extensions-1.10.0/intelmq_extensions/tests}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/base.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/collectors/disp → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/collectors/xmpp → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/collectors}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/experts → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/collectors/blackkite}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/collectors/blackkite/base.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/collectors/blackkite/test_client.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/collectors/blackkite/test_collector.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/experts/certat_contact_intern → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/collectors/disp}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/collectors/disp/base.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/collectors/disp/test_client.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/collectors/disp/test_collector.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/experts/copy_extra → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/collectors/modat}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/experts/event_group_splitter → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/collectors/xmpp}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/collectors/xmpp/test_collector.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/experts/event_splitter → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/experts}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/experts/squelcher → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/experts/certat_contact_intern}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/experts/certat_contact_intern/test_expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/experts/vulnerability_lookup → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/experts/copy_extra}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/experts/copy_extra/test_expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/outputs → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/experts/event_group_splitter}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/experts/event_group_splitter/test_expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/outputs/mattermost → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/experts/event_splitter}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/experts/event_splitter/test_expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/outputs/xmpp → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/experts/replace_in_dict}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/parsers → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/experts/squelcher}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/experts/squelcher/test_expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/parsers/blackkite → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/experts/vulnerability_lookup}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/experts/vulnerability_lookup/test_expert.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/parsers/disp → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/outputs}/__init__.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/bots/parsers/malwaredomains → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/outputs/mattermost}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/outputs/mattermost/test_output.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/cli → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/outputs/xmpp}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/outputs/xmpp/test_output.py +0 -0
- {intelmq_extensions-1.8.1/intelmq_extensions/tests/lib → intelmq_extensions-1.10.0/intelmq_extensions/tests/bots/parsers}/__init__.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/parsers/blackkite/data.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/parsers/blackkite/test_parser.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/parsers/disp/test_parser.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/bots/parsers/malwaredomains/test_parser.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/cli/test_create_reports.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/cli/test_intelmqcli.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/lib/base.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/tests/lib/test_api_helpers.py +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions.egg-info/dependency_links.txt +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions.egg-info/requires.txt +5 -5
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions.egg-info/top_level.txt +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/setup.cfg +0 -0
- {intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/setup.py +0 -0
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: intelmq_extensions
|
|
3
|
-
Version: 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.
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
10
|
Description-Content-Type: text/markdown
|
|
11
11
|
License-File: LICENSE
|
|
12
|
+
Requires-Dist: slixmpp
|
|
13
|
+
Requires-Dist: importlib_metadata; python_version < "3.8"
|
|
14
|
+
Requires-Dist: psycopg2-binary
|
|
12
15
|
Requires-Dist: rt<3.0.0,>=1.0.9
|
|
13
|
-
Requires-Dist: netaddr>=0.7.14
|
|
14
16
|
Requires-Dist: psycopg2-binary>=2.5.5
|
|
15
17
|
Requires-Dist: mergedeep
|
|
16
|
-
Requires-Dist: intelmq
|
|
17
|
-
Requires-Dist: slixmpp
|
|
18
|
-
Requires-Dist: tabulate>=0.7.5
|
|
19
|
-
Requires-Dist: psycopg2-binary
|
|
20
18
|
Requires-Dist: netaddr>=0.7.14
|
|
21
19
|
Requires-Dist: python-termstyle>=0.1.10
|
|
22
|
-
Requires-Dist:
|
|
20
|
+
Requires-Dist: tabulate>=0.7.5
|
|
21
|
+
Requires-Dist: netaddr>=0.7.14
|
|
22
|
+
Requires-Dist: intelmq
|
|
23
23
|
Provides-Extra: dev
|
|
24
24
|
Requires-Dist: pytest; extra == "dev"
|
|
25
25
|
Requires-Dist: tox>=4; extra == "dev"
|
|
@@ -33,14 +33,25 @@ Dynamic: requires-dist
|
|
|
33
33
|
|
|
34
34
|
# IntelMQ Extensions
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
[](https://github.com/certat/intelmq-extensions/actions/workflows/ci.yml)
|
|
37
|
+
|
|
38
|
+
This project collects customized bots and some helper scripts for
|
|
39
|
+
[IntelMQ](https://github.com/certtools/intelmq) used primary by CERT.at.
|
|
40
|
+
|
|
41
|
+
It's a combination of customization previously available in [certat/intelmq](https://github.com/certat/intelmq)
|
|
42
|
+
as well as newer solutions.
|
|
37
43
|
|
|
38
44
|
## Usage
|
|
39
45
|
|
|
40
|
-
Install the package on the machine
|
|
41
|
-
|
|
46
|
+
Install the package on the machine or virtualenv, where you have the IntelMQ, using
|
|
47
|
+
`pip install intelmq-extensions`. Then, the bots will be available as any other IntelMQ
|
|
48
|
+
bot in the Manager as well to import using `intelmq.bots.*.certat` namespace, e.g.
|
|
49
|
+
`intelmq.bots.experts.certat.vulnerability_lookup.expert`
|
|
42
50
|
|
|
51
|
+
## Documentation
|
|
43
52
|
|
|
53
|
+
There is a limited documentation available. Consult bot Python code to see information
|
|
54
|
+
about the usage and available configuration.
|
|
44
55
|
|
|
45
56
|
## Running tests
|
|
46
57
|
|
|
@@ -61,3 +72,9 @@ This package comes with test runners configured using `tox`. To use them:
|
|
|
61
72
|
tox -efull-with-docker -- intelmq_extensions/tests/bots/experts/squelcher/test_expert.py::TestSquelcherExpertBot::test_address_match1
|
|
62
73
|
|
|
63
74
|
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
Part of the development was financed by the European Union.
|
|
79
|
+
|
|
80
|
+

|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# IntelMQ Extensions
|
|
2
|
+
|
|
3
|
+
[](https://github.com/certat/intelmq-extensions/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
This project collects customized bots and some helper scripts for
|
|
6
|
+
[IntelMQ](https://github.com/certtools/intelmq) used primary by CERT.at.
|
|
7
|
+
|
|
8
|
+
It's a combination of customization previously available in [certat/intelmq](https://github.com/certat/intelmq)
|
|
9
|
+
as well as newer solutions.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Install the package on the machine or virtualenv, where you have the IntelMQ, using
|
|
14
|
+
`pip install intelmq-extensions`. Then, the bots will be available as any other IntelMQ
|
|
15
|
+
bot in the Manager as well to import using `intelmq.bots.*.certat` namespace, e.g.
|
|
16
|
+
`intelmq.bots.experts.certat.vulnerability_lookup.expert`
|
|
17
|
+
|
|
18
|
+
## Documentation
|
|
19
|
+
|
|
20
|
+
There is a limited documentation available. Consult bot Python code to see information
|
|
21
|
+
about the usage and available configuration.
|
|
22
|
+
|
|
23
|
+
## Running tests
|
|
24
|
+
|
|
25
|
+
This package comes with test runners configured using `tox`. To use them:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
|
|
29
|
+
tox -elint # run code style checks
|
|
30
|
+
tox -epy310 # run simple unittests against Python 3.10
|
|
31
|
+
|
|
32
|
+
# For running all unittests, including connecting to external services / database
|
|
33
|
+
# use on of the following:
|
|
34
|
+
tox -efull # assuming you run redis, postgres etc. on your own
|
|
35
|
+
tox -efull-with-docker # this will use docker compose to provision services for tests;
|
|
36
|
+
# please note it uses default ports
|
|
37
|
+
|
|
38
|
+
# You can pass arguments to the pytest, e.g. to run a specific test:
|
|
39
|
+
tox -efull-with-docker -- intelmq_extensions/tests/bots/experts/squelcher/test_expert.py::TestSquelcherExpertBot::test_address_match1
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
Part of the development was financed by the European Union.
|
|
46
|
+
|
|
47
|
+

|
|
@@ -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
|
|
@@ -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
|
{intelmq_extensions-1.8.1 → intelmq_extensions-1.10.0}/intelmq_extensions/etc/harmonization.conf
RENAMED
|
@@ -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": "
|
|
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
|
|
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
|
|
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
|
-
"
|
|
237
|
-
"description": "Request Tracker Incident Response
|
|
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
|
-
"
|
|
253
|
-
"description": "
|
|
254
|
-
"
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
"
|
|
426
|
-
"description": "Request Tracker Incident Response
|
|
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)
|