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
psengine/constants.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
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 sys
|
|
15
|
+
|
|
16
|
+
#####################
|
|
17
|
+
# Multithreading Defaults
|
|
18
|
+
#####################
|
|
19
|
+
DEFAULT_LIMIT = 10
|
|
20
|
+
DEFAULT_MAX_WORKERS = 10
|
|
21
|
+
|
|
22
|
+
#####################
|
|
23
|
+
# Recorded Future API
|
|
24
|
+
#####################
|
|
25
|
+
RF_TOKEN_ENV_VAR = 'RF_TOKEN'
|
|
26
|
+
RF_TOKEN_VALIDATION_REGEX = r'^[a-f0-9]{32}$'
|
|
27
|
+
|
|
28
|
+
#####################
|
|
29
|
+
# Recorded Future Portal
|
|
30
|
+
#####################
|
|
31
|
+
RF_PORTAL_BASE_URL = 'https://app.recordedfuture.com'
|
|
32
|
+
|
|
33
|
+
# 0: entity type, 1: entity name
|
|
34
|
+
INDICATOR_INTEL_CARD_URL = RF_PORTAL_BASE_URL + '/live/sc/entity/{}:{}'
|
|
35
|
+
|
|
36
|
+
#####################
|
|
37
|
+
# HTTP Client Defaults
|
|
38
|
+
#####################
|
|
39
|
+
# In seconds
|
|
40
|
+
REQUEST_TIMEOUT = 120
|
|
41
|
+
POOL_MAX_SIZE = 120
|
|
42
|
+
|
|
43
|
+
# Request RETRY configuration
|
|
44
|
+
RETRY_TOTAL = 5
|
|
45
|
+
BACKOFF_FACTOR = 1
|
|
46
|
+
STATUS_FORCELIST = [502, 503, 504]
|
|
47
|
+
|
|
48
|
+
SSL_VERIFY = True
|
|
49
|
+
|
|
50
|
+
#####################
|
|
51
|
+
# Misc
|
|
52
|
+
#####################
|
|
53
|
+
# Output
|
|
54
|
+
ROOT_DIR = sys.path[0]
|
|
55
|
+
|
|
56
|
+
# String for timestamp conversion
|
|
57
|
+
TIMESTAMP_STR = '%Y-%m-%d %H:%M:%S'
|
|
58
|
+
|
|
59
|
+
# Markdown truncation string
|
|
60
|
+
TRUNCATE_COMMENT = (
|
|
61
|
+
'\n\nThis {type_} has been truncated due to a character limit imposed by this tool. '
|
|
62
|
+
'View the full {type_} at {url}'
|
|
63
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
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 .detection_mgr import DetectionMgr
|
|
15
|
+
from .detection_rule import DetectionRule, DetectionRuleSearchOut
|
|
16
|
+
from .errors import DetectionRuleFetchError, DetectionRuleSearchError
|
|
17
|
+
from .helpers import save_rule
|
|
@@ -0,0 +1,135 @@
|
|
|
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 typing import Optional, Union
|
|
16
|
+
|
|
17
|
+
from pydantic import validate_call
|
|
18
|
+
|
|
19
|
+
from ..constants import DEFAULT_LIMIT
|
|
20
|
+
from ..endpoints import EP_DETECTION_RULES
|
|
21
|
+
from ..helpers import debug_call
|
|
22
|
+
from ..helpers.helpers import connection_exceptions
|
|
23
|
+
from ..rf_client import RFClient
|
|
24
|
+
from .detection_rule import DetectionRule, DetectionRuleSearchOut
|
|
25
|
+
from .errors import DetectionRuleFetchError, DetectionRuleSearchError
|
|
26
|
+
|
|
27
|
+
SEARCH_LIMIT = 100
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DetectionMgr:
|
|
31
|
+
"""Class to manage DetectionRules and interaction with the Detection API."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, rf_token: str = None):
|
|
34
|
+
"""Initializes the DetectionMgr object.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
rf_token (str, optional): Recorded Future API token. Defaults to None
|
|
38
|
+
"""
|
|
39
|
+
self.log = logging.getLogger(__name__)
|
|
40
|
+
self.rf_client = RFClient(api_token=rf_token) if rf_token else RFClient()
|
|
41
|
+
|
|
42
|
+
@debug_call
|
|
43
|
+
@validate_call
|
|
44
|
+
@connection_exceptions(ignore_status_code=[], exception_to_raise=DetectionRuleSearchError)
|
|
45
|
+
def search(
|
|
46
|
+
self,
|
|
47
|
+
detection_rule: Union[list[str], str, None] = None,
|
|
48
|
+
entities: Optional[list[str]] = None,
|
|
49
|
+
created_before: Optional[str] = None,
|
|
50
|
+
created_after: Optional[str] = None,
|
|
51
|
+
updated_before: Optional[str] = None,
|
|
52
|
+
updated_after: Optional[str] = None,
|
|
53
|
+
doc_id: Optional[str] = None,
|
|
54
|
+
title: Optional[str] = None,
|
|
55
|
+
tagged_entities: Optional[bool] = None,
|
|
56
|
+
max_results: Optional[int] = DEFAULT_LIMIT,
|
|
57
|
+
) -> list[DetectionRule]:
|
|
58
|
+
"""Search for detection rules based on various filter criteria.
|
|
59
|
+
|
|
60
|
+
Endpoint:
|
|
61
|
+
``detection-rule/search``
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
detection_rule: Types of detection rules to search for. Defaults to None.
|
|
65
|
+
entities: List of entities to filter the search. Defaults to None.
|
|
66
|
+
created_before: Filter for rules created before this date. Defaults to None.
|
|
67
|
+
created_after: Filter for rules created after this date. Defaults to None.
|
|
68
|
+
updated_before: Filter for rules updated before this date. Defaults to None.
|
|
69
|
+
updated_after: Filter for rules updated after this date. Defaults to None.
|
|
70
|
+
doc_id: Filter by document ID. Defaults to None.
|
|
71
|
+
title: Filter by title. Defaults to None.
|
|
72
|
+
tagged_entities: Whether to filter by tagged entities. Defaults to None.
|
|
73
|
+
max_results: Limit the total number of results returned. Defaults to 10.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValidationError if any supplied parameter is of incorrect type.
|
|
77
|
+
DetectionRuleSearchError: if connection error occurs.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
List[DetectionRule]: A list of detection rules matching the search criteria.
|
|
81
|
+
"""
|
|
82
|
+
detection_rule = [detection_rule] if isinstance(detection_rule, str) else detection_rule
|
|
83
|
+
filters = {
|
|
84
|
+
'types': detection_rule,
|
|
85
|
+
'entities': entities,
|
|
86
|
+
'created': {'before': created_before, 'after': created_after},
|
|
87
|
+
'updated': {'before': updated_before, 'after': updated_after},
|
|
88
|
+
'doc_id': doc_id,
|
|
89
|
+
'title': title,
|
|
90
|
+
}
|
|
91
|
+
data = {
|
|
92
|
+
'filter': filters,
|
|
93
|
+
'tagged_entities': tagged_entities,
|
|
94
|
+
'limit': SEARCH_LIMIT,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
data = DetectionRuleSearchOut.model_validate(data)
|
|
98
|
+
results = self.rf_client.request_paged(
|
|
99
|
+
'post',
|
|
100
|
+
EP_DETECTION_RULES,
|
|
101
|
+
data=data.json(),
|
|
102
|
+
results_path='result',
|
|
103
|
+
offset_key='offset',
|
|
104
|
+
max_results=max_results,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
results = results if isinstance(results, list) else results.json().get('result', [])
|
|
108
|
+
return [DetectionRule.model_validate(data) for data in results]
|
|
109
|
+
|
|
110
|
+
@debug_call
|
|
111
|
+
@validate_call
|
|
112
|
+
def fetch(self, doc_id: str) -> Optional[DetectionRule]:
|
|
113
|
+
"""Fetch of a detection rule based on rule id.
|
|
114
|
+
|
|
115
|
+
Endpoint:
|
|
116
|
+
``detection-rule/search``
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
doc_id (str): Detection rule id to lookup.
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
ValidationError if any supplied parameter is of incorrect type.
|
|
123
|
+
DetectionRuleLookupError: If no rule is found for the given id.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Optional[DetectionRule]: The detection rule found for the given id.
|
|
127
|
+
"""
|
|
128
|
+
try:
|
|
129
|
+
result = self.search(doc_id=doc_id)
|
|
130
|
+
except DetectionRuleSearchError as e:
|
|
131
|
+
raise DetectionRuleFetchError(f'Error in fething of {doc_id}') from e
|
|
132
|
+
|
|
133
|
+
if result:
|
|
134
|
+
return result[0]
|
|
135
|
+
self.log.info(f'No rule found for id {doc_id}')
|
|
@@ -0,0 +1,85 @@
|
|
|
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 typing import Optional
|
|
17
|
+
|
|
18
|
+
from pydantic import Field
|
|
19
|
+
|
|
20
|
+
from ..common_models import RFBaseModel
|
|
21
|
+
from ..constants import TIMESTAMP_STR
|
|
22
|
+
from .models import DetectionRuleType, RuleContext, SearchFilter
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@total_ordering
|
|
26
|
+
class DetectionRule(RFBaseModel):
|
|
27
|
+
"""Detection rule model to validate output of the ``/search`` endpoint.
|
|
28
|
+
|
|
29
|
+
Methods:
|
|
30
|
+
__hash__:
|
|
31
|
+
Returns a hash value based on ``id_`` and updated timestamp.
|
|
32
|
+
|
|
33
|
+
__eq__:
|
|
34
|
+
Checks equality between two DetectionRule instances based on ``id_`` and updated time.
|
|
35
|
+
|
|
36
|
+
__gt__:
|
|
37
|
+
Defines a greater-than comparison between two DetectionRule instances based on
|
|
38
|
+
updated timestamp and ``id_``.
|
|
39
|
+
|
|
40
|
+
__str__:
|
|
41
|
+
Returns a string representation of the DetectionRule instance with:
|
|
42
|
+
``id_``, created timestamp, updated timestamp, and title.
|
|
43
|
+
|
|
44
|
+
.. code-block:: python
|
|
45
|
+
|
|
46
|
+
>>> print(detection_rule)
|
|
47
|
+
ID: rule123, Created: 2024-05-21 10:42:30AM, Updated: 2024-05-21 10:42:30AM,
|
|
48
|
+
Title: Example Rule
|
|
49
|
+
|
|
50
|
+
Total Ordering:
|
|
51
|
+
The ordering of DetectionRule instances is determined primarily by the updated timestamp.
|
|
52
|
+
If two instances have the same updated timestamp, ``id_`` is used as a secondary criterion.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
id_: str = Field(alias='id')
|
|
56
|
+
type_: DetectionRuleType = Field(alias='type')
|
|
57
|
+
title: str
|
|
58
|
+
description: str
|
|
59
|
+
created: datetime
|
|
60
|
+
updated: datetime
|
|
61
|
+
rules: list[RuleContext]
|
|
62
|
+
|
|
63
|
+
def __hash__(self):
|
|
64
|
+
return hash((self.id_, self.updated))
|
|
65
|
+
|
|
66
|
+
def __eq__(self, other: 'DetectionRule'):
|
|
67
|
+
return (self.id_, self.updated) == (other.id_, other.updated)
|
|
68
|
+
|
|
69
|
+
def __gt__(self, other: 'DetectionRule'):
|
|
70
|
+
return (self.updated, self.id_) > (other.updated, other.id_)
|
|
71
|
+
|
|
72
|
+
def __str__(self):
|
|
73
|
+
return (
|
|
74
|
+
f'ID: {self.id_}, Created: {self.created.strftime(TIMESTAMP_STR)}, '
|
|
75
|
+
f'Updated: {self.updated.strftime(TIMESTAMP_STR)}, Title: {self.title}'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class DetectionRuleSearchOut(RFBaseModel):
|
|
80
|
+
"""Model to validate ``/search`` endpoint payload sent."""
|
|
81
|
+
|
|
82
|
+
filter_: Optional[SearchFilter] = Field(alias='filter', default={})
|
|
83
|
+
tagged_entities: Optional[bool] = False
|
|
84
|
+
limit: Optional[int] = None
|
|
85
|
+
offset: Optional[str] = None
|
|
@@ -0,0 +1,26 @@
|
|
|
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 RecordedFutureError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DetectionRulesError(RecordedFutureError):
|
|
18
|
+
"""Error raised when there was an error with the detection rules API."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DetectionRuleSearchError(DetectionRulesError):
|
|
22
|
+
"""Error raised when there was an error searching for detection rules."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DetectionRuleFetchError(DetectionRulesError):
|
|
26
|
+
"""Error raised when there was an error fetching of detection rule id."""
|
|
@@ -0,0 +1,56 @@
|
|
|
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 pathlib import Path
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
from pydantic import validate_call
|
|
19
|
+
|
|
20
|
+
from ..errors import WriteFileError
|
|
21
|
+
from ..helpers import OSHelpers, debug_call
|
|
22
|
+
from .detection_rule import DetectionRule
|
|
23
|
+
|
|
24
|
+
LOG = logging.getLogger('psengine.detection.helpers')
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@debug_call
|
|
28
|
+
@validate_call
|
|
29
|
+
def save_rule(rule: DetectionRule, output_directory: Union[str, Path] = None):
|
|
30
|
+
"""Write detection rule content to file. If more than one detection rule is attached to rule,
|
|
31
|
+
all will be saved.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
rule (DetectionRule): single detection to write.
|
|
35
|
+
output_directory (Union[str, Path]): a path to write to. If not provided, it will be the
|
|
36
|
+
current working directory.
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
WriteFileError: if the path provided is not a directory or it cannot be created.
|
|
40
|
+
WriteFileError: if the write operations fail.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
if not rule.rules:
|
|
44
|
+
LOG.info(f'No rules to write for {rule.id_}')
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
output_directory = Path(output_directory).absolute() if output_directory else Path().cwd()
|
|
48
|
+
OSHelpers.mkdir(output_directory)
|
|
49
|
+
|
|
50
|
+
for data in rule.rules:
|
|
51
|
+
try:
|
|
52
|
+
full_path = output_directory / data.file_name
|
|
53
|
+
full_path.write_text(data.content)
|
|
54
|
+
LOG.info(f'Wrote: {full_path}')
|
|
55
|
+
except (FileNotFoundError, IsADirectoryError, PermissionError, OSError) as err: # noqa: PERF203
|
|
56
|
+
raise WriteFileError(f"Could not write file '{data.file_name}': {err}") from err
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
|
18
|
+
|
|
19
|
+
from ..common_models import DetectionRuleType, RFBaseModel
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Entity(RFBaseModel):
|
|
23
|
+
id_: str = Field(alias='id', default=None)
|
|
24
|
+
name: Optional[str] = None
|
|
25
|
+
type_: str = Field(alias='type', default=None)
|
|
26
|
+
|
|
27
|
+
display_name: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class RuleContext(RFBaseModel):
|
|
31
|
+
entities: list[Entity]
|
|
32
|
+
content: str
|
|
33
|
+
file_name: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TimeRange(RFBaseModel):
|
|
37
|
+
after: Optional[datetime] = None
|
|
38
|
+
before: Optional[datetime] = None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SearchFilter(RFBaseModel):
|
|
42
|
+
types: Optional[list[DetectionRuleType]] = None
|
|
43
|
+
entities: Optional[list[str]] = None
|
|
44
|
+
created: Optional[TimeRange] = None
|
|
45
|
+
updated: Optional[TimeRange] = None
|
|
46
|
+
doc_id: Optional[str] = None
|
|
47
|
+
title: Optional[str] = None
|
psengine/endpoints.py
ADDED
|
@@ -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
|
+
from os import environ
|
|
14
|
+
|
|
15
|
+
###############################################################################
|
|
16
|
+
# API Version
|
|
17
|
+
###############################################################################
|
|
18
|
+
API_VERSION = 'v2'
|
|
19
|
+
|
|
20
|
+
###############################################################################
|
|
21
|
+
# API Base URLs
|
|
22
|
+
###############################################################################
|
|
23
|
+
BASE_URL = 'https://api.recordedfuture.com'
|
|
24
|
+
CONNECT_API_BASE_URL = BASE_URL + '/' + API_VERSION
|
|
25
|
+
|
|
26
|
+
BASE_URL = environ.get('RF_BASE_URL') if environ.get('RF_BASE_URL') else BASE_URL
|
|
27
|
+
CONNECT_API_BASE_URL = (
|
|
28
|
+
environ.get('RF_BASE_URL') if environ.get('RF_BASE_URL') else CONNECT_API_BASE_URL
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
###############################################################################
|
|
32
|
+
# Classic Alerts Endpoints V3
|
|
33
|
+
###############################################################################
|
|
34
|
+
EP_CLASSIC_ALERTS_V3 = BASE_URL + '/v3'
|
|
35
|
+
EP_CLASSIC_ALERTS_RULES = CONNECT_API_BASE_URL + '/alert/rule'
|
|
36
|
+
EP_CLASSIC_ALERTS_UPDATE = CONNECT_API_BASE_URL + '/alert/update'
|
|
37
|
+
EP_CLASSIC_ALERTS_SEARCH = EP_CLASSIC_ALERTS_V3 + '/alerts/'
|
|
38
|
+
EP_CLASSIC_ALERTS_HITS = EP_CLASSIC_ALERTS_V3 + '/alerts/hits'
|
|
39
|
+
EP_CLASSIC_ALERTS_IMAGE = EP_CLASSIC_ALERTS_V3 + '/alerts/image'
|
|
40
|
+
EP_CLASSIC_ALERTS_ID = EP_CLASSIC_ALERTS_V3 + '/alerts/{}'
|
|
41
|
+
|
|
42
|
+
###############################################################################
|
|
43
|
+
# Fusion Endpoints
|
|
44
|
+
###############################################################################
|
|
45
|
+
EP_RISKLIST = f'{BASE_URL}/{API_VERSION}/' + '{}/risklist'
|
|
46
|
+
EP_FUSION_FILES = CONNECT_API_BASE_URL + '/fusion/files'
|
|
47
|
+
|
|
48
|
+
###############################################################################
|
|
49
|
+
# Playbook Alert Endpoints
|
|
50
|
+
###############################################################################
|
|
51
|
+
EP_PLAYBOOK_ALERT = BASE_URL + '/playbook-alert'
|
|
52
|
+
EP_PLAYBOOK_ALERT_SEARCH = EP_PLAYBOOK_ALERT + '/search'
|
|
53
|
+
EP_PLAYBOOK_ALERT_COMMON = EP_PLAYBOOK_ALERT + '/common'
|
|
54
|
+
EP_PLAYBOOK_ALERT_DOMAIN_ABUSE = EP_PLAYBOOK_ALERT + '/domain_abuse'
|
|
55
|
+
EP_PLAYBOOK_ALERT_CYBER_VULNERABILITY = EP_PLAYBOOK_ALERT + '/vulnerability'
|
|
56
|
+
EP_PLAYBOOK_ALERT_CODE_REPO_LEAKAGE = EP_PLAYBOOK_ALERT + '/code_repo_leakage'
|
|
57
|
+
EP_PLAYBOOK_ALERT_THIRD_PARTY_RISK = EP_PLAYBOOK_ALERT + '/third_party_risk'
|
|
58
|
+
EP_PLAYBOOK_ALERT_IDENTITY_NOVEL_EXPOSURES = EP_PLAYBOOK_ALERT + '/identity_novel_exposures'
|
|
59
|
+
|
|
60
|
+
###############################################################################
|
|
61
|
+
# Entity Match Endpoint
|
|
62
|
+
###############################################################################
|
|
63
|
+
EP_ENTITY_MATCH = BASE_URL + '/entity-match/match'
|
|
64
|
+
EP_ENTITY_LOOKUP = BASE_URL + '/entity-match/entity/{}'
|
|
65
|
+
|
|
66
|
+
###############################################################################
|
|
67
|
+
# List API Endpoints
|
|
68
|
+
###############################################################################
|
|
69
|
+
EP_LIST = BASE_URL + '/list'
|
|
70
|
+
EP_CREATE_LIST = EP_LIST + '/create'
|
|
71
|
+
EP_SEARCH_LIST = EP_LIST + '/search'
|
|
72
|
+
|
|
73
|
+
###############################################################################
|
|
74
|
+
# SOAR Endpoints
|
|
75
|
+
###############################################################################
|
|
76
|
+
EP_SOAR_ENRICHMENT = CONNECT_API_BASE_URL + '/soar/enrichment'
|
|
77
|
+
|
|
78
|
+
###############################################################################
|
|
79
|
+
# Detection Rules API Endpoints
|
|
80
|
+
###############################################################################
|
|
81
|
+
EP_DETECTION_RULES = BASE_URL + '/detection-rule/search'
|
|
82
|
+
|
|
83
|
+
###############################################################################
|
|
84
|
+
# Collective Insights API Endpoints
|
|
85
|
+
###############################################################################
|
|
86
|
+
EP_COLLECTIVE_INSIGHTS = BASE_URL + '/collective-insights'
|
|
87
|
+
EP_COLLECTIVE_INSIGHTS_DETECTIONS = EP_COLLECTIVE_INSIGHTS + '/detections'
|
|
88
|
+
|
|
89
|
+
###############################################################################
|
|
90
|
+
# Analyst Notes API Endpoints
|
|
91
|
+
###############################################################################
|
|
92
|
+
EP_ANALYST_NOTE = BASE_URL + '/analyst-note/'
|
|
93
|
+
EP_ANALYST_NOTE_SEARCH = EP_ANALYST_NOTE + 'search'
|
|
94
|
+
EP_ANALYST_NOTE_LOOKUP = EP_ANALYST_NOTE + 'lookup/{}'
|
|
95
|
+
EP_ANALYST_NOTE_PREVIEW = EP_ANALYST_NOTE + 'preview'
|
|
96
|
+
EP_ANALYST_NOTE_PUBLISH = EP_ANALYST_NOTE + 'publish'
|
|
97
|
+
EP_ANALYST_NOTE_DELETE = EP_ANALYST_NOTE + 'delete/{}'
|
|
98
|
+
EP_ANALYST_NOTE_ATTACHMENT = EP_ANALYST_NOTE + 'attachment/{}'
|
|
@@ -0,0 +1,28 @@
|
|
|
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 EnrichmentLookupError, EnrichmentSoarError
|
|
16
|
+
from .lookup import (
|
|
17
|
+
EnrichedCompany,
|
|
18
|
+
EnrichedDomain,
|
|
19
|
+
EnrichedHash,
|
|
20
|
+
EnrichedIP,
|
|
21
|
+
EnrichedMalware,
|
|
22
|
+
EnrichedURL,
|
|
23
|
+
EnrichedVulnerability,
|
|
24
|
+
EnrichmentData,
|
|
25
|
+
)
|
|
26
|
+
from .lookup_mgr import LookupMgr
|
|
27
|
+
from .soar import SOAREnrichedEntity, SOAREnrichIn, SOAREnrichOut
|
|
28
|
+
from .soar_mgr import SoarMgr
|
|
@@ -0,0 +1,73 @@
|
|
|
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 typing import Literal
|
|
15
|
+
|
|
16
|
+
from ..enrich import (
|
|
17
|
+
EnrichedCompany,
|
|
18
|
+
EnrichedDomain,
|
|
19
|
+
EnrichedHash,
|
|
20
|
+
EnrichedIP,
|
|
21
|
+
EnrichedMalware,
|
|
22
|
+
EnrichedURL,
|
|
23
|
+
EnrichedVulnerability,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
SOAR_POST_ROWS = 2000
|
|
27
|
+
|
|
28
|
+
ALLOWED_ENTITIES = Literal[
|
|
29
|
+
'company',
|
|
30
|
+
'company_by_domain',
|
|
31
|
+
'company/by_domain',
|
|
32
|
+
'domain',
|
|
33
|
+
'hash',
|
|
34
|
+
'ip',
|
|
35
|
+
'malware',
|
|
36
|
+
'url',
|
|
37
|
+
'vulnerability',
|
|
38
|
+
'Company',
|
|
39
|
+
'Organization',
|
|
40
|
+
'InternetDomainName',
|
|
41
|
+
'Hash',
|
|
42
|
+
'IpAddress',
|
|
43
|
+
'Malware',
|
|
44
|
+
'URL',
|
|
45
|
+
'CyberVulnerability',
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
ENTITY_FIELDS = ['entity', 'risk', 'timestamps']
|
|
49
|
+
MALWARE_FIELDS = ['entity', 'timestamps']
|
|
50
|
+
TYPE_MAPPING = {
|
|
51
|
+
'company/by_domain': 'company_by_domain',
|
|
52
|
+
'Organization': 'company',
|
|
53
|
+
'Company': 'company',
|
|
54
|
+
'IpAddress': 'ip',
|
|
55
|
+
'InternetDomainName': 'domain',
|
|
56
|
+
'CyberVulnerability': 'vulnerability',
|
|
57
|
+
'URL': 'url',
|
|
58
|
+
'Malware': 'malware',
|
|
59
|
+
'Hash': 'hash',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
IOC_TO_MODEL = {
|
|
64
|
+
'ip': EnrichedIP,
|
|
65
|
+
'domain': EnrichedDomain,
|
|
66
|
+
'hash': EnrichedHash,
|
|
67
|
+
'url': EnrichedURL,
|
|
68
|
+
'malware': EnrichedMalware,
|
|
69
|
+
'vulnerability': EnrichedVulnerability,
|
|
70
|
+
'company': EnrichedCompany,
|
|
71
|
+
'company/by_domain': EnrichedCompany,
|
|
72
|
+
}
|
|
73
|
+
MESSAGE_404 = '404 received. Nothing known on this entity'
|
|
@@ -0,0 +1,26 @@
|
|
|
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 RecordedFutureError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EnrichmentError(RecordedFutureError):
|
|
18
|
+
"""Error raised when there was an error enriching an entity via Lookup or SOAR."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EnrichmentLookupError(EnrichmentError):
|
|
22
|
+
"""Error raised when there was an error looking up for an entity name or entity id."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class EnrichmentSoarError(EnrichmentError):
|
|
26
|
+
"""Error raised when there was an error performing a SOAR enrichment."""
|