Skip to content

Commit f1952d8

Browse files
committed
chore(appsec): report everything on the entry_span
1 parent af09421 commit f1952d8

File tree

27 files changed

+634
-501
lines changed

27 files changed

+634
-501
lines changed

.github/codeql-config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
name: "CodeQL config"
22
paths-ignore:
3-
- 'tests/appsec/iast_packages/packages/**'
3+
- "tests/appsec/iast_packages/packages/**"
4+
- "tests/appsec/contrib_appsec/**"

ddtrace/appsec/_api_security/api_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ def _should_collect_schema(self, env, priority: int) -> Optional[bool]:
131131
def _schema_callback(self, env):
132132
if env.span is None or not asm_config._api_security_feature_active:
133133
return
134-
root = env.span._local_root or env.span
134+
root = env.entry_span
135135
collected = self.BLOCK_COLLECTED if env.blocked else self.COLLECTED
136136
if not root or any(meta_name in root._meta for _, meta_name, _ in collected):
137137
return

ddtrace/appsec/_asm_request_context.py

Lines changed: 44 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,12 @@ class WARNING_TAGS(metaclass=Constant_Class):
6464
GLOBAL_CALLBACKS: Dict[str, List[Callable]] = {_CONTEXT_CALL: []}
6565

6666

67-
def report_error_on_span(error: str, message: str) -> None:
68-
span = getattr(_get_asm_context(), "span", None) or core.get_span()
69-
if not span:
70-
root_span = core.get_root_span()
71-
else:
72-
root_span = span._local_root or span
73-
if not root_span:
67+
def report_error_on_entry_span(error: str, message: str) -> None:
68+
entry_span = get_entry_span()
69+
if not entry_span:
7470
return
75-
root_span.set_tag_str(APPSEC.ERROR_TYPE, error)
76-
root_span.set_tag_str(APPSEC.ERROR_MESSAGE, message)
71+
entry_span.set_tag_str(APPSEC.ERROR_TYPE, error)
72+
entry_span.set_tag_str(APPSEC.ERROR_MESSAGE, message)
7773

7874

7975
class ASM_Environment:
@@ -93,6 +89,7 @@ def __init__(self, span: Optional[Span] = None, rc_products: str = ""):
9389
logger.warning(WARNING_TAGS.ASM_ENV_NO_SPAN, extra=log_extra, stack_info=True)
9490
raise TypeError("ASM_Environment requires a span")
9591
self.span: Span = context_span
92+
self.entry_span: Span = self.span._service_entry_span
9693
if self.span.name.endswith(".request"):
9794
self.framework = self.span.name[:-8]
9895
else:
@@ -132,6 +129,17 @@ def get_blocked() -> Dict[str, Any]:
132129
return env.blocked or {}
133130

134131

132+
def get_entry_span() -> Optional[Span]:
133+
env = _get_asm_context()
134+
if env is None:
135+
span = core.get_span()
136+
if span:
137+
return span._service_entry_span
138+
else:
139+
return core.get_root_span()
140+
return env.entry_span
141+
142+
135143
def get_framework() -> str:
136144
env = _get_asm_context()
137145
if env is None:
@@ -193,42 +201,41 @@ def update_span_metrics(span: Span, name: str, value: Union[float, int]) -> None
193201
def flush_waf_triggers(env: ASM_Environment) -> None:
194202
from ddtrace.appsec._metrics import ddwaf_version
195203

196-
# Make sure we find a root span to attach the triggers to
197-
root_span = env.span._local_root or env.span
204+
entry_span = env.entry_span
198205
if env.waf_triggers:
199-
report_list = get_triggers(root_span)
206+
report_list = get_triggers(entry_span)
200207
if report_list is not None:
201208
report_list.extend(env.waf_triggers)
202209
else:
203210
report_list = env.waf_triggers
204211
if asm_config._use_metastruct_for_triggers:
205-
root_span.set_struct_tag(APPSEC.STRUCT, {"triggers": report_list})
212+
entry_span.set_struct_tag(APPSEC.STRUCT, {"triggers": report_list})
206213
else:
207-
root_span.set_tag(APPSEC.JSON, json.dumps({"triggers": report_list}, separators=(",", ":")))
214+
entry_span.set_tag(APPSEC.JSON, json.dumps({"triggers": report_list}, separators=(",", ":")))
208215
env.waf_triggers = []
209216
telemetry_results: Telemetry_result = env.telemetry
210217

