Skip to content

Commit bd9d326

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

File tree

10 files changed

+467
-376
lines changed

10 files changed

+467
-376
lines changed

ddtrace/appsec/_api_security/api_manager.py

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

ddtrace/appsec/_asm_request_context.py

Lines changed: 42 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:
@@ -109,6 +105,10 @@ def __init__(self, span: Optional[Span] = None, rc_products: str = ""):
109105
self.api_security_reported: int = 0
110106
self.rc_products: str = rc_products
111107

108+
@property
109+
def entry_span(self):
110+
return self.span._service_entry_span
111+
112112

113113
def _get_asm_context() -> Optional[ASM_Environment]:
114114
return core.get_item(_ASM_CONTEXT)
@@ -132,6 +132,14 @@ def get_blocked() -> Dict[str, Any]:
132132
return env.blocked or {}
133133

134134

135+
def get_entry_span() -> Optional[Span]:
136+
env = _get_asm_context()
137+
if env is None:
138+
span = core.get_span()
139+
return span._service_entry_span if span else None
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,40 @@ 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
198204
if env.waf_triggers:
199-
report_list = get_triggers(root_span)
205+
report_list = get_triggers(env.entry_span)
200206
if report_list is not None:
201207
report_list.extend(env.waf_triggers)
202208
else:
203209
report_list = env.waf_triggers
204210
if asm_config._use_metastruct_for_triggers:
205-
root_span.set_struct_tag(APPSEC.STRUCT, {"triggers": report_list})
211+
env.entry_span.set_struct_tag(APPSEC.STRUCT, {"triggers": report_list})
206212
else:
207-
root_span.set_tag(APPSEC.JSON, json.dumps({"triggers": report_list}, separators=(",", ":")))
213+
env.entry_span.set_tag(APPSEC.JSON, json.dumps({"triggers": report_list}, separators=(",", ":")))
208214
env.waf_triggers = []
209215
telemetry_results: Telemetry_result = env.telemetry
210216

211-
root_span.set_tag_str(APPSEC.WAF_VERSION, ddwaf_version)
217+
env.entry_span.set_tag_str(APPSEC.WAF_VERSION, ddwaf_version)
212218
if telemetry_results.total_duration:
213-
update_span_metrics(root_span, APPSEC.WAF_DURATION, telemetry_results.duration)
219+
update_span_metrics(env.entry_span, APPSEC.WAF_DURATION, telemetry_results.duration)
214220
telemetry_results.duration = 0.0
215-
update_span_metrics(root_span, APPSEC.WAF_DURATION_EXT, telemetry_results.total_duration)
221+
update_span_metrics(env.entry_span, APPSEC.WAF_DURATION_EXT, telemetry_results.total_duration)
216222
telemetry_results.total_duration = 0.0
217223
if telemetry_results.timeout:
218-
update_span_metrics(root_span, APPSEC.WAF_TIMEOUTS, telemetry_results.timeout)
224+
update_span_metrics(env.entry_span, APPSEC.WAF_TIMEOUTS, telemetry_results.timeout)
219225
rasp_timeouts = sum(telemetry_results.rasp.timeout.values())
220226
if rasp_timeouts:
221-
update_span_metrics(root_span, APPSEC.RASP_TIMEOUTS, rasp_timeouts)
227+
update_span_metrics(env.entry_span, APPSEC.RASP_TIMEOUTS, rasp_timeouts)
222228
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)
229+
update_span_metrics(env.entry_span, APPSEC.RASP_DURATION, telemetry_results.rasp.duration)
230+
update_span_metrics(env.entry_span, APPSEC.RASP_DURATION_EXT, telemetry_results.rasp.total_duration)
231+
update_span_metrics(env.entry_span, APPSEC.RASP_RULE_EVAL, telemetry_results.rasp.sum_eval)
226232
if telemetry_results.truncation.string_length:
227-
root_span.set_metric(APPSEC.TRUNCATION_STRING_LENGTH, max(telemetry_results.truncation.string_length))
233+
env.entry_span.set_metric(APPSEC.TRUNCATION_STRING_LENGTH, max(telemetry_results.truncation.string_length))
228234
if telemetry_results.truncation.container_size:
229-
root_span.set_metric(APPSEC.TRUNCATION_CONTAINER_SIZE, max(telemetry_results.truncation.container_size))
235+
env.entry_span.set_metric(APPSEC.TRUNCATION_CONTAINER_SIZE, max(telemetry_results.truncation.container_size))
230236
if telemetry_results.truncation.container_depth:
231-
root_span.set_metric(APPSEC.TRUNCATION_CONTAINER_DEPTH, max(telemetry_results.truncation.container_depth))
237+
env.entry_span.set_metric(APPSEC.TRUNCATION_CONTAINER_DEPTH, max(telemetry_results.truncation.container_depth))
232238

233239

