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.
Files changed (115) hide show
  1. psengine/__init__.py +22 -0
  2. psengine/_sdk_id.py +16 -0
  3. psengine/_version.py +14 -0
  4. psengine/analyst_notes/__init__.py +32 -0
  5. psengine/analyst_notes/constants.py +15 -0
  6. psengine/analyst_notes/errors.py +42 -0
  7. psengine/analyst_notes/helpers.py +90 -0
  8. psengine/analyst_notes/models.py +219 -0
  9. psengine/analyst_notes/note.py +149 -0
  10. psengine/analyst_notes/note_mgr.py +400 -0
  11. psengine/base_http_client.py +285 -0
  12. psengine/classic_alerts/__init__.py +24 -0
  13. psengine/classic_alerts/classic_alert.py +275 -0
  14. psengine/classic_alerts/classic_alert_mgr.py +507 -0
  15. psengine/classic_alerts/constants.py +31 -0
  16. psengine/classic_alerts/errors.py +38 -0
  17. psengine/classic_alerts/helpers.py +87 -0
  18. psengine/classic_alerts/markdown/__init__.py +13 -0
  19. psengine/classic_alerts/markdown/markdown.py +359 -0
  20. psengine/classic_alerts/models.py +141 -0
  21. psengine/collective_insights/__init__.py +29 -0
  22. psengine/collective_insights/collective_insights.py +164 -0
  23. psengine/collective_insights/constants.py +44 -0
  24. psengine/collective_insights/errors.py +18 -0
  25. psengine/collective_insights/insight.py +89 -0
  26. psengine/collective_insights/models.py +81 -0
  27. psengine/common_models.py +89 -0
  28. psengine/config/__init__.py +15 -0
  29. psengine/config/config.py +284 -0
  30. psengine/config/errors.py +18 -0
  31. psengine/constants.py +63 -0
  32. psengine/detection/__init__.py +17 -0
  33. psengine/detection/detection_mgr.py +135 -0
  34. psengine/detection/detection_rule.py +85 -0
  35. psengine/detection/errors.py +26 -0
  36. psengine/detection/helpers.py +56 -0
  37. psengine/detection/models.py +47 -0
  38. psengine/endpoints.py +98 -0
  39. psengine/enrich/__init__.py +28 -0
  40. psengine/enrich/constants.py +73 -0
  41. psengine/enrich/errors.py +26 -0
  42. psengine/enrich/lookup.py +299 -0
  43. psengine/enrich/lookup_mgr.py +341 -0
  44. psengine/enrich/models/__init__.py +13 -0
  45. psengine/enrich/models/base_enriched_entity.py +43 -0
  46. psengine/enrich/models/lookup.py +271 -0
  47. psengine/enrich/models/soar.py +138 -0
  48. psengine/enrich/soar.py +89 -0
  49. psengine/enrich/soar_mgr.py +176 -0
  50. psengine/entity_lists/__init__.py +16 -0
  51. psengine/entity_lists/constants.py +19 -0
  52. psengine/entity_lists/entity_list.py +435 -0
  53. psengine/entity_lists/entity_list_mgr.py +185 -0
  54. psengine/entity_lists/errors.py +26 -0
  55. psengine/entity_lists/models.py +87 -0
  56. psengine/entity_match/__init__.py +16 -0
  57. psengine/entity_match/entity_match.py +90 -0
  58. psengine/entity_match/entity_match_mgr.py +235 -0
  59. psengine/entity_match/errors.py +18 -0
  60. psengine/entity_match/models.py +22 -0
  61. psengine/errors.py +41 -0
  62. psengine/helpers/__init__.py +23 -0
  63. psengine/helpers/helpers.py +471 -0
  64. psengine/logger/__init__.py +15 -0
  65. psengine/logger/constants.py +39 -0
  66. psengine/logger/errors.py +18 -0
  67. psengine/logger/rf_logger.py +148 -0
  68. psengine/markdown/__init__.py +21 -0
  69. psengine/markdown/markdown.py +169 -0
  70. psengine/markdown/models.py +22 -0
  71. psengine/playbook_alerts/__init__.py +34 -0
  72. psengine/playbook_alerts/constants.py +35 -0
  73. psengine/playbook_alerts/errors.py +35 -0
  74. psengine/playbook_alerts/helpers.py +80 -0
  75. psengine/playbook_alerts/mappings.py +44 -0
  76. psengine/playbook_alerts/markdown/__init__.py +13 -0
  77. psengine/playbook_alerts/markdown/markdown.py +98 -0
  78. psengine/playbook_alerts/markdown/markdown_code_repo.py +64 -0
  79. psengine/playbook_alerts/markdown/markdown_domain_abuse.py +118 -0
  80. psengine/playbook_alerts/markdown/markdown_identity_exposure.py +158 -0
  81. psengine/playbook_alerts/models/__init__.py +36 -0
  82. psengine/playbook_alerts/models/common_models.py +18 -0
  83. psengine/playbook_alerts/models/panel_log.py +329 -0
  84. psengine/playbook_alerts/models/panel_status.py +70 -0
  85. psengine/playbook_alerts/models/pba_code_repo_leak.py +52 -0
  86. psengine/playbook_alerts/models/pba_cyber_vulnerability.py +53 -0
  87. psengine/playbook_alerts/models/pba_domain_abuse.py +139 -0
  88. psengine/playbook_alerts/models/pba_identity_exposures.py +93 -0
  89. psengine/playbook_alerts/models/pba_third_party_risk.py +103 -0
  90. psengine/playbook_alerts/models/search_endpoint.py +68 -0
  91. psengine/playbook_alerts/pa_category.py +37 -0
  92. psengine/playbook_alerts/playbook_alert_mgr.py +593 -0
  93. psengine/playbook_alerts/playbook_alerts.py +393 -0
  94. psengine/rf_client.py +430 -0
  95. psengine/risklists/__init__.py +17 -0
  96. psengine/risklists/constants.py +15 -0
  97. psengine/risklists/errors.py +20 -0
  98. psengine/risklists/models.py +65 -0
  99. psengine/risklists/risklist_mgr.py +156 -0
  100. psengine/stix2/__init__.py +21 -0
  101. psengine/stix2/base_stix_entity.py +62 -0
  102. psengine/stix2/complex_entity.py +372 -0
  103. psengine/stix2/constants.py +81 -0
  104. psengine/stix2/enriched_indicator.py +261 -0
  105. psengine/stix2/errors.py +22 -0
  106. psengine/stix2/helpers.py +68 -0
  107. psengine/stix2/rf_bundle.py +240 -0
  108. psengine/stix2/simple_entity.py +145 -0
  109. psengine/stix2/util.py +53 -0
  110. psengine-2.0.4.dist-info/METADATA +189 -0
  111. psengine-2.0.4.dist-info/RECORD +115 -0
  112. psengine-2.0.4.dist-info/WHEEL +5 -0
  113. psengine-2.0.4.dist-info/entry_points.txt +2 -0
  114. psengine-2.0.4.dist-info/licenses/LICENSE +21 -0
  115. psengine-2.0.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,271 @@
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 typing import Optional, Union
16
+
17
+ from pydantic import Field
18
+
19
+ from ...common_models import IdNameType, IdNameTypeDescription, RFBaseModel
20
+
21
+
22
+ ###########################################################
23
+ # Enterprise Lists
24
+ ###########################################################
25
+ class EnterpriseList(RFBaseModel):
26
+ added: Optional[datetime]
27
+ list_: IdNameTypeDescription = Field(alias='list')
28
+
29
+
30
+ ###########################################################
31
+ class RiskyCIDRPIP(RFBaseModel):
32
+ score: int
33
+ ip: IdNameType
34
+
35
+
36
+ class AIInsights(RFBaseModel):
37
+ comment: Optional[str] = None
38
+ text: Optional[str] = None
39
+ number_of_references: Optional[int] = Field(alias='numberOfReferences', default=None)
40
+
41
+
42
+ class EvidenceDetails(RFBaseModel):
43
+ mitigation_string: str = Field(alias='mitigationString')
44
+ evidence_string: str = Field(alias='evidenceString')
45
+ rule: str
46
+ criticality: int
47
+ timestamp: datetime
48
+ criticality_label: str = Field(alias='criticalityLabel')
49
+
50
+
51
+ class EntityRisk(RFBaseModel):
52
+ criticality_label: str = Field(alias='criticalityLabel')
53
+ risk_string: str = Field(alias='riskString')
54
+ rules: int
55
+ criticality: int
56
+ risk_summary: str = Field(alias='riskSummary')
57
+ score: int
58
+ evidence_details: list[EvidenceDetails] = Field(alias='evidenceDetails')
59
+
60
+
61
+ class Sighting(RFBaseModel):
62
+ source: str
63
+ url: str
64
+ published: datetime
65
+ fragment: str
66
+ title: str
67
+ type_: str = Field(alias='type')
68
+
69
+
70
+ class RiskMappingCategory(RFBaseModel):
71
+ framework: str
72
+ name: str
73
+
74
+
75
+ class RiskMapping(RFBaseModel):
76
+ rule: str
77
+ categories: Optional[list[RiskMappingCategory]] = None
78
+
79
+
80
+ class RelatedEntity(RFBaseModel):
81
+ count: int
82
+ entity: IdNameTypeDescription
83
+
84
+
85
+ class RelatedEntities(RFBaseModel):
86
+ entities: list[RelatedEntity]
87
+ type_: str = Field(alias='type')
88
+
89
+
90
+ class GeoLocation(RFBaseModel):
91
+ continent: Optional[str] = None
92
+ country: Optional[str] = None
93
+ city: Optional[str] = None
94
+
95
+
96
+ class IPLocation(RFBaseModel):
97
+ organization: Optional[str]
98
+ cidr: IdNameType
99
+ location: GeoLocation
100
+ asn: Optional[str] = None
101
+
102
+
103
+ class Timestamps(RFBaseModel):
104
+ last_seen: datetime = Field(alias='lastSeen')
105
+ first_seen: datetime = Field(alias='firstSeen')
106
+
107
+
108
+ class ReferenceCount(RFBaseModel):
109
+ date: datetime
110
+ count: int
111
+
112
+
113
+ class Metric(RFBaseModel):
114
+ type_: str = Field(alias='type')
115
+ value: Union[int, float]
116
+
117
+
118
+ ###########################################################
119
+ # Links
120
+ ###########################################################
121
+ class LinksCounts(RFBaseModel):
122
+ count: int
123
+ type_: IdNameTypeDescription = Field(alias='type')
124
+
125
+
126
+ class LinksList(RFBaseModel):
127
+ entities: list[IdNameTypeDescription]
128
+ total_count: int
129
+ type_: IdNameTypeDescription = Field(alias='type')
130
+
131
+
132
+ class SectionHits(RFBaseModel):
133
+ section_id: IdNameType
134
+ total_count: int
135
+ lists: Optional[list[LinksList]] = None
136
+
137
+
138
+ class Hits(RFBaseModel):
139
+ sections: list[SectionHits]
140
+ start_date: datetime
141
+ stop_date: datetime
142
+ total_count: int
143
+ sample_reference_ids: list[str]
144
+ counts: list[LinksCounts]
145
+ event_count: int
146
+
147
+
148
+ class MethodAggregate(RFBaseModel):
149
+ count: int
150
+ type_: str = Field(alias='type')
151
+
152
+
153
+ class Links(RFBaseModel):
154
+ hits: list[Hits]
155
+ method_aggregates: list[MethodAggregate]
156
+ counts: list[LinksCounts]
157
+
158
+
159
+ ###########################################################
160
+ # Linked Malware
161
+ ###########################################################
162
+ class LinkedMalware(RFBaseModel):
163
+ entities: list[IdNameType]
164
+ total_count: int
165
+
166
+
167
+ ###########################################################
168
+ # CVSS
169
+ ###########################################################
170
+ class CVSS(RFBaseModel):
171
+ access_vector: Optional[str] = Field(alias='accessVector', default=None)
172
+ last_modified: Optional[datetime] = Field(alias='lastModified', default=None)
173
+ published: Optional[datetime] = None
174
+ score: Optional[float] = None
175
+ availability: Optional[str] = None
176
+ authentication: Optional[str] = None
177
+ access_complexity: Optional[str] = Field(alias='accessComplexity', default=None)
178
+ integrity: Optional[str] = None
179
+ confidentiality: Optional[str] = None
180
+ version: Optional[str] = None
181
+
182
+
183
+ class CVSSRating(RFBaseModel):
184
+ score: float
185
+ modified: datetime
186
+ version: str
187
+ type_: str = Field(alias='type')
188
+ created: datetime
189
+
190
+
191
+ class CVSSV3(RFBaseModel):
192
+ scope: Optional[str] = None
193
+ exploitability_score: Optional[float] = Field(alias='exploitabilityScore', default=None)
194
+ modified: Optional[datetime] = None
195
+ base_severity: Optional[str] = Field(alias='baseSeverity', default=None)
196
+ base_score: Optional[float] = Field(alias='baseScore', default=None)
197
+ privileges_required: Optional[str] = Field(alias='privilegesRequired', default=None)
198
+ user_interaction: Optional[str] = Field(alias='userInteraction', default=None)
199
+ impact_score: Optional[float] = Field(alias='impactScore', default=None)
200
+ attack_vector: Optional[str] = Field(alias='attackVector', default=None)
201
+ integrity_impact: Optional[str] = Field(alias='integrityImpact', default=None)
202
+ confidentiality_impact: Optional[str] = Field(alias='confidentialityImpact', default=None)
203
+ vector_string: Optional[str] = Field(alias='vectorString', default=None)
204
+ version: Optional[str] = None
205
+ attack_complexity: Optional[str] = Field(alias='attackComplexity', default=None)
206
+ created: Optional[datetime] = None
207
+ availability_impact: Optional[str] = Field(alias='availabilityImpact', default=None)
208
+
209
+
210
+ ###########################################################
211
+ # Raw Risk
212
+ ###########################################################
213
+ class RawRisk(RFBaseModel):
214
+ rule: str
215
+ timestamp: datetime
216
+
217
+
218
+ ###########################################################
219
+ # DNS Port Cert
220
+ ###########################################################
221
+ class Validity(RFBaseModel):
222
+ valid_from: datetime = Field(alias='validFrom')
223
+ valid_to: datetime = Field(alias='validTo')
224
+
225
+
226
+ class Issuer(RFBaseModel):
227
+ organization: Optional[str] = None
228
+ location: Optional[str] = None
229
+
230
+
231
+ class Certificate(RFBaseModel):
232
+ subject: Optional[str] = None
233
+ validity: Validity
234
+ issuer: Issuer
235
+ seen_on_port: list[int] = Field(alias='seenOnPort')
236
+
237
+
238
+ class ForwardDNS(RFBaseModel):
239
+ hostname: Optional[str] = None
240
+ last_seen: Union[datetime, None] = Field(alias='lastSeen')
241
+ first_seen: Union[datetime, None] = Field(alias='firstSeen')
242
+
243
+
244
+ class DNS(RFBaseModel):
245
+ forward_dns: list[ForwardDNS] = Field(alias='forwardDns')
246
+ reverse_dns: Optional[str] = Field(alias='reverseDns', default=None)
247
+
248
+
249
+ class Port(RFBaseModel):
250
+ name: str
251
+ version: Union[str, None]
252
+ port: int
253
+ extra_info: Union[str, None] = Field(alias='extraInfo')
254
+ protocol: str
255
+ product: Union[str, None]
256
+
257
+
258
+ class DnsPortCert(RFBaseModel):
259
+ certificates: Optional[list[Certificate]] = None
260
+ dns: Optional[DNS] = None
261
+ ports: Optional[list[Port]] = None
262
+
263
+
264
+ ###########################################################
265
+ # NVD
266
+ ###########################################################
267
+
268
+
269
+ class NvdReference(RFBaseModel):
270
+ url: str
271
+ tags: list[str]
@@ -0,0 +1,138 @@
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 typing import Optional
16
+
17
+ from pydantic import Field, model_validator
18
+
19
+ from ...common_models import RFBaseModel
20
+
21
+
22
+ class ScoreCount(RFBaseModel):
23
+ count: Optional[int] = None
24
+ max_count: int = Field(alias='maxCount')
25
+
26
+
27
+ class ScoreRule(RFBaseModel):
28
+ score: int
29
+ rule: ScoreCount
30
+
31
+
32
+ class Public(ScoreRule):
33
+ summary: list
34
+ most_critical_rule: str = Field(alias='mostCriticalRule')
35
+
36
+
37
+ class Evidence(RFBaseModel):
38
+ count: int
39
+ timestamp: datetime
40
+ description: str
41
+ rule: str
42
+ # TODO - temp fix until API team fixes/confirms behaviour of sightings
43
+ sightings: int = 0
44
+ mitigation: str
45
+ level: int
46
+ type_: str = Field(alias='type')
47
+
48
+
49
+ class RiskRule(ScoreCount):
50
+ score: Optional[int] = None
51
+ summary: list
52
+ most_critical: str = Field(alias='mostCritical')
53
+ evidence: Optional[list[Evidence]] = None
54
+
55
+ @model_validator(mode='before')
56
+ @classmethod
57
+ def evidence_transform(cls, data: dict) -> dict:
58
+ """Transforms the evidence field into a list of dicts, each with a 'type' key.
59
+
60
+ From:
61
+
62
+ .. code-block::
63
+
64
+ "evidence": {
65
+ "recentValidatedCnc": {
66
+ "count": 1,
67
+ "timestamp": "2024-03-25T07:18:35.000Z",
68
+ "description": "xyz",
69
+ "rule": "Validated C&C Server",
70
+ "sightings": 41,
71
+ "mitigation": "",
72
+ "level": 4
73
+ },
74
+ "recentSuspectedCnc": {
75
+ "count": 1,
76
+ "timestamp": "2024-03-24T16:05:31.634Z",
77
+ "description": "xyz",
78
+ "rule": "Recent Suspected C&C Server",
79
+ "sightings": 5,
80
+ "mitigation": "",
81
+ "level": 2
82
+ }
83
+ }
84
+
85
+ To:
86
+
87
+ .. code-block::
88
+
89
+ "evidence": [
90
+ {
91
+ "count": 1,
92
+ "timestamp": "2023-12-11T19:25:25.892000Z",
93
+ "description": "xyz",
94
+ "sightings": 1,
95
+ "mitigation": "",
96
+ "level": 3,
97
+ "type": "recentReportedCnc"
98
+ },
99
+ {
100
+ "count": 2,
101
+ "timestamp": "2023-12-25T22:09:55.398000Z",
102
+ "description": "xyz",
103
+ "rule": "Historical Suspected C&C Server",
104
+ "sightings": 2,
105
+ "mitigation": "",
106
+ "level": 1,
107
+ "type": "suspectedCnc"
108
+ }
109
+ ]
110
+
111
+ Args:
112
+ data (dict): The data to be validated.
113
+
114
+ Returns:
115
+ dict: The transformed data.
116
+ """
117
+ if 'evidence' not in data:
118
+ return data
119
+
120
+ evidence = data.pop('evidence')
121
+
122
+ data['evidence'] = [{**ev, 'type': key} for key, ev in evidence.items()]
123
+
124
+ return data
125
+
126
+
127
+ class Context(RFBaseModel):
128
+ phishing: Optional[ScoreRule] = None
129
+ public: Optional[Public] = None
130
+ c2: Optional[ScoreRule] = None
131
+ malware: Optional[ScoreRule] = None
132
+
133
+
134
+ class Risk(RFBaseModel):
135
+ score: int
136
+ level: int
137
+ context: Context
138
+ rule: RiskRule
@@ -0,0 +1,89 @@
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 functools import total_ordering
15
+ from typing import Optional
16
+
17
+ from pydantic import Field
18
+
19
+ from ..common_models import IdNameTypeDescription, RFBaseModel
20
+ from .models.soar import Risk
21
+
22
+
23
+ @total_ordering
24
+ class SOAREnrichedEntity(RFBaseModel):
25
+ """Model used for validating returned data from SOAR enrichment endpoint for bulk enrichment.
26
+
27
+ Methods:
28
+ __hash__:
29
+ Returns a hash value based on the entity ``id_`` and the risk score.
30
+
31
+ __eq__:
32
+ Checks equality between two ``SOAREnrichedEntity`` instances based on their entity name
33
+ and the risk score.
34
+
35
+ __gt__:
36
+ Defines a greater-than comparison between two ``SOAREnrichedEntity`` instances based on
37
+ their risk score and entity name.
38
+
39
+ __str__:
40
+ Returns a string representation of the ``SOAREnrichedEntity`` instance with:
41
+ enriched entity name, risk score, and most critical rule.
42
+
43
+ .. code-block:: python
44
+
45
+ >>> print(soar_enriched_entity)
46
+ Enriched Entity: 1.1.1.1, Risk Score: 95, Most Critical Rule: C&C Server
47
+
48
+ Total Ordering:
49
+ The ordering of ``SOAREnrichedEntity`` instances is determined primarily by the risk score.
50
+ If two instances have the same risk score, their entity name is used as a secondary
51
+ criterion for ordering.
52
+ """
53
+
54
+ risk: Risk
55
+ entity: IdNameTypeDescription
56
+
57
+ def __hash__(self):
58
+ return hash((self.entity.id_, self.risk.score))
59
+
60
+ def __eq__(self, other: 'SOAREnrichedEntity'):
61
+ return (self.entity.name, self.risk.score) == (other.entity.name, other.risk.score)
62
+
63
+ def __gt__(self, other: 'SOAREnrichedEntity'):
64
+ return (self.risk.score, self.entity.name) > (other.risk.score, other.entity.name)
65
+
66
+ def __str__(self):
67
+ return (
68
+ f'Enriched Entity: {self.entity.name}, Risk Score: {self.risk.score}, '
69
+ f'Most Critical Rule: {self.risk.rule.most_critical}'
70
+ )
71
+
72
+
73
+ class SOAREnrichIn(RFBaseModel):
74
+ """Model used to validate payload sent to SOAR enrichment endpoint."""
75
+
76
+ ip: Optional[list[str]] = None
77
+ domain: Optional[list[str]] = None
78
+ url: Optional[list[str]] = None
79
+ hash_: Optional[list[str]] = Field(alias='hash', default=None)
80
+ vulnerability: Optional[list[str]] = None
81
+ companybydomain: Optional[list[str]] = None
82
+
83
+
84
+ class SOAREnrichOut(RFBaseModel):
85
+ """Model used for collecting all the data returned in a SOAR call."""
86
+
87
+ entity: str
88
+ is_enriched: bool
89
+ content: Optional[SOAREnrichedEntity] = None
@@ -0,0 +1,176 @@
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 logging
15
+ from itertools import chain
16
+ from typing import Optional
17
+
18
+ from pydantic import validate_call
19
+
20
+ from ..endpoints import EP_SOAR_ENRICHMENT
21
+ from ..helpers import MultiThreadingHelper, connection_exceptions, debug_call
22
+ from ..rf_client import RFClient
23
+ from .constants import SOAR_POST_ROWS
24
+ from .errors import EnrichmentSoarError
25
+ from .soar import SOAREnrichedEntity, SOAREnrichIn, SOAREnrichOut
26
+
27
+
28
+ class SoarMgr:
29
+ """Perform SOAR enrichment of entities."""
30
+
31
+ def __init__(self, rf_token: str = None):
32
+ """Initializes the SoarMgr object.
33
+
34
+ Args:
35
+ rf_token (str, optional): Recorded Future API token. Defaults to None
36
+ """
37
+ self.log = logging.getLogger(__name__)
38
+ self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
39
+
40
+ @validate_call
41
+ @debug_call
42
+ def soar(
43
+ self,
44
+ ip: Optional[list[str]] = None,
45
+ domain: Optional[list[str]] = None,
46
+ hash_: Optional[list[str]] = None,
47
+ vulnerability: Optional[list[str]] = None,
48
+ url: Optional[list[str]] = None,
49
+ companybydomain: Optional[list[str]] = None,
50
+ max_workers: Optional[int] = 0,
51
+ ) -> list[SOAREnrichOut]:
52
+ """Enrich multiple types of IOCs via the SOAR API.
53
+
54
+ Endpoint:
55
+ ``v2/soar/enrichment``
56
+
57
+ This method allows for batch processing of various IOC types, including IP addresses,
58
+ domains, file hashes, vulnerabilities, URLs, and company domains. It utilizes either
59
+ multi-threading or sequential processing based on the `max_workers` parameter.
60
+
61
+ Examples:
62
+ Simple SOAR enrichment:
63
+
64
+ .. code-block:: python
65
+ :linenos:
66
+
67
+ mgr.soar(ip=['1.1.1.1'])
68
+
69
+ Multithreaded example:
70
+
71
+ .. code-block:: python
72
+ :linenos:
73
+
74
+ mgr.soar(ip=['1.1.1.1'], max_workers=10)
75
+
76
+ To write an enriched object to file:
77
+
78
+ .. code-block:: python
79
+ :linenos:
80
+
81
+ from pathlib import Path
82
+ from json import dump
83
+
84
+ mgr = SoarMgr()
85
+ OUTPUT_DIR = Path('your' / 'path')
86
+ OUTPUT_DIR.mkdir(exists_ok=True)
87
+ data = mgr.soar(ip=['1.1.1.1', '8.8.8.8'])
88
+ for ip in data:
89
+ (OUTPUT_DIR / f'{ip.entity}.json').write_text(dumps(ip.json(), indent=2))
90
+
91
+ Args:
92
+ ip (List[str], optional): List of IP addresses to enrich.
93
+ domain (List[str], optional): List of domains to enrich.
94
+ hash_ (List[str], optional): List of file hashes to enrich.
95
+ vulnerability (List[str], optional): List of vulnerabilities to enrich.
96
+ url (List[str], optional): List of URLs to enrich.
97
+ companybydomain (List[str], optional): List of company domains to enrich.
98
+ max_workers (int, optional): number of workers to multithread requests.
99
+
100
+ Returns:
101
+ List[SOAREnrichOut]: A list of enriched data for the provided IOCs.
102
+
103
+ Raises:
104
+ ValueError: If no parameters are provided or all provided lists are empty.
105
+ ValidationError If the arguments have incorrect types or if the data returned
106
+ by the API is malformed.
107
+ EnrichmentSoarError: If an HTTP or JSON decoding error occurs during enrichment.
108
+ """
109
+ iocs = {
110
+ 'ip': ip,
111
+ 'domain': domain,
112
+ 'hash': hash_,
113
+ 'vulnerability': vulnerability,
114
+ 'url': url,
115
+ 'companybydomain': companybydomain,
116
+ }
117
+ iocs = {k: v for k, v in iocs.items() if v}
118
+ if not iocs:
119
+ raise ValueError('At least one parameter must be used')
120
+
121
+ results = []
122
+ if max_workers:
123
+ results.append(
124
+ chain.from_iterable(
125
+ MultiThreadingHelper.multithread_it(
126
+ max_workers,
127
+ self._fetch_data,
128
+ iterator=self._batched_cross_entity(iocs, SOAR_POST_ROWS),
129
+ )
130
+ )
131
+ )
132
+ else:
133
+ results = [
134
+ self._fetch_data(batched_iocs)
135
+ for batched_iocs in self._batched_cross_entity(iocs, SOAR_POST_ROWS)
136
+ ]
137
+
138
+ return list(chain.from_iterable(results))
139
+
140
+ def _batched_cross_entity(self, iocs, batch_size):
141
+ """Batches the SOAR data in dict of maximum SOAR_POST_ROWS elements in total.
142
+ It always return a list of SoarRequest compatible object as dict.
143
+ """
144
+ batches = []
145
+ current_batch = {k: [] for k in iocs}
146
+ current_count = 0
147
+
148
+ for k, vals in iocs.items():
149
+ for v in vals:
150
+ if current_count >= batch_size:
151
+ batches.append({k: v for k, v in current_batch.items() if v})
152
+ current_batch = {k: [] for k in iocs}
153
+ current_count = 0
154
+
155
+ current_batch[k].append(v)
156
+ current_count += 1
157
+
158
+ batches.append({k: v for k, v in current_batch.items() if v})
159
+ return batches
160
+
161
+ @connection_exceptions(ignore_status_code=[], exception_to_raise=EnrichmentSoarError)
162
+ @debug_call
163
+ def _fetch_data(self, data: dict) -> list[SOAREnrichOut]:
164
+ """Perform soar post request and raises EnrichementSoarError if something goes wrong."""
165
+ data = SOAREnrichIn.model_validate(data)
166
+ res = self.rf_client.request('post', EP_SOAR_ENRICHMENT, data=data.json()).json()['data'][
167
+ 'results'
168
+ ]
169
+ result = []
170
+
171
+ for d in res:
172
+ content = SOAREnrichedEntity.model_validate(d)
173
+ entity = content.entity.name
174
+ result.append(SOAREnrichOut(entity=entity, is_enriched=True, content=content))
175
+
176
+ return result
@@ -0,0 +1,16 @@
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 .entity_list import EntityList, ListEntity, ListInfoOut, ListStatusOut
15
+ from .entity_list_mgr import EntityListMgr
16
+ from .errors import ListApiError, ListResolutionError, ListStateError