Skip to content

Commit e7a8d99

Browse files
avara1986juanjux
andauthored
chore(iast): fix IAST imports when under pytest [Backport 2.21] (#12762)
Backport # 12323 to 2.21 ## Description PR #12198 had the unintended consequence of not honoring `DD_IAST_ENABLED` if set after the `_common_module_patches.py` was evaluated. This make some tests (`ssrf` and probably others) to not run. This fixes the problem by moving `is_iast_request_enabled` and `_IAST_CONTEXT` to `asm_config`. ## Checklist - [X] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) Co-authored-by: Juanjo Alvarez Martinez <[email protected]>
1 parent 217ce01 commit e7a8d99

File tree

15 files changed

+43
-70
lines changed

15 files changed

+43
-70
lines changed

ddtrace/appsec/_common_module_patches.py

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,6 @@
2525
from ddtrace.settings.asm import config as asm_config
2626

2727

28-
if asm_config._iast_enabled:
29-
from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled
30-
else:
31-
32-
def is_iast_request_enabled() -> bool:
33-
return False
34-
35-
3628
log = get_logger(__name__)
3729
_DD_ORIGINAL_ATTRIBUTES: Dict[Any, Any] = {}
3830

@@ -50,15 +42,6 @@ def patch_common_modules():
5042
subprocess_patch.add_lst_callback(_RASP_POPEN, popen_FD233052260D8B4D)
5143
if _is_patched:
5244
return
53-
# for testing purposes, we need to update is_iast_request_enabled
54-
if asm_config._iast_enabled:
55-
global is_iast_request_enabled
56-
from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled
57-
else:
58-
global is_iast_request_enabled
59-
60-
def is_iast_request_enabled() -> bool:
61-
return False
6245

6346
try_wrap_function_wrapper("builtins", "open", wrapped_open_CFDDB7ABBA9081B6)
6447
try_wrap_function_wrapper("urllib.request", "OpenerDirector.open", wrapped_open_ED4CF71136E15EBF)
@@ -91,7 +74,7 @@ def wrapped_read_F3E51D71B4EC16EF(original_read_callable, instance, args, kwargs
9174
wrapper for _io.BytesIO and _io.StringIO read function
9275
"""
9376
result = original_read_callable(*args, **kwargs)
94-
if asm_config._iast_enabled and is_iast_request_enabled():
77+
if asm_config._iast_enabled and asm_config.is_iast_request_enabled:
9578
from ddtrace.appsec._iast._taint_tracking import OriginType
9679
from ddtrace.appsec._iast._taint_tracking import Source
9780
from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges
@@ -117,7 +100,7 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs
117100
"""
118101
wrapper for open file function
119102
"""
120-
if asm_config._iast_enabled and is_iast_request_enabled():
103+
if asm_config._iast_enabled and asm_config.is_iast_request_enabled:
121104
try:
122105
from ddtrace.appsec._iast.taint_sinks.path_traversal import check_and_report_path_traversal
123106

@@ -208,7 +191,7 @@ def wrapped_request_D8CB81E472AF98A2(original_request_callable, instance, args,
208191
wrapper for third party requests.request function
209192
https://requests.readthedocs.io
210193
"""
211-
if asm_config._iast_enabled and is_iast_request_enabled():
194+
if asm_config._iast_enabled and asm_config.is_iast_request_enabled:
212195
from ddtrace.appsec._iast.taint_sinks.ssrf import _iast_report_ssrf
213196

214197
_iast_report_ssrf(original_request_callable, *args, **kwargs)

ddtrace/appsec/_constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class IAST(metaclass=Constant_Class):
161161

162162
TEXT_TYPES = (str, bytes, bytearray)
163163
TAINTEABLE_TYPES = (str, bytes, bytearray, Match, BytesIO, StringIO)
164+
REQUEST_CONTEXT_KEY: Literal["_iast_env"] = "_iast_env"
164165

165166

166167
class IAST_SPAN_TAGS(metaclass=Constant_Class):

ddtrace/appsec/_iast/_handlers.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from ddtrace.internal.logger import get_logger
1818
from ddtrace.settings.asm import config as asm_config
1919

20-
from ._iast_request_context import is_iast_request_enabled
2120
from ._logs import iast_instrumentation_wrapt_debug_log
2221
from ._logs import iast_propagation_listener_log_log
2322
from ._taint_tracking._taint_objects import taint_pyobject
@@ -57,7 +56,7 @@ def _on_set_http_meta_iast(
5756

5857
def _on_request_init(wrapped, instance, args, kwargs):
5958
wrapped(*args, **kwargs)
60-
if asm_config._iast_enabled and is_iast_request_enabled():
59+
if asm_config._iast_enabled and asm_config.is_iast_request_enabled:
6160
try:
6261
instance.query_string = taint_pyobject(
6362
pyobject=instance.query_string,
@@ -154,7 +153,7 @@ def _iast_on_wrapped_view(kwargs):
154153

155154

156155
def _on_wsgi_environ(wrapped, _instance, args, kwargs):
157-
if asm_config._iast_enabled and args and is_iast_request_enabled():
156+
if asm_config._iast_enabled and args and asm_config.is_iast_request_enabled:
158157
return wrapped(*((taint_structure(args[0], OriginType.HEADER_NAME, OriginType.HEADER),) + args[1:]), **kwargs)
159158

160159
return wrapped(*args, **kwargs)
@@ -189,7 +188,7 @@ def _on_django_func_wrapped(fn_args, fn_kwargs, first_arg_expected_type, *_):
189188
# If IAST is enabled, and we're wrapping a Django view call, taint the kwargs (view's
190189
# path parameters)
191190
if asm_config._iast_enabled and fn_args and isinstance(fn_args[0], first_arg_expected_type):
192-
if not is_iast_request_enabled():
191+
if not asm_config.is_iast_request_enabled:
193192
return
194193

195194
http_req = fn_args[0]
@@ -304,7 +303,7 @@ def _on_grpc_response(message):
304303

305304

306305
def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs):
307-
if asm_config._iast_enabled and is_iast_request_enabled():
306+
if asm_config._iast_enabled and asm_config.is_iast_request_enabled:
308307
try:
309308
for key, value in wrapped(*args, **kwargs):
310309
new_key = taint_pyobject(pyobject=key, source_name=key, source_value=key, source_origin=origins[0])
@@ -321,7 +320,7 @@ def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs):
321320

322321
def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs):
323322
value = wrapped(*args, **kwargs)
324-
if asm_config._iast_enabled and is_iast_request_enabled():
323+
if asm_config._iast_enabled and asm_config.is_iast_request_enabled:
325324
try:
326325
if not is_pyobject_tainted(value):
327326
name = str(args[0]) if len(args) else "http.request.body"
@@ -335,7 +334,7 @@ def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs):
335334