211-
root_span.set_tag_str(APPSEC.WAF_VERSION, ddwaf_version)
218+
entry_span.set_tag_str(APPSEC.WAF_VERSION, ddwaf_version)
212219
if telemetry_results.total_duration:
213-
update_span_metrics(root_span, APPSEC.WAF_DURATION, telemetry_results.duration)
220+
update_span_metrics(entry_span, APPSEC.WAF_DURATION, telemetry_results.duration)
214221
telemetry_results.duration = 0.0
215-
update_span_metrics(root_span, APPSEC.WAF_DURATION_EXT, telemetry_results.total_duration)
222+
update_span_metrics(entry_span, APPSEC.WAF_DURATION_EXT, telemetry_results.total_duration)
216223
telemetry_results.total_duration = 0.0
217224
if telemetry_results.timeout:
218-
update_span_metrics(root_span, APPSEC.WAF_TIMEOUTS, telemetry_results.timeout)
225+
update_span_metrics(entry_span, APPSEC.WAF_TIMEOUTS, telemetry_results.timeout)
219226
rasp_timeouts = sum(telemetry_results.rasp.timeout.values())
220227
if rasp_timeouts:
221-
update_span_metrics(root_span, APPSEC.RASP_TIMEOUTS, rasp_timeouts)
228+
update_span_metrics(entry_span, APPSEC.RASP_TIMEOUTS, rasp_timeouts)
222229
if telemetry_results.rasp.sum_eval:
223-
update_span_metrics(root_span, APPSEC.RASP_DURATION, telemetry_results.rasp.duration)
224-
update_span_metrics(root_span, APPSEC.RASP_DURATION_EXT, telemetry_results.rasp.total_duration)
225-
update_span_metrics(root_span, APPSEC.RASP_RULE_EVAL, telemetry_results.rasp.sum_eval)
230+
update_span_metrics(entry_span, APPSEC.RASP_DURATION, telemetry_results.rasp.duration)
231+
update_span_metrics(entry_span, APPSEC.RASP_DURATION_EXT, telemetry_results.rasp.total_duration)
232+
update_span_metrics(entry_span, APPSEC.RASP_RULE_EVAL, telemetry_results.rasp.sum_eval)
226233
if telemetry_results.truncation.string_length:
227-
root_span.set_metric(APPSEC.TRUNCATION_STRING_LENGTH, max(telemetry_results.truncation.string_length))
234+
entry_span.set_metric(APPSEC.TRUNCATION_STRING_LENGTH, max(telemetry_results.truncation.string_length))
228235
if telemetry_results.truncation.container_size:
229-
root_span.set_metric(APPSEC.TRUNCATION_CONTAINER_SIZE, max(telemetry_results.truncation.container_size))
236+
entry_span.set_metric(APPSEC.TRUNCATION_CONTAINER_SIZE, max(telemetry_results.truncation.container_size))
230237
if telemetry_results.truncation.container_depth:
231-
root_span.set_metric(APPSEC.TRUNCATION_CONTAINER_DEPTH, max(telemetry_results.truncation.container_depth))
238+
entry_span.set_metric(APPSEC.TRUNCATION_CONTAINER_DEPTH, max(telemetry_results.truncation.container_depth))
232239

233240

