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
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."""