Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(iast): refactor iast request context by core.context #10988

Open
wants to merge 90 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
9898b74
first version
christophe-papazian Oct 2, 2024
1ca626c
Merge remote-tracking branch 'origin/main' into christophe-papazian/r…
christophe-papazian Oct 2, 2024
c2fff2c
merge forget
christophe-papazian Oct 2, 2024
619cb7c
revert unwanted changes
christophe-papazian Oct 2, 2024
f86884e
revert unwanted changes
christophe-papazian Oct 2, 2024
ccef5df
revert unwanted changes
christophe-papazian Oct 2, 2024
715dab4
Merge remote-tracking branch 'origin/main' into christophe-papazian/r…
christophe-papazian Oct 2, 2024
f16df62
move appsec load for iast
christophe-papazian Oct 2, 2024
c9f15cc
revert config raise
christophe-papazian Oct 2, 2024
26c373d
starting the change for fastapi
christophe-papazian Oct 2, 2024
f96a48d
Merge remote-tracking branch 'origin/main' into christophe-papazian/r…
christophe-papazian Oct 3, 2024
c16565c
fix
christophe-papazian Oct 3, 2024
b9f0c15
fix
christophe-papazian Oct 3, 2024
e547cd6
fix
christophe-papazian Oct 3, 2024
271c9e4
fix asgi blocking mechanism
christophe-papazian Oct 3, 2024
10411ad
improve block logic for fastapi/asgi
christophe-papazian Oct 3, 2024
7c2b9c0
use new block functions everywhere
christophe-papazian Oct 3, 2024
33adb33
fix test_processor
christophe-papazian Oct 4, 2024
20b584f
fix appsec tests
christophe-papazian Oct 4, 2024
0b2819a
Merge remote-tracking branch 'origin/main' into christophe-papazian/r…
christophe-papazian Oct 7, 2024
a927991
fix test_remoteconfiguration
christophe-papazian Oct 7, 2024
4203c4f
fix iast/test_telemetry
christophe-papazian Oct 7, 2024
7d7f3c5
make core context real context and add exception catching. fix test_a…
christophe-papazian Oct 7, 2024
3f310b8
Merge remote-tracking branch 'origin/main' into christophe-papazian/r…
christophe-papazian Oct 7, 2024
ebe7f02
context return self
christophe-papazian Oct 7, 2024
6e26fc3
more test fixes
christophe-papazian Oct 7, 2024
9f7372b
remove context slots and fix more tests
christophe-papazian Oct 7, 2024
41502da
Merge branch 'main' into christophe-papazian/refactor_asm_request_con…
christophe-papazian Oct 8, 2024
538b0bf
remove code commented
christophe-papazian Oct 8, 2024
f55a018
chore(appsec): move common context to class
avara1986 Oct 8, 2024
c84c840
chore(appsec): move common context to class
avara1986 Oct 9, 2024
f2a584f
chore(appsec): move common context to class
avara1986 Oct 9, 2024
4b29b90
chore(appsec): move common context to class
avara1986 Oct 9, 2024
45849ea
chore(appsec): move common context to class
avara1986 Oct 9, 2024
dab6a6a
chore(appsec): move common context to class
avara1986 Oct 9, 2024
a42eba6
chore(appsec): move common context to class
avara1986 Oct 9, 2024
41d6c4a
chore(appsec): move common context to class
avara1986 Oct 9, 2024
66e5996
chore(appsec): move common context to class
avara1986 Oct 10, 2024
06da8c6
chore(appsec): move common context to class
avara1986 Oct 10, 2024
cac89f8
chore(appsec): move common context to class
avara1986 Oct 10, 2024
d7af85c
chore(appsec): move common context to class
avara1986 Oct 10, 2024
2452893
chore(appsec): move common context to class
avara1986 Oct 10, 2024
d830dd2
chore(appsec): move common context to class
avara1986 Oct 10, 2024
1f3efe7
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 10, 2024
0d8288a
chore(appsec): move common context to class
avara1986 Oct 10, 2024
fe59ea6
chore(appsec): move common context to class
avara1986 Oct 10, 2024
2b9f436
chore(appsec): move common context to class
avara1986 Oct 10, 2024
33b522b
chore(appsec): move common context to class
avara1986 Oct 10, 2024
811b5f8
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 10, 2024
3b1237f
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 11, 2024
2153c4e
chore(appsec): move common context to class
avara1986 Oct 11, 2024
5bbe3c8
chore(appsec): move common context to class
avara1986 Oct 11, 2024
491ef4d
chore(appsec): move common context to class
avara1986 Oct 11, 2024
7aa40a8
chore(appsec): move common context to class
avara1986 Oct 11, 2024
deffd91
chore(appsec): move common context to class
avara1986 Oct 11, 2024
4725518
chore(appsec): move common context to class
avara1986 Oct 11, 2024
75bf96a
chore(appsec): move common context to class
avara1986 Oct 11, 2024
f597cd1
chore(appsec): move common context to class
avara1986 Oct 11, 2024
1cc399f
chore(appsec): move common context to class
avara1986 Oct 11, 2024
97f5904
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 11, 2024
b14030a
chore(appsec): move common context to class
avara1986 Oct 11, 2024
72f73f1
chore(appsec): move common context to class
avara1986 Oct 11, 2024
15d1b9e
chore(appsec): move common context to class
avara1986 Oct 11, 2024
b76a7bf
chore(appsec): move common context to class
avara1986 Oct 11, 2024
df8194b
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 11, 2024
6369ff5
chore(appsec): move common context to class
avara1986 Oct 11, 2024
ef7200a
chore(appsec): move common context to class
avara1986 Oct 11, 2024
8815a91
chore(appsec): move common context to class
avara1986 Oct 11, 2024
62606e0
chore(appsec): move common context to class
avara1986 Oct 11, 2024
a8b6638
chore(appsec): move common context to class
avara1986 Oct 11, 2024
32faf3e
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 11, 2024
232d98b
chore(appsec): move common context to class
avara1986 Oct 11, 2024
c0d19a3
chore(appsec): move common context to class
avara1986 Oct 11, 2024
dbb6b55
chore(appsec): move common context to class
avara1986 Oct 11, 2024
27f8e96
chore(appsec): move common context to class
avara1986 Oct 14, 2024
e4cc5f4
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 14, 2024
d057d9d
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 14, 2024
fb74d23
chore(appsec): move common context to class
avara1986 Oct 14, 2024
8df1c33
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 14, 2024
20b386a
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 15, 2024
3ac963d
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 15, 2024
61bda6e
fix: enable fastapi header source
avara1986 Oct 15, 2024
95f30af
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 15, 2024
fe76bef
fix(iast): re.finditer error
avara1986 Oct 15, 2024
528573e
fix re match aspects related to #11027
avara1986 Oct 15, 2024
7d1eab1
revert
avara1986 Oct 15, 2024
5582bbe
remove Match from tainteable_types. APPSEC_55239
avara1986 Oct 16, 2024
3b43efe
revert changes
avara1986 Oct 16, 2024
8df5394
Merge branch 'main' into avara1986/refactor_iast_request_context_to_core
avara1986 Oct 16, 2024
c6edcb6
disable re tests
avara1986 Oct 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions benchmarks/appsec_iast_aspects/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
import bm
from bm.utils import override_env