234241
def finalize_asm_env(env: ASM_Environment) -> None:
@@ -240,31 +247,31 @@ def finalize_asm_env(env: ASM_Environment) -> None:
240247
flush_waf_triggers(env)
241248
for function in env.callbacks[_CONTEXT_CALL]:
242249
function(env)
243-
root_span = env.span._local_root or env.span
244-
if root_span:
250+
entry_span = env.entry_span
251+
if entry_span:
245252
if env.waf_info:
246253
info = env.waf_info()
247254
try:
248255
if info.errors:
249-
root_span.set_tag_str(APPSEC.EVENT_RULE_ERRORS, info.errors)
256+
entry_span.set_tag_str(APPSEC.EVENT_RULE_ERRORS, info.errors)
250257
extra = {"product": "appsec", "more_info": info.errors, "stack_limit": 4}
251258
logger.debug("asm_context::finalize_asm_env::waf_errors", extra=extra, stack_info=True)
252-
root_span.set_tag_str(APPSEC.EVENT_RULE_VERSION, info.version)
253-
root_span.set_metric(APPSEC.EVENT_RULE_LOADED, info.loaded)
254-
root_span.set_metric(APPSEC.EVENT_RULE_ERROR_COUNT, info.failed)
259+
entry_span.set_tag_str(APPSEC.EVENT_RULE_VERSION, info.version)
260+
entry_span.set_metric(APPSEC.EVENT_RULE_LOADED, info.loaded)
261+
entry_span.set_metric(APPSEC.EVENT_RULE_ERROR_COUNT, info.failed)
255262
except Exception:
256263
logger.debug("asm_context::finalize_asm_env::exception", extra=log_extra, exc_info=True)
257264
if asm_config._rc_client_id is not None:
258-
root_span._local_root.set_tag(APPSEC.RC_CLIENT_ID, asm_config._rc_client_id)
265+
entry_span.set_tag(APPSEC.RC_CLIENT_ID, asm_config._rc_client_id)
259266
waf_adresses = env.waf_addresses
260267
req_headers = waf_adresses.get(SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, {})
261268
if req_headers:
262-
_set_headers(root_span, req_headers, kind="request")
269+
_set_headers(entry_span, req_headers, kind="request")
263270
res_headers = waf_adresses.get(SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES, {})
264271
if res_headers:
265-
_set_headers(root_span, res_headers, kind="response")
272+
_set_headers(entry_span, res_headers, kind="response")
266273
if env.rc_products:
267-
root_span.set_tag_str(APPSEC.RC_PRODUCTS, env.rc_products)
274+
entry_span.set_tag_str(APPSEC.RC_PRODUCTS, env.rc_products)
268275

269276
core.discard_local_item(_ASM_CONTEXT)
270277

@@ -364,7 +371,7 @@ def call_waf_callback(custom_data: Optional[Dict[str, Any]] = None, **kwargs) ->
364371
return callback(custom_data, **kwargs)
365372
else:
366373
logger.warning(WARNING_TAGS.CALL_WAF_CALLBACK_NOT_SET, extra=log_extra, stack_info=True)
367-
report_error_on_span("appsec::instrumentation::diagnostic", WARNING_TAGS.CALL_WAF_CALLBACK_NOT_SET)
374+
report_error_on_entry_span("appsec::instrumentation::diagnostic", WARNING_TAGS.CALL_WAF_CALLBACK_NOT_SET)
368375
return None
369376

370377

