pycti 6.0.9__py3-none-any.whl → 6.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pycti might be problematic. Click here for more details.
- pycti/__init__.py +11 -1
- pycti/api/opencti_api_client.py +17 -5
- pycti/api/opencti_api_playbook.py +3 -2
- pycti/api/opencti_api_work.py +63 -54
- pycti/connector/opencti_connector_helper.py +145 -44
- pycti/entities/opencti_case_incident.py +1 -1
- pycti/entities/opencti_incident.py +4 -0
- pycti/entities/opencti_indicator.py +11 -0
- pycti/entities/opencti_intrusion_set.py +37 -4
- pycti/entities/opencti_malware_analysis.py +11 -0
- pycti/entities/opencti_report.py +13 -0
- pycti/entities/opencti_stix_core_object.py +38 -5
- pycti/entities/opencti_stix_cyber_observable.py +91 -13
- pycti/entities/opencti_stix_domain_object.py +25 -7
- pycti/entities/opencti_stix_object_or_stix_relationship.py +63 -4
- pycti/utils/constants.py +110 -1
- pycti/utils/opencti_stix2.py +268 -68
- pycti/utils/opencti_stix2_utils.py +12 -1
- {pycti-6.0.9.dist-info → pycti-6.1.0.dist-info}/METADATA +5 -5
- {pycti-6.0.9.dist-info → pycti-6.1.0.dist-info}/RECORD +23 -23
- {pycti-6.0.9.dist-info → pycti-6.1.0.dist-info}/LICENSE +0 -0
- {pycti-6.0.9.dist-info → pycti-6.1.0.dist-info}/WHEEL +0 -0
- {pycti-6.0.9.dist-info → pycti-6.1.0.dist-info}/top_level.txt +0 -0
pycti/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
__version__ = "6.0
|
|
2
|
+
__version__ = "6.1.0"
|
|
3
3
|
|
|
4
4
|
from .api.opencti_api_client import OpenCTIApiClient
|
|
5
5
|
from .api.opencti_api_connector import OpenCTIApiConnector
|
|
@@ -53,9 +53,14 @@ from .entities.opencti_vulnerability import Vulnerability
|
|
|
53
53
|
from .utils.constants import (
|
|
54
54
|
CustomObjectCaseIncident,
|
|
55
55
|
CustomObjectTask,
|
|
56
|
+
CustomObservableBankAccount,
|
|
57
|
+
CustomObservableCredential,
|
|
56
58
|
CustomObservableCryptocurrencyWallet,
|
|
57
59
|
CustomObservableHostname,
|
|
60
|
+
CustomObservablePaymentCard,
|
|
61
|
+
CustomObservablePhoneNumber,
|
|
58
62
|
CustomObservableText,
|
|
63
|
+
CustomObservableTrackingNumber,
|
|
59
64
|
CustomObservableUserAgent,
|
|
60
65
|
MultipleRefRelationship,
|
|
61
66
|
StixCyberObservableTypes,
|
|
@@ -128,9 +133,14 @@ __all__ = [
|
|
|
128
133
|
"CustomObjectCaseIncident",
|
|
129
134
|
"CustomObjectTask",
|
|
130
135
|
"StixCyberObservableTypes",
|
|
136
|
+
"CustomObservableCredential",
|
|
131
137
|
"CustomObservableHostname",
|
|
132
138
|
"CustomObservableUserAgent",
|
|
139
|
+
"CustomObservableBankAccount",
|
|
133
140
|
"CustomObservableCryptocurrencyWallet",
|
|
141
|
+
"CustomObservablePaymentCard",
|
|
142
|
+
"CustomObservablePhoneNumber",
|
|
143
|
+
"CustomObservableTrackingNumber",
|
|
134
144
|
"CustomObservableText",
|
|
135
145
|
"STIX_EXT_MITRE",
|
|
136
146
|
"STIX_EXT_OCTI_SCO",
|
pycti/api/opencti_api_client.py
CHANGED
|
@@ -108,6 +108,7 @@ class OpenCTIApiClient:
|
|
|
108
108
|
ssl_verify=False,
|
|
109
109
|
proxies=None,
|
|
110
110
|
json_logging=False,
|
|
111
|
+
bundle_send_to_queue=True,
|
|
111
112
|
cert=None,
|
|
112
113
|
auth=None,
|
|
113
114
|
perform_health_check=True,
|
|
@@ -115,6 +116,7 @@ class OpenCTIApiClient:
|
|
|
115
116
|
"""Constructor method"""
|
|
116
117
|
|
|
117
118
|
# Check configuration
|
|
119
|
+
self.bundle_send_to_queue = bundle_send_to_queue
|
|
118
120
|
self.ssl_verify = ssl_verify
|
|
119
121
|
self.cert = cert
|
|
120
122
|
self.proxies = proxies
|
|
@@ -205,6 +207,12 @@ class OpenCTIApiClient:
|
|
|
205
207
|
def set_applicant_id_header(self, applicant_id):
|
|
206
208
|
self.request_headers["opencti-applicant-id"] = applicant_id
|
|
207
209
|
|
|
210
|
+
def set_playbook_id_header(self, playbook_id):
|
|
211
|
+
self.request_headers["opencti-playbook-id"] = playbook_id
|
|
212
|
+
|
|
213
|
+
def set_event_id(self, event_id):
|
|
214
|
+
self.request_headers["opencti-event-id"] = event_id
|
|
215
|
+
|
|
208
216
|
def set_synchronized_upsert_header(self, synchronized):
|
|
209
217
|
self.request_headers["synchronized-upsert"] = (
|
|
210
218
|
"true" if synchronized is True else "false"
|
|
@@ -614,18 +622,19 @@ class OpenCTIApiClient:
|
|
|
614
622
|
"""upload a file to OpenCTI API
|
|
615
623
|
|
|
616
624
|
:param `**kwargs`: arguments for file upload (required: `file_name` and `data`)
|
|
617
|
-
:return: returns the query
|
|
625
|
+
:return: returns the query response for the file upload
|
|
618
626
|
:rtype: dict
|
|
619
627
|
"""
|
|
620
628
|
|
|
621
629
|
file_name = kwargs.get("file_name", None)
|
|
630
|
+
file_markings = kwargs.get("file_markings", None)
|
|
622
631
|
data = kwargs.get("data", None)
|
|
623
632
|
mime_type = kwargs.get("mime_type", "text/plain")
|
|
624
633
|
if file_name is not None:
|
|
625
634
|
self.app_logger.info("Uploading a file.")
|
|
626
635
|
query = """
|
|
627
|
-
mutation UploadImport($file: Upload
|
|
628
|
-
uploadImport(file: $file) {
|
|
636
|
+
mutation UploadImport($file: Upload!, $fileMarkings: [String]) {
|
|
637
|
+
uploadImport(file: $file, fileMarkings: $fileMarkings) {
|
|
629
638
|
id
|
|
630
639
|
name
|
|
631
640
|
}
|
|
@@ -637,8 +646,11 @@ class OpenCTIApiClient:
|
|
|
637
646
|
mime_type = "application/json"
|
|
638
647
|
else:
|
|
639
648
|
mime_type = magic.from_file(file_name, mime=True)
|
|
640
|
-
|
|
641
|
-
|
|
649
|
+
query_vars = {"file": (File(file_name, data, mime_type))}
|
|
650
|
+
# optional file markings
|
|
651
|
+
if file_markings is not None:
|
|
652
|
+
query_vars["fileMarkings"] = file_markings
|
|
653
|
+
return self.query(query, query_vars)
|
|
642
654
|
else:
|
|
643
655
|
self.app_logger.error("[upload] Missing parameter: file_name")
|
|
644
656
|
return None
|
|
@@ -9,14 +9,15 @@ class OpenCTIApiPlaybook:
|
|
|
9
9
|
"Executing playbook step", {"playbook_id": playbook["playbook_id"]}
|
|
10
10
|
)
|
|
11
11
|
query = """
|
|
12
|
-
mutation PlaybookStepExecution($execution_id: ID!, $execution_start: DateTime!, $data_instance_id: ID!, $playbook_id: ID!, $previous_step_id: ID!, $step_id: ID!, $previous_bundle: String!, $bundle: String!) {
|
|
13
|
-
playbookStepExecution(execution_id: $execution_id, execution_start: $execution_start, data_instance_id: $data_instance_id, playbook_id: $playbook_id, previous_step_id: $previous_step_id, step_id: $step_id, previous_bundle: $previous_bundle, bundle: $bundle)
|
|
12
|
+
mutation PlaybookStepExecution($execution_id: ID!, $event_id: ID!, $execution_start: DateTime!, $data_instance_id: ID!, $playbook_id: ID!, $previous_step_id: ID!, $step_id: ID!, $previous_bundle: String!, $bundle: String!) {
|
|
13
|
+
playbookStepExecution(execution_id: $execution_id, event_id: $event_id, execution_start: $execution_start, data_instance_id: $data_instance_id, playbook_id: $playbook_id, previous_step_id: $previous_step_id, step_id: $step_id, previous_bundle: $previous_bundle, bundle: $bundle)
|
|
14
14
|
}
|
|
15
15
|
"""
|
|
16
16
|
self.api.query(
|
|
17
17
|
query,
|
|
18
18
|
{
|
|
19
19
|
"execution_id": playbook["execution_id"],
|
|
20
|
+
"event_id": playbook["event_id"],
|
|
20
21
|
"execution_start": playbook["execution_start"],
|
|
21
22
|
"playbook_id": playbook["playbook_id"],
|
|
22
23
|
"data_instance_id": playbook["data_instance_id"],
|
pycti/api/opencti_api_work.py
CHANGED
|
@@ -9,28 +9,34 @@ class OpenCTIApiWork:
|
|
|
9
9
|
self.api = api
|
|
10
10
|
|
|
11
11
|
def to_received(self, work_id: str, message: str):
|
|
12
|
-
self.api.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
if self.api.bundle_send_to_queue:
|
|
13
|
+
self.api.app_logger.info(
|
|
14
|
+
"Reporting work update_received", {"work_id": work_id}
|
|
15
|
+
)
|
|
16
|
+
query = """
|
|
17
|
+
mutation workToReceived($id: ID!, $message: String) {
|
|
18
|
+
workEdit(id: $id) {
|
|
19
|
+
toReceived (message: $message)
|
|
20
|
+
}
|
|
17
21
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
self.api.query(query, {"id": work_id, "message": message})
|
|
22
|
+
"""
|
|
23
|
+
self.api.query(query, {"id": work_id, "message": message})
|
|
21
24
|
|
|
22
25
|
def to_processed(self, work_id: str, message: str, in_error: bool = False):
|
|
23
|
-
self.api.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
if self.api.bundle_send_to_queue:
|
|
27
|
+
self.api.app_logger.info(
|
|
28
|
+
"Reporting work update_processed", {"work_id": work_id}
|
|
29
|
+
)
|
|
30
|
+
query = """
|
|
31
|
+
mutation workToProcessed($id: ID!, $message: String, $inError: Boolean) {
|
|
32
|
+
workEdit(id: $id) {
|
|
33
|
+
toProcessed (message: $message, inError: $inError)
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
"""
|
|
37
|
+
self.api.query(
|
|
38
|
+
query, {"id": work_id, "message": message, "inError": in_error}
|
|
39
|
+
)
|
|
34
40
|
|
|
35
41
|
def ping(self, work_id: str):
|
|
36
42
|
self.api.app_logger.info("Ping work", {"work_id": work_id})
|
|
@@ -44,49 +50,52 @@ class OpenCTIApiWork:
|
|
|
44
50
|
self.api.query(query, {"id": work_id})
|
|
45
51
|
|
|
46
52
|
def report_expectation(self, work_id: str, error):
|
|
47
|
-
self.api.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
if self.api.bundle_send_to_queue:
|
|
54
|
+
self.api.app_logger.info("Report expectation", {"work_id": work_id})
|
|
55
|
+
query = """
|
|
56
|
+
mutation reportExpectation($id: ID!, $error: WorkErrorInput) {
|
|
57
|
+
workEdit(id: $id) {
|
|
58
|
+
reportExpectation(error: $error)
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
self.api.app_logger.error("Cannot report expectation")
|
|
61
|
+
"""
|
|
62
|
+
try:
|
|
63
|
+
self.api.query(query, {"id": work_id, "error": error})
|
|
64
|
+
except:
|
|
65
|
+
self.api.app_logger.error("Cannot report expectation")
|
|
59
66
|
|
|
60
67
|
def add_expectations(self, work_id: str, expectations: int):
|
|
61
|
-
self.api.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
if self.api.bundle_send_to_queue:
|
|
69
|
+
self.api.app_logger.info(
|
|
70
|
+
"Update action expectations",
|
|
71
|
+
{"work_id": work_id, "expectations": expectations},
|
|
72
|
+
)
|
|
73
|
+
query = """
|
|
74
|
+
mutation addExpectations($id: ID!, $expectations: Int) {
|
|
75
|
+
workEdit(id: $id) {
|
|
76
|
+
addExpectations(expectations: $expectations)
|
|
77
|
+
}
|
|
69
78
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
self.api.app_logger.error("Cannot report expectation")
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
self.api.query(query, {"id": work_id, "expectations": expectations})
|
|
82
|
+
except:
|
|
83
|
+
self.api.app_logger.error("Cannot report expectation")
|
|
76
84
|
|
|
77
85
|
def initiate_work(self, connector_id: str, friendly_name: str) -> str:
|
|
78
|
-
self.api.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
workAdd(connectorId: $
|
|
82
|
-
|
|
86
|
+
if self.api.bundle_send_to_queue:
|
|
87
|
+
self.api.app_logger.info("Initiate work", {"connector_id": connector_id})
|
|
88
|
+
query = """
|
|
89
|
+
mutation workAdd($connectorId: String!, $friendlyName: String) {
|
|
90
|
+
workAdd(connectorId: $connectorId, friendlyName: $friendlyName) {
|
|
91
|
+
id
|
|
92
|
+
}
|
|
83
93
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return work["data"]["workAdd"]["id"]
|
|
94
|
+
"""
|
|
95
|
+
work = self.api.query(
|
|
96
|
+
query, {"connectorId": connector_id, "friendlyName": friendly_name}
|
|
97
|
+
)
|
|
98
|
+
return work["data"]["workAdd"]["id"]
|
|
90
99
|
|
|
91
100
|
def delete_work(self, work_id: str):
|
|
92
101
|
query = """
|
|
@@ -282,18 +282,20 @@ class ListenQueue(threading.Thread):
|
|
|
282
282
|
is_playbook = "playbook" in json_data["internal"]
|
|
283
283
|
# If playbook, compute object on data bundle
|
|
284
284
|
if is_playbook:
|
|
285
|
-
execution_id = json_data["internal"]["playbook"]["execution_id"]
|
|
286
285
|
execution_start = self.helper.date_now()
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
event_id = json_data["internal"]["playbook"].get("event_id")
|
|
287
|
+
execution_id = json_data["internal"]["playbook"].get("execution_id")
|
|
288
|
+
playbook_id = json_data["internal"]["playbook"].get("playbook_id")
|
|
289
|
+
data_instance_id = json_data["internal"]["playbook"].get(
|
|
289
290
|
"data_instance_id"
|
|
290
|
-
|
|
291
|
+
)
|
|
291
292
|
previous_bundle = json.dumps((json_data["event"]["bundle"]))
|
|
292
293
|
step_id = json_data["internal"]["playbook"]["step_id"]
|
|
293
294
|
previous_step_id = json_data["internal"]["playbook"][
|
|
294
295
|
"previous_step_id"
|
|
295
296
|
]
|
|
296
297
|
playbook_data = {
|
|
298
|
+
"event_id": event_id,
|
|
297
299
|
"execution_id": execution_id,
|
|
298
300
|
"execution_start": execution_start,
|
|
299
301
|
"playbook_id": playbook_id,
|
|
@@ -728,6 +730,32 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
728
730
|
self.connect_auto = get_config_variable(
|
|
729
731
|
"CONNECTOR_AUTO", ["connector", "auto"], config, False, False
|
|
730
732
|
)
|
|
733
|
+
self.bundle_send_to_queue = get_config_variable(
|
|
734
|
+
"CONNECTOR_SEND_TO_QUEUE",
|
|
735
|
+
["connector", "send_to_queue"],
|
|
736
|
+
config,
|
|
737
|
+
False,
|
|
738
|
+
True,
|
|
739
|
+
)
|
|
740
|
+
self.bundle_send_to_directory = get_config_variable(
|
|
741
|
+
"CONNECTOR_SEND_TO_DIRECTORY",
|
|
742
|
+
["connector", "send_to_directory"],
|
|
743
|
+
config,
|
|
744
|
+
False,
|
|
745
|
+
False,
|
|
746
|
+
)
|
|
747
|
+
self.bundle_send_to_directory_path = get_config_variable(
|
|
748
|
+
"CONNECTOR_SEND_TO_DIRECTORY_PATH",
|
|
749
|
+
["connector", "send_to_directory_path"],
|
|
750
|
+
config,
|
|
751
|
+
)
|
|
752
|
+
self.bundle_send_to_directory_retention = get_config_variable(
|
|
753
|
+
"CONNECTOR_SEND_TO_DIRECTORY_RETENTION",
|
|
754
|
+
["connector", "send_to_directory_retention"],
|
|
755
|
+
config,
|
|
756
|
+
True,
|
|
757
|
+
7,
|
|
758
|
+
)
|
|
731
759
|
self.connect_only_contextual = get_config_variable(
|
|
732
760
|
"CONNECTOR_ONLY_CONTEXTUAL",
|
|
733
761
|
["connector", "only_contextual"],
|
|
@@ -749,6 +777,8 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
749
777
|
"CONNECTOR_VALIDATE_BEFORE_IMPORT",
|
|
750
778
|
["connector", "validate_before_import"],
|
|
751
779
|
config,
|
|
780
|
+
False,
|
|
781
|
+
False,
|
|
752
782
|
)
|
|
753
783
|
# Start up the server to expose the metrics.
|
|
754
784
|
expose_metrics = get_config_variable(
|
|
@@ -769,6 +799,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
769
799
|
self.opencti_token,
|
|
770
800
|
self.log_level,
|
|
771
801
|
json_logging=self.opencti_json_logging,
|
|
802
|
+
bundle_send_to_queue=self.bundle_send_to_queue,
|
|
772
803
|
)
|
|
773
804
|
# - Impersonate API that will use applicant id
|
|
774
805
|
# Behave like standard api if applicant not found
|
|
@@ -777,6 +808,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
777
808
|
self.opencti_token,
|
|
778
809
|
self.log_level,
|
|
779
810
|
json_logging=self.opencti_json_logging,
|
|
811
|
+
bundle_send_to_queue=self.bundle_send_to_queue,
|
|
780
812
|
)
|
|
781
813
|
self.connector_logger = self.api.logger_class(self.connect_name)
|
|
782
814
|
# For retro compatibility
|
|
@@ -1075,6 +1107,16 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1075
1107
|
bypass_validation = kwargs.get("bypass_validation", False)
|
|
1076
1108
|
entity_id = kwargs.get("entity_id", None)
|
|
1077
1109
|
file_name = kwargs.get("file_name", None)
|
|
1110
|
+
bundle_send_to_queue = kwargs.get("send_to_queue", self.bundle_send_to_queue)
|
|
1111
|
+
bundle_send_to_directory = kwargs.get(
|
|
1112
|
+
"send_to_directory", self.bundle_send_to_directory
|
|
1113
|
+
)
|
|
1114
|
+
bundle_send_to_directory_path = kwargs.get(
|
|
1115
|
+
"send_to_directory_path", self.bundle_send_to_directory_path
|
|
1116
|
+
)
|
|
1117
|
+
bundle_send_to_directory_retention = kwargs.get(
|
|
1118
|
+
"send_to_directory_retention", self.bundle_send_to_directory_retention
|
|
1119
|
+
)
|
|
1078
1120
|
|
|
1079
1121
|
# In case of enrichment ingestion, ensure the sharing if needed
|
|
1080
1122
|
if self.enrichment_shared_organizations is not None:
|
|
@@ -1114,13 +1156,14 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1114
1156
|
)
|
|
1115
1157
|
bundle = json.dumps(bundle_data)
|
|
1116
1158
|
|
|
1159
|
+
# If execution in playbook, callback the api
|
|
1117
1160
|
if self.playbook is not None:
|
|
1118
1161
|
self.api.playbook.playbook_step_execution(self.playbook, bundle)
|
|
1119
1162
|
return [bundle]
|
|
1120
1163
|
|
|
1164
|
+
# Upload workbench in case of pending validation
|
|
1121
1165
|
if not file_name and work_id:
|
|
1122
1166
|
file_name = f"{work_id}.json"
|
|
1123
|
-
|
|
1124
1167
|
if self.connect_validate_before_import and not bypass_validation and file_name:
|
|
1125
1168
|
self.api.upload_pending_file(
|
|
1126
1169
|
file_name=file_name,
|
|
@@ -1130,8 +1173,61 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1130
1173
|
)
|
|
1131
1174
|
return []
|
|
1132
1175
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1176
|
+
# If directory setup, write the bundle to the target directory
|
|
1177
|
+
if bundle_send_to_directory and bundle_send_to_directory_path is not None:
|
|
1178
|
+
self.connector_logger.info(
|
|
1179
|
+
"The connector sending bundle to directory",
|
|
1180
|
+
{
|
|
1181
|
+
"connector": self.connect_name,
|
|
1182
|
+
"directory": bundle_send_to_directory_path,
|
|
1183
|
+
"also_queuing": bundle_send_to_queue,
|
|
1184
|
+
},
|
|
1185
|
+
)
|
|
1186
|
+
bundle_file = (
|
|
1187
|
+
self.connect_name.lower().replace(" ", "_")
|
|
1188
|
+
+ "-"
|
|
1189
|
+
+ time.strftime("%Y%m%d-%H%M%S-")
|
|
1190
|
+
+ str(time.time())
|
|
1191
|
+
+ ".json"
|
|
1192
|
+
)
|
|
1193
|
+
write_file = os.path.join(
|
|
1194
|
+
bundle_send_to_directory_path, bundle_file + ".tmp"
|
|
1195
|
+
)
|
|
1196
|
+
message_bundle = {
|
|
1197
|
+
"bundle_type": "DIRECTORY_BUNDLE",
|
|
1198
|
+
"applicant_id": self.applicant_id,
|
|
1199
|
+
"connector": {
|
|
1200
|
+
"id": self.connect_id,
|
|
1201
|
+
"name": self.connect_name,
|
|
1202
|
+
"type": self.connect_type,
|
|
1203
|
+
"scope": self.connect_scope,
|
|
1204
|
+
"auto": self.connect_auto,
|
|
1205
|
+
"validate_before_import": self.connect_validate_before_import,
|
|
1206
|
+
},
|
|
1207
|
+
"entities_types": entities_types,
|
|
1208
|
+
"bundle": json.loads(bundle),
|
|
1209
|
+
"update": update,
|
|
1210
|
+
}
|
|
1211
|
+
# Maintains the list of files under control
|
|
1212
|
+
if bundle_send_to_directory_retention > 0: # If 0, disable the auto remove
|
|
1213
|
+
current_time = time.time()
|
|
1214
|
+
for f in os.listdir(bundle_send_to_directory_path):
|
|
1215
|
+
if f.endswith(".json"):
|
|
1216
|
+
file_location = os.path.join(bundle_send_to_directory_path, f)
|
|
1217
|
+
file_time = os.stat(file_location).st_mtime
|
|
1218
|
+
is_expired_file = (
|
|
1219
|
+
file_time
|
|
1220
|
+
< current_time - 86400 * bundle_send_to_directory_retention
|
|
1221
|
+
) # 86400 = 1 day
|
|
1222
|
+
if is_expired_file:
|
|
1223
|
+
os.remove(file_location)
|
|
1224
|
+
# Write the bundle to target directory
|
|
1225
|
+
with open(write_file, "w") as f:
|
|
1226
|
+
str_bundle = json.dumps(message_bundle)
|
|
1227
|
+
f.write(str_bundle)
|
|
1228
|
+
# Rename the file after full write
|
|
1229
|
+
final_write_file = os.path.join(bundle_send_to_directory_path, bundle_file)
|
|
1230
|
+
os.rename(write_file, final_write_file)
|
|
1135
1231
|
|
|
1136
1232
|
if bypass_split:
|
|
1137
1233
|
bundles = [bundle]
|
|
@@ -1143,44 +1239,48 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1143
1239
|
self.metric.inc("error_count")
|
|
1144
1240
|
raise ValueError("Nothing to import")
|
|
1145
1241
|
|
|
1146
|
-
if
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
host=self.connector_config["connection"]["host"],
|
|
1155
|
-
port=self.connector_config["connection"]["port"],
|
|
1156
|
-
virtual_host=self.connector_config["connection"]["vhost"],
|
|
1157
|
-
credentials=pika_credentials,
|
|
1158
|
-
ssl_options=(
|
|
1159
|
-
pika.SSLOptions(
|
|
1160
|
-
create_mq_ssl_context(self.config),
|
|
1161
|
-
self.connector_config["connection"]["host"],
|
|
1162
|
-
)
|
|
1163
|
-
if self.connector_config["connection"]["use_ssl"]
|
|
1164
|
-
else None
|
|
1165
|
-
),
|
|
1166
|
-
)
|
|
1167
|
-
pika_connection = pika.BlockingConnection(pika_parameters)
|
|
1168
|
-
channel = pika_connection.channel()
|
|
1169
|
-
try:
|
|
1170
|
-
channel.confirm_delivery()
|
|
1171
|
-
except Exception as err: # pylint: disable=broad-except
|
|
1172
|
-
self.connector_logger.warning(str(err))
|
|
1173
|
-
for sequence, bundle in enumerate(bundles, start=1):
|
|
1174
|
-
self._send_bundle(
|
|
1175
|
-
channel,
|
|
1176
|
-
bundle,
|
|
1177
|
-
work_id=work_id,
|
|
1178
|
-
entities_types=entities_types,
|
|
1179
|
-
sequence=sequence,
|
|
1180
|
-
update=update,
|
|
1242
|
+
if bundle_send_to_queue:
|
|
1243
|
+
if work_id:
|
|
1244
|
+
self.api.work.add_expectations(work_id, len(bundles))
|
|
1245
|
+
if entities_types is None:
|
|
1246
|
+
entities_types = []
|
|
1247
|
+
pika_credentials = pika.PlainCredentials(
|
|
1248
|
+
self.connector_config["connection"]["user"],
|
|
1249
|
+
self.connector_config["connection"]["pass"],
|
|
1181
1250
|
)
|
|
1182
|
-
|
|
1183
|
-
|
|
1251
|
+
pika_parameters = pika.ConnectionParameters(
|
|
1252
|
+
host=self.connector_config["connection"]["host"],
|
|
1253
|
+
port=self.connector_config["connection"]["port"],
|
|
1254
|
+
virtual_host=self.connector_config["connection"]["vhost"],
|
|
1255
|
+
credentials=pika_credentials,
|
|
1256
|
+
ssl_options=(
|
|
1257
|
+
pika.SSLOptions(
|
|
1258
|
+
create_mq_ssl_context(self.config),
|
|
1259
|
+
self.connector_config["connection"]["host"],
|
|
1260
|
+
)
|
|
1261
|
+
if self.connector_config["connection"]["use_ssl"]
|
|
1262
|
+
else None
|
|
1263
|
+
),
|
|
1264
|
+
)
|
|
1265
|
+
pika_connection = pika.BlockingConnection(pika_parameters)
|
|
1266
|
+
channel = pika_connection.channel()
|
|
1267
|
+
try:
|
|
1268
|
+
channel.confirm_delivery()
|
|
1269
|
+
except Exception as err: # pylint: disable=broad-except
|
|
1270
|
+
self.connector_logger.warning(str(err))
|
|
1271
|
+
self.connector_logger.info(self.connect_name + " sending bundle to queue")
|
|
1272
|
+
for sequence, bundle in enumerate(bundles, start=1):
|
|
1273
|
+
self._send_bundle(
|
|
1274
|
+
channel,
|
|
1275
|
+
bundle,
|
|
1276
|
+
work_id=work_id,
|
|
1277
|
+
entities_types=entities_types,
|
|
1278
|
+
sequence=sequence,
|
|
1279
|
+
update=update,
|
|
1280
|
+
)
|
|
1281
|
+
channel.close()
|
|
1282
|
+
pika_connection.close()
|
|
1283
|
+
|
|
1184
1284
|
return bundles
|
|
1185
1285
|
|
|
1186
1286
|
def _send_bundle(self, channel, bundle, **kwargs) -> None:
|
|
@@ -1212,6 +1312,7 @@ class OpenCTIConnectorHelper: # pylint: disable=too-many-public-methods
|
|
|
1212
1312
|
# if self.current_work_id is None:
|
|
1213
1313
|
# raise ValueError('The job id must be specified')
|
|
1214
1314
|
message = {
|
|
1315
|
+
"bundle_type": "QUEUE_BUNDLE",
|
|
1215
1316
|
"applicant_id": self.applicant_id,
|
|
1216
1317
|
"action_sequence": sequence,
|
|
1217
1318
|
"entities_types": entities_types,
|
|
@@ -530,7 +530,7 @@ class CaseIncident:
|
|
|
530
530
|
data = self.opencti.process_multiple(result["data"]["caseIncidents"])
|
|
531
531
|
final_data = final_data + data
|
|
532
532
|
while result["data"]["caseIncidents"]["pageInfo"]["hasNextPage"]:
|
|
533
|
-
after = result["
|
|
533
|
+
after = result["data"]["caseIncidents"]["pageInfo"]["endCursor"]
|
|
534
534
|
self.opencti.app_logger.info("Listing Case Incidents", {"after": after})
|
|
535
535
|
result = self.opencti.query(
|
|
536
536
|
query,
|
|
@@ -458,6 +458,10 @@ class Incident:
|
|
|
458
458
|
stix_object["x_opencti_granted_refs"] = (
|
|
459
459
|
self.opencti.get_attribute_in_extension("granted_refs", stix_object)
|
|
460
460
|
)
|
|
461
|
+
if "x_opencti_workflow_id" not in stix_object:
|
|
462
|
+
stix_object["x_opencti_workflow_id"] = (
|
|
463
|
+
self.opencti.get_attribute_in_extension("workflow_id", stix_object)
|
|
464
|
+
)
|
|
461
465
|
|
|
462
466
|
return self.create(
|
|
463
467
|
stix_id=stix_object["id"],
|
|
@@ -468,6 +468,7 @@ class Indicator:
|
|
|
468
468
|
x_opencti_stix_ids = kwargs.get("x_opencti_stix_ids", None)
|
|
469
469
|
create_observables = kwargs.get("x_opencti_create_observables", False)
|
|
470
470
|
granted_refs = kwargs.get("objectOrganization", None)
|
|
471
|
+
x_opencti_workflow_id = kwargs.get("x_opencti_workflow_id", None)
|
|
471
472
|
update = kwargs.get("update", False)
|
|
472
473
|
|
|
473
474
|
if (
|
|
@@ -528,6 +529,7 @@ class Indicator:
|
|
|
528
529
|
"x_opencti_stix_ids": x_opencti_stix_ids,
|
|
529
530
|
"killChainPhases": kill_chain_phases,
|
|
530
531
|
"createObservables": create_observables,
|
|
532
|
+
"x_opencti_workflow_id": x_opencti_workflow_id,
|
|
531
533
|
"update": update,
|
|
532
534
|
}
|
|
533
535
|
},
|
|
@@ -642,6 +644,10 @@ class Indicator:
|
|
|
642
644
|
stix_object["x_opencti_granted_refs"] = (
|
|
643
645
|
self.opencti.get_attribute_in_extension("granted_refs", stix_object)
|
|
644
646
|
)
|
|
647
|
+
if "x_opencti_workflow_id" not in stix_object:
|
|
648
|
+
stix_object["x_opencti_workflow_id"] = (
|
|
649
|
+
self.opencti.get_attribute_in_extension("workflow_id", stix_object)
|
|
650
|
+
)
|
|
645
651
|
|
|
646
652
|
return self.create(
|
|
647
653
|
stix_id=stix_object["id"],
|
|
@@ -740,6 +746,11 @@ class Indicator:
|
|
|
740
746
|
if "x_opencti_granted_refs" in stix_object
|
|
741
747
|
else None
|
|
742
748
|
),
|
|
749
|
+
x_opencti_workflow_id=(
|
|
750
|
+
stix_object["x_opencti_workflow_id"]
|
|
751
|
+
if "x_opencti_workflow_id" in stix_object
|
|
752
|
+
else None
|
|
753
|
+
),
|
|
743
754
|
update=update,
|
|
744
755
|
)
|
|
745
756
|
else:
|