splunk-soar-sdk 3.4.0__py3-none-any.whl → 3.6.0__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.
- soar_sdk/abstract.py +7 -6
- soar_sdk/action_results.py +7 -7
- soar_sdk/actions_manager.py +7 -13
- soar_sdk/apis/artifact.py +3 -3
- soar_sdk/apis/container.py +2 -2
- soar_sdk/apis/es/findings.py +27 -0
- soar_sdk/apis/utils.py +3 -2
- soar_sdk/apis/vault.py +1 -0
- soar_sdk/app.py +24 -27
- soar_sdk/app_cli_runner.py +7 -6
- soar_sdk/app_client.py +3 -4
- soar_sdk/asset.py +7 -9
- soar_sdk/asset_state.py +1 -2
- soar_sdk/async_utils.py +1 -2
- soar_sdk/cli/cli.py +2 -2
- soar_sdk/cli/init/cli.py +5 -5
- soar_sdk/cli/manifests/deserializers.py +4 -3
- soar_sdk/cli/manifests/processors.py +4 -2
- soar_sdk/cli/manifests/serializers.py +4 -4
- soar_sdk/cli/package/cli.py +14 -14
- soar_sdk/cli/package/utils.py +3 -2
- soar_sdk/cli/path_utils.py +1 -1
- soar_sdk/code_renderers/action_renderer.py +5 -4
- soar_sdk/code_renderers/app_renderer.py +1 -1
- soar_sdk/code_renderers/asset_renderer.py +1 -1
- soar_sdk/code_renderers/renderer.py +2 -2
- soar_sdk/compat.py +2 -1
- soar_sdk/decorators/__init__.py +3 -3
- soar_sdk/decorators/action.py +7 -11
- soar_sdk/decorators/make_request.py +9 -11
- soar_sdk/decorators/on_es_poll.py +105 -136
- soar_sdk/decorators/on_poll.py +7 -11
- soar_sdk/decorators/test_connectivity.py +5 -6
- soar_sdk/decorators/view_handler.py +6 -7
- soar_sdk/decorators/webhook.py +3 -5
- soar_sdk/es_client.py +43 -0
- soar_sdk/extras/__init__.py +0 -0
- soar_sdk/extras/email/__init__.py +9 -0
- soar_sdk/extras/email/processor.py +1171 -0
- soar_sdk/extras/email/rfc5322.py +335 -0
- soar_sdk/extras/email/utils.py +178 -0
- soar_sdk/input_spec.py +4 -3
- soar_sdk/logging.py +5 -4
- soar_sdk/meta/actions.py +3 -3
- soar_sdk/meta/app.py +1 -0
- soar_sdk/meta/dependencies.py +47 -11
- soar_sdk/meta/webhooks.py +2 -1
- soar_sdk/models/__init__.py +1 -1
- soar_sdk/models/artifact.py +1 -0
- soar_sdk/models/attachment_input.py +1 -1
- soar_sdk/models/container.py +2 -1
- soar_sdk/models/finding.py +4 -6
- soar_sdk/models/vault_attachment.py +1 -0
- soar_sdk/models/view.py +2 -0
- soar_sdk/params.py +13 -7
- soar_sdk/shims/phantom/action_result.py +1 -1
- soar_sdk/shims/phantom/app.py +1 -1
- soar_sdk/shims/phantom/base_connector.py +3 -4
- soar_sdk/shims/phantom/connector_result.py +0 -1
- soar_sdk/shims/phantom/install_info.py +1 -1
- soar_sdk/shims/phantom/ph_ipc.py +2 -1
- soar_sdk/shims/phantom/vault.py +8 -6
- soar_sdk/shims/phantom_common/app_interface/app_interface.py +1 -0
- soar_sdk/types.py +1 -1
- soar_sdk/views/component_registry.py +0 -1
- soar_sdk/views/template_filters.py +4 -4
- soar_sdk/views/template_renderer.py +3 -2
- soar_sdk/views/view_parser.py +8 -6
- soar_sdk/webhooks/models.py +3 -3
- soar_sdk/webhooks/routing.py +3 -4
- {splunk_soar_sdk-3.4.0.dist-info → splunk_soar_sdk-3.6.0.dist-info}/METADATA +5 -1
- splunk_soar_sdk-3.6.0.dist-info/RECORD +117 -0
- splunk_soar_sdk-3.4.0.dist-info/RECORD +0 -110
- {splunk_soar_sdk-3.4.0.dist-info → splunk_soar_sdk-3.6.0.dist-info}/WHEEL +0 -0
- {splunk_soar_sdk-3.4.0.dist-info → splunk_soar_sdk-3.6.0.dist-info}/entry_points.txt +0 -0
- {splunk_soar_sdk-3.4.0.dist-info → splunk_soar_sdk-3.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,27 +1,30 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import inspect
|
|
2
|
-
from functools import wraps
|
|
3
|
-
from typing import Any
|
|
4
3
|
from collections.abc import Callable
|
|
5
|
-
from
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import TYPE_CHECKING, Any, get_args
|
|
6
|
+
|
|
7
|
+
from pydantic import ValidationError
|
|
6
8
|
|
|
7
9
|
from soar_sdk.abstract import SOARClient
|
|
8
10
|
from soar_sdk.action_results import ActionResult
|
|
9
|
-
from soar_sdk.
|
|
10
|
-
from soar_sdk.meta.actions import ActionMeta
|
|
11
|
-
from soar_sdk.types import Action, action_protocol
|
|
11
|
+
from soar_sdk.es_client import ESClient
|
|
12
12
|
from soar_sdk.exceptions import ActionFailure
|
|
13
|
-
from soar_sdk.async_utils import run_async_if_needed
|
|
14
13
|
from soar_sdk.logging import getLogger
|
|
15
|
-
from soar_sdk.
|
|
16
|
-
from soar_sdk.models.attachment_input import AttachmentInput
|
|
14
|
+
from soar_sdk.meta.actions import ActionMeta
|
|
17
15
|
from soar_sdk.models.container import Container
|
|
18
|
-
|
|
19
|
-
from
|
|
16
|
+
from soar_sdk.models.finding import Finding
|
|
17
|
+
from soar_sdk.params import OnESPollParams
|
|
18
|
+
from soar_sdk.types import Action, action_protocol
|
|
20
19
|
|
|
21
20
|
if TYPE_CHECKING:
|
|
22
21
|
from soar_sdk.app import App
|
|
23
22
|
|
|
24
23
|
|
|
24
|
+
ESPollingYieldType = Finding
|
|
25
|
+
ESPollingSendType = int | None
|
|
26
|
+
|
|
27
|
+
|
|
25
28
|
class OnESPollDecorator:
|
|
26
29
|
"""Class-based decorator for tagging a function as the special 'on es poll' action."""
|
|
27
30
|
|
|
@@ -29,12 +32,12 @@ class OnESPollDecorator:
|
|
|
29
32
|
self.app = app
|
|
30
33
|
|
|
31
34
|
def __call__(self, function: Callable) -> Action:
|
|
32
|
-
"""Decorator for the 'on es poll' action.
|
|
33
|
-
|
|
34
|
-
The decorated function must be a generator (using yield) or return an Iterator that yields tuples of (Finding, list[AttachmentInput]). Only one on_es_poll action is allowed per app.
|
|
35
|
+
"""Decorator for the 'on es poll' action. The decorated function must be a Generator or AsyncGenerator. Only one on_es_poll action is allowed per app.
|
|
35
36
|
|
|
36
37
|
Usage:
|
|
37
|
-
|
|
38
|
+
The generator should yield a `Finding`. Upon receiving an event from the generator, the SDK will submit the Finding to Splunk Enterprise Security and create a linked SOAR Container.
|
|
39
|
+
The generator should accept a "send type" of `int | None`. When a Finding is successfully delivered to ES and linked to a Container, the SDK will send the Container ID back into the generator. The Container is useful for storing large attachments included with the Finding.
|
|
40
|
+
If the Finding cannot be successfully delivered to ES, the SDK will stop polling and return a failed result for the action run.
|
|
38
41
|
"""
|
|
39
42
|
if self.app.actions_manager.get_action("on_es_poll"):
|
|
40
43
|
raise TypeError(
|
|
@@ -43,16 +46,25 @@ class OnESPollDecorator:
|
|
|
43
46
|
|
|
44
47
|
is_generator = inspect.isgeneratorfunction(function)
|
|
45
48
|
is_async_generator = inspect.isasyncgenfunction(function)
|
|
46
|
-
signature = inspect.signature(function)
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
and getattr(signature.return_annotation, "__origin__", None) is Iterator
|
|
51
|
-
)
|
|
50
|
+
generator_type = inspect.signature(function).return_annotation
|
|
51
|
+
generator_type_args = get_args(generator_type)
|
|
52
52
|
|
|
53
|
-
if not (is_generator or is_async_generator or
|
|
53
|
+
if not (is_generator or is_async_generator) or len(generator_type_args) < 2:
|
|
54
54
|
raise TypeError(
|
|
55
|
-
"The on_es_poll function must be a
|
|
55
|
+
"The on_es_poll function must be a Generator or AsyncGenerator (use 'yield')."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
yield_type = generator_type_args[0]
|
|
59
|
+
send_type = generator_type_args[1]
|
|
60
|
+
|
|
61
|
+
if yield_type != ESPollingYieldType:
|
|
62
|
+
raise TypeError(
|
|
63
|
+
f"@on_es_poll generator should have yield type {ESPollingYieldType}."
|
|
64
|
+
)
|
|
65
|
+
if send_type != ESPollingSendType:
|
|
66
|
+
raise TypeError(
|
|
67
|
+
f"@on_es_poll generator should have send type {ESPollingSendType}."
|
|
56
68
|
)
|
|
57
69
|
|
|
58
70
|
action_identifier = "on_es_poll"
|
|
@@ -70,129 +82,85 @@ class OnESPollDecorator:
|
|
|
70
82
|
**kwargs: Any, # noqa: ANN401
|
|
71
83
|
) -> bool:
|
|
72
84
|
try:
|
|
85
|
+
action_params = validated_params_class.model_validate(params)
|
|
86
|
+
except ValidationError as e:
|
|
87
|
+
logger.info(f"Parameter validation error: {e!s}")
|
|
88
|
+
return self.app._adapt_action_result(
|
|
89
|
+
ActionResult(status=False, message=f"Invalid parameters: {e!s}"),
|
|
90
|
+
self.app.actions_manager,
|
|
91
|
+
)
|
|
92
|
+
es = ESClient(params.es_base_url, params.es_session_key)
|
|
93
|
+
kwargs = self.app._build_magic_args(function, soar=soar, **kwargs)
|
|
94
|
+
generator = function(action_params, *args, **kwargs)
|
|
95
|
+
|
|
96
|
+
if is_async_generator:
|
|
97
|
+
|
|
98
|
+
def polling_step(
|
|
99
|
+
last_container_id: ESPollingSendType,
|
|
100
|
+
) -> ESPollingYieldType:
|
|
101
|
+
return asyncio.run(generator.asend(last_container_id))
|
|
102
|
+
else:
|
|
103
|
+
|
|
104
|
+
def polling_step(
|
|
105
|
+
last_container_id: ESPollingSendType,
|
|
106
|
+
) -> ESPollingYieldType:
|
|
107
|
+
return generator.send(last_container_id)
|
|
108
|
+
|
|
109
|
+
last_container_id = None
|
|
110
|
+
while True:
|
|
73
111
|
try:
|
|
74
|
-
|
|
75
|
-
except
|
|
76
|
-
logger.info(f"Parameter validation error: {e!s}")
|
|
112
|
+
item = polling_step(last_container_id)
|
|
113
|
+
except (StopIteration, StopAsyncIteration):
|
|
77
114
|
return self.app._adapt_action_result(
|
|
78
115
|
ActionResult(
|
|
79
|
-
status=
|
|
116
|
+
status=True, message="Finding processing complete"
|
|
80
117
|
),
|
|
81
118
|
self.app.actions_manager,
|
|
82
119
|
)
|
|
120
|
+
except ActionFailure as e:
|
|
121
|
+
e.set_action_name(action_name)
|
|
122
|
+
return self.app._adapt_action_result(
|
|
123
|
+
ActionResult(status=False, message=str(e)),
|
|
124
|
+
self.app.actions_manager,
|
|
125
|
+
)
|
|
126
|
+
except Exception as e:
|
|
127
|
+
self.app.actions_manager.add_exception(e)
|
|
128
|
+
logger.info(f"Error during finding processing: {e!s}")
|
|
129
|
+
return self.app._adapt_action_result(
|
|
130
|
+
ActionResult(status=False, message=str(e)),
|
|
131
|
+
self.app.actions_manager,
|
|
132
|
+
)
|
|
83
133
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
finding,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
108
|
-
continue
|
|
109
|
-
|
|
110
|
-
for attachment in attachments:
|
|
111
|
-
if not isinstance(attachment, AttachmentInput):
|
|
112
|
-
logger.info(
|
|
113
|
-
f"Warning: Attachment must be AttachmentInput, got: {type(attachment)}"
|
|
114
|
-
)
|
|
115
|
-
break
|
|
116
|
-
else:
|
|
117
|
-
finding_dict = finding.to_dict()
|
|
118
|
-
logger.info(
|
|
119
|
-
f"Processing finding: {finding_dict.get('rule_title', 'Unnamed finding')}"
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
# Send finding to ES and get finding_id back
|
|
123
|
-
finding_id = self.app.actions_manager.send_finding_to_es(
|
|
124
|
-
finding_dict
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
container = Container(
|
|
128
|
-
name=finding.rule_title,
|
|
129
|
-
description=finding.rule_description,
|
|
130
|
-
severity=finding.urgency or "medium",
|
|
131
|
-
status=finding.status,
|
|
132
|
-
owner_id=finding.owner,
|
|
133
|
-
sensitivity=finding.disposition,
|
|
134
|
-
tags=finding.source,
|
|
135
|
-
external_id=finding_id,
|
|
136
|
-
data={
|
|
137
|
-
"security_domain": finding.security_domain,
|
|
138
|
-
"risk_score": finding.risk_score,
|
|
139
|
-
"risk_object": finding.risk_object,
|
|
140
|
-
"risk_object_type": finding.risk_object_type,
|
|
141
|
-
},
|
|
142
|
-
)
|
|
143
|
-
|
|
144
|
-
ret_val, message, container_id = (
|
|
145
|
-
self.app.actions_manager.save_container(container.to_dict())
|
|
146
|
-
)
|
|
147
|
-
logger.info(
|
|
148
|
-
f"Creating container for finding: {finding.rule_title}"
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
if not ret_val:
|
|
152
|
-
logger.info(f"Failed to create container: {message}")
|
|
153
|
-
continue
|
|
154
|
-
|
|
155
|
-
for attachment in attachments:
|
|
156
|
-
try:
|
|
157
|
-
if attachment.file_content is not None:
|
|
158
|
-
vault_id = soar.vault.create_attachment(
|
|
159
|
-
container_id=container_id,
|
|
160
|
-
file_content=attachment.file_content,
|
|
161
|
-
file_name=attachment.file_name,
|
|
162
|
-
metadata=attachment.metadata,
|
|
163
|
-
)
|
|
164
|
-
else:
|
|
165
|
-
vault_id = soar.vault.add_attachment(
|
|
166
|
-
container_id=container_id,
|
|
167
|
-
file_location=attachment.file_location,
|
|
168
|
-
file_name=attachment.file_name,
|
|
169
|
-
metadata=attachment.metadata,
|
|
170
|
-
)
|
|
171
|
-
logger.info(
|
|
172
|
-
f"Added attachment {attachment.file_name} with vault_id: {vault_id}"
|
|
173
|
-
)
|
|
174
|
-
except Exception as e:
|
|
175
|
-
logger.info(
|
|
176
|
-
f"Failed to add attachment {attachment.file_name}: {e!s}"
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
return self.app._adapt_action_result(
|
|
180
|
-
ActionResult(status=True, message="Finding processing complete"),
|
|
181
|
-
self.app.actions_manager,
|
|
182
|
-
)
|
|
183
|
-
except ActionFailure as e:
|
|
184
|
-
e.set_action_name(action_name)
|
|
185
|
-
return self.app._adapt_action_result(
|
|
186
|
-
ActionResult(status=False, message=str(e)),
|
|
187
|
-
self.app.actions_manager,
|
|
134
|
+
if type(item) is not ESPollingYieldType:
|
|
135
|
+
logger.info(
|
|
136
|
+
f"Warning: expected {ESPollingYieldType}, got {type(item)}, skipping"
|
|
137
|
+
)
|
|
138
|
+
continue
|
|
139
|
+
finding = es.findings.create(item)
|
|
140
|
+
logger.info(f"Created finding {finding.finding_id}")
|
|
141
|
+
|
|
142
|
+
container = Container(
|
|
143
|
+
name=finding.rule_title,
|
|
144
|
+
description=finding.rule_description,
|
|
145
|
+
severity=finding.urgency or "medium",
|
|
146
|
+
status=finding.status,
|
|
147
|
+
owner_id=finding.owner,
|
|
148
|
+
sensitivity=finding.disposition,
|
|
149
|
+
tags=finding.source,
|
|
150
|
+
external_id=finding.finding_id,
|
|
151
|
+
data={
|
|
152
|
+
"security_domain": finding.security_domain,
|
|
153
|
+
"risk_score": finding.risk_score,
|
|
154
|
+
"risk_object": finding.risk_object,
|
|
155
|
+
"risk_object_type": finding.risk_object_type,
|
|
156
|
+
},
|
|
188
157
|
)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
logger.info(f"Error during finding processing: {e!s}")
|
|
192
|
-
return self.app._adapt_action_result(
|
|
193
|
-
ActionResult(status=False, message=str(e)),
|
|
194
|
-
self.app.actions_manager,
|
|
158
|
+
ret_val, message, last_container_id = (
|
|
159
|
+
self.app.actions_manager.save_container(container.to_dict())
|
|
195
160
|
)
|
|
161
|
+
logger.info(f"Creating container for finding: {finding.rule_title}")
|
|
162
|
+
if not ret_val:
|
|
163
|
+
raise ActionFailure(f"Failed to create container: {message}")
|
|
196
164
|
|
|
197
165
|
inner.params_class = validated_params_class
|
|
198
166
|
|
|
@@ -214,5 +182,6 @@ class OnESPollDecorator:
|
|
|
214
182
|
)
|
|
215
183
|
|
|
216
184
|
self.app.actions_manager.set_action(action_identifier, inner)
|
|
185
|
+
self.app.actions_manager.supports_es_polling = True
|
|
217
186
|
self.app._dev_skip_in_pytest(function, inner)
|
|
218
187
|
return inner
|
soar_sdk/decorators/on_poll.py
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
from collections.abc import Callable, Iterator
|
|
2
3
|
from functools import wraps
|
|
3
|
-
from typing import Any
|
|
4
|
-
from collections.abc import Callable
|
|
5
|
-
from collections.abc import Iterator
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
6
5
|
|
|
7
6
|
from soar_sdk.abstract import SOARClient
|
|
8
7
|
from soar_sdk.action_results import ActionResult
|
|
9
|
-
from soar_sdk.params import OnPollParams
|
|
10
|
-
from soar_sdk.meta.actions import ActionMeta
|
|
11
|
-
from soar_sdk.types import Action, action_protocol
|
|
12
|
-
from soar_sdk.exceptions import ActionFailure
|
|
13
8
|
from soar_sdk.async_utils import run_async_if_needed
|
|
9
|
+
from soar_sdk.exceptions import ActionFailure
|
|
14
10
|
from soar_sdk.logging import getLogger
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
from
|
|
11
|
+
from soar_sdk.meta.actions import ActionMeta
|
|
12
|
+
from soar_sdk.params import OnPollParams
|
|
13
|
+
from soar_sdk.types import Action, action_protocol
|
|
18
14
|
|
|
19
15
|
if TYPE_CHECKING:
|
|
20
16
|
from soar_sdk.app import App
|
|
@@ -75,8 +71,8 @@ class OnPollDecorator:
|
|
|
75
71
|
**kwargs: Any, # noqa: ANN401
|
|
76
72
|
) -> bool:
|
|
77
73
|
# Lazy imports to avoid circular dependencies
|
|
78
|
-
from soar_sdk.models.container import Container
|
|
79
74
|
from soar_sdk.models.artifact import Artifact
|
|
75
|
+
from soar_sdk.models.container import Container
|
|
80
76
|
|
|
81
77
|
try:
|
|
82
78
|
# Validate poll params
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
|
|
2
|
+
import traceback
|
|
3
3
|
from collections.abc import Callable
|
|
4
|
+
from functools import wraps
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
4
6
|
|
|
5
7
|
from soar_sdk.abstract import SOARClient
|
|
6
8
|
from soar_sdk.action_results import ActionResult
|
|
9
|
+
from soar_sdk.async_utils import run_async_if_needed
|
|
10
|
+
from soar_sdk.exceptions import ActionFailure
|
|
7
11
|
from soar_sdk.meta.actions import ActionMeta
|
|
8
12
|
from soar_sdk.types import Action, action_protocol
|
|
9
|
-
from soar_sdk.exceptions import ActionFailure
|
|
10
|
-
from soar_sdk.async_utils import run_async_if_needed
|
|
11
|
-
import traceback
|
|
12
|
-
|
|
13
|
-
from typing import TYPE_CHECKING
|
|
14
13
|
|
|
15
14
|
if TYPE_CHECKING:
|
|
16
15
|
from soar_sdk.app import App
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import inspect
|
|
2
|
-
from functools import wraps
|
|
3
|
-
from typing import Any
|
|
4
2
|
from collections.abc import Callable
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
5
7
|
|
|
6
8
|
from soar_sdk.action_results import ActionResult
|
|
9
|
+
from soar_sdk.models.view import AllAppRuns, ResultSummary, ViewContext
|
|
7
10
|
from soar_sdk.views.component_registry import COMPONENT_REGISTRY
|
|
8
|
-
from pydantic import BaseModel
|
|
9
|
-
from soar_sdk.models.view import ViewContext, AllAppRuns, ResultSummary
|
|
10
|
-
from soar_sdk.views.view_parser import ViewFunctionParser
|
|
11
11
|
from soar_sdk.views.template_renderer import (
|
|
12
12
|
get_template_renderer,
|
|
13
13
|
get_templates_dir,
|
|
14
14
|
)
|
|
15
|
-
|
|
16
|
-
from typing import TYPE_CHECKING
|
|
15
|
+
from soar_sdk.views.view_parser import ViewFunctionParser
|
|
17
16
|
|
|
18
17
|
if TYPE_CHECKING:
|
|
19
18
|
from soar_sdk.app import App
|
soar_sdk/decorators/webhook.py
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from functools import wraps
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
|
-
from soar_sdk.webhooks.models import WebhookRequest, WebhookResponse, WebhookHandler
|
|
6
|
-
from soar_sdk.meta.webhooks import WebhookRouteMeta
|
|
7
6
|
from soar_sdk.async_utils import run_async_if_needed
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
from typing import TYPE_CHECKING
|
|
7
|
+
from soar_sdk.meta.webhooks import WebhookRouteMeta
|
|
8
|
+
from soar_sdk.webhooks.models import WebhookHandler, WebhookRequest, WebhookResponse
|
|
11
9
|
|
|
12
10
|
if TYPE_CHECKING:
|
|
13
11
|
from soar_sdk.app import App
|
soar_sdk/es_client.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from httpx_retries import Retry, RetryTransport
|
|
3
|
+
from httpx_retries.retry import HTTPMethod, HTTPStatus
|
|
4
|
+
|
|
5
|
+
from soar_sdk.apis.es.findings import Findings
|
|
6
|
+
|
|
7
|
+
RETRYABLE_METHODS = [
|
|
8
|
+
HTTPMethod.GET,
|
|
9
|
+
HTTPMethod.PUT,
|
|
10
|
+
HTTPMethod.POST,
|
|
11
|
+
HTTPMethod.PATCH,
|
|
12
|
+
HTTPMethod.DELETE,
|
|
13
|
+
]
|
|
14
|
+
RETRYABLE_STATUSES = [
|
|
15
|
+
HTTPStatus.TOO_MANY_REQUESTS,
|
|
16
|
+
HTTPStatus.BAD_GATEWAY,
|
|
17
|
+
HTTPStatus.SERVICE_UNAVAILABLE,
|
|
18
|
+
HTTPStatus.GATEWAY_TIMEOUT,
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ESClient:
|
|
23
|
+
"""A client for accessing Splunk Enterprise Security APIs."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, base_url: str, session_key: str, verify: bool = True) -> None:
|
|
26
|
+
transport = RetryTransport(
|
|
27
|
+
transport=httpx.HTTPTransport(verify=verify),
|
|
28
|
+
retry=Retry(
|
|
29
|
+
allowed_methods=RETRYABLE_METHODS,
|
|
30
|
+
status_forcelist=RETRYABLE_STATUSES,
|
|
31
|
+
total=5,
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
self._client = httpx.Client(
|
|
35
|
+
base_url=base_url,
|
|
36
|
+
transport=transport,
|
|
37
|
+
headers={"Authorization": f"Splunk {session_key}"},
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def findings(self) -> Findings:
|
|
42
|
+
"""The ES /public/v2/findings API."""
|
|
43
|
+
return Findings(self._client)
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from soar_sdk.extras.email.processor import EmailProcessor, ProcessEmailContext
|
|
2
|
+
from soar_sdk.extras.email.rfc5322 import RFC5322EmailData, extract_rfc5322_email_data
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"EmailProcessor",
|
|
6
|
+
"ProcessEmailContext",
|
|
7
|
+
"RFC5322EmailData",
|
|
8
|
+
"extract_rfc5322_email_data",
|
|
9
|
+
]
|