@@ -661,7 +668,7 @@ def _set_headers(span: Span, headers: Any, kind: str, only_asm_enabled: bool = F
661668
value = value.decode()
662669
if key.lower() in (_COLLECTED_REQUEST_HEADERS_ASM_ENABLED if only_asm_enabled else _COLLECTED_REQUEST_HEADERS):
663670
# since the header value can be a list, use `set_tag()` to ensure it is converted to a string
664-
(span._local_root or span).set_tag(_normalize_tag_name(kind, key), value)
671+
span.set_tag(_normalize_tag_name(kind, key), value)
665672

666673

667674
def asm_listen():

ddtrace/appsec/_exploit_prevention/stack_traces.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from typing import Optional
77

88
from ddtrace._trace.span import Span
9+
from ddtrace.appsec import _asm_request_context
910
from ddtrace.appsec._constants import STACK_TRACE
1011
from ddtrace.internal import core
1112
from ddtrace.settings.asm import config as asm_config
@@ -33,13 +34,15 @@ def report_stack(
3334
# iast stack trace with iast disabled
3435
return False
3536

36-
if span is None:
37+
if namespace == STACK_TRACE.IAST and asm_config._iast_use_root_span:
3738
span = core.get_root_span()
3839

40+
if span is None:
41+
span = _asm_request_context.get_entry_span()
42+
3943
if span is None or stack_id is None:
4044
return False
41-
root_span = span._local_root or span
42-
appsec_traces = root_span.get_struct_tag(STACK_TRACE.TAG) or {}
45+
appsec_traces = span.get_struct_tag(STACK_TRACE.TAG) or {}
4346
current_list = appsec_traces.get(namespace, [])
4447
total_length = len(current_list)
4548

@@ -77,5 +80,5 @@ def report_stack(
7780
res["frames"] = frames
7881
current_list.append(res)
7982
appsec_traces[namespace] = current_list
80-
root_span.set_struct_tag(STACK_TRACE.TAG, appsec_traces)
83+
span.set_struct_tag(STACK_TRACE.TAG, appsec_traces)
8184
return True

ddtrace/appsec/_iast/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def wrapped_function(wrapped, instance, args, kwargs):
2727
)
2828
return wrapped(*args, **kwargs)
2929
"""
30+
3031
import os
3132
import sys
3233
import types
@@ -107,7 +108,6 @@ def _iast_pytest_activation():
107108
if _iast_propagation_enabled:
108109
return
109110
os.environ["DD_IAST_ENABLED"] = os.environ.get("DD_IAST_ENABLED") or "1"
110-
os.environ["_DD_IAST_USE_ROOT_SPAN"] = os.environ.get("_DD_IAST_USE_ROOT_SPAN") or "true"
111111
os.environ["DD_IAST_REQUEST_SAMPLING"] = os.environ.get("DD_IAST_REQUEST_SAMPLING") or "100.0"
112112
os.environ["_DD_APPSEC_DEDUPLICATION_ENABLED"] = os.environ.get("_DD_APPSEC_DEDUPLICATION_ENABLED") or "false"
113113
os.environ["DD_IAST_VULNERABILITIES_PER_REQUEST"] = os.environ.get("DD_IAST_VULNERABILITIES_PER_REQUEST") or "1000"

ddtrace/appsec/_iast/_iast_request_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def _create_and_attach_iast_report_to_span(
7777

7878
def _iast_end_request(ctx=None, span=None, *args, **kwargs):
7979
try:
80-
move_to_root = base._move_iast_data_to_root_span()
80+
move_to_root = asm_config._iast_use_root_span
8181
if move_to_root:
8282
req_span = core.get_root_span()
8383
else:

ddtrace/appsec/_iast/_iast_request_context_base.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
from typing import Optional
32

43
from ddtrace._trace.span import Span
@@ -13,7 +12,6 @@
1312
from ddtrace.appsec._iast.sampling.vulnerability_detection import update_global_vulnerability_limit
1413
from ddtrace.internal import core
1514
from ddtrace.internal.logger import get_logger
16-
from ddtrace.internal.utils.formats import asbool
1715
from ddtrace.settings.asm import config as asm_config
1816

1917

@@ -80,10 +78,6 @@ def set_iast_request_endpoint(method, route) -> None:
8078
log.debug("iast::propagation::context::Trying to set IAST request endpoint but no context is present")
8179

8280

83-
def _move_iast_data_to_root_span():
84-
return asbool(os.getenv("_DD_IAST_USE_ROOT_SPAN"))
85-
86-
8781
def _iast_start_request(span=None, *args, **kwargs):
8882
try:
8983
if asm_config._iast_enabled:

ddtrace/appsec/_processor.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -211,13 +211,12 @@ def on_span_start(self, span: Span) -> None:
211211
peer_ip = _asm_request_context.get_ip()
212212
headers = _asm_request_context.get_headers()
213213
headers_case_sensitive = _asm_request_context.get_headers_case_sensitive()
214-
root_span = span._local_root or span
215-
216-
root_span.set_metric(APPSEC.ENABLED, 1.0)
217-
root_span.set_tag_str(_RUNTIME_FAMILY, "python")
214+
entry_span = span._service_entry_span
215+
entry_span.set_metric(APPSEC.ENABLED, 1.0)
216+
entry_span.set_tag_str(_RUNTIME_FAMILY, "python")
218217

219218
def waf_callable(custom_data=None, **kwargs):
220-
return self._waf_action(root_span, ctx, custom_data, **kwargs)
219+
return self._waf_action(entry_span, ctx, custom_data, **kwargs)
221220

222221
_asm_request_context.set_waf_callback(waf_callable)
223222
_asm_request_context.add_context_callback(self.metrics._set_waf_request_metrics)
@@ -238,7 +237,7 @@ def waf_callable(custom_data=None, **kwargs):
238237

239238
def _waf_action(
240239
self,
241-
span: Span,
240+
entry_span: Span,
242241
ctx: "ddwaf.ddwaf_types.ddwaf_context_capsule",
243242
custom_data: Optional[Dict[str, Any]] = None,
244243
crop_trace: Optional[str] = None,
@@ -304,18 +303,17 @@ def _waf_action(
304303
log.debug("appsec::processor::waf::run", exc_info=True)
305304
waf_results = Binding_error
306305
_asm_request_context.set_waf_info(lambda: self._ddwaf.info)
307-
root_span = span._local_root or span
308306
if waf_results.return_code < 0:
309307
error_tag = APPSEC.RASP_ERROR if rule_type else APPSEC.WAF_ERROR
310-
previous = root_span.get_tag(error_tag)
308+
previous = entry_span.get_tag(error_tag)
311309
if previous is None:
312-
root_span.set_tag_str(error_tag, str(waf_results.return_code))
310+
entry_span.set_tag_str(error_tag, str(waf_results.return_code))
313311
else:
314312
try:
315313
int_previous = int(previous)
316314
except ValueError:
317315
int_previous = -128
318-
root_span.set_tag_str(error_tag, str(max(int_previous, waf_results.return_code)))
316+
entry_span.set_tag_str(error_tag, str(max(int_previous, waf_results.return_code)))
319317

320318
blocked = {}
321319
for action, parameters in waf_results.actions.items():
@@ -326,15 +324,17 @@ def _waf_action(
326324
blocked[WAF_ACTIONS.TYPE] = "none"
327325
elif action == WAF_ACTIONS.STACK_ACTION:
328326
stack_trace_id = parameters["stack_id"]
329-
report_stack("exploit detected", span, crop_trace, stack_id=stack_trace_id, namespace=STACK_TRACE.RASP)
327+
report_stack(
328+
"exploit detected", entry_span, crop_trace, stack_id=stack_trace_id, namespace=STACK_TRACE.RASP
329+
)
330330
for rule in waf_results.data:
331331
rule[EXPLOIT_PREVENTION.STACK_TRACE_ID] = stack_trace_id
332332

333333
# Trace tagging
334334
for key, value in waf_results.meta_tags.items():
335-
root_span.set_tag_str(key, value)
335+
entry_span.set_tag_str(key, value)
336336
for key, value in waf_results.metrics.items():
337-
root_span.set_metric(key, value)
337+
entry_span.set_metric(key, value)
338338

339339
if waf_results.data:
340340
log.debug("[DDAS-011-00] ASM In-App WAF returned: %s. Timeout %s", waf_results.data, waf_results.timeout)
@@ -357,24 +357,24 @@ def _waf_action(
357357
if waf_results.data:
358358
_asm_request_context.store_waf_results_data(waf_results.data)
359359
if blocked:
360-
span.set_tag(APPSEC.BLOCKED, "true")
360+
entry_span.set_tag(APPSEC.BLOCKED, "true")
361361

362362
# Partial DDAS-011-00
363-
span.set_tag_str(APPSEC.EVENT, "true")
363+
entry_span.set_tag_str(APPSEC.EVENT, "true")
364364

365365
remote_ip = _asm_request_context.get_waf_address(SPAN_DATA_NAMES.REQUEST_HTTP_IP)
366366
if remote_ip:
367367
# Note that if the ip collection is disabled by the env var
368368
# DD_TRACE_CLIENT_IP_HEADER_DISABLED actor.ip won't be sent
369-
span.set_tag_str("actor.ip", remote_ip)
369+
entry_span.set_tag_str("actor.ip", remote_ip)
370370

371371
# Right now, we overwrite any value that could be already there. We need to reconsider when ASM/AppSec's
372372
# specs are updated.
373-
if span.get_tag(_ORIGIN_KEY) is None:
374-
span.set_tag_str(_ORIGIN_KEY, APPSEC.ORIGIN_VALUE)
373+
if entry_span.get_tag(_ORIGIN_KEY) is None:
374+
entry_span.set_tag_str(_ORIGIN_KEY, APPSEC.ORIGIN_VALUE)
375375

376376
if waf_results.keep and allowed:
377-
_asm_manual_keep(span)
377+
_asm_manual_keep(entry_span)
378378

379379
return waf_results
380380

0 commit comments

Comments
 (0)