Skip to content

Commit cf74a41

Browse files
committed
fix(appsec): report all tags on the service entry span instead of the local root span
1 parent 070b62a commit cf74a41

File tree

4 files changed

+22
-9
lines changed

4 files changed

+22
-9
lines changed

ddtrace/appsec/_api_security/api_manager.py

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

88
from ddtrace._trace._limits import MAX_SPAN_META_VALUE_LEN
9+
from ddtrace.appsec._asm_request_context import get_service_entry_span
910
from ddtrace.appsec._constants import API_SECURITY
1011
from ddtrace.appsec._constants import SPAN_DATA_NAMES
1112
from ddtrace.appsec._trace_utils import _asm_manual_keep
@@ -133,7 +134,7 @@ def _should_collect_schema(self, env, priority: int) -> Optional[bool]:
133134
def _schema_callback(self, env):
134135
if env.span is None or not asm_config._api_security_feature_active:
135136
return
136-
root = env.span._local_root or env.span
137+
root = get_service_entry_span(env.span)
137138
collected = self.BLOCK_COLLECTED if env.blocked else self.COLLECTED
138139
if not root or any(meta_name in root._meta for _, meta_name, _ in collected):
139140
return

ddtrace/appsec/_asm_request_context.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def report_error_on_span(error: str, message: str) -> None:
6969
if not span:
7070
root_span = core.get_root_span()
7171
else:
72-
root_span = span._local_root or span
72+
root_span = get_service_entry_span(span)
7373
if not root_span:
7474
return
7575
root_span.set_tag_str(APPSEC.ERROR_TYPE, error)
@@ -118,6 +118,17 @@ def in_asm_context() -> bool:
118118
return core.get_item(_ASM_CONTEXT) is not None
119119

120120

121+
def get_service_entry_span(span: Span) -> Span:
122+
"""Find the service entry span of a span
123+
124+
We can't simply go for the span._local_root
125+
because it may belong to another service.
126+
"""
127+
while not span._is_top_level and span._parent:
128+
span = span._parent
129+
return span
130+
131+
121132
def is_blocked() -> bool:
122133
env = _get_asm_context()
123134
if env is None:
@@ -194,7 +205,7 @@ def flush_waf_triggers(env: ASM_Environment) -> None:
194205
from ddtrace.appsec._metrics import ddwaf_version
195206

196207
# Make sure we find a root span to attach the triggers to
197-
root_span = env.span._local_root or env.span
208+
root_span = get_service_entry_span(env.span)
198209
if env.waf_triggers:
199210
report_list = get_triggers(root_span)
200211
if report_list is not None:
@@ -240,7 +251,7 @@ def finalize_asm_env(env: ASM_Environment) -> None:
240251
flush_waf_triggers(env)
241252
for function in env.callbacks[_CONTEXT_CALL]:
242253
function(env)
243-
root_span = env.span._local_root or env.span
254+
root_span = get_service_entry_span(env.span)
244255
if root_span:
245256
if env.waf_info:
246257
info = env.waf_info()
@@ -255,7 +266,7 @@ def finalize_asm_env(env: ASM_Environment) -> None:
255266
except Exception:
256267
logger.debug("asm_context::finalize_asm_env::exception", extra=log_extra, exc_info=True)
257268
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)
269+
root_span.set_tag(APPSEC.RC_CLIENT_ID, asm_config._rc_client_id)
259270
waf_adresses = env.waf_addresses
260271
req_headers = waf_adresses.get(SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, {})
261272
if req_headers:
@@ -661,7 +672,7 @@ def _set_headers(span: Span, headers: Any, kind: str, only_asm_enabled: bool = F
661672
value = value.decode()
662673
if key.lower() in (_COLLECTED_REQUEST_HEADERS_ASM_ENABLED if only_asm_enabled else _COLLECTED_REQUEST_HEADERS):
663674
# 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)
675+
get_service_entry_span(span).set_tag(_normalize_tag_name(kind, key), value)
665676

666677

667678
def asm_listen():

ddtrace/appsec/_exploit_prevention/stack_traces.py

Lines changed: 2 additions & 1 deletion
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._asm_request_context import get_service_entry_span
910
from ddtrace.appsec._constants import STACK_TRACE
1011
from ddtrace.internal import core
1112
from ddtrace.settings.asm import config as asm_config
@@ -38,7 +39,7 @@ def report_stack(
3839

3940
if span is None or stack_id is None:
4041
return False
41-
root_span = span._local_root or span
42+
root_span = get_service_entry_span(span)
4243
appsec_traces = root_span.get_struct_tag(STACK_TRACE.TAG) or {}
4344
current_list = appsec_traces.get(namespace, [])
4445
total_length = len(current_list)

ddtrace/appsec/_processor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def on_span_start(self, span: Span) -> None:
199199
peer_ip = _asm_request_context.get_ip()
200200
headers = _asm_request_context.get_headers()
201201
headers_case_sensitive = _asm_request_context.get_headers_case_sensitive()
202-
root_span = span._local_root or span
202+
root_span = _asm_request_context.get_service_entry_span(span)
203203

204204
root_span.set_metric(APPSEC.ENABLED, 1.0)
205205
root_span.set_tag_str(_RUNTIME_FAMILY, "python")
@@ -293,7 +293,7 @@ def _waf_action(
293293
waf_results = Binding_error
294294

295295
_asm_request_context.set_waf_info(lambda: self._ddwaf.info)
296-
root_span = span._local_root or span
296+
root_span = _asm_request_context.get_service_entry_span(span)
297297
if waf_results.return_code < 0:
298298
error_tag = APPSEC.RASP_ERROR if rule_type else APPSEC.WAF_ERROR
299299
previous = root_span.get_tag(error_tag)

0 commit comments

Comments
 (0)