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,137 @@
1
+ """Tests for DISP Collector
2
+
3
+ SPDX-FileCopyrightText: 2023 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 freezegun import freeze_time
14
+
15
+ from intelmq_extensions.bots.collectors.disp.collector import DISPCollectorBot
16
+
17
+ from ....base import BotTestCase
18
+ from .base import DISP_APIMockMixIn
19
+
20
+
21
+ @freeze_time("2023-09-19 12:18")
22
+ class TestDISPCollectorBot(BotTestCase, DISP_APIMockMixIn, unittest.TestCase):
23
+ @classmethod
24
+ def set_bot(cls):
25
+ cls.bot_reference = DISPCollectorBot
26
+ cls.sysconfig = {
27
+ "api_url": "https://disp.example.at/v1",
28
+ "auth_token": "XXX",
29
+ "oauth_clientid": "client1",
30
+ "oauth_clientsecret": "secret1",
31
+ "ouath_url": "https://oauthip.example.at/v1",
32
+ "mark_as_read": False,
33
+ "wait_for_evidences": True,
34
+ "check_last": "7 days",
35
+ "code": "feed-code",
36
+ }
37
+
38
+ def catch_http_session(self):
39
+ session = requests.Session()
40
+ session_mock = mock.patch(
41
+ "intelmq_extensions.bots.collectors.disp.collector.create_request_session",
42
+ return_value=session,
43
+ )
44
+ session_mock.start()
45
+ self.addCleanup(session_mock.stop)
46
+ return session
47
+
48
+ def setUp(self) -> None:
49
+ super().setUp()
50
+ session = self.catch_http_session()
51
+ self.setup_api_mock(session=session)
52
+ self._mock_incidents()
53
+
54
+ def test_no_incidents(self):
55
+ self.run_bot()
56
+
57
+ self.assertOutputQueueLen(0)
58
+ self.assertEqual(1, self.requests.call_count)
59
+
60
+ def test_ignore_incidents_without_evidences(self):
61
+ self.add_incident(evidence_id=None)
62
+
63
+ self.run_bot()
64
+
65
+ self.assertOutputQueueLen(0)
66
+ self.assertEqual(1, self.requests.call_count)
67
+
68
+ def test_generate_event_without_waiting_for_evidence(self):
69
+ self.add_incident(evidence_id=None)
70
+
71
+ self.run_bot(parameters={"wait_for_evidences": False})
72
+
73
+ self.assertOutputQueueLen(1)
74
+
75
+ def _create_report_dict(self, incident_data: str, evidence_data: str, **kwargs):
76
+ report = message.Report(kwargs, harmonization=self.harmonization)
77
+ data = {"incident": incident_data, "evidences": evidence_data}
78
+ report.add("feed.name", "Test Bot")
79
+ report.add("feed.accuracy", 100.0)
80
+ report.add("feed.code", "feed-code")
81
+ report.add("raw", json.dumps(data))
82
+ return report.to_dict(with_type=True)
83
+
84
+ def test_generate_event(self):
85
+ incident_id, incident_data = self.add_incident(evidence_id="file-1")
86
+ self.mock_evidence(incident_id, "file-1", {"my": "evidence"})
87
+
88
+ self.run_bot()
89
+
90
+ self.assertOutputQueueLen(1)
91
+ self.assertMessageEqual(
92
+ 0, self._create_report_dict(incident_data, {"my": "evidence"})
93
+ )
94
+
95
+ def test_mark_as_read(self):
96
+ incident_id, _ = self.add_incident(evidence_id="file-1")
97
+ self.mock_evidence(incident_id, "file-1", {"my": "evidence"})
98
+ self.mock_request(f"incident/read?id={incident_id}&read=true", method="post")
99
+
100
+ self.run_bot(parameters={"mark_as_read": True})
101
+
102
+ self.assertOutputQueueLen(1)
103
+ self.assertIn("incident/read", self.requests.last_request.path)
104
+ self.assertIn(f"id={incident_id}&read=true", self.requests.last_request.query)
105
+
106
+ def test_mask_password(self):
107
+ incident_id, incident_data = self.add_incident(evidence_id="file-1")
108
+ self.mock_evidence(
109
+ incident_id,
110
+ "file-1",
111
+ {
112
+ "credentials": [
113
+ {"password": "123"},
114
+ {"password": "ab"},
115
+ {"password": ""},
116
+ {"password": "mysuperpassword"},
117
+ ]
118
+ },
119
+ )
120
+
121
+ self.run_bot()
122
+
123
+ self.assertOutputQueueLen(1)
124
+ self.assertMessageEqual(
125
+ 0,
126
+ self._create_report_dict(
127
+ incident_data,
128
+ {
129
+ "credentials": [
130
+ {"password": "***"},
131
+ {"password": "**"},
132
+ {"password": ""},
133
+ {"password": "mys************"},
134
+ ]
135
+ },
136
+ ),
137
+ )
@@ -0,0 +1,10 @@
1
+ """
2
+ Test for the XMPP Collector Bot
3
+ """
4
+
5
+ import os
6
+
7
+ if os.environ.get("INTELMQ_TEST_EXOTIC"):
8
+ import intelmq_extensions.bots.collectors.xmpp.collector # noqa
9
+
10
+ # This file is a stub.
File without changes
@@ -0,0 +1,176 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Testing certat_contact
4
+ """
5
+ import os
6
+ import unittest
7
+
8
+ import intelmq.lib.test as test
9
+
10
+ from intelmq_extensions.bots.experts.certat_contact_intern.expert import (
11
+ CERTatContactExpertBot,
12
+ )
13
+
14
+ from ....base import POSTGRES_CONFIG, BotTestCase
15
+
16
+ if os.environ.get("INTELMQ_TEST_DATABASES"):
17
+ import psycopg2
18
+ from psycopg2 import sql
19
+
20
+ INPUT1 = {
21
+ "__type": "Event",
22
+ "source.asn": 64496,
23
+ "time.observation": "2015-01-01T00:00:00+00:00",
24
+ "feed.code": "another-feed-code",
25
+ }
26
+ OUTPUT1 = {
27
+ "__type": "Event",
28
+ "source.asn": 64496,
29
+ "source.abuse_contact": "cert@example.com",
30
+ "feed.code": "another-feed-code",
31
+ "time.observation": "2015-01-01T00:00:00+00:00",
32
+ "destination_visible": True,
33
+ }
34
+ INPUT2 = {
35
+ "__type": "Event",
36
+ "source.asn": 64496,
37
+ "time.observation": "2015-01-01T00:00:00+00:00",
38
+ "feed.code": "example-feed",
39
+ }
40
+ OUTPUT2 = {
41
+ "__type": "Event",
42
+ "source.asn": 64496,
43
+ "source.abuse_contact": "cert@example.com",
44
+ "time.observation": "2015-01-01T00:00:00+00:00",
45
+ "feed.code": "example-feed",
46
+ "destination_visible": False,
47
+ }
48
+ OUTPUT3 = {
49
+ "__type": "Event",
50
+ "source.asn": 64496,
51
+ "source.abuse_contact": "cert@example.com",
52
+ "time.observation": "2015-01-01T00:00:00+00:00",
53
+ "feed.code": "example-feed",
54
+ "destination_visible": True,
55
+ }
56
+ INPUT4 = {
57
+ "__type": "Event",
58
+ "source.asn": 64497,
59
+ "time.observation": "2015-01-01T00:00:00+00:00",
60
+ "feed.code": "another-feed-code",
61
+ }
62
+ OUTPUT4 = {
63
+ "__type": "Event",
64
+ "source.asn": 64497,
65
+ "time.observation": "2015-01-01T00:00:00+00:00",
66
+ "feed.code": "another-feed-code",
67
+ "destination_visible": True,
68
+ }
69
+
70
+
71
+ @test.skip_database()
72
+ class TestCERTatContactExpertBot(BotTestCase, unittest.TestCase):
73
+ """
74
+ A TestCase for CERTatContactExpertBot.
75
+ """
76
+
77
+ @classmethod
78
+ def set_bot(cls):
79
+ cls.bot_reference = CERTatContactExpertBot
80
+ cls.default_input_message = INPUT1
81
+ if not os.environ.get("INTELMQ_TEST_DATABASES"):
82
+ return
83
+ cls.sysconfig = {
84
+ "autocommit": True,
85
+ "ascolumn": "asn",
86
+ "column": "contact",
87
+ "feed_code": "example-feed",
88
+ "table": "test_contacts",
89
+ "overwrite": False,
90
+ }
91
+ cls.sysconfig.update(POSTGRES_CONFIG)
92
+ cls.con = psycopg2.connect(
93
+ database=cls.sysconfig["database"],
94
+ user=cls.sysconfig["user"],
95
+ password=cls.sysconfig["password"],
96
+ host=cls.sysconfig["host"],
97
+ port=cls.sysconfig["port"],
98
+ sslmode=cls.sysconfig["sslmode"],
99
+ )
100
+ cls.con.autocommit = True
101
+ cls.cur = cls.con.cursor()
102
+ cls.create_if_not_exists(cls)
103
+ cls.truncate(cls)
104
+
105
+ def create_if_not_exists(self):
106
+ self.cur.execute(
107
+ sql.SQL(
108
+ "CREATE TABLE IF NOT EXISTS {} ({} integer, {} text, {} boolean);"
109
+ ).format(
110
+ sql.Identifier(self.sysconfig["table"]),
111
+ sql.Identifier(self.sysconfig["ascolumn"]),
112
+ sql.Identifier(self.sysconfig["column"]),
113
+ sql.Identifier(
114
+ "can-see-tlp-amber_{}".format(self.sysconfig["feed_code"])
115
+ ),
116
+ ),
117
+ )
118
+
119
+ def truncate(self):
120
+ self.cur.execute("TRUNCATE TABLE {}".format(self.sysconfig["table"]))
121
+
122
+ def insert(self, asn, contact, tlp_amber):
123
+ query = """
124
+ INSERT INTO {table}(
125
+ "{ascolumn}", "{column}", "can-see-tlp-amber_{feed_code}"
126
+ ) VALUES (%s, %s, %s)
127
+ """.format(
128
+ table=self.sysconfig["table"],
129
+ column=self.sysconfig["column"],
130
+ feed_code=self.sysconfig["feed_code"],
131
+ ascolumn=self.sysconfig["ascolumn"],
132
+ )
133
+ self.cur.execute(query, (asn, contact, tlp_amber))
134
+
135
+ def test_simple(self):
136
+ "simple query"
137
+ self.insert(64496, "cert@example.com", False)
138
+ self.input_message = INPUT1
139
+ self.run_bot()
140
+ self.assertMessageEqual(0, OUTPUT1)
141
+
142
+ def test_special_feed(self):
143
+ "query with special feed code"
144
+ self.insert(64496, "cert@example.com", False)
145
+ self.input_message = INPUT2
146
+ self.run_bot()
147
+ self.assertMessageEqual(0, OUTPUT2)
148
+
149
+ def test_special_feed_amber(self):
150
+ "query with special feed code"
151
+ self.insert(64496, "cert@example.com", True)
152
+ self.input_message = INPUT2
153
+ self.run_bot()
154
+ self.assertMessageEqual(0, OUTPUT3)
155
+
156
+ def test_no_result_destination_visible(self):
157
+ "check if destination_visible to set to true if there's no match in the DB"
158
+ self.input_message = INPUT4
159
+ self.run_bot()
160
+ self.assertMessageEqual(0, OUTPUT4)
161
+
162
+ def tearDown(self):
163
+ self.truncate()
164
+ super().tearDown()
165
+
166
+ @classmethod
167
+ def tearDownClass(cls):
168
+ if not os.environ.get("INTELMQ_TEST_DATABASES"):
169
+ return
170
+ cls.truncate(cls)
171
+ cls.cur.close()
172
+ cls.con.close()
173
+
174
+
175
+ if __name__ == "__main__": # pragma: no cover
176
+ unittest.main()
@@ -0,0 +1,42 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Testing Copy Extra expert bot.
4
+ """
5
+
6
+ import unittest
7
+
8
+ from intelmq_extensions.bots.experts.copy_extra.expert import CopyExtraExpertBot
9
+
10
+ from ....base import BotTestCase
11
+
12
+ INPUT = {
13
+ "__type": "Event",
14
+ "time.observation": "2015-01-01T00:00:00+00:00",
15
+ "extra.deviceid": "foo",
16
+ "extra.devicerev": "bar",
17
+ "extra.firmwarerev": 1,
18
+ }
19
+ OUTPUT = INPUT.copy()
20
+ OUTPUT["shareable_extra_info"] = '{"deviceid": "foo", "firmwarerev": 1}'
21
+
22
+
23
+ class TestCopyExtraExpertBot(BotTestCase, unittest.TestCase):
24
+ """
25
+ A TestCase for CopyExtraExpertBot.
26
+ """
27
+
28
+ @classmethod
29
+ def set_bot(cls):
30
+ cls.bot_reference = CopyExtraExpertBot
31
+ cls.sysconfig = {"keys": ["deviceid", "firmwarerev"]}
32
+ cls.default_input_message = {"__type": "Event"}
33
+
34
+ def test_events(self):
35
+ """Test if correct Events have been produced."""
36
+ self.input_message = INPUT
37
+ self.run_bot()
38
+ self.assertMessageEqual(0, OUTPUT)
39
+
40
+
41
+ if __name__ == "__main__":
42
+ unittest.main()
@@ -0,0 +1,302 @@
1
+ """Testing events splitter
2
+
3
+ SPDX-FileCopyrightText: 2024 CERT.at GmbH <https://cert.at/>
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+ """
6
+
7
+ import json
8
+ import unittest
9
+
10
+ from intelmq_extensions.bots.experts.event_group_splitter.expert import (
11
+ EventGroupSplitterExpertBot,
12
+ )
13
+
14
+ from ....base import BotTestCase
15
+
16
+ INPUT = {
17
+ "__type": "Event",
18
+ "classification.identifier": "zeus",
19
+ "classification.type": "infected-system",
20
+ "notify": False,
21
+ "source.asn": 1,
22
+ "source.ip": "192.0.2.1",
23
+ "feed.name": "Example Feed",
24
+ "feed.code": "feed-1",
25
+ }
26
+
27
+
28
+ class TestEventGroupSplitter(BotTestCase, unittest.TestCase):
29
+ "Test cases prepared to handle tagging in ShadowServer Compromised Website report"
30
+
31
+ @classmethod
32
+ def set_bot(cls):
33
+ cls.bot_reference = EventGroupSplitterExpertBot
34
+ cls.sysconfig = {
35
+ "look_in": "extra.tag",
36
+ "copy_to": ["classification.identifier"],
37
+ "regex": r"([a-zA-Z0-9\-]+)[;]{0,1}",
38
+ "ignore": ["tag2", "tag4"],
39
+ "groups_file": None,
40
+ }
41
+
42
+ def setUp(self) -> None:
43
+ super().setUp()
44
+ self.create_dynamic_groups_file()
45
+
46
+ def create_dynamic_groups_file(self):
47
+ self.groups_file_path = f"{self.tmp_dir}/groups.json"
48
+ groups = {
49
+ "feed-1": [
50
+ ["citrix", "injected-code"],
51
+ ["backdoor-activity", "ivanti-connect-secure"],
52
+ ["ivanti-connect-secure", "credential-stealer", "injected-code"],
53
+ ],
54
+ "feed-3": [["test"]],
55
+ }
56
+ with open(self.groups_file_path, "w+") as f:
57
+ json.dump(groups, f)
58
+
59
+ def test_ignore_selected_tags(self):
60
+ message = {
61
+ **INPUT,
62
+ "extra.tag": "tag1,tag2,tag3,tag4",
63
+ }
64
+ self.input_message = message
65
+
66
+ self.run_bot(parameters={"groups_file": self.groups_file_path})
67
+
68
+ self.assertOutputQueueLen(1)
69
+ self.assertMessageEqual(
70
+ 0,
71
+ {
72
+ **message,
73
+ "classification.identifier": "tag1-tag3",
74
+ },
75
+ )
76
+
77
+ def test_handle_configured_groups(self):
78
+ message = {
79
+ **INPUT,
80
+ "extra.tag": "citrix;injected-code;backdoor-activity;ivanti-connect-secure",
81
+ }
82
+ self.input_message = message
83
+
84
+ self.run_bot(parameters={"groups_file": self.groups_file_path})
85
+
86
+ self.assertOutputQueueLen(2)
87
+ self.assertMessageEqual(
88
+ 0,
89
+ {
90
+ **message,
91
+ "classification.identifier": "citrix-injected-code",
92
+ },
93
+ )
94
+ self.assertMessageEqual(
95
+ 1,
96
+ {
97
+ **message,
98
+ "classification.identifier": "backdoor-activity-ivanti-connect-secure",
99
+ },
100
+ )
101
+
102
+ def test_generate_new_groups(self):
103
+ message = {
104
+ **INPUT,
105
+ "extra.tag": "injected-code;cisco;backdoor-activity;ivanti-connect-secure",
106
+ }
107
+ self.input_message = message
108
+
109
+ self.run_bot(parameters={"groups_file": self.groups_file_path})
110
+
111
+ self.assertOutputQueueLen(2)
112
+ self.assertMessageEqual(
113
+ 0,
114
+ {
115
+ **message,
116
+ "classification.identifier": "backdoor-activity-ivanti-connect-secure",
117
+ },
118
+ )
119
+ self.assertMessageEqual(
120
+ 1,
121
+ {
122
+ **message,
123
+ "classification.identifier": "cisco-injected-code",
124
+ },
125
+ )
126
+
127
+ def test_generate_new_groups_when_duplicates(self):
128
+ message = {
129
+ **INPUT,
130
+ "extra.tag": "citrix;injected-code;injected-code;cisco",
131
+ }
132
+ self.input_message = message
133
+
134
+ self.run_bot(parameters={"groups_file": self.groups_file_path})
135
+
136
+ self.assertOutputQueueLen(2)
137
+ self.assertMessageEqual(
138
+ 0,
139
+ {
140
+ **message,
141
+ "classification.identifier": "citrix-injected-code",
142
+ },
143
+ )
144
+ self.assertMessageEqual(
145
+ 1,
146
+ {
147
+ **message,
148
+ "classification.identifier": "cisco-injected-code",
149
+ },
150
+ )
151
+
152
+ with open(self.groups_file_path) as f:
153
+ new_groups = json.load(f)["feed-1"]
154
+ self.assertEqual(4, len(new_groups))
155
+ self.assertIn(set(["cisco", "injected-code"]), (set(g) for g in new_groups))
156
+
157
+ def test_new_groups_are_kept(self):
158
+ message = {
159
+ **INPUT,
160
+ "extra.tag": "injected-code;cisco",
161
+ }
162
+ self.input_message = message
163
+
164
+ self.run_bot(parameters={"groups_file": self.groups_file_path})
165
+
166
+ self.assertOutputQueueLen(1)
167
+ self.assertMessageEqual(
168
+ 0,
169
+ {
170
+ **message,
171
+ "classification.identifier": "cisco-injected-code",
172
+ },
173
+ )
174
+
175
+ with open(self.groups_file_path) as f:
176
+ new_groups = json.load(f)["feed-1"]
177
+ self.assertIn(set(["cisco", "injected-code"]), (set(g) for g in new_groups))
178
+
179
+ message = {
180
+ **INPUT,
181
+ "extra.tag": "cisco;injected-code;new-tag",
182
+ }
183
+ self.input_message = message
184
+
185
+ self.run_bot(parameters={"groups_file": self.groups_file_path})
186
+ self.assertOutputQueueLen(2)
187
+ self.assertMessageEqual(
188
+ 0,
189
+ {
190
+ **message,
191
+ "classification.identifier": "cisco-injected-code",
192
+ },
193
+ )
194
+ self.assertMessageEqual(
195
+ 1,
196
+ {
197
+ **message,
198
+ "classification.identifier": "new-tag",
199
+ },
200
+ )
201
+
202
+ with open(self.groups_file_path) as f:
203
+ new_groups = json.load(f)["feed-1"]
204
+ self.assertEqual(5, len(new_groups))
205
+ self.assertIn(set(["cisco", "injected-code"]), (set(g) for g in new_groups))
206
+ self.assertIn(set(["new-tag"]), (set(g) for g in new_groups))
207
+
208
+ def test_groups_are_separated_between_feeds(self):
209
+ message = {
210
+ **INPUT,
211
+ "extra.tag": "citrix,injected-code",
212
+ "feed.code": "feed-2",
213
+ }
214
+ self.input_message = message
215
+
216
+ self.run_bot(parameters={"groups_file": self.groups_file_path})
217
+
218
+ self.assertOutputQueueLen(1)
219
+ self.assertMessageEqual(
220
+ 0,
221
+ {
222
+ **message,
223
+ "classification.identifier": "citrix-injected-code",
224
+ },
225
+ )
226
+
227
+ with open(self.groups_file_path) as f:
228
+ new_groups = json.load(f)
229
+ self.assertEqual(3, len(new_groups["feed-1"]))
230
+ self.assertEqual(1, len(new_groups["feed-2"]))
231
+ self.assertIn(
232
+ set(["citrix", "injected-code"]), (set(g) for g in new_groups["feed-2"])
233
+ )
234
+
235
+ def test_groups_are_not_separated_between_feeds_if_set(self):
236
+ message = {
237
+ **INPUT,
238
+ "extra.tag": "citrix,injected-code",
239
+ "feed.code": "feed-2",
240
+ }
241
+ self.input_message = message
242
+
243
+ self.prepare_bot(
244
+ parameters={
245
+ "groups_file": self.groups_file_path,
246
+ "treat_feeds_separately": False,
247
+ },
248
+ destination_queues=["_default", "new_tag_groups"],
249
+ )
250
+ self.run_bot(prepare=False)
251
+
252
+ self.assertOutputQueueLen(1)
253
+ self.assertOutputQueueLen(0, "new_tag_groups")
254
+ self.assertMessageEqual(
255
+ 0,
256
+ {
257
+ **message,
258
+ "classification.identifier": "citrix-injected-code",
259
+ },
260
+ )
261
+
262
+ with open(self.groups_file_path) as f:
263
+ new_groups = json.load(f)
264
+ self.assertEqual(3, len(new_groups["feed-1"]))
265
+ self.assertNotIn("feed-2", new_groups)
266
+
267
+ def test_events_with_new_tag_groups_are_sent_to_specific_path(self):
268
+ message_with_old_tag = {**INPUT, "extra.tag": "citrix,injected-code"}
269
+ self.input_message = message_with_old_tag
270
+
271
+ self.prepare_bot(
272
+ parameters={"groups_file": self.groups_file_path},
273
+ destination_queues=["_default", "new_tag_groups"],
274
+ )
275
+ self.run_bot(prepare=False)
276
+
277
+ self.assertOutputQueueLen(1)
278
+ self.assertOutputQueueLen(0, "new_tag_groups")
279
+
280
+ message_with_new_tags = {**INPUT, "extra.tag": "new,group"}
281
+ self.input_message = message_with_new_tags
282
+
283
+ self.prepare_bot(
284
+ parameters={"groups_file": self.groups_file_path},
285
+ destination_queues=["_default", "new_tag_groups"],
286
+ )
287
+ self.run_bot(prepare=False)
288
+
289
+ self.assertOutputQueueLen(1)
290
+ self.assertOutputQueueLen(1, "new_tag_groups")
291
+
292
+ # new_tag_groups should be notified only once
293
+ self.input_message = message_with_new_tags
294
+
295
+ self.prepare_bot(
296
+ parameters={"groups_file": self.groups_file_path},
297
+ destination_queues=["_default", "new_tag_groups"],
298
+ )
299
+ self.run_bot(prepare=False)
300
+
301
+ self.assertOutputQueueLen(1)
302
+ self.assertOutputQueueLen(0, "new_tag_groups")