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,21 @@
|
|
|
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 .markdown import (
|
|
15
|
+
MarkdownMaker,
|
|
16
|
+
clean_text,
|
|
17
|
+
divider,
|
|
18
|
+
escape_pipe_characters,
|
|
19
|
+
html_textarea,
|
|
20
|
+
table_from_rows,
|
|
21
|
+
)
|
|
@@ -0,0 +1,169 @@
|
|
|
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
|
+
import html
|
|
15
|
+
from typing import Union
|
|
16
|
+
|
|
17
|
+
import markdown_strings
|
|
18
|
+
from markdown_strings import header
|
|
19
|
+
from pydantic import validate_call
|
|
20
|
+
|
|
21
|
+
from .models import Section
|
|
22
|
+
|
|
23
|
+
TITLE_HEADER_LEVEL = 2
|
|
24
|
+
|
|
25
|
+
###############################################################################
|
|
26
|
+
# Helpers
|
|
27
|
+
###############################################################################
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def divider() -> str:
|
|
31
|
+
"""Return divider."""
|
|
32
|
+
return '\n---\n'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def table_from_rows(table_list) -> str:
|
|
36
|
+
r"""Return a formatted table, using each list as the list. The specifics are
|
|
37
|
+
the same as those for the table function.
|
|
38
|
+
|
|
39
|
+
>>> table_from_rows([["1","2","3"],["4","5","6"],["7","8","9"]])
|
|
40
|
+
'| 1 | 2 | 3 |\\n| --- | --- | --- |\\n| 4 | 5 | 6 |\\n| 7 | 8 | 9 |'
|
|
41
|
+
"""
|
|
42
|
+
longest_list = max(table_list, key=len)
|
|
43
|
+
number_of_columns = len(longest_list)
|
|
44
|
+
columns = [
|
|
45
|
+
[
|
|
46
|
+
str(row[column]).replace('|', ' ').replace('\n', ' ') if column < len(row) else ''
|
|
47
|
+
for row in table_list
|
|
48
|
+
]
|
|
49
|
+
for column in range(number_of_columns)
|
|
50
|
+
]
|
|
51
|
+
return markdown_strings.table(columns)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def clean_text(text: str) -> str:
|
|
55
|
+
"""Cleanup legacy alerts data from markdown and html text special chars."""
|
|
56
|
+
text = (
|
|
57
|
+
text.replace('\n', '')
|
|
58
|
+
.replace('#', '\\#')
|
|
59
|
+
.replace('_', '\\_')
|
|
60
|
+
.replace('-', '\\-')
|
|
61
|
+
.replace('*', '•')
|
|
62
|
+
.replace('\u2028', ' ')
|
|
63
|
+
)
|
|
64
|
+
return html.escape(text)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def escape_pipe_characters(text: str) -> str:
|
|
68
|
+
"""Escape pipe characters in text."""
|
|
69
|
+
return text.replace('|', '\\|')
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def html_textarea(text: str) -> str:
|
|
73
|
+
"""Wrap text in a markdown code block."""
|
|
74
|
+
return f'<textarea readonly="true">{text}</textarea>'
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class MarkdownMaker:
|
|
78
|
+
"""Class to manage markdown inputs and formatting of markdown output."""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
addendum: str = '',
|
|
83
|
+
character_limit: int = None,
|
|
84
|
+
defang_iocs: bool = False,
|
|
85
|
+
iocs_to_defang: list = None,
|
|
86
|
+
):
|
|
87
|
+
self.title = None
|
|
88
|
+
self.sections = []
|
|
89
|
+
self.addendum = addendum
|
|
90
|
+
if character_limit is not None and character_limit < len(addendum):
|
|
91
|
+
raise ValueError(f'Character limit must be at least {len(addendum)}')
|
|
92
|
+
self.character_limit = character_limit
|
|
93
|
+
self.defang_iocs = defang_iocs
|
|
94
|
+
if self.defang_iocs and iocs_to_defang is None:
|
|
95
|
+
raise ValueError('If `defang_iocs` is True, you need to supply `iocs_to_defang`.')
|
|
96
|
+
self.iocs_to_defang = iocs_to_defang
|
|
97
|
+
|
|
98
|
+
@validate_call
|
|
99
|
+
def add_title(self, title: str) -> None:
|
|
100
|
+
"""Add title to the markdown."""
|
|
101
|
+
self.title = title
|
|
102
|
+
|
|
103
|
+
def validate_section(self, title: str, content: Union[list[dict], list[str]]) -> Section:
|
|
104
|
+
"""Recursive function to validate a section and its content.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
title (str): Section title
|
|
108
|
+
content (Union[list[dict], list[str]]): Section content
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ValueError: Raised if the section content is empty.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Section: Returns a validated section object.
|
|
115
|
+
"""
|
|
116
|
+
if len(content) == 0:
|
|
117
|
+
raise ValueError('Section content cannot be empty.')
|
|
118
|
+
if isinstance(content[0], str):
|
|
119
|
+
return Section(title=title, content=content)
|
|
120
|
+
if isinstance(content[0], dict):
|
|
121
|
+
return Section(
|
|
122
|
+
title=title, content=[self.validate_section(**section) for section in content]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@validate_call
|
|
126
|
+
def add_section(self, title: str, content: Union[list[dict], list[str]]) -> None:
|
|
127
|
+
"""Add a section to the markdown."""
|
|
128
|
+
self.sections.append(self.validate_section(title, content))
|
|
129
|
+
|
|
130
|
+
def format_section(self, section: Section, header_level: int) -> str:
|
|
131
|
+
"""Recursive function to format a section and its content."""
|
|
132
|
+
md_str = header(section.title, header_level) + '\n\n'
|
|
133
|
+
if isinstance(section.content[0], str):
|
|
134
|
+
md_str += '\n'.join(section.content)
|
|
135
|
+
else:
|
|
136
|
+
for sub_section in section.content:
|
|
137
|
+
md_str += self.format_section(sub_section, header_level + 1)
|
|
138
|
+
|
|
139
|
+
md_str += '\n\n'
|
|
140
|
+
|
|
141
|
+
return md_str
|
|
142
|
+
|
|
143
|
+
def format_defang_iocs(self, entities: set, md_str: str) -> str:
|
|
144
|
+
"""Replace `.` with `[.]` for every IOC found."""
|
|
145
|
+
for entity in entities:
|
|
146
|
+
defanged = entity.replace('.', '[.]')
|
|
147
|
+
md_str = md_str.replace(entity, defanged)
|
|
148
|
+
|
|
149
|
+
return md_str
|
|
150
|
+
|
|
151
|
+
def format_output(self) -> str:
|
|
152
|
+
"""Format the markdown output.
|
|
153
|
+
|
|
154
|
+
Call this method after adding a title and any sections.
|
|
155
|
+
"""
|
|
156
|
+
md_str = ''
|
|
157
|
+
if self.title is not None:
|
|
158
|
+
md_str += header(self.title, TITLE_HEADER_LEVEL) + '\n\n'
|
|
159
|
+
|
|
160
|
+
for section in self.sections:
|
|
161
|
+
md_str += self.format_section(section, TITLE_HEADER_LEVEL + 1)
|
|
162
|
+
|
|
163
|
+
if self.defang_iocs:
|
|
164
|
+
md_str = self.format_defang_iocs(self.iocs_to_defang, md_str)
|
|
165
|
+
|
|
166
|
+
if self.character_limit is not None and len(md_str) > self.character_limit:
|
|
167
|
+
md_str = md_str[: self.character_limit - len(self.addendum)] + self.addendum
|
|
168
|
+
|
|
169
|
+
return md_str
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
|
|
15
|
+
from typing import Union
|
|
16
|
+
|
|
17
|
+
from ..common_models import RFBaseModel
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Section(RFBaseModel):
|
|
21
|
+
title: str
|
|
22
|
+
content: Union[list['Section'], list[str]]
|
|
@@ -0,0 +1,34 @@
|
|
|
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 .errors import (
|
|
15
|
+
PlaybookAlertError,
|
|
16
|
+
PlaybookAlertFetchError,
|
|
17
|
+
PlaybookAlertRetrieveImageError,
|
|
18
|
+
PlaybookAlertSearchError,
|
|
19
|
+
PlaybookAlertUpdateError,
|
|
20
|
+
)
|
|
21
|
+
from .models import SearchResponse
|
|
22
|
+
from .pa_category import PACategory
|
|
23
|
+
from .playbook_alert_mgr import PlaybookAlertMgr
|
|
24
|
+
from .playbook_alerts import (
|
|
25
|
+
PBA_CodeRepoLeakage,
|
|
26
|
+
PBA_CyberVulnerability,
|
|
27
|
+
PBA_DomainAbuse,
|
|
28
|
+
PBA_Generic,
|
|
29
|
+
PBA_IdentityNovelExposure,
|
|
30
|
+
PBA_ThirdPartyRisk,
|
|
31
|
+
PreviewAlertOut,
|
|
32
|
+
SearchIn,
|
|
33
|
+
UpdateAlertIn,
|
|
34
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
from typing import Union
|
|
14
|
+
|
|
15
|
+
from .playbook_alerts import (
|
|
16
|
+
PBA_CodeRepoLeakage,
|
|
17
|
+
PBA_CyberVulnerability,
|
|
18
|
+
PBA_DomainAbuse,
|
|
19
|
+
PBA_Generic,
|
|
20
|
+
PBA_IdentityNovelExposure,
|
|
21
|
+
PBA_ThirdPartyRisk,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
STATUS_PANEL_NAME = 'status'
|
|
25
|
+
DEFAULT_PBA_OUTPUT_DIR = 'playbook_alerts'
|
|
26
|
+
DEFAULT_PBA_FILE_NAME = 'playbook_alerts_{}.json'
|
|
27
|
+
|
|
28
|
+
PLAYBOOK_ALERT_TYPE = Union[
|
|
29
|
+
PBA_Generic,
|
|
30
|
+
PBA_CodeRepoLeakage,
|
|
31
|
+
PBA_CyberVulnerability,
|
|
32
|
+
PBA_DomainAbuse,
|
|
33
|
+
PBA_IdentityNovelExposure,
|
|
34
|
+
PBA_ThirdPartyRisk,
|
|
35
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
|
|
15
|
+
from ..errors import RecordedFutureError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PlaybookAlertError(RecordedFutureError):
|
|
19
|
+
"""Error raised when playbook alert fails."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PlaybookAlertSearchError(PlaybookAlertError):
|
|
23
|
+
"""Error raised when playbook alert search query fails."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PlaybookAlertUpdateError(PlaybookAlertError):
|
|
27
|
+
"""Error raised when playbook alert update fails."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class PlaybookAlertFetchError(PlaybookAlertError):
|
|
31
|
+
"""Error raised when playbook alert fetch fails."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class PlaybookAlertRetrieveImageError(PlaybookAlertError):
|
|
35
|
+
"""Error raised when playbook alert image fetch fails."""
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Union
|
|
16
|
+
|
|
17
|
+
from ..errors import WriteFileError
|
|
18
|
+
from ..helpers import OSHelpers, debug_call
|
|
19
|
+
from .constants import DEFAULT_PBA_OUTPUT_DIR
|
|
20
|
+
from .playbook_alerts import PBA_DomainAbuse
|
|
21
|
+
|
|
22
|
+
LOG = logging.getLogger('psengine.playbook_alerts.helpers')
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@debug_call
|
|
26
|
+
def save_pba_images(
|
|
27
|
+
playbook_alerts: Union[PBA_DomainAbuse, list[PBA_DomainAbuse]],
|
|
28
|
+
output_directory: str = DEFAULT_PBA_OUTPUT_DIR,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Save Domain Abuse images/screenshots to disk as a .png file.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
playbook_alerts (Union[PBA_DomainAbuse, List[PBA_DomainAbuse]]): Domain Abuse alert(s)
|
|
34
|
+
output_directory (str): The directory to save the images to
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
TypeError: If alerts are not PBA_DomainAbuse objects
|
|
38
|
+
WriteFileError: If the image save fails with an OSError
|
|
39
|
+
"""
|
|
40
|
+
if not isinstance(playbook_alerts, (list, PBA_DomainAbuse)):
|
|
41
|
+
raise TypeError('Image saving is only supported by Domain Abuse alerts')
|
|
42
|
+
|
|
43
|
+
playbook_alerts = playbook_alerts if isinstance(playbook_alerts, list) else [playbook_alerts]
|
|
44
|
+
if not all(isinstance(alert, PBA_DomainAbuse) for alert in playbook_alerts):
|
|
45
|
+
raise TypeError('Image saving is only supported by Domain Abuse alerts')
|
|
46
|
+
|
|
47
|
+
for alert in playbook_alerts:
|
|
48
|
+
LOG.info(f'Saving {len(alert.images)} image(s) to disk for alert {alert.playbook_alert_id}')
|
|
49
|
+
for image_id, meta in alert.images.items():
|
|
50
|
+
file_name = f'{alert.playbook_alert_id[5:]}_{image_id[4:]}'
|
|
51
|
+
_save_image(
|
|
52
|
+
file_name=file_name,
|
|
53
|
+
image_bytes=meta['image_bytes'],
|
|
54
|
+
output_directory=output_directory,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _save_image(
|
|
59
|
+
file_name: str, image_bytes: bytes, output_directory: Union[str, Path] = DEFAULT_PBA_OUTPUT_DIR
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Save image to disk as a .png file.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
file_name (str): File name without the extension
|
|
65
|
+
image_bytes (bytes): Image bytes
|
|
66
|
+
output_directory (str or Path): The directory to save the image to
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
WriteFileError: If the image save fails with an OSError
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
LOG.debug(f"Saving image '{file_name}' to disk")
|
|
73
|
+
dir_path = OSHelpers.mkdir(output_directory)
|
|
74
|
+
image_filepath = dir_path / f'{file_name}.png'
|
|
75
|
+
with image_filepath.open('wb') as file:
|
|
76
|
+
file.write(image_bytes)
|
|
77
|
+
except OSError as err:
|
|
78
|
+
raise WriteFileError(
|
|
79
|
+
f'Failed to save playbook alert image to disk. Cause: {err.args}',
|
|
80
|
+
) from err
|
|
@@ -0,0 +1,44 @@
|
|
|
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 ..endpoints import (
|
|
15
|
+
EP_PLAYBOOK_ALERT_CODE_REPO_LEAKAGE,
|
|
16
|
+
EP_PLAYBOOK_ALERT_CYBER_VULNERABILITY,
|
|
17
|
+
EP_PLAYBOOK_ALERT_DOMAIN_ABUSE,
|
|
18
|
+
EP_PLAYBOOK_ALERT_IDENTITY_NOVEL_EXPOSURES,
|
|
19
|
+
EP_PLAYBOOK_ALERT_THIRD_PARTY_RISK,
|
|
20
|
+
)
|
|
21
|
+
from .pa_category import PACategory
|
|
22
|
+
from .playbook_alerts import (
|
|
23
|
+
PBA_CodeRepoLeakage,
|
|
24
|
+
PBA_CyberVulnerability,
|
|
25
|
+
PBA_DomainAbuse,
|
|
26
|
+
PBA_IdentityNovelExposure,
|
|
27
|
+
PBA_ThirdPartyRisk,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
CATEGORY_ENDPOINTS = {
|
|
31
|
+
PACategory.CODE_REPO_LEAKAGE.value: EP_PLAYBOOK_ALERT_CODE_REPO_LEAKAGE,
|
|
32
|
+
PACategory.CYBER_VULNERABILITY.value: EP_PLAYBOOK_ALERT_CYBER_VULNERABILITY,
|
|
33
|
+
PACategory.DOMAIN_ABUSE.value: EP_PLAYBOOK_ALERT_DOMAIN_ABUSE,
|
|
34
|
+
PACategory.IDENTITY_NOVEL_EXPOSURES.value: EP_PLAYBOOK_ALERT_IDENTITY_NOVEL_EXPOSURES,
|
|
35
|
+
PACategory.THIRD_PARTY_RISK.value: EP_PLAYBOOK_ALERT_THIRD_PARTY_RISK,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
CATEGORY_TO_OBJECT_MAP = {
|
|
39
|
+
PACategory.CODE_REPO_LEAKAGE.value: PBA_CodeRepoLeakage,
|
|
40
|
+
PACategory.CYBER_VULNERABILITY.value: PBA_CyberVulnerability,
|
|
41
|
+
PACategory.DOMAIN_ABUSE.value: PBA_DomainAbuse,
|
|
42
|
+
PACategory.IDENTITY_NOVEL_EXPOSURES.value: PBA_IdentityNovelExposure,
|
|
43
|
+
PACategory.THIRD_PARTY_RISK.value: PBA_ThirdPartyRisk,
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
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 markdown_strings import bold, link
|
|
15
|
+
|
|
16
|
+
from ...constants import TIMESTAMP_STR, TRUNCATE_COMMENT
|
|
17
|
+
from ...markdown import (
|
|
18
|
+
MarkdownMaker,
|
|
19
|
+
)
|
|
20
|
+
from ..models.common_models import ResolvedEntity
|
|
21
|
+
from ..pa_category import PACategory
|
|
22
|
+
from .markdown_code_repo import _code_repo_markdown
|
|
23
|
+
from .markdown_domain_abuse import _domain_abuse_markdown
|
|
24
|
+
from .markdown_identity_exposure import _identity_exposure_markdown
|
|
25
|
+
|
|
26
|
+
PORTAL_URL = 'https://app.recordedfuture.com/portal/playbook-alerts/{}'
|
|
27
|
+
API_URL = 'https://api.recordedfuture.com/playbook-alert/{}/{}'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
MARKDOWN_BY_PBA_TYPE = {
|
|
31
|
+
PACategory.CODE_REPO_LEAKAGE.value: _code_repo_markdown,
|
|
32
|
+
PACategory.DOMAIN_ABUSE.value: _domain_abuse_markdown,
|
|
33
|
+
PACategory.IDENTITY_NOVEL_EXPOSURES.value: _identity_exposure_markdown,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _generic_pba_summary(pba, md_maker: MarkdownMaker):
|
|
38
|
+
md_maker.add_title(pba.panel_status.case_rule_label)
|
|
39
|
+
id_ = pba.playbook_alert_id
|
|
40
|
+
general_info = [
|
|
41
|
+
f'{bold("ID:")} {id_}',
|
|
42
|
+
f'{bold("Created:")} {pba.panel_status.created.strftime(TIMESTAMP_STR)}',
|
|
43
|
+
f'{bold("Updated:")} {pba.panel_status.updated.strftime(TIMESTAMP_STR)}',
|
|
44
|
+
f'{bold("Status:")} {pba.panel_status.status}',
|
|
45
|
+
f'{bold("Priority:")} {pba.panel_status.priority}',
|
|
46
|
+
'{} | {}'.format(
|
|
47
|
+
link('API', API_URL.format(pba.category, id_)), link('Portal', PORTAL_URL.format(id_))
|
|
48
|
+
),
|
|
49
|
+
]
|
|
50
|
+
md_maker.add_section('Summary', general_info)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _unmapped_pba(pba, md_maker: MarkdownMaker, *args, **kwargs) -> str: # noqa: ARG001
|
|
54
|
+
md_maker.sections.insert(
|
|
55
|
+
0,
|
|
56
|
+
md_maker.validate_section(
|
|
57
|
+
'Warning',
|
|
58
|
+
[
|
|
59
|
+
(
|
|
60
|
+
'> 🚨 The markdown of this alert is not fully supported yet. '
|
|
61
|
+
'For full details check the portal 🚨'
|
|
62
|
+
)
|
|
63
|
+
],
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if targets := pba.panel_status.targets:
|
|
68
|
+
if any(isinstance(t, str) for t in targets):
|
|
69
|
+
md_maker.add_section('Targets', ', '.join(t for t in targets))
|
|
70
|
+
elif any(isinstance(t, ResolvedEntity) for t in targets):
|
|
71
|
+
md_maker.add_section('Targets', [t.name for t in targets])
|
|
72
|
+
|
|
73
|
+
return md_maker.format_output()
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _markdown_playbook_alert(
|
|
77
|
+
playbook_alert,
|
|
78
|
+
html_tags: bool = True,
|
|
79
|
+
character_limit: int = None,
|
|
80
|
+
defang_iocs: bool = False,
|
|
81
|
+
iocs_to_defang: list = None,
|
|
82
|
+
) -> str:
|
|
83
|
+
md_maker = MarkdownMaker(
|
|
84
|
+
addendum=TRUNCATE_COMMENT.format(
|
|
85
|
+
type_='alert', url=PORTAL_URL.format(playbook_alert.playbook_alert_id)
|
|
86
|
+
),
|
|
87
|
+
character_limit=character_limit,
|
|
88
|
+
defang_iocs=defang_iocs,
|
|
89
|
+
iocs_to_defang=iocs_to_defang or [],
|
|
90
|
+
)
|
|
91
|
+
_generic_pba_summary(playbook_alert, md_maker)
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
return MARKDOWN_BY_PBA_TYPE.get(playbook_alert.category, _unmapped_pba)(
|
|
95
|
+
playbook_alert, md_maker, html_tags
|
|
96
|
+
)
|
|
97
|
+
except AttributeError:
|
|
98
|
+
return _unmapped_pba(playbook_alert, md_maker, html_tags)
|
|
@@ -0,0 +1,64 @@
|
|
|
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 collections import defaultdict
|
|
15
|
+
|
|
16
|
+
from markdown_strings import blockquote, bold
|
|
17
|
+
|
|
18
|
+
from ...constants import TIMESTAMP_STR
|
|
19
|
+
from ...markdown import MarkdownMaker, clean_text, divider, html_textarea
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _add_repository(pba, md_maker: MarkdownMaker):
|
|
23
|
+
repos = [
|
|
24
|
+
f'{bold("Owner:" )} {pba.panel_evidence_summary.repository.owner.name}',
|
|
25
|
+
f'{bold("URL:" )} {pba.panel_evidence_summary.repository.name}',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
md_maker.add_section('Repository', repos)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _add_assessment(pba, md_maker: MarkdownMaker, html_tags: bool):
|
|
32
|
+
assessments = []
|
|
33
|
+
|
|
34
|
+
for assess in pba.panel_evidence_summary.evidence:
|
|
35
|
+
details = defaultdict(list)
|
|
36
|
+
for a in assess.assessments:
|
|
37
|
+
details[a.title].append(clean_text(a.value))
|
|
38
|
+
|
|
39
|
+
details_list = []
|
|
40
|
+
for k, v in details.items():
|
|
41
|
+
details_list.append(f'{bold(k + ":")} {", ".join(v)}')
|
|
42
|
+
|
|
43
|
+
targets = ''
|
|
44
|
+
if targets := ', '.join(t.name for t in assess.targets):
|
|
45
|
+
targets = f'{bold("Assessment targets:")} {targets}'
|
|
46
|
+
|
|
47
|
+
commit = f'{bold("Commit:")} {assess.url}'
|
|
48
|
+
published = f'{bold("Published:")} {assess.published.strftime(TIMESTAMP_STR)}'
|
|
49
|
+
content = blockquote(
|
|
50
|
+
html_textarea(clean_text(assess.content)) if html_tags else clean_text(assess.content)
|
|
51
|
+
)
|
|
52
|
+
content = f'{bold("Content:")}\n\n{content}'
|
|
53
|
+
|
|
54
|
+
assessments.extend([published, targets, *details_list, commit, content, divider()])
|
|
55
|
+
md_maker.add_section('Assessments', assessments)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _code_repo_markdown(pba, md_maker: MarkdownMaker, html_tags: bool) -> str:
|
|
59
|
+
if targets := pba.panel_status.targets:
|
|
60
|
+
md_maker.add_section('Targets', [t.name for t in targets])
|
|
61
|
+
|
|
62
|
+
_add_repository(pba, md_maker)
|
|
63
|
+
_add_assessment(pba, md_maker, html_tags)
|
|
64
|
+
return md_maker.format_output()
|