234240
def finalize_asm_env(env: ASM_Environment) -> None:
@@ -240,31 +246,30 @@ def finalize_asm_env(env: ASM_Environment) -> None:
240246
flush_waf_triggers(env)
241247
for function in env.callbacks[_CONTEXT_CALL]:
242248
function(env)
243-
root_span = env.span._local_root or env.span
244-
if root_span:
249+
if env.entry_span:
245250
if env.waf_info:
246251
info = env.waf_info()
247252
try:
248253
if info.errors:
249-
root_span.set_tag_str(APPSEC.EVENT_RULE_ERRORS, info.errors)
254+
env.entry_span.set_tag_str(APPSEC.EVENT_RULE_ERRORS, info.errors)
250255
extra = {"product": "appsec", "more_info": info.errors, "stack_limit": 4}
251256
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)
257+
env.entry_span.set_tag_str(APPSEC.EVENT_RULE_VERSION, info.version)
258+
env.entry_span.set_metric(APPSEC.EVENT_RULE_LOADED, info.loaded)
259+
env.entry_span.set_metric(APPSEC.EVENT_RULE_ERROR_COUNT, info.failed)
255260
except Exception:
256261
logger.debug("asm_context::finalize_asm_env::exception", extra=log_extra, exc_info=True)
257262
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)
263+
env.entry_span.set_tag(APPSEC.RC_CLIENT_ID, asm_config._rc_client_id)
259264
waf_adresses = env.waf_addresses
260265
req_headers = waf_adresses.get(SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, {})
261266
if req_headers:
262-
_set_headers(root_span, req_headers, kind="request")
267+
_set_headers(env.entry_span, req_headers, kind="request")
263268
res_headers = waf_adresses.get(SPAN_DATA_NAMES.RESPONSE_HEADERS_NO_COOKIES, {})
264269
if res_headers:
265-
_set_headers(root_span, res_headers, kind="response")
270+
_set_headers(env.entry_span, res_headers, kind="response")
266271
if env.rc_products:
267-
root_span.set_tag_str(APPSEC.RC_PRODUCTS, env.rc_products)
272+
env.entry_span.set_tag_str(APPSEC.RC_PRODUCTS, env.rc_products)
268273

269274
core.discard_local_item(_ASM_CONTEXT)
270275

@@ -364,7 +369,7 @@ def call_waf_callback(custom_data: Optional[Dict[str, Any]] = None, **kwargs) ->
364369
return callback(custom_data, **kwargs)
365370
else:
366371
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)
372+
report_error_on_entry_span("appsec::instrumentation::diagnostic", WARNING_TAGS.CALL_WAF_CALLBACK_NOT_SET)
368373
return None
369374

370375