from ddtrace.appsec._iast import oce
from ddtrace.appsec._iast._ast.ast_patching import astpatch_module
from ddtrace.appsec._iast._iast_request_context import end_iast_context
from ddtrace.appsec._iast._iast_request_context import set_iast_request_enabled
from ddtrace.appsec._iast._iast_request_context import start_iast_context


# Copypasted here from tests.iast.aspects.conftest since the benchmarks can't access tests.*
Expand All @@ -19,6 +23,18 @@ def _iast_patched_module(module_name):
return module_changed


def _start_iast_context_and_oce():
oce.reconfigure()
oce.acquire_request(None)
start_iast_context()
set_iast_request_enabled(True)


def _end_iast_context_and_oce():
end_iast_context()
oce.release_request()


class IAST_Aspects(bm.Scenario):
iast_enabled: bool
mod_original_name: str
Expand All @@ -27,6 +43,9 @@ class IAST_Aspects(bm.Scenario):

def run(self):
args = ast.literal_eval(self.args)
if self.iast_enabled:
with override_env({"DD_IAST_ENABLED": "True"}):
_start_iast_context_and_oce()

def _(loops):
for _ in range(loops):
Expand All @@ -40,3 +59,6 @@ def _(loops):
getattr(module_unpatched, self.function_name)(*args)