336335
def if_iast_taint_starlette_datastructures(origin, wrapped, instance, args, kwargs):
337336
value = wrapped(*args, **kwargs)
338-
if asm_config._iast_enabled and is_iast_request_enabled():
337+
if asm_config._iast_enabled and asm_config.is_iast_request_enabled:
339338
try:
340339
res = []
341340
for element in value:
@@ -441,7 +440,7 @@ def _on_pre_tracedrequest_iast(ctx):
441440

442441

443442
def _on_set_request_tags_iast(request, span, flask_config):
444-
if asm_config._iast_enabled and is_iast_request_enabled():
443+
if asm_config._iast_enabled and asm_config.is_iast_request_enabled:
445444
request.cookies = taint_structure(
446445
request.cookies,
447446
OriginType.COOKIE_NAME,

ddtrace/appsec/_iast/_iast_request_context.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
else:
3030
from typing_extensions import Literal # noqa:F401
3131

32-
_IAST_CONTEXT: Literal["_iast_env"] = "_iast_env"
33-
3432

3533
class IASTEnvironment:
3634
"""
@@ -49,17 +47,17 @@ def __init__(self, span: Optional[Span] = None):
4947

5048

5149
def _get_iast_context() -> Optional[IASTEnvironment]:
52-
return core.get_item(_IAST_CONTEXT)
50+
return core.get_item(IAST.REQUEST_CONTEXT_KEY)
5351

5452

5553
def in_iast_context() -> bool:
56-
return core.get_item(_IAST_CONTEXT) is not None
54+
return core.get_item(IAST.REQUEST_CONTEXT_KEY) is not None
5755

5856

5957
def start_iast_context():
6058
if asm_config._iast_enabled:
6159
create_propagation_context()
62-
core.set_item(_IAST_CONTEXT, IASTEnvironment())
60+
core.set_item(IAST.REQUEST_CONTEXT_KEY, IASTEnvironment())
6361

6462

6563
def end_iast_context(span: Optional[Span] = None):
@@ -70,7 +68,7 @@ def end_iast_context(span: Optional[Span] = None):
7068

7169

7270
def finalize_iast_env(env: IASTEnvironment) -> None:
73-
core.discard_item(_IAST_CONTEXT)
71+
core.discard_item(IAST.REQUEST_CONTEXT_KEY)
7472

7573

7674
def set_iast_reporter(iast_reporter: IastSpanReporter) -> None:
@@ -104,13 +102,6 @@ def set_iast_request_enabled(request_enabled) -> None:
104102
log.debug("iast::propagation::context::Trying to set IAST reporter but no context is present")
105103

106104

107-
def is_iast_request_enabled() -> bool:
108-
env = _get_iast_context()
109-
if env:
110-
return env.request_enabled
111-
return False
112-
113-
114105
def _move_iast_data_to_root_span():
115106
return asbool(os.getenv("_DD_IAST_USE_ROOT_SPAN"))
116107

@@ -156,7 +147,7 @@ def _iast_end_request(ctx=None, span=None, *args, **kwargs):
156147
existing_data = req_span.get_tag(IAST.JSON)
157148
if existing_data is None:
158149
if req_span.get_metric(IAST.ENABLED) is None:
159-
if not is_iast_request_enabled():
150+
if not asm_config.is_iast_request_enabled:
160151
req_span.set_metric(IAST.ENABLED, 0.0)
161152
end_iast_context(req_span)
162153
oce.release_request()

ddtrace/appsec/_iast/_patches/json_tainting.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from ddtrace.internal.logger import get_logger
55
from ddtrace.settings.asm import config as asm_config
66

7-
from .._iast_request_context import is_iast_request_enabled
87
from .._patch import set_and_check_module_is_patched
98
from .._patch import set_module_unpatched
109
from .._patch import try_wrap_function_wrapper
@@ -42,7 +41,7 @@ def wrapped_loads(wrapped, instance, args, kwargs):
4241
from .._taint_utils import taint_structure
4342

4443
obj = wrapped(*args, **kwargs)
45-
if asm_config._iast_enabled and is_iast_request_enabled():
44+
if asm_config._iast_enabled and asm_config.is_iast_request_enabled:
4645
from .._taint_tracking._taint_objects import get_tainted_ranges
4746
from .._taint_tracking._taint_objects import taint_pyobject
4847

ddtrace/appsec/_iast/_taint_tracking/_taint_objects.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from ddtrace.appsec._constants import IAST
77
from ddtrace.appsec._constants import IAST_SPAN_TAGS
8-
from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled
98
from ddtrace.appsec._iast._logs import iast_propagation_debug_log
109
from ddtrace.appsec._iast._logs import iast_propagation_error_log
1110
from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_source
@@ -18,13 +17,14 @@
1817
from ddtrace.appsec._iast._taint_tracking import set_ranges
1918
from ddtrace.appsec._iast._taint_tracking import set_ranges_from_values
2019
from ddtrace.internal.logger import get_logger
20+
from ddtrace.settings.asm import config as asm_config
2121

2222

2323
log = get_logger(__name__)
2424

2525

2626
def is_pyobject_tainted(pyobject: Any) -> bool:
27-
if not is_iast_request_enabled():
27+
if not asm_config.is_iast_request_enabled:
2828
return False
2929
if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc]
3030
return False
@@ -37,7 +37,7 @@ def is_pyobject_tainted(pyobject: Any) -> bool:
3737

3838

3939
def _taint_pyobject_base(pyobject: Any, source_name: Any, source_value: Any, source_origin=None) -> Any:
40-
if not is_iast_request_enabled():
40+
if not asm_config.is_iast_request_enabled:
4141
return pyobject
4242

4343
if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc]
@@ -69,7 +69,7 @@ def _taint_pyobject_base(pyobject: Any, source_name: Any, source_value: Any, sou
6969

7070

7171
def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool:
72-
if not is_iast_request_enabled():
72+
if not asm_config.is_iast_request_enabled:
7373
return False
7474
if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc]
7575
return False
@@ -82,7 +82,7 @@ def taint_pyobject_with_ranges(pyobject: Any, ranges: Tuple) -> bool:
8282

8383

8484
def get_tainted_ranges(pyobject: Any) -> Tuple:
85-
if not is_iast_request_enabled():
85+
if not asm_config.is_iast_request_enabled:
8686
return tuple()
8787
if not isinstance(pyobject, IAST.TAINTEABLE_TYPES): # type: ignore[misc]
8888
return tuple()

ddtrace/appsec/_iast/reporter.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ddtrace.appsec._iast.constants import VULN_WEAK_CIPHER_TYPE
2020
from ddtrace.appsec._iast.constants import VULN_WEAK_RANDOMNESS
2121
from ddtrace.internal.logger import get_logger
22+
from ddtrace.settings.asm import config as asm_config
2223

2324

2425
log = get_logger(__name__)
@@ -237,9 +238,7 @@ def taint_ranges_as_evidence_info(pyobject: Any) -> Tuple[List[Source], List[Dic
237238
return sources, tainted_ranges_to_dict
238239

239240
def add_ranges_to_evidence_and_extract_sources(self, vuln):
240-
from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled
241-
242-
if not is_iast_request_enabled():
241+
if not asm_config.is_iast_request_enabled:
243242
log.debug(
244243
"iast::propagation::context::add_ranges_to_evidence_and_extract_sources. "
245244
"No request quota or this vulnerability is outside the context"
@@ -258,9 +257,7 @@ def build_and_scrub_value_parts(self) -> Dict[str, Any]:
258257
Returns:
259258
- Dict[str, Any]: Dictionary representation of the IAST span reporter.
260259
"""
261-
from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled
262-
263-
if not is_iast_request_enabled():
260+
if not asm_config.is_iast_request_enabled:
264261
log.debug(
265262
"iast::propagation::context::build_and_scrub_value_parts. "
266263
"No request quota or this vulnerability is outside the context"

ddtrace/appsec/_iast/taint_sinks/_base.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from ddtrace.trace import tracer
1212

1313
from .._iast_request_context import get_iast_reporter
14-
from .._iast_request_context import is_iast_request_enabled
1514
from .._iast_request_context import set_iast_reporter
1615
from .._overhead_control_engine import Operation
1716
from .._stacktrace import get_info_frame
@@ -57,7 +56,7 @@ def wrapper(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any:
5756
"""Get the current root Span and attach it to the wrapped function. We need the span to report the
5857
vulnerability and update the context with the report information.
5958
"""
60-
if not is_iast_request_enabled():
59+
if not asm_config.is_iast_request_enabled:
6160
if _is_iast_debug_enabled():
6261
log.debug(
6362
"iast::propagation::context::VulnerabilityBase.wrapper. No request quota or this vulnerability "
@@ -74,7 +73,7 @@ def wrapper(wrapped: Callable, instance: Any, args: Any, kwargs: Any) -> Any:
7473
@classmethod
7574
@taint_sink_deduplication
7675
def _prepare_report(cls, vulnerability_type, evidence, file_name, line_number):
77-
if not is_iast_request_enabled():
76+
if not asm_config.is_iast_request_enabled:
7877
if _is_iast_debug_enabled():
7978
log.debug(
8079
"iast::propagation::context::VulnerabilityBase._prepare_report. "

ddtrace/appsec/_iast/taint_sinks/code_injection.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from ddtrace.appsec._common_module_patches import try_unwrap
55
from ddtrace.appsec._constants import IAST_SPAN_TAGS
66
from ddtrace.appsec._iast import oce
7-
from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled
87
from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink
98
from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink
109
from ddtrace.appsec._iast._metrics import increment_iast_span_metric
@@ -82,6 +81,6 @@ class CodeInjection(VulnerabilityBase):
8281
def _iast_report_code_injection(code_string: Text):
8382
increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, CodeInjection.vulnerability_type)
8483
_set_metric_iast_executed_sink(CodeInjection.vulnerability_type)
85-
if is_iast_request_enabled():
84+
if asm_config.is_iast_request_enabled:
8685
if is_pyobject_tainted(code_string):
8786
CodeInjection.report(evidence_value=code_string)

ddtrace/appsec/_iast/taint_sinks/command_injection.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
from ddtrace.appsec._constants import IAST_SPAN_TAGS
55
from ddtrace.appsec._iast import oce
6-
from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled
76
from ddtrace.appsec._iast._metrics import _set_metric_iast_executed_sink
87
from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink
98
from ddtrace.appsec._iast._metrics import increment_iast_span_metric
@@ -50,7 +49,7 @@ def _iast_report_cmdi(shell_args: Union[str, List[str]]) -> None:
5049
increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, CommandInjection.vulnerability_type)
5150
_set_metric_iast_executed_sink(CommandInjection.vulnerability_type)
5251

53-
if is_iast_request_enabled() and CommandInjection.has_quota():
52+
if asm_config.is_iast_request_enabled and CommandInjection.has_quota():
5453
from .._taint_tracking.aspects import join_aspect
5554

5655
if isinstance(shell_args, (list, tuple)):

0 commit comments

Comments
 (0)