@@ -661,7 +666,7 @@ def _set_headers(span: Span, headers: Any, kind: str, only_asm_enabled: bool = F
661666
value = value.decode()
662667
if key.lower() in (_COLLECTED_REQUEST_HEADERS_ASM_ENABLED if only_asm_enabled else _COLLECTED_REQUEST_HEADERS):
663668
# 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)
669+
span.set_tag(_normalize_tag_name(kind, key), value)
665670

666671

667672
def asm_listen():

ddtrace/appsec/_exploit_prevention/stack_traces.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
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
10-
from ddtrace.internal import core
1111
from ddtrace.settings.asm import config as asm_config
1212

1313

@@ -34,12 +34,11 @@ def report_stack(
3434
return False
3535

3636
if span is None:
37-
span = core.get_root_span()
37+
span = _asm_request_context.get_entry_span()
3838

3939
if span is None or stack_id is None:
4040
return False
41-
root_span = span._local_root or span
42-
appsec_traces = root_span.get_struct_tag(STACK_TRACE.TAG) or {}
41+
appsec_traces = span.get_struct_tag(STACK_TRACE.TAG) or {}
4342
current_list = appsec_traces.get(namespace, [])
4443
total_length = len(current_list)
4544

@@ -77,5 +76,5 @@ def report_stack(
7776
res["frames"] = frames
7877
current_list.append(res)
7978
appsec_traces[namespace] = current_list
80-
root_span.set_struct_tag(STACK_TRACE.TAG, appsec_traces)
79+
span.set_struct_tag(STACK_TRACE.TAG, appsec_traces)
8180
return True

ddtrace/appsec/_processor.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,15 @@ def on_span_start(self, span: Span) -> None:
198198
peer_ip = _asm_request_context.get_ip()
199199
headers = _asm_request_context.get_headers()
200200
headers_case_sensitive = _asm_request_context.get_headers_case_sensitive()
201-
root_span = span._local_root or span
201+
entry_span = _asm_request_context.get_entry_span()
202+
if not entry_span:
203+
return
202204

203-
root_span.set_metric(APPSEC.ENABLED, 1.0)
204-
root_span.set_tag_str(_RUNTIME_FAMILY, "python")
205+
entry_span.set_metric(APPSEC.ENABLED, 1.0)
206+
entry_span.set_tag_str(_RUNTIME_FAMILY, "python")
205207

206208
def waf_callable(custom_data=None, **kwargs):
207-
return self._waf_action(root_span, ctx, custom_data, **kwargs)
209+
return self._waf_action(entry_span, ctx, custom_data, **kwargs)
208210

209211
_asm_request_context.set_waf_callback(waf_callable)
210212
_asm_request_context.add_context_callback(self.metrics._set_waf_request_metrics)
@@ -225,7 +227,7 @@ def waf_callable(custom_data=None, **kwargs):
225227

226228
def _waf_action(
227229
self,
228-
span: Span,
230+
entry_span: Span,
229231
ctx: "ddwaf.ddwaf_types.ddwaf_context_capsule",
230232
custom_data: Optional[Dict[str, Any]] = None,
231233
crop_trace: Optional[str] = None,
@@ -292,18 +294,17 @@ def _waf_action(
292294
waf_results = Binding_error
293295

294296
_asm_request_context.set_waf_info(lambda: self._ddwaf.info)
295-
root_span = span._local_root or span
296297
if waf_results.return_code < 0:
297298
error_tag = APPSEC.RASP_ERROR if rule_type else APPSEC.WAF_ERROR
298-
previous = root_span.get_tag(error_tag)
299+
previous = entry_span.get_tag(error_tag)
299300
if previous is None:
300-
root_span.set_tag_str(error_tag, str(waf_results.return_code))
301+
entry_span.set_tag_str(error_tag, str(waf_results.return_code))
301302
else:
302303
try:
303304
int_previous = int(previous)
304305
except ValueError:
305306
int_previous = -128
306-
root_span.set_tag_str(error_tag, str(max(int_previous, waf_results.return_code)))
307+
entry_span.set_tag_str(error_tag, str(max(int_previous, waf_results.return_code)))
307308

308309
blocked = {}
309310
for action, parameters in waf_results.actions.items():
@@ -314,15 +315,17 @@ def _waf_action(
314315
blocked[WAF_ACTIONS.TYPE] = "none"
315316
elif action == WAF_ACTIONS.STACK_ACTION:
316317
stack_trace_id = parameters["stack_id"]
317-
report_stack("exploit detected", span, crop_trace, stack_id=stack_trace_id, namespace=STACK_TRACE.RASP)
318+
report_stack(
319+
"exploit detected", entry_span, crop_trace, stack_id=stack_trace_id, namespace=STACK_TRACE.RASP
320+
)
318321
for rule in waf_results.data:
319322
rule[EXPLOIT_PREVENTION.STACK_TRACE_ID] = stack_trace_id
320323

321324
# Trace tagging
322325
for key, value in waf_results.meta_tags.items():
323-
root_span.set_tag_str(key, value)
326+
entry_span.set_tag_str(key, value)
324327
for key, value in waf_results.metrics.items():
325-
root_span.set_metric(key, value)
328+
entry_span.set_metric(key, value)
326329

327330
if waf_results.data:
328331
log.debug("[DDAS-011-00] ASM In-App WAF returned: %s. Timeout %s", waf_results.data, waf_results.timeout)
@@ -345,24 +348,24 @@ def _waf_action(
345348
if waf_results.data:
346349
_asm_request_context.store_waf_results_data(waf_results.data)
347350
if blocked:
348-
span.set_tag(APPSEC.BLOCKED, "true")
351+
entry_span.set_tag(APPSEC.BLOCKED, "true")
349352

350353
# Partial DDAS-011-00
351-
span.set_tag_str(APPSEC.EVENT, "true")
354+
entry_span.set_tag_str(APPSEC.EVENT, "true")
352355

353356
remote_ip = _asm_request_context.get_waf_address(SPAN_DATA_NAMES.REQUEST_HTTP_IP)
354357
if remote_ip:
355358
# Note that if the ip collection is disabled by the env var
356359
# DD_TRACE_CLIENT_IP_HEADER_DISABLED actor.ip won't be sent
357-
span.set_tag_str("actor.ip", remote_ip)
360+
entry_span.set_tag_str("actor.ip", remote_ip)
358361

359362
# Right now, we overwrite any value that could be already there. We need to reconsider when ASM/AppSec's
360363
# specs are updated.
361-
if span.get_tag(_ORIGIN_KEY) is None:
362-
span.set_tag_str(_ORIGIN_KEY, APPSEC.ORIGIN_VALUE)
364+
if entry_span.get_tag(_ORIGIN_KEY) is None:
365+
entry_span.set_tag_str(_ORIGIN_KEY, APPSEC.ORIGIN_VALUE)
363366

364367
if waf_results.keep and allowed:
365-
_asm_manual_keep(span)
368+
_asm_manual_keep(entry_span)
366369

367370
return waf_results
368371

0 commit comments

Comments
 (0)