psengine 2.1.0__tar.gz → 2.2.0__tar.gz
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-2.2.0/PKG-INFO +103 -0
- psengine-2.2.0/README.md +49 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/_version.py +1 -1
- {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/helpers.py +13 -22
- {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/models.py +8 -4
- {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/note.py +43 -46
- {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/note_mgr.py +114 -157
- {psengine-2.1.0 → psengine-2.2.0}/psengine/base_http_client.py +48 -71
- {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/classic_alert.py +99 -114
- psengine-2.2.0/psengine/classic_alerts/classic_alert_mgr.py +505 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/helpers.py +25 -28
- {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/markdown/markdown.py +15 -12
- {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/models.py +1 -1
- {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/collective_insights.py +35 -42
- {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/insight.py +28 -19
- {psengine-2.1.0 → psengine-2.2.0}/psengine/common_models.py +29 -22
- {psengine-2.1.0 → psengine-2.2.0}/psengine/config/config.py +84 -92
- {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/detection_mgr.py +47 -48
- {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/detection_rule.py +21 -20
- {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/helpers.py +15 -10
- {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/models.py +8 -5
- {psengine-2.1.0 → psengine-2.2.0}/psengine/endpoints.py +1 -1
- {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/constants.py +14 -17
- {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/lookup.py +68 -56
- psengine-2.2.0/psengine/enrich/lookup_mgr.py +316 -0
- {psengine-2.1.0/psengine/playbook_alerts/markdown → psengine-2.2.0/psengine/enrich/models}/__init__.py +1 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/models/lookup.py +1 -4
- {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/models/soar.py +45 -45
- {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/soar.py +21 -19
- {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/soar_mgr.py +50 -56
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/entity_list.py +119 -109
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/entity_list_mgr.py +37 -47
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/models.py +8 -8
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/entity_match.py +24 -21
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/entity_match_mgr.py +76 -93
- {psengine-2.1.0 → psengine-2.2.0}/psengine/helpers/helpers.py +144 -148
- {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/identity.py +101 -105
- psengine-2.2.0/psengine/identity/identity_mgr.py +836 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/models/common_models.py +1 -1
- {psengine-2.1.0 → psengine-2.2.0}/psengine/logger/rf_logger.py +20 -10
- {psengine-2.1.0 → psengine-2.2.0}/psengine/markdown/markdown.py +48 -16
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/__init__.py +1 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/helpers.py +13 -15
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_geopolitics_facility.py +2 -3
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/panel_log.py +7 -7
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/pa_category.py +6 -7
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/playbook_alert_mgr.py +192 -180
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/playbook_alerts.py +121 -110
- psengine-2.2.0/psengine/py.typed +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/rf_client.py +93 -124
- {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/models.py +14 -3
- {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/risklist_mgr.py +42 -48
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/base_stix_entity.py +10 -13
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/complex_entity.py +56 -76
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/enriched_indicator.py +19 -32
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/helpers.py +20 -16
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/rf_bundle.py +25 -41
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/simple_entity.py +17 -17
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/util.py +8 -13
- psengine-2.2.0/psengine.egg-info/PKG-INFO +103 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine.egg-info/SOURCES.txt +2 -1
- psengine-2.2.0/psengine.egg-info/requires.txt +32 -0
- {psengine-2.1.0 → psengine-2.2.0}/pyproject.toml +37 -33
- psengine-2.1.0/PKG-INFO +0 -189
- psengine-2.1.0/README.rst +0 -146
- psengine-2.1.0/psengine/classic_alerts/classic_alert_mgr.py +0 -504
- psengine-2.1.0/psengine/enrich/lookup_mgr.py +0 -341
- psengine-2.1.0/psengine/identity/identity_mgr.py +0 -895
- psengine-2.1.0/psengine.egg-info/PKG-INFO +0 -189
- psengine-2.1.0/psengine.egg-info/requires.txt +0 -30
- {psengine-2.1.0 → psengine-2.2.0}/LICENSE +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/_sdk_id.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/analyst_notes/markdown.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/classic_alerts/markdown/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/collective_insights/models.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/config/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/config/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/detection/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/enrich/models/base_enriched_entity.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_lists/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/entity_match/models.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/helpers/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/errors.py +0 -0
- {psengine-2.1.0/psengine/enrich → psengine-2.2.0/psengine/identity}/models/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/models/detections.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/models/incident_report.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/identity/models/lookup.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/logger/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/logger/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/logger/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/markdown/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/markdown/models.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/mappings.py +0 -0
- {psengine-2.1.0/psengine/identity/models → psengine-2.2.0/psengine/playbook_alerts/markdown}/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_code_repo.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_cyber_vulnerability.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_domain_abuse.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_identity_exposure.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_malware_report.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/markdown/markdown_third_party_risk.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/common_models.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/panel_status.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_code_repo_leak.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_cyber_vulnerability.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_domain_abuse.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_geopolitics_facility.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_identity_exposures.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_malware_report.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/pba_third_party_risk.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/playbook_alerts/models/search_endpoint.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/risklists/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/__init__.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/constants.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine/stix2/errors.py +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine.egg-info/dependency_links.txt +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/psengine.egg-info/top_level.txt +0 -0
- {psengine-2.1.0 → psengine-2.2.0}/setup.cfg +0 -0
psengine-2.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: psengine
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: psengine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
|
|
5
|
+
Author-email: Moise Medici <moise.medici@recordedfuture.com>, Patrick Kinsella <patrick.kinsella@recordedfuture.com>, Ernest Bartosevic <ernest.bartosevic@recordedfuture.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/RecordedFuture-ProfessionalServices/psengine
|
|
8
|
+
Project-URL: Changelog, https://recordedfuture-professionalservices.github.io/psengine/CHANGELOG/
|
|
9
|
+
Keywords: API,Recorded Future,Cyber Security Engineering,Threat Intelligence
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Topic :: Security
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Python: <3.14,>=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: requests>=2.27.1
|
|
24
|
+
Requires-Dist: jsonpath_ng<=1.6.1,>=1.5.3
|
|
25
|
+
Requires-Dist: stix2~=3.0.1
|
|
26
|
+
Requires-Dist: python-dateutil>=2.7.0
|
|
27
|
+
Requires-Dist: more-itertools<=10.2.0,>=9.0.0
|
|
28
|
+
Requires-Dist: pydantic<3.0.0,>=2.7
|
|
29
|
+
Requires-Dist: pydantic-settings[toml]<2.11.0,>=2.5.2
|
|
30
|
+
Requires-Dist: markdown-strings==3.4.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest==8.3.4; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov==6.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-mock==3.14.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-md==0.2.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-random-order==1.1.1; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-httpdbg==0.9.0; extra == "dev"
|
|
38
|
+
Requires-Dist: ruff~=0.11.0; extra == "dev"
|
|
39
|
+
Requires-Dist: mimesis>=12.1.0; extra == "dev"
|
|
40
|
+
Requires-Dist: build==1.3.0; extra == "dev"
|
|
41
|
+
Requires-Dist: wheel==0.45.1; extra == "dev"
|
|
42
|
+
Requires-Dist: setuptools==80.9.0; extra == "dev"
|
|
43
|
+
Provides-Extra: docs
|
|
44
|
+
Requires-Dist: ruff~=0.11.0; extra == "docs"
|
|
45
|
+
Requires-Dist: mike~=2.1.3; extra == "docs"
|
|
46
|
+
Requires-Dist: mkdocs~=1.6.1; extra == "docs"
|
|
47
|
+
Requires-Dist: mkdocs-material~=9.6.18; extra == "docs"
|
|
48
|
+
Requires-Dist: mkdocstrings[python]>=0.18; extra == "docs"
|
|
49
|
+
Requires-Dist: griffe-typingdoc~=0.2.8; extra == "docs"
|
|
50
|
+
Requires-Dist: mkdocs-codeinclude-plugin~=0.2.1; extra == "docs"
|
|
51
|
+
Requires-Dist: markdown-include~=0.8.1; extra == "docs"
|
|
52
|
+
Requires-Dist: mkdocs-exclude~=1.0.2; extra == "docs"
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
|
|
55
|
+
**Documentation**: <https://recordedfuture-professionalservices.github.io/psengine/>
|
|
56
|
+
|
|
57
|
+
**Github**: <https://github.com/RecordedFuture-ProfessionalServices/psengine>
|
|
58
|
+
|
|
59
|
+
**PyPi**: <https://pypi.org/project/psengine/>
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
PSEngine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
|
|
64
|
+
|
|
65
|
+
PSEngine allows you to interact with the Recorded Future API extremely easily. There’s no need to manually build the URLs and query parameters, just use the modules dedicated to individual API endpoints.
|
|
66
|
+
|
|
67
|
+
PSEngine is a Python package solely built and maintained by the Cyber Security Engineering team powering a number of high profile integrations, such as: Elasticsearch, QRadar, Anomali, Jira, TheHive, etc.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
PSEngine is a Python package that can be installed using `pip`. To install PSengine, run the following command:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install psengine
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
PSEngine officially supports Python >= 3.9, < 3.14.
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
## Supported Features & Best Practices
|
|
81
|
+
|
|
82
|
+
PSEngine is ready for the demands of building robust and reliable integrations.
|
|
83
|
+
|
|
84
|
+
It can easily interact with the following Recorded Future datasets:
|
|
85
|
+
|
|
86
|
+
* Analyst Notes
|
|
87
|
+
* Collective Insights
|
|
88
|
+
* Classic & Playbook Alerts
|
|
89
|
+
* Detection Rules
|
|
90
|
+
- Identity Exposures management
|
|
91
|
+
* List management
|
|
92
|
+
* On demand IOC enrichment
|
|
93
|
+
* Risklists
|
|
94
|
+
- STIX conversion
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
And facilitate the development with features like:
|
|
98
|
+
|
|
99
|
+
* Built-in logging
|
|
100
|
+
* Easy configuration management
|
|
101
|
+
- Markdown creation from certain data types
|
|
102
|
+
* Proxy support
|
|
103
|
+
|
psengine-2.2.0/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
**Documentation**: <https://recordedfuture-professionalservices.github.io/psengine/>
|
|
2
|
+
|
|
3
|
+
**Github**: <https://github.com/RecordedFuture-ProfessionalServices/psengine>
|
|
4
|
+
|
|
5
|
+
**PyPi**: <https://pypi.org/project/psengine/>
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
PSEngine is a simple, yet elegant, library for rapid development of integrations with Recorded Future.
|
|
10
|
+
|
|
11
|
+
PSEngine allows you to interact with the Recorded Future API extremely easily. There’s no need to manually build the URLs and query parameters, just use the modules dedicated to individual API endpoints.
|
|
12
|
+
|
|
13
|
+
PSEngine is a Python package solely built and maintained by the Cyber Security Engineering team powering a number of high profile integrations, such as: Elasticsearch, QRadar, Anomali, Jira, TheHive, etc.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
PSEngine is a Python package that can be installed using `pip`. To install PSengine, run the following command:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install psengine
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
PSEngine officially supports Python >= 3.9, < 3.14.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Supported Features & Best Practices
|
|
27
|
+
|
|
28
|
+
PSEngine is ready for the demands of building robust and reliable integrations.
|
|
29
|
+
|
|
30
|
+
It can easily interact with the following Recorded Future datasets:
|
|
31
|
+
|
|
32
|
+
* Analyst Notes
|
|
33
|
+
* Collective Insights
|
|
34
|
+
* Classic & Playbook Alerts
|
|
35
|
+
* Detection Rules
|
|
36
|
+
- Identity Exposures management
|
|
37
|
+
* List management
|
|
38
|
+
* On demand IOC enrichment
|
|
39
|
+
* Risklists
|
|
40
|
+
- STIX conversion
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
And facilitate the development with features like:
|
|
44
|
+
|
|
45
|
+
* Built-in logging
|
|
46
|
+
* Easy configuration management
|
|
47
|
+
- Markdown creation from certain data types
|
|
48
|
+
* Proxy support
|
|
49
|
+
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
import json
|
|
15
15
|
import logging
|
|
16
16
|
from pathlib import Path
|
|
17
|
-
from typing import Union
|
|
17
|
+
from typing import Annotated, Union
|
|
18
18
|
|
|
19
19
|
from pydantic import validate_call
|
|
20
|
+
from typing_extensions import Doc
|
|
20
21
|
|
|
21
22
|
from ..errors import WriteFileError
|
|
22
23
|
from ..helpers import OSHelpers, debug_call
|
|
@@ -28,16 +29,14 @@ LOG = logging.getLogger('psengine.analyst_notes.helpers')
|
|
|
28
29
|
@debug_call
|
|
29
30
|
@validate_call
|
|
30
31
|
def save_attachment(
|
|
31
|
-
note_id:
|
|
32
|
+
note_id: Annotated[str, Doc('The ID of the AnalystNote.')],
|
|
33
|
+
data: Annotated[Union[bytes, str], Doc('The data returned from `fetch_attachment`.')],
|
|
34
|
+
ext: Annotated[str, Doc('The extension of the attachment, returned by `fetch_attachment`.')],
|
|
35
|
+
output_directory: Annotated[Union[str, Path], Doc('The directory to save the file into.')],
|
|
32
36
|
) -> None:
|
|
33
|
-
"""Save
|
|
34
|
-
``note_id`` to create the filename.
|
|
37
|
+
"""Save a YARA, Sigma, Snort, or PDF attachment to a file.
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
note_id (str): ``AnalystNote`` id
|
|
38
|
-
data (Union[bytes, str]): data, returned from ``fetch_attachment``
|
|
39
|
-
ext (str): extension of the attachment, returned by ``fetch_attachment``
|
|
40
|
-
output_directory (str, Path): the directory to save the file into
|
|
39
|
+
The file will use the provided extension and the `note_id` to create the filename.
|
|
41
40
|
"""
|
|
42
41
|
output_directory = (
|
|
43
42
|
output_directory if isinstance(output_directory, str) else output_directory.as_posix()
|
|
@@ -47,13 +46,11 @@ def save_attachment(
|
|
|
47
46
|
|
|
48
47
|
@debug_call
|
|
49
48
|
@validate_call
|
|
50
|
-
def save_note(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
output_directory (str, Path): the directory to save the file into
|
|
56
|
-
"""
|
|
49
|
+
def save_note(
|
|
50
|
+
note: Annotated[AnalystNote, Doc('The note to save.')],
|
|
51
|
+
output_directory: Annotated[Union[str, Path], Doc('The directory to save the file into.')],
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Save an `AnalystNote` object to a file named with the note ID."""
|
|
57
54
|
output_directory = (
|
|
58
55
|
output_directory if isinstance(output_directory, str) else output_directory.as_posix()
|
|
59
56
|
)
|
|
@@ -70,12 +67,6 @@ def _save_attachment(
|
|
|
70
67
|
) -> None:
|
|
71
68
|
"""Save attachment from bytes or note itself from json.
|
|
72
69
|
|
|
73
|
-
Args:
|
|
74
|
-
note_id (str): id of the note, will be the filename
|
|
75
|
-
data (bytes | str): content to save
|
|
76
|
-
ext (str): extension of the file.
|
|
77
|
-
output_directory (str): the directory to save the file into
|
|
78
|
-
|
|
79
70
|
Raises:
|
|
80
71
|
WriteFileError: if saving to file fails
|
|
81
72
|
"""
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
|
|
14
14
|
import logging
|
|
15
15
|
from datetime import datetime
|
|
16
|
-
from typing import Any, Optional, Union
|
|
16
|
+
from typing import Annotated, Any, Optional, Union
|
|
17
17
|
|
|
18
|
-
from pydantic import Field, ValidationError, field_validator, model_validator
|
|
18
|
+
from pydantic import BeforeValidator, Field, ValidationError, field_validator, model_validator
|
|
19
19
|
|
|
20
20
|
from ..common_models import IdNameType, IdNameTypeDescription, RFBaseModel
|
|
21
|
+
from ..helpers import Validators
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class DiamondModel(RFBaseModel):
|
|
@@ -189,7 +190,7 @@ class Attributes(RFBaseModel):
|
|
|
189
190
|
@field_validator('events', mode='after')
|
|
190
191
|
@classmethod
|
|
191
192
|
def remove_empty_events(cls, values):
|
|
192
|
-
"""Remove empty events when
|
|
193
|
+
"""Remove empty events when `NoteEvent` skip the validation."""
|
|
193
194
|
return [v for v in values if v.type_ and v.attributes]
|
|
194
195
|
|
|
195
196
|
|
|
@@ -198,7 +199,10 @@ class PreviewAttributesIn(RFBaseModel):
|
|
|
198
199
|
text: str
|
|
199
200
|
note_entities: Optional[list[str]] = []
|
|
200
201
|
context_entities: Optional[list[str]] = []
|
|
201
|
-
topic:
|
|
202
|
+
topic: Annotated[
|
|
203
|
+
Union[list[str], str, None],
|
|
204
|
+
BeforeValidator(Validators.convert_str_to_list),
|
|
205
|
+
] = []
|
|
202
206
|
labels: Optional[list[str]] = []
|
|
203
207
|
validation_urls: Optional[list[str]] = []
|
|
204
208
|
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
##############################################################################################
|
|
13
13
|
|
|
14
14
|
from functools import total_ordering
|
|
15
|
-
from typing import Optional, Union
|
|
15
|
+
from typing import Annotated, Optional, Union
|
|
16
16
|
|
|
17
17
|
from pydantic import Field
|
|
18
|
+
from typing_extensions import Doc
|
|
18
19
|
|
|
19
20
|
from ..common_models import IdNameTypeDescription, RFBaseModel
|
|
20
21
|
from ..constants import TIMESTAMP_STR
|
|
@@ -26,32 +27,34 @@ from .models import Attributes, PreviewAttributesIn, PreviewAttributesOut, Reque
|
|
|
26
27
|
# AnalystNote also used by BaseEnrichedEntity
|
|
27
28
|
@total_ordering
|
|
28
29
|
class AnalystNote(RFBaseModel):
|
|
29
|
-
"""Validate data received from
|
|
30
|
+
"""Validate data received from the `/search` and `/analystnote/{note}` endpoints.
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Returns a hash value based on note ``id_``.
|
|
32
|
+
This class supports hashing, equality comparison, string representation, and total ordering
|
|
33
|
+
of `AnalystNote` instances.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
Hashing:
|
|
36
|
+
Returns a hash value based on the note `id_`.
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
and the ``id_``
|
|
38
|
+
Equality:
|
|
39
|
+
Checks equality between two `AnalystNote` instances based on the `id_` and published time.
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
Greater-than Comparison:
|
|
42
|
+
Defines a greater-than comparison between two `AnalystNote` instances based on the published
|
|
43
|
+
time and the `id_`.
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
String Representation:
|
|
46
|
+
Returns a string representation of the `AnalystNote` instance including the `id_`, `title`,
|
|
47
|
+
and published timestamp.
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
```python
|
|
50
|
+
>>> print(analyst_note)
|
|
51
|
+
Analyst Note ID: 12345, Title: Cyber Vuln, Published: 2024-05-21 10:42:30AM
|
|
52
|
+
```
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
The ordering of AnalystNote instances is determined primarily by the published timestamp
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
Ordering:
|
|
55
|
+
The ordering of `AnalystNote` instances is determined primarily by the published timestamp.
|
|
56
|
+
If two instances have the same published timestamp, the note `id_` is used as a secondary
|
|
57
|
+
criterion.
|
|
55
58
|
"""
|
|
56
59
|
|
|
57
60
|
external_id: Optional[str] = None
|
|
@@ -77,7 +80,7 @@ class AnalystNote(RFBaseModel):
|
|
|
77
80
|
@property
|
|
78
81
|
def detection_rule_type(self) -> Optional[str]:
|
|
79
82
|
"""Returns the attachment type if present, else None. It checks for specific types like
|
|
80
|
-
|
|
83
|
+
`sigma rule`, `yara rule`, and `snort rule` in the topics of the note.
|
|
81
84
|
"""
|
|
82
85
|
topics_type = ('sigma rule', 'yara rule', 'snort rule')
|
|
83
86
|
|
|
@@ -101,26 +104,20 @@ class AnalystNote(RFBaseModel):
|
|
|
101
104
|
|
|
102
105
|
def markdown(
|
|
103
106
|
self,
|
|
104
|
-
extract_entities:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
character_limit (int, optional): Limit the output to a specified number of characters.
|
|
119
|
-
Defaults to None.
|
|
120
|
-
|
|
121
|
-
Returns:
|
|
122
|
-
str: The generated markdown string.
|
|
123
|
-
"""
|
|
107
|
+
extract_entities: Annotated[
|
|
108
|
+
bool, Doc('Extract and include entities in the markdown.')
|
|
109
|
+
] = True,
|
|
110
|
+
diamond_model: Annotated[bool, Doc('Include a diamond model visualization.')] = True,
|
|
111
|
+
html_tags: Annotated[bool, Doc('Include HTML tags in the output.')] = False,
|
|
112
|
+
defang_malicious_infrastructure: Annotated[
|
|
113
|
+
bool, Doc('Defang URLs or other malicious indicators.')
|
|
114
|
+
] = False,
|
|
115
|
+
character_limit: Annotated[
|
|
116
|
+
Optional[int],
|
|
117
|
+
Doc('Limit the output to a specified number of characters.'),
|
|
118
|
+
] = None,
|
|
119
|
+
) -> Annotated[str, Doc('The generated markdown string.')]:
|
|
120
|
+
"""Return the markdown representation of the note."""
|
|
124
121
|
return _markdown(
|
|
125
122
|
self,
|
|
126
123
|
extract_entities=extract_entities,
|
|
@@ -132,7 +129,7 @@ class AnalystNote(RFBaseModel):
|
|
|
132
129
|
|
|
133
130
|
|
|
134
131
|
class AnalystNotePreviewIn(RFBaseModel):
|
|
135
|
-
"""Validate data sent to
|
|
132
|
+
"""Validate data sent to `/preview` endpoint."""
|
|
136
133
|
|
|
137
134
|
attributes: PreviewAttributesIn
|
|
138
135
|
source: Optional[str]
|
|
@@ -141,14 +138,14 @@ class AnalystNotePreviewIn(RFBaseModel):
|
|
|
141
138
|
|
|
142
139
|
|
|
143
140
|
class AnalystNotePreviewOut(RFBaseModel):
|
|
144
|
-
"""Validate data received from
|
|
141
|
+
"""Validate data received from `/preview` endpoint."""
|
|
145
142
|
|
|
146
143
|
attributes: PreviewAttributesOut
|
|
147
144
|
source: IdNameTypeDescription
|
|
148
145
|
|
|
149
146
|
|
|
150
147
|
class AnalystNotePublishIn(AnalystNotePreviewIn):
|
|
151
|
-
"""Validate data sent to
|
|
148
|
+
"""Validate data sent to `/publish` endpoint."""
|
|
152
149
|
|
|
153
150
|
attributes: PreviewAttributesIn
|
|
154
151
|
source: Optional[str] = None
|
|
@@ -160,13 +157,13 @@ class AnalystNotePublishIn(AnalystNotePreviewIn):
|
|
|
160
157
|
|
|
161
158
|
|
|
162
159
|
class AnalystNotePublishOut(RFBaseModel):
|
|
163
|
-
"""Validate data received from
|
|
160
|
+
"""Validate data received from `/publish` endpoint."""
|
|
164
161
|
|
|
165
162
|
note_id: str
|
|
166
163
|
|
|
167
164
|
|
|
168
165
|
class AnalystNoteSearchIn(RFBaseModel):
|
|
169
|
-
"""Validate data sent to
|
|
166
|
+
"""Validate data sent to `/search` endpoint."""
|
|
170
167
|
|
|
171
168
|
published: Optional[str] = None
|
|
172
169
|
entity: Optional[str] = None
|