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,670 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Utilities for intelmqcli.
4
+
5
+ Static data (queries)
6
+ """
7
+ import argparse
8
+ import copy
9
+ import datetime
10
+ import json
11
+ import os
12
+ import subprocess
13
+ import sys
14
+
15
+ import intelmq
16
+ import intelmq.lib.utils as utils
17
+ import pkg_resources
18
+ import psycopg2
19
+ import psycopg2.extras
20
+ import rt
21
+ from intelmq import CONFIG_DIR
22
+
23
+ INTELMQCLI_CONF_FILE = os.path.join(CONFIG_DIR, "intelmqcli.conf")
24
+
25
+
26
+ __all__ = [
27
+ "BASE_WHERE",
28
+ "CSV_FIELDS",
29
+ "EPILOG",
30
+ "QUERY_DISTINCT_CONTACTS_BY_INCIDENT",
31
+ "QUERY_EVENTS_BY_ASCONTACT_INCIDENT",
32
+ "QUERY_FEED_NAMES",
33
+ "QUERY_GET_TEXT",
34
+ "QUERY_IDENTIFIER_NAMES",
35
+ "QUERY_INSERT_CONTACT",
36
+ "QUERY_OPEN_EVENTS_BY_FEEDCODE",
37
+ "QUERY_HALF_PROC_INCIDENTS",
38
+ "QUERY_OPEN_EVENT_IDS_BY_TAXONOMY",
39
+ "QUERY_OPEN_EVENT_REPORTS_BY_TAXONOMY",
40
+ "QUERY_OPEN_FEEDCODES",
41
+ "QUERY_OPEN_TAXONOMIES",
42
+ "QUERY_TAXONOMY_NAMES",
43
+ "QUERY_TEXT_NAMES",
44
+ "QUERY_TYPE_NAMES",
45
+ "QUERY_UPDATE_CONTACT",
46
+ "USAGE",
47
+ "getTerminalHeight",
48
+ "IntelMQCLIContollerTemplate",
49
+ ]
50
+
51
+ EPILOG = """
52
+ Searches for all unprocessed incidents. Incidents will be filtered by country
53
+ code and the TLD of a domain according to configuration.
54
+ The search can be restricted to one source feed.
55
+
56
+ After the start, intelmqcli will immediately connect to RT with the given
57
+ credentials. The incidents will be shown grouped by the contact address if
58
+ known or the ASN otherwise.
59
+
60
+ You have 3 options here:
61
+ * Select one group by giving the id (number in first column) and show the email
62
+ and all events in detail
63
+ * Automatic sending of all incidents with 'a'
64
+ * Quit with 'q'
65
+
66
+ For the detailed view, the recipient, the subject and the mail text will be
67
+ shown, and below the technical data as csv. If the terminal is not big enough,
68
+ the data will not be shown in full. In this case, you can press 't' for the
69
+ table mode. less will be opened with the full text and data, whereas the data
70
+ will be formated as table, which is much easier to read and interpret.
71
+ The requestor (recipient of the mail) can be changed manually by pressing 'r'
72
+ and in the following prompt the address is asked. After sending, you can
73
+ optionally save the (new) address to the database linked to the ASNs.
74
+ If you are ready to submit the incidents to RT and send the mails out, press
75
+ 's'.
76
+ 'b' for back jumps to the incident overview and 'q' quits.
77
+
78
+ Exit codes:
79
+ 0 if no errors happend or only errors which could be handled. Check the
80
+ output if recoverable errors happened.
81
+ 1 if unrecoverable errors happened
82
+ 2 if user input or configuration is faulty
83
+ """
84
+ USAGE = """
85
+ intelmqcli
86
+ intelmqcli --dry-run
87
+ intelmqcli --verbose
88
+ intelmqcli --batch
89
+ intelmqcli --quiet
90
+ intelmqcli --compress-csv
91
+ intelmqcli --list-feeds
92
+ intelmqcli --list-identifiers
93
+ intelmqcli --list-taxonomies
94
+ intelmqcli --taxonomy='taxonomy'
95
+ intelmqcli --type='type'
96
+ intelmqcli --identifier='identifier'
97
+ intelmqcli --list-types
98
+ intelmqcli --list-texts
99
+ intelmqcli --text='boilerplate name'
100
+ intelmqcli --feed='feedcode' """
101
+
102
+ SUBJECT = {
103
+ "abusive-content": "Abusive content (spam, ...)",
104
+ "malicious code": "Malicious code (malware, botnet, ...)",
105
+ "malicious-code": "Malicious code (malware, botnet, ...)",
106
+ "information-gathering": "Information Gathering (scanning, ...)",
107
+ "intrusion-attempts": "Intrusion Attempt",
108
+ "intrusions": "Network intrusion",
109
+ "availability": "Availability (DDOS, ...)",
110
+ "information-content-security": "Information Content Security (dropzone,...)",
111
+ "fraud": "Fraud",
112
+ "vulnerable": "Vulnerable device",
113
+ "other": "Other",
114
+ "test": "Test",
115
+ }
116
+
117
+ QUERY_FEED_NAMES = 'SELECT DISTINCT "feed.code" from {events}'
118
+
119
+ QUERY_IDENTIFIER_NAMES = 'SELECT DISTINCT "classification.identifier" from {events}'
120
+
121
+ QUERY_TAXONOMY_NAMES = 'SELECT DISTINCT "classification.taxonomy" from {events}'
122
+
123
+ QUERY_TYPE_NAMES = 'SELECT DISTINCT "classification.type" from {events}'
124
+
125
+ QUERY_TEXT_NAMES = 'SELECT DISTINCT "key" from {boilerplates}'
126
+
127
+ """ This is the list of fields (and their respective order) which we intend to
128
+ send out. This is based on the order and fields of shadowserver.
129
+
130
+ Shadowserver format:
131
+ timestamp,"ip","protocol","port","hostname","packets","size","asn","geo","region","city","naics","sic","sector"
132
+ """
133
+ CSV_FIELDS = [
134
+ "time.source",
135
+ "source.ip",
136
+ "protocol.transport",
137
+ "source.port",
138
+ "protocol.application",
139
+ "source.fqdn",
140
+ "source.local_hostname",
141
+ "source.local_ip",
142
+ "source.url",
143
+ "source.asn",
144
+ "source.geolocation.cc",
145
+ "source.geolocation.city",
146
+ "classification.taxonomy",
147
+ "classification.type",
148
+ "classification.identifier",
149
+ "destination.ip",
150
+ "destination.port",
151
+ "destination.fqdn",
152
+ "destination.url",
153
+ "feed",
154
+ "event_description.text",
155
+ "event_description.url",
156
+ "malware.name",
157
+ "extra",
158
+ "comment",
159
+ "additional_field_freetext",
160
+ "feed.documentation",
161
+ "version: 1.2",
162
+ ]
163
+
164
+ QUERY_UPDATE_CONTACT = """
165
+ UPDATE as_contacts SET
166
+ contacts = %s
167
+ WHERE
168
+ asnum = %s
169
+ """
170
+
171
+ QUERY_INSERT_CONTACT = """
172
+ INSERT INTO as_contacts (
173
+ asnum, contacts, comment, unreliable
174
+ ) VALUES (
175
+ %s, %s, %s, FALSE
176
+ )
177
+ """
178
+
179
+ QUERY_GET_TEXT = """
180
+ SELECT
181
+ body
182
+ FROM {texttab}
183
+ WHERE
184
+ key = %s
185
+ """
186
+
187
+ _BASE_FILTERS = """
188
+ "notify" = TRUE AND
189
+ "time.source" >= now() - interval %s AND
190
+ "sent_at" IS NULL AND
191
+ "feed.code" IS NOT NULL AND
192
+ "classification.taxonomy" IS NOT NULL AND
193
+ (UPPER("source.geolocation.cc") = %s OR lower("source.fqdn") similar to %s
194
+ OR lower("source.reverse_dns") similar to %s {asset})
195
+ """
196
+ BASE_WHERE = (
197
+ """
198
+ "source.abuse_contact" IS NOT NULL AND
199
+ """
200
+ + _BASE_FILTERS
201
+ )
202
+ _MONITORED_ASSETS_FILTER = """
203
+ OR "extra" ->> 'monitored_asset' != ''
204
+ """
205
+ # PART 1: CREATE REPORTS
206
+ _OPEN_FEEDCODES = """
207
+ SELECT
208
+ DISTINCT "feed.code"
209
+ FROM "{events}"
210
+ WHERE
211
+ "rtir_report_id" IS NULL AND
212
+ """
213
+ QUERY_OPEN_FEEDCODES = _OPEN_FEEDCODES + BASE_WHERE
214
+ _OPEN_EVENTS = """
215
+ SELECT *
216
+ FROM "{events}"
217
+ WHERE
218
+ "feed.code" = %s AND
219
+ "rtir_report_id" IS NULL AND
220
+ """
221
+ QUERY_OPEN_EVENTS_BY_FEEDCODE = _OPEN_EVENTS + BASE_WHERE
222
+ # For internal-only notifications, ignore abuse data
223
+ INTERNAL_QUERY_OPEN_FEEDCODES = _OPEN_FEEDCODES + _BASE_FILTERS
224
+ INTERNAL_QUERY_OPEN_EVENTS_BY_FEEDCODE = _OPEN_EVENTS + _BASE_FILTERS
225
+ # PART 2: INCIDENTS
226
+ QUERY_OPEN_TAXONOMIES = (
227
+ """
228
+ SELECT
229
+ DISTINCT "classification.taxonomy"
230
+ FROM "{events}"
231
+ WHERE
232
+ "rtir_report_id" IS NOT NULL AND
233
+ "rtir_incident_id" IS NULL AND
234
+ """
235
+ + BASE_WHERE
236
+ )
237
+ QUERY_OPEN_EVENT_REPORTS_BY_TAXONOMY = (
238
+ """
239
+ SELECT
240
+ DISTINCT "rtir_report_id"
241
+ FROM "{events}"
242
+ WHERE
243
+ "rtir_report_id" IS NOT NULL AND
244
+ "rtir_incident_id" IS NULL AND
245
+ "classification.taxonomy" = %s AND
246
+ """
247
+ + BASE_WHERE
248
+ )
249
+ QUERY_OPEN_EVENT_IDS_BY_TAXONOMY = (
250
+ """
251
+ SELECT
252
+ "id"
253
+ FROM "{events}"
254
+ WHERE
255
+ "rtir_report_id" IS NOT NULL AND
256
+ "rtir_incident_id" IS NULL AND
257
+ "classification.taxonomy" = %s AND
258
+ """
259
+ + BASE_WHERE
260
+ )
261
+ QUERY_HALF_PROC_INCIDENTS = (
262
+ """
263
+ SELECT
264
+ DISTINCT "rtir_incident_id",
265
+ "classification.taxonomy"
266
+ FROM "{events}"
267
+ WHERE
268
+ "rtir_report_id" IS NOT NULL AND
269
+ "rtir_incident_id" IS NOT NULL AND
270
+ rtir_investigation_id IS NULL AND
271
+ """
272
+ + BASE_WHERE
273
+ )
274
+ # PART 3: INVESTIGATIONS
275
+ QUERY_DISTINCT_CONTACTS_BY_INCIDENT = (
276
+ """
277
+ SELECT
278
+ DISTINCT "source.abuse_contact"
279
+ FROM {events}
280
+ WHERE
281
+ rtir_report_id IS NOT NULL AND
282
+ rtir_incident_id = %s AND
283
+ rtir_investigation_id IS NULL AND
284
+ """
285
+ + BASE_WHERE
286
+ )
287
+ DRY_QUERY_DISTINCT_CONTACTS_BY_TAXONOMY = (
288
+ """
289
+ SELECT
290
+ DISTINCT "source.abuse_contact"
291
+ FROM {events}
292
+ WHERE
293
+ rtir_report_id IS NOT NULL AND
294
+ "rtir_incident_id" IS NULL AND
295
+ rtir_investigation_id IS NULL AND
296
+ "classification.taxonomy" = %s AND
297
+ """
298
+ + BASE_WHERE
299
+ )
300
+ QUERY_EVENTS_BY_ASCONTACT_INCIDENT = (
301
+ """
302
+ SELECT
303
+ to_char("time.source",
304
+ 'YYYY-MM-DD"T"HH24:MI:SSOF') as "time.source",
305
+ id,
306
+ "feed.code" as feed,
307
+ "source.ip",
308
+ "source.port",
309
+ "source.url",
310
+ "source.asn",
311
+ "source.geolocation.cc",
312
+ "source.geolocation.city",
313
+ "source.fqdn",
314
+ "source.local_hostname",
315
+ "source.local_ip",
316
+ "classification.identifier",
317
+ "classification.taxonomy",
318
+ "classification.type",
319
+ "comment",
320
+ "destination.ip",
321
+ "destination.port",
322
+ "destination.fqdn",
323
+ "destination.url",
324
+ "event_description.text",
325
+ "event_description.url",
326
+ "shareable_extra_info" AS "extra",
327
+ "feed.documentation",
328
+ "malware.name",
329
+ "protocol.application",
330
+ "protocol.transport"
331
+ FROM {v_events_filtered}
332
+ WHERE
333
+ rtir_report_id IS NOT NULL AND
334
+ rtir_incident_id = %s AND
335
+ rtir_investigation_id IS NULL AND
336
+ "source.abuse_contact" = %s AND
337
+ """
338
+ + BASE_WHERE
339
+ )
340
+ DRY_QUERY_EVENTS_BY_ASCONTACT_TAXONOMY = (
341
+ QUERY_EVENTS_BY_ASCONTACT_INCIDENT[
342
+ : QUERY_EVENTS_BY_ASCONTACT_INCIDENT.find("WHERE") + 6
343
+ ]
344
+ + """
345
+ rtir_report_id IS NOT NULL AND
346
+ rtir_investigation_id IS NULL AND
347
+ "classification.taxonomy" = %s AND
348
+ "source.abuse_contact" = %s AND
349
+ """
350
+ + BASE_WHERE
351
+ )
352
+
353
+ DEFAULT_TABLES = {
354
+ "events": "events",
355
+ "v_events_filtered": "v_events_filtered",
356
+ "boilerplates": "boilerplates",
357
+ }
358
+
359
+
360
+ def getTerminalHeight():
361
+ try:
362
+ return int(subprocess.check_output(["stty", "size"]).strip().split()[0])
363
+ except Exception:
364
+ return 80 # If running in
365
+
366
+
367
+ class IntelMQCLIContollerTemplate:
368
+ additional_where = ""
369
+ usage = ""
370
+ epilog = ""
371
+ additional_params = ()
372
+ dryrun = False
373
+ quiet = False
374
+
375
+ def __init__(self, overridden_config: dict = None):
376
+ self._asset_filter = ""
377
+ self.overridden_config = overridden_config
378
+
379
+ usage_configuration = (
380
+ "\n\nThe configuration can be found by default at %r."
381
+ % INTELMQCLI_CONF_FILE
382
+ )
383
+
384
+ self.parser = argparse.ArgumentParser(
385
+ prog=self.appname,
386
+ usage=self.usage + usage_configuration,
387
+ epilog=self.epilog,
388
+ formatter_class=argparse.RawDescriptionHelpFormatter,
389
+ )
390
+ VERSION = pkg_resources.get_distribution("intelmq").version
391
+ self.parser.add_argument("--version", action="version", version=VERSION)
392
+ self.parser.add_argument(
393
+ "-v", "--verbose", action="store_true", help="Print verbose messages."
394
+ )
395
+
396
+ self.parser.add_argument(
397
+ "--config", help="Config file path", default=INTELMQCLI_CONF_FILE
398
+ )
399
+
400
+ self.parser.add_argument(
401
+ "-f",
402
+ "--feed",
403
+ nargs="+",
404
+ help="Show only incidents reported by one of the given feeds.",
405
+ )
406
+ self.parser.add_argument(
407
+ "--skip-feed",
408
+ nargs="+",
409
+ help="Skip incidents reported by one of the given feeds.",
410
+ )
411
+ self.parser.add_argument(
412
+ "--taxonomy", nargs="+", help="Select only events with given taxonomy."
413
+ )
414
+ self.parser.add_argument(
415
+ "--skip-taxonomy", nargs="+", help="Skip events with given taxonomy."
416
+ )
417
+ self.parser.add_argument(
418
+ "-a",
419
+ "--asn",
420
+ type=int,
421
+ nargs="+",
422
+ help="Specify one or more AS numbers (integers) to process.",
423
+ )
424
+ self.parser.add_argument(
425
+ "--skip-asn",
426
+ type=int,
427
+ nargs="+",
428
+ help="Specify one or more AS numbers (integers) to skip.",
429
+ )
430
+ self.parser.add_argument(
431
+ "--type",
432
+ nargs="+",
433
+ help="Specify one or more classifications types to process.",
434
+ )
435
+ self.parser.add_argument(
436
+ "--skip-type",
437
+ nargs="+",
438
+ help="Specify one or more classifications types to skip.",
439
+ )
440
+ self.parser.add_argument(
441
+ "--identifier",
442
+ nargs="+",
443
+ help="Specify one or more classifications identifiers to process.",
444
+ )
445
+ self.parser.add_argument(
446
+ "--skip-identifier",
447
+ nargs="+",
448
+ help="Specify one or more classifications identifiers to skip.",
449
+ )
450
+
451
+ self.parser.add_argument(
452
+ "-b",
453
+ "--batch",
454
+ action="store_true",
455
+ help='Run in batch mode (defaults to "yes" to all).',
456
+ )
457
+ self.parser.add_argument(
458
+ "-q",
459
+ "--quiet",
460
+ action="store_true",
461
+ help="Do not output anything, except for error messages."
462
+ " Useful in combination with --batch.",
463
+ )
464
+ self.parser.add_argument(
465
+ "-n",
466
+ "--dry-run",
467
+ action="store_true",
468
+ help="Do not store anything or change anything. Just simulate.",
469
+ )
470
+
471
+ self.parser.add_argument(
472
+ "--time-interval",
473
+ nargs="+",
474
+ default="4 days",
475
+ help="time interval, parseable by postgres." 'defaults to "4 days".',
476
+ )
477
+ self.parser.add_argument(
478
+ "--ton",
479
+ "--time-observation-newer-than",
480
+ nargs=1,
481
+ help="Select only events with 'time.observation' newer than the "
482
+ "given ISO-formatted datetime.",
483
+ )
484
+ self.parser.add_argument(
485
+ "--monitored-assets",
486
+ action="store_true",
487
+ help="Include tickets that has extra.monitored_asset regardless of geo-filters",
488
+ )
489
+ self.parser.add_argument(
490
+ "--stdout-only", action="store_true", help="Log only to stdout"
491
+ )
492
+
493
+ def setup(self, args: list):
494
+ self.args = self.parser.parse_args(args)
495
+
496
+ self.config = self._load_config()
497
+
498
+ if self.args.verbose:
499
+ self.verbose = True
500
+ if self.args.dry_run:
501
+ self.dryrun = True
502
+ if self.args.batch:
503
+ self.batch = True
504
+ if self.args.quiet:
505
+ self.quiet = True
506
+ self.time_interval = "".join(self.args.time_interval)
507
+
508
+ if self.quiet:
509
+ stream = None
510
+ else:
511
+ stream = sys.stderr
512
+
513
+ self.logger = utils.log(
514
+ "intelmqcli",
515
+ syslog=(
516
+ None if self.args.stdout_only else self.config.get("syslog", "/dev/log")
517
+ ),
518
+ log_level=self.config["log_level"].upper(),
519
+ stream=stream,
520
+ log_format_stream="%(message)s",
521
+ log_path=(
522
+ None
523
+ if self.args.stdout_only
524
+ else self.config.get("log_path", intelmq.DEFAULT_LOGGING_PATH)
525
+ ),
526
+ )
527
+ self.logger.info(
528
+ "Started %r at %s.", " ".join(sys.argv), datetime.datetime.now().isoformat()
529
+ )
530
+
531
+ if self.args.feed:
532
+ self.additional_where += """ AND "feed.code" = ANY(%s::VARCHAR[]) """
533
+ self.additional_params += ("{" + ",".join(self.args.feed) + "}",)
534
+ if self.args.skip_feed:
535
+ self.additional_where += """ AND "feed.code" != ANY(%s::VARCHAR[]) """
536
+ self.additional_params += ("{" + ",".join(self.args.skip_feed) + "}",)
537
+ if self.args.asn:
538
+ self.additional_where += """ AND "source.asn" = ANY(%s::INT[]) """
539
+ self.additional_params += ("{" + ",".join(map(str, self.args.asn)) + "}",)
540
+ if self.args.skip_asn:
541
+ self.additional_where += """ AND "source.asn" != ANY(%s::INT[]) """
542
+ self.additional_params += (
543
+ "{" + ",".join(map(str, self.args.skip_asn)) + "}",
544
+ )
545
+ if self.args.taxonomy:
546
+ self.additional_where += (
547
+ """ AND "classification.taxonomy" = ANY(%s::VARCHAR[]) """
548
+ )
549
+ self.additional_params += ("{" + ",".join(self.args.taxonomy) + "}",)
550
+ if self.args.skip_taxonomy:
551
+ self.additional_where += (
552
+ """ AND "classification.taxonomy" != ANY(%s::VARCHAR[]) """
553
+ )
554
+ self.additional_params += ("{" + ",".join(self.args.skip_taxonomy) + "}",)
555
+ if self.args.type:
556
+ self.additional_where += (
557
+ """ AND "classification.type" = ANY(%s::VARCHAR[]) """
558
+ )
559
+ self.additional_params += ("{" + ",".join(self.args.type) + "}",)
560
+ if self.args.skip_type:
561
+ self.additional_where += (
562
+ """ AND "classification.type" != ANY(%s::VARCHAR[]) """
563
+ )
564
+ self.additional_params += ("{" + ",".join(self.args.skip_type) + "}",)
565
+ if self.args.identifier:
566
+ self.additional_where += (
567
+ """ AND "classification.identifier" = ANY(%s::VARCHAR[]) """
568
+ )
569
+ self.additional_params += ("{" + ",".join(self.args.identifier) + "}",)
570
+ if self.args.skip_identifier:
571
+ self.additional_where += (
572
+ """ AND "classification.identifier" != ALL(%s::VARCHAR[]) """
573
+ )
574
+ self.additional_params += ("{" + ",".join(self.args.skip_identifier) + "}",)
575
+ if self.args.ton:
576
+ self.additional_where += """ AND "time.observation" >= %s """
577
+ self.additional_params += (self.args.ton[0],)
578
+ if self.args.monitored_assets:
579
+ self._asset_filter = _MONITORED_ASSETS_FILTER
580
+
581
+ if self.config.get("constituency"):
582
+ conditions = []
583
+ if self.config["constituency"].get("default"):
584
+ conditions.append(""" "constituency" is null """)
585
+
586
+ if key := self.config["constituency"].get("key"):
587
+ conditions.append(""" "constituency" = %s """)
588
+ self.additional_params += (key,)
589
+
590
+ if conditions:
591
+ self.additional_where += f""" AND ({' OR '.join(conditions)}) """
592
+ self.logger.debug("Initializing with constituency: %s", conditions)
593
+
594
+ self.rt = rt.Rt(
595
+ self.config["rt"]["uri"],
596
+ self.config["rt"]["user"],
597
+ self.config["rt"]["password"],
598
+ )
599
+
600
+ def _load_config(self):
601
+ if self.overridden_config:
602
+ return copy.deepcopy(self.overridden_config)
603
+
604
+ with open(self.args.config) as conf_handle:
605
+ return json.load(conf_handle)
606
+
607
+ def connect_database(self):
608
+ self.con = psycopg2.connect(
609
+ database=self.config["database"]["database"],
610
+ user=self.config["database"]["user"],
611
+ password=self.config["database"]["password"],
612
+ host=self.config["database"]["host"],
613
+ port=self.config["database"]["port"],
614
+ sslmode=self.config["database"]["sslmode"],
615
+ )
616
+ self.con.autocommit = False # Starts transaction in the beginning
617
+ self.cur = self.con.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
618
+
619
+ def execute(self, query, parameters=(), extend=True):
620
+ """
621
+ Passes query to database.
622
+
623
+ Parameters:
624
+ extend:
625
+ If True, the parameters for BASE_WHERE are added (time interval,
626
+ country code, FQDN from config, and additional parameters)
627
+ If False, parameters are used as given.
628
+ """
629
+ if extend:
630
+ query = query + self.additional_where
631
+ parameters = (
632
+ parameters
633
+ + (
634
+ self.time_interval,
635
+ self.config["filter"]["cc"],
636
+ "%%.(%s)" % self.config["filter"]["fqdn"],
637
+ "%%.(%s)" % self.config["filter"]["fqdn"],
638
+ )
639
+ + self.additional_params
640
+ )
641
+ query = self._format_query(query)
642
+ self.logger.debug(self.cur.mogrify(query, parameters))
643
+ if not self.dryrun or query.strip().upper().startswith("SELECT"):
644
+ self.cur.execute(query, parameters)
645
+
646
+ def _format_query(self, query: str):
647
+ return query.format(
648
+ **self.config.get("tables", DEFAULT_TABLES), asset=self._asset_filter
649
+ )
650
+
651
+ def executemany(self, query, parameters=(), extend=True):
652
+ """Passes query to database."""
653
+ if extend:
654
+ query = query + self.additional_where
655
+ parameters = [
656
+ param + (self.time_interval,) + self.additional_params
657
+ for param in parameters
658
+ ]
659
+ query = self._format_query(query)
660
+ if (
661
+ self.config["log_level"].upper() == "DEBUG"
662
+ ): # on other log levels we can skip the iteration
663
+ for param in parameters:
664
+ self.logger.debug(self.cur.mogrify(query, param))
665
+ if not parameters:
666
+ self.logger.debug(self.cur.mogrify(query))
667
+ if not self.dryrun or query.strip().upper().startswith(
668
+ "SELECT"
669
+ ): # no update in dry run
670
+ self.cur.executemany(query, parameters)
@@ -0,0 +1,12 @@
1
+ from intelmq import HARMONIZATION_CONF_FILE
2
+ from intelmq.lib.utils import load_configuration
3
+ from mergedeep import merge
4
+
5
+
6
+ def merge_harmonization(
7
+ additional_definitions: list[dict], harmonization_path: str = None
8
+ ):
9
+ harmonization_path = harmonization_path or HARMONIZATION_CONF_FILE
10
+ harmonization = load_configuration(harmonization_path)
11
+
12
+ return merge(*additional_definitions, harmonization)