psengine 2.0.4__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.
- psengine/__init__.py +22 -0
- psengine/_sdk_id.py +16 -0
- psengine/_version.py +14 -0
- psengine/analyst_notes/__init__.py +32 -0
- psengine/analyst_notes/constants.py +15 -0
- psengine/analyst_notes/errors.py +42 -0
- psengine/analyst_notes/helpers.py +90 -0
- psengine/analyst_notes/models.py +219 -0
- psengine/analyst_notes/note.py +149 -0
- psengine/analyst_notes/note_mgr.py +400 -0
- psengine/base_http_client.py +285 -0
- psengine/classic_alerts/__init__.py +24 -0
- psengine/classic_alerts/classic_alert.py +275 -0
- psengine/classic_alerts/classic_alert_mgr.py +507 -0
- psengine/classic_alerts/constants.py +31 -0
- psengine/classic_alerts/errors.py +38 -0
- psengine/classic_alerts/helpers.py +87 -0
- psengine/classic_alerts/markdown/__init__.py +13 -0
- psengine/classic_alerts/markdown/markdown.py +359 -0
- psengine/classic_alerts/models.py +141 -0
- psengine/collective_insights/__init__.py +29 -0
- psengine/collective_insights/collective_insights.py +164 -0
- psengine/collective_insights/constants.py +44 -0
- psengine/collective_insights/errors.py +18 -0
- psengine/collective_insights/insight.py +89 -0
- psengine/collective_insights/models.py +81 -0
- psengine/common_models.py +89 -0
- psengine/config/__init__.py +15 -0
- psengine/config/config.py +284 -0
- psengine/config/errors.py +18 -0
- psengine/constants.py +63 -0
- psengine/detection/__init__.py +17 -0
- psengine/detection/detection_mgr.py +135 -0
- psengine/detection/detection_rule.py +85 -0
- psengine/detection/errors.py +26 -0
- psengine/detection/helpers.py +56 -0
- psengine/detection/models.py +47 -0
- psengine/endpoints.py +98 -0
- psengine/enrich/__init__.py +28 -0
- psengine/enrich/constants.py +73 -0
- psengine/enrich/errors.py +26 -0
- psengine/enrich/lookup.py +299 -0
- psengine/enrich/lookup_mgr.py +341 -0
- psengine/enrich/models/__init__.py +13 -0
- psengine/enrich/models/base_enriched_entity.py +43 -0
- psengine/enrich/models/lookup.py +271 -0
- psengine/enrich/models/soar.py +138 -0
- psengine/enrich/soar.py +89 -0
- psengine/enrich/soar_mgr.py +176 -0
- psengine/entity_lists/__init__.py +16 -0
- psengine/entity_lists/constants.py +19 -0
- psengine/entity_lists/entity_list.py +435 -0
- psengine/entity_lists/entity_list_mgr.py +185 -0
- psengine/entity_lists/errors.py +26 -0
- psengine/entity_lists/models.py +87 -0
- psengine/entity_match/__init__.py +16 -0
- psengine/entity_match/entity_match.py +90 -0
- psengine/entity_match/entity_match_mgr.py +235 -0
- psengine/entity_match/errors.py +18 -0
- psengine/entity_match/models.py +22 -0
- psengine/errors.py +41 -0
- psengine/helpers/__init__.py +23 -0
- psengine/helpers/helpers.py +471 -0
- psengine/logger/__init__.py +15 -0
- psengine/logger/constants.py +39 -0
- psengine/logger/errors.py +18 -0
- psengine/logger/rf_logger.py +148 -0
- psengine/markdown/__init__.py +21 -0
- psengine/markdown/markdown.py +169 -0
- psengine/markdown/models.py +22 -0
- psengine/playbook_alerts/__init__.py +34 -0
- psengine/playbook_alerts/constants.py +35 -0
- psengine/playbook_alerts/errors.py +35 -0
- psengine/playbook_alerts/helpers.py +80 -0
- psengine/playbook_alerts/mappings.py +44 -0
- psengine/playbook_alerts/markdown/__init__.py +13 -0
- psengine/playbook_alerts/markdown/markdown.py +98 -0
- psengine/playbook_alerts/markdown/markdown_code_repo.py +64 -0
- psengine/playbook_alerts/markdown/markdown_domain_abuse.py +118 -0
- psengine/playbook_alerts/markdown/markdown_identity_exposure.py +158 -0
- psengine/playbook_alerts/models/__init__.py +36 -0
- psengine/playbook_alerts/models/common_models.py +18 -0
- psengine/playbook_alerts/models/panel_log.py +329 -0
- psengine/playbook_alerts/models/panel_status.py +70 -0
- psengine/playbook_alerts/models/pba_code_repo_leak.py +52 -0
- psengine/playbook_alerts/models/pba_cyber_vulnerability.py +53 -0
- psengine/playbook_alerts/models/pba_domain_abuse.py +139 -0
- psengine/playbook_alerts/models/pba_identity_exposures.py +93 -0
- psengine/playbook_alerts/models/pba_third_party_risk.py +103 -0
- psengine/playbook_alerts/models/search_endpoint.py +68 -0
- psengine/playbook_alerts/pa_category.py +37 -0
- psengine/playbook_alerts/playbook_alert_mgr.py +593 -0
- psengine/playbook_alerts/playbook_alerts.py +393 -0
- psengine/rf_client.py +430 -0
- psengine/risklists/__init__.py +17 -0
- psengine/risklists/constants.py +15 -0
- psengine/risklists/errors.py +20 -0
- psengine/risklists/models.py +65 -0
- psengine/risklists/risklist_mgr.py +156 -0
- psengine/stix2/__init__.py +21 -0
- psengine/stix2/base_stix_entity.py +62 -0
- psengine/stix2/complex_entity.py +372 -0
- psengine/stix2/constants.py +81 -0
- psengine/stix2/enriched_indicator.py +261 -0
- psengine/stix2/errors.py +22 -0
- psengine/stix2/helpers.py +68 -0
- psengine/stix2/rf_bundle.py +240 -0
- psengine/stix2/simple_entity.py +145 -0
- psengine/stix2/util.py +53 -0
- psengine-2.0.4.dist-info/METADATA +189 -0
- psengine-2.0.4.dist-info/RECORD +115 -0
- psengine-2.0.4.dist-info/WHEEL +5 -0
- psengine-2.0.4.dist-info/entry_points.txt +2 -0
- psengine-2.0.4.dist-info/licenses/LICENSE +21 -0
- psengine-2.0.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from .classic_alert import AlertRuleOut, ClassicAlert, ClassicAlertHit
|
|
15
|
+
from .classic_alert_mgr import ClassicAlertMgr
|
|
16
|
+
from .constants import ALL_CA_FIELDS, REQUIRED_CA_FIELDS
|
|
17
|
+
from .errors import (
|
|
18
|
+
AlertFetchError,
|
|
19
|
+
AlertImageFetchError,
|
|
20
|
+
AlertMarkdownError,
|
|
21
|
+
AlertSearchError,
|
|
22
|
+
AlertUpdateError,
|
|
23
|
+
NoRulesFoundError,
|
|
24
|
+
)
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
##################################### TERMS OF USE ###########################################
|
|
2
|
+
# The following code is provided for demonstration purpose only, and should not be used #
|
|
3
|
+
# without independent verification. Recorded Future makes no representations or warranties, #
|
|
4
|
+
# express, implied, statutory, or otherwise, regarding any aspect of this code or of the #
|
|
5
|
+
# information it may retrieve, and provides it both strictly “as-is” and without assuming #
|
|
6
|
+
# responsibility for any information it may retrieve. Recorded Future shall not be liable #
|
|
7
|
+
# for, and you assume all risk of using, the foregoing. By using this code, Customer #
|
|
8
|
+
# represents that it is solely responsible for having all necessary licenses, permissions, #
|
|
9
|
+
# rights, and/or consents to connect to third party APIs, and that it is solely responsible #
|
|
10
|
+
# for having all necessary licenses, permissions, rights, and/or consents to any data #
|
|
11
|
+
# accessed from any third party API. #
|
|
12
|
+
##############################################################################################
|
|
13
|
+
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from functools import total_ordering
|
|
16
|
+
from itertools import chain
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
from pydantic import Field, field_validator
|
|
20
|
+
|
|
21
|
+
from ..common_models import IdName, IdNameTypeDescription, RFBaseModel
|
|
22
|
+
from ..constants import TIMESTAMP_STR
|
|
23
|
+
from .markdown.markdown import _markdown_alert
|
|
24
|
+
from .models import (
|
|
25
|
+
AlertAiInsight,
|
|
26
|
+
AlertDeprecation,
|
|
27
|
+
AlertLog,
|
|
28
|
+
AlertReview,
|
|
29
|
+
AlertURL,
|
|
30
|
+
ClassicAlertHit,
|
|
31
|
+
EnrichedEntity,
|
|
32
|
+
NotificationSettings,
|
|
33
|
+
OwnerOrganisationDetails,
|
|
34
|
+
TriggeredBy,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@total_ordering
|
|
39
|
+
class ClassicAlert(RFBaseModel):
|
|
40
|
+
"""Validate data received from ``/v3/alerts/{id}``.
|
|
41
|
+
|
|
42
|
+
Methods:
|
|
43
|
+
__hash__:
|
|
44
|
+
Returns a hash value based on the ``id_``.
|
|
45
|
+
|
|
46
|
+
__eq__:
|
|
47
|
+
Checks equality between two ClassicAlert instances based on their ``id_``.
|
|
48
|
+
|
|
49
|
+
__gt__:
|
|
50
|
+
Defines a greater-than comparison between two ClassicAlert instances based on their
|
|
51
|
+
log triggered timestamp.
|
|
52
|
+
|
|
53
|
+
__str__:
|
|
54
|
+
Returns a string representation of the ClassicAlert instance with:
|
|
55
|
+
``id_``, triggered timestamp, title, and alerting rule name.
|
|
56
|
+
|
|
57
|
+
.. code-block:: python
|
|
58
|
+
|
|
59
|
+
>>> print(alert_id_response)
|
|
60
|
+
Classic Alert ID: a123, Triggered: 2024-05-21 10:42:30AM, Title: Example Alert
|
|
61
|
+
|
|
62
|
+
Total Ordering:
|
|
63
|
+
The ordering of ClassicAlert instances is determined primarily by the log triggered
|
|
64
|
+
timestamp. If two instances have the same triggered timestamp, their ``id_`` is used as a
|
|
65
|
+
secondary criterion for ordering.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
id_: str = Field(alias='id')
|
|
69
|
+
log: AlertLog
|
|
70
|
+
title: str
|
|
71
|
+
review: Optional[AlertReview] = None
|
|
72
|
+
owner_organisation_details: Optional[OwnerOrganisationDetails] = None
|
|
73
|
+
url: Optional[AlertURL] = None
|
|
74
|
+
rule: Optional[AlertDeprecation] = None
|
|
75
|
+
hits: Optional[list[ClassicAlertHit]] = None
|
|
76
|
+
enriched_entities: Optional[list[EnrichedEntity]] = None
|
|
77
|
+
ai_insights: Optional[AlertAiInsight] = None
|
|
78
|
+
type_: str = Field(alias='type', default=None)
|
|
79
|
+
triggered_by: Optional[list[TriggeredBy]] = None
|
|
80
|
+
|
|
81
|
+
_images: Optional[dict] = {}
|
|
82
|
+
|
|
83
|
+
@field_validator('triggered_by', mode='before')
|
|
84
|
+
@classmethod
|
|
85
|
+
def parse_trigger_by(cls, data: list[dict]) -> list[dict]:
|
|
86
|
+
"""Parses a list of data dictionaries to extract and format entity paths.
|
|
87
|
+
|
|
88
|
+
Each entity path is transformed into a formatted string where each entity is represented as
|
|
89
|
+
'EntityName (EntityType)', joined by ' -> '.
|
|
90
|
+
If an entity's type is 'MetaType', it is formatted as 'Any EntityName' instead.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
data (List[Dict]): A list of dictionaries, each containing a 'reference_id' and an
|
|
94
|
+
'entity_paths' list.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
List[Dict]: A list of dictionaries with 'reference_id' and a list of unique formatted
|
|
98
|
+
'triggered_by_strings' paths.
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
Input:
|
|
102
|
+
|
|
103
|
+
.. code-block::
|
|
104
|
+
|
|
105
|
+
[
|
|
106
|
+
{
|
|
107
|
+
'reference_id': '123',
|
|
108
|
+
'entity_paths': [
|
|
109
|
+
[
|
|
110
|
+
{'entity': {'name': 'URL1', 'type': 'URL'}},
|
|
111
|
+
{'entity': {'name': 'Domain1', 'type': 'InternetDomainName'}}
|
|
112
|
+
],
|
|
113
|
+
[
|
|
114
|
+
{'entity': {'name': 'URL1', 'type': 'URL'}},
|
|
115
|
+
{'entity': {'name': 'Domain1', 'type': 'InternetDomainName'}}
|
|
116
|
+
]
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
Output:
|
|
122
|
+
|
|
123
|
+
.. code-block::
|
|
124
|
+
|
|
125
|
+
[
|
|
126
|
+
{
|
|
127
|
+
'reference_id': '123',
|
|
128
|
+
'triggered_by_strings': [
|
|
129
|
+
'URL1 (URL) -> Domain1 (InternetDomainName)'
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
"""
|
|
134
|
+
result = []
|
|
135
|
+
for item in data:
|
|
136
|
+
reference_id = item.get('reference_id')
|
|
137
|
+
entity_paths = item.get('entity_paths', [])
|
|
138
|
+
seen_strings = set()
|
|
139
|
+
to_string = []
|
|
140
|
+
|
|
141
|
+
for path in entity_paths:
|
|
142
|
+
formatted_entities = [
|
|
143
|
+
(
|
|
144
|
+
f'Any {entity["name"]}'
|
|
145
|
+
if entity.get('type') == 'MetaType'
|
|
146
|
+
else f'{entity["name"]} ({entity["type"]})'
|
|
147
|
+
)
|
|
148
|
+
for obj in path
|
|
149
|
+
if (entity := obj.get('entity'))
|
|
150
|
+
]
|
|
151
|
+
parsed_string = ' -> '.join(formatted_entities)
|
|
152
|
+
if parsed_string not in seen_strings:
|
|
153
|
+
seen_strings.add(parsed_string)
|
|
154
|
+
to_string.append(parsed_string)
|
|
155
|
+
|
|
156
|
+
result.append({'reference_id': reference_id, 'triggered_by_strings': to_string})
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
def __hash__(self):
|
|
160
|
+
return hash(self.id_)
|
|
161
|
+
|
|
162
|
+
def __eq__(self, other: 'ClassicAlert'):
|
|
163
|
+
return self.id_ == other.id_
|
|
164
|
+
|
|
165
|
+
def __gt__(self, other: 'ClassicAlert'):
|
|
166
|
+
return self.log.triggered > other.log.triggered
|
|
167
|
+
|
|
168
|
+
def __str__(self):
|
|
169
|
+
return (
|
|
170
|
+
f'Classic Alert ID: {self.id_}, '
|
|
171
|
+
f'Triggered: {self.log.triggered.strftime(TIMESTAMP_STR)}, '
|
|
172
|
+
f'Title: {self.title}, Alerting Rule: {self.rule.name}'
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def triggered_by_from_hit(self, hit: ClassicAlertHit) -> list[str]:
|
|
176
|
+
"""From an Alert Hit block, returns the related Triggered By string representation."""
|
|
177
|
+
return list(
|
|
178
|
+
chain.from_iterable(
|
|
179
|
+
t.triggered_by_strings for t in self.triggered_by if t.reference_id == hit.id_
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def store_image(self, image_id: str, image_bytes: bytes) -> None:
|
|
184
|
+
"""Stores the image id and image bytes in ``@images`` dictionary.
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
.. code-block:: python
|
|
188
|
+
|
|
189
|
+
{
|
|
190
|
+
image_id: image_bytes,
|
|
191
|
+
image_id: image_bytes
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
image_id (str): image id
|
|
197
|
+
image_bytes (bytes): image bytes
|
|
198
|
+
"""
|
|
199
|
+
self._images[image_id] = image_bytes
|
|
200
|
+
|
|
201
|
+
def markdown(
|
|
202
|
+
self,
|
|
203
|
+
owner_org: bool = False,
|
|
204
|
+
ai_insights: bool = True,
|
|
205
|
+
fragment_entities: bool = True,
|
|
206
|
+
triggered_by: bool = True,
|
|
207
|
+
html_tags: bool = False,
|
|
208
|
+
character_limit: int = None,
|
|
209
|
+
defang_iocs: bool = False,
|
|
210
|
+
):
|
|
211
|
+
"""Returns a markdown string representation of the ``ClassicAlert`` instance.
|
|
212
|
+
|
|
213
|
+
This function works on ``ClassicAlert`` instances returned by ``ClassicAlertMgr.fetch()``,
|
|
214
|
+
if you are passing the result of ``ClassicAlertMgr.search()`` make sure the ``search``
|
|
215
|
+
method has been called with all the fields. Keep in mind that this will make the
|
|
216
|
+
``search`` slower.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
self (ClassicAlert): ClassicAlert instance to create markdown from.
|
|
220
|
+
owner_org (bool, optional): Include owner org details. Defaults to False.
|
|
221
|
+
ai_insights (bool, optional): Include AI insights. Defaults to True.
|
|
222
|
+
fragment_entities (bool, optional): Include fragment entities. Defaults to True.
|
|
223
|
+
triggered_by (bool, optional): Include triggered by. Defaults to True.
|
|
224
|
+
html_tags (bool, optional): Include HTML tags in the markdown. Defaults to False.
|
|
225
|
+
character_limit (int, optional): Character limit for the markdown. Defaults to None.
|
|
226
|
+
defang_iocs (bool, optional): Defang IOCs in hits. Defaults to False.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
AlertMarkdownError: If fields are not available.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
str: Markdown representation of the alert.
|
|
233
|
+
"""
|
|
234
|
+
return _markdown_alert(
|
|
235
|
+
self,
|
|
236
|
+
owner_org=owner_org,
|
|
237
|
+
ai_insights=ai_insights,
|
|
238
|
+
fragment_entities=fragment_entities,
|
|
239
|
+
triggered_by=triggered_by,
|
|
240
|
+
html_tags=html_tags,
|
|
241
|
+
character_limit=character_limit,
|
|
242
|
+
defang_iocs=defang_iocs,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def images(self) -> dict:
|
|
247
|
+
"""If the alert has images, then return them in a dict.
|
|
248
|
+
|
|
249
|
+
Example:
|
|
250
|
+
.. code-block:: python
|
|
251
|
+
|
|
252
|
+
{
|
|
253
|
+
image_id: image_bytes,
|
|
254
|
+
image_id: image_bytes
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
dict: dictionary of image ids and image bytes
|
|
260
|
+
"""
|
|
261
|
+
return self._images
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class AlertRuleOut(RFBaseModel):
|
|
265
|
+
"""Validate data received from ``v2/alert/rule``."""
|
|
266
|
+
|
|
267
|
+
intelligence_goals: list[IdName]
|
|
268
|
+
priority: bool = None
|
|
269
|
+
tags: list[IdNameTypeDescription] = None
|
|
270
|
+
id_: str = Field(alias='id')
|
|
271
|
+
owner: IdName
|
|
272
|
+
title: str
|
|
273
|
+
created: datetime
|
|
274
|
+
notification_settings: NotificationSettings
|
|
275
|
+
enabled: bool
|