yield _
if self.iast_enabled:
with override_env({"DD_IAST_ENABLED": "True"}):
_end_iast_context_and_oce()
38 changes: 25 additions & 13 deletions benchmarks/appsec_iast_propagation/scenario.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
from typing import Any

import bm
from bm.utils import override_env


with override_env({"DD_IAST_ENABLED": "True"}):
from ddtrace.appsec._iast._taint_tracking import OriginType
from ddtrace.appsec._iast._taint_tracking import Source
from ddtrace.appsec._iast._taint_tracking import TaintRange
from ddtrace.appsec._iast._taint_tracking import create_context
from ddtrace.appsec._iast._taint_tracking import reset_context
from ddtrace.appsec._iast._taint_tracking import set_ranges
from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect
from ddtrace.appsec._iast._taint_tracking.aspects import join_aspect
from ddtrace.appsec._iast import oce
from ddtrace.appsec._iast._iast_request_context import end_iast_context
from ddtrace.appsec._iast._iast_request_context import set_iast_request_enabled
from ddtrace.appsec._iast._iast_request_context import start_iast_context
from ddtrace.appsec._iast._taint_tracking import OriginType
from ddtrace.appsec._iast._taint_tracking import Source
from ddtrace.appsec._iast._taint_tracking import TaintRange
from ddtrace.appsec._iast._taint_tracking import set_ranges
from ddtrace.appsec._iast._taint_tracking.aspects import add_aspect
from ddtrace.appsec._iast._taint_tracking.aspects import join_aspect


TAINT_ORIGIN = Source(name="sample_name", value="sample_value", origin=OriginType.PARAMETER)

CHECK_RANGES = [TaintRange(0, 3, TAINT_ORIGIN), TaintRange(21, 3, TAINT_ORIGIN), TaintRange(41, 3, TAINT_ORIGIN)]


def _start_iast_context_and_oce():
oce.reconfigure()
oce.acquire_request(None)
start_iast_context()
set_iast_request_enabled(True)


def _end_iast_context_and_oce():
end_iast_context()
oce.release_request()


def taint_pyobject_with_ranges(pyobject: Any, ranges: tuple) -> None:
set_ranges(pyobject, tuple(ranges))

Expand Down Expand Up @@ -53,8 +64,6 @@ def aspect_function(internal_loop, tainted):

def new_request(enable_propagation):
tainted = b"my_string".decode("ascii")
reset_context()
create_context()

if enable_propagation:
taint_pyobject_with_ranges(tainted, (CHECK_RANGES[0],))
Expand All @@ -74,6 +83,7 @@ class IastPropagation(bm.Scenario):
def run(self):
caller_loop = 10
if self.iast_enabled:
_start_iast_context_and_oce()
func = aspect_function
else:
func = normal_function
Expand All @@ -83,3 +93,5 @@ def _(loops):
launch_function(self.iast_enabled, func, self.internal_loop, caller_loop)

yield _
if self.iast_enabled:
_end_iast_context_and_oce()
5 changes: 2 additions & 3 deletions benchmarks/bm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,9 @@ def drop_traces(tracer):
def drop_telemetry_events():
# Avoids sending instrumentation telemetry payloads to the agent
try:
if telemetry.telemetry_writer.is_periodic:
telemetry.telemetry_writer.stop()
telemetry.telemetry_writer.stop()
telemetry.telemetry_writer.reset_queues()
telemetry.telemetry_writer.enable(start_worker_thread=False)
telemetry.telemetry_writer.enable()
except AttributeError:
# telemetry.telemetry_writer is not defined in this version of dd-trace-py
# Telemetry events will not be mocked!
Expand Down
6 changes: 4 additions & 2 deletions ddtrace/appsec/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@

def load_appsec():
"""Lazily load the appsec module listeners."""
from ddtrace.appsec._asm_request_context import listen
from ddtrace.appsec._asm_request_context import asm_listen
from ddtrace.appsec._iast._iast_request_context import iast_listen

global _APPSEC_TO_BE_LOADED
if _APPSEC_TO_BE_LOADED:
listen()
asm_listen()
iast_listen()
christophe-papazian marked this conversation as resolved.
Show resolved Hide resolved
_APPSEC_TO_BE_LOADED = False
29 changes: 18 additions & 11 deletions ddtrace/appsec/_asm_request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import json
import re
import sys
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable
from typing import Dict
Expand All @@ -16,8 +17,6 @@
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
from ddtrace.appsec._constants import SPAN_DATA_NAMES
from ddtrace.appsec._constants import WAF_CONTEXT_NAMES
from ddtrace.appsec._ddwaf import DDWaf_result
from ddtrace.appsec._iast._utils import _is_iast_enabled
from ddtrace.appsec._utils import get_triggers
from ddtrace.internal import core
from ddtrace.internal._exceptions import BlockingException
Expand All @@ -26,6 +25,9 @@
from ddtrace.settings.asm import config as asm_config


if TYPE_CHECKING:
from ddtrace.appsec._ddwaf import DDWaf_result

log = get_logger(__name__)

# Stopgap module for providing ASM context for the blocking features wrapping some contextvars.
Expand Down Expand Up @@ -56,7 +58,7 @@ class ASM_Environment:
"""

def __init__(self, span: Optional[Span] = None):
self.root = not in_context()
self.root = not in_asm_context()
if self.root:
core.add_suppress_exception(BlockingException)
if span is None:
Expand Down Expand Up @@ -92,7 +94,7 @@ def _get_asm_context() -> Optional[ASM_Environment]:
return core.get_item(_ASM_CONTEXT)


def in_context() -> bool:
def in_asm_context() -> bool:
return core.get_item(_ASM_CONTEXT) is not None


Expand Down Expand Up @@ -293,7 +295,7 @@ def set_waf_callback(value) -> None:
set_value(_CALLBACKS, _WAF_CALL, value)


def call_waf_callback(custom_data: Optional[Dict[str, Any]] = None, **kwargs) -> Optional[DDWaf_result]:
def call_waf_callback(custom_data: Optional[Dict[str, Any]] = None, **kwargs) -> Optional["DDWaf_result"]:
if not asm_config._asm_enabled:
return None
callback = get_value(_CALLBACKS, _WAF_CALL)
Expand Down Expand Up @@ -451,7 +453,7 @@ def _on_context_ended(ctx):
def _on_wrapped_view(kwargs):
return_value = [None, None]
# if Appsec is enabled, we can try to block as we have the path parameters at that point
if asm_config._asm_enabled and in_context():
if asm_config._asm_enabled and in_asm_context():
log.debug("Flask WAF call for Suspicious Request Blocking on request")
if kwargs:
set_waf_address(REQUEST_PATH_PARAMS, kwargs)
Expand All @@ -461,12 +463,14 @@ def _on_wrapped_view(kwargs):
return_value[0] = callback_block

# If IAST is enabled, taint the Flask function kwargs (path parameters)
from ddtrace.appsec._iast._utils import _is_iast_enabled

if _is_iast_enabled() and kwargs:
from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled
from ddtrace.appsec._iast._taint_tracking import OriginType
from ddtrace.appsec._iast._taint_tracking import taint_pyobject
from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor

if not AppSecIastSpanProcessor.is_span_analyzed():
if not is_iast_request_enabled():
return return_value

_kwargs = {}
Expand All @@ -479,16 +483,18 @@ def _on_wrapped_view(kwargs):


def _on_set_request_tags(request, span, flask_config):
from ddtrace.appsec._iast._utils import _is_iast_enabled

if _is_iast_enabled():
from ddtrace.appsec._iast._iast_request_context import is_iast_request_enabled
from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source
from ddtrace.appsec._iast._taint_tracking import OriginType
from ddtrace.appsec._iast._taint_utils import taint_structure
from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor

_set_metric_iast_instrumented_source(OriginType.COOKIE_NAME)
_set_metric_iast_instrumented_source(OriginType.COOKIE)

if not AppSecIastSpanProcessor.is_span_analyzed(span._local_root or span):
if not is_iast_request_enabled():
return

request.cookies = taint_structure(
Expand Down Expand Up @@ -556,10 +562,11 @@ def _get_headers_if_appsec():
return get_headers()


def listen():
def asm_listen():
from ddtrace.appsec._handlers import listen

listen()

core.on("flask.finalize_request.post", _set_headers_and_response)
core.on("flask.wrapped_view", _on_wrapped_view, "callback_and_args")
core.on("flask._patched_request", _on_pre_tracedrequest)
Expand Down
20 changes: 10 additions & 10 deletions ddtrace/appsec/_common_module_patches.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs
):
try:
from ddtrace.appsec._asm_request_context import call_waf_callback
from ddtrace.appsec._asm_request_context import in_context
from ddtrace.appsec._asm_request_context import in_asm_context
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
except ImportError:
# open is used during module initialization
Expand All @@ -93,7 +93,7 @@ def wrapped_open_CFDDB7ABBA9081B6(original_open_callable, instance, args, kwargs
filename = os.fspath(filename_arg)
except Exception:
filename = ""
if filename and in_context():
if filename and in_asm_context():
res = call_waf_callback(
{EXPLOIT_PREVENTION.ADDRESS.LFI: filename},
crop_trace="wrapped_open_CFDDB7ABBA9081B6",
Expand Down Expand Up @@ -126,15 +126,15 @@ def wrapped_open_ED4CF71136E15EBF(original_open_callable, instance, args, kwargs
):
try:
from ddtrace.appsec._asm_request_context import call_waf_callback
from ddtrace.appsec._asm_request_context import in_context
from ddtrace.appsec._asm_request_context import in_asm_context
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
except ImportError:
# open is used during module initialization
# and shouldn't be changed at that time
return original_open_callable(*args, **kwargs)

url = args[0] if args else kwargs.get("fullurl", None)
if url and in_context():
if url and in_asm_context():
if url.__class__.__name__ == "Request":
url = url.get_full_url()
if isinstance(url, str):
Expand Down Expand Up @@ -166,15 +166,15 @@ def wrapped_request_D8CB81E472AF98A2(original_request_callable, instance, args,
):
try:
from ddtrace.appsec._asm_request_context import call_waf_callback
from ddtrace.appsec._asm_request_context import in_context
from ddtrace.appsec._asm_request_context import in_asm_context
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
except ImportError:
# open is used during module initialization
# and shouldn't be changed at that time
return original_request_callable(*args, **kwargs)

url = args[1] if len(args) > 1 else kwargs.get("url", None)
if url and in_context():
if url and in_asm_context():
if isinstance(url, str):
res = call_waf_callback(
{EXPLOIT_PREVENTION.ADDRESS.SSRF: url},
Expand Down Expand Up @@ -206,12 +206,12 @@ def wrapped_system_5542593D237084A7(original_command_callable, instance, args, k
):
try:
from ddtrace.appsec._asm_request_context import call_waf_callback
from ddtrace.appsec._asm_request_context import in_context
from ddtrace.appsec._asm_request_context import in_asm_context
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
except ImportError:
return original_command_callable(*args, **kwargs)

if in_context():
if in_asm_context():
res = call_waf_callback(
{EXPLOIT_PREVENTION.ADDRESS.CMDI: command},
crop_trace="wrapped_system_5542593D237084A7",
Expand Down Expand Up @@ -254,14 +254,14 @@ def execute_4C9BAC8E228EB347(instrument_self, query, args, kwargs) -> None:
):
try:
from ddtrace.appsec._asm_request_context import call_waf_callback
from ddtrace.appsec._asm_request_context import in_context
from ddtrace.appsec._asm_request_context import in_asm_context
from ddtrace.appsec._constants import EXPLOIT_PREVENTION
except ImportError:
# execute is used during module initialization
# and shouldn't be changed at that time
return

if instrument_self and query and in_context():
if instrument_self and query and in_asm_context():
db_type = _DB_DIALECTS.get(
getattr(instrument_self, "_self_config", {}).get("_dbapi_span_name_prefix", ""), ""
)
Expand Down
2 changes: 0 additions & 2 deletions ddtrace/appsec/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,9 @@ class IAST(metaclass=Constant_Class):
LAZY_TAINT: Literal["_DD_IAST_LAZY_TAINT"] = "_DD_IAST_LAZY_TAINT"
JSON: Literal["_dd.iast.json"] = "_dd.iast.json"
ENABLED: Literal["_dd.iast.enabled"] = "_dd.iast.enabled"
CONTEXT_KEY: Literal["_iast_data"] = "_iast_data"
PATCH_MODULES: Literal["_DD_IAST_PATCH_MODULES"] = "_DD_IAST_PATCH_MODULES"
DENY_MODULES: Literal["_DD_IAST_DENY_MODULES"] = "_DD_IAST_DENY_MODULES"
SEP_MODULES: Literal[","] = ","
REQUEST_IAST_ENABLED: Literal["_dd.iast.request_enabled"] = "_dd.iast.request_enabled"
TEXT_TYPES = (str, bytes, bytearray)
TAINTEABLE_TYPES = (str, bytes, bytearray, Match, BytesIO, StringIO)

Expand Down
Loading
Loading