Skip to content

Commit 37948e9

Browse files
authored
Merge branch '3.x-staging' into munir/upgrade-min-aws-lambda
2 parents da40186 + 4ebedf0 commit 37948e9

File tree

203 files changed

+1881
-12087
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

203 files changed

+1881
-12087
lines changed

ddtrace/_trace/trace_handlers.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,14 @@ def _get_parameters_for_new_span_directly_from_context(ctx: core.ExecutionContex
109109
def _start_span(ctx: core.ExecutionContext, call_trace: bool = True, **kwargs) -> "Span":
110110
span_kwargs = _get_parameters_for_new_span_directly_from_context(ctx)
111111
call_trace = ctx.get_item("call_trace", call_trace)
112-
tracer = (ctx.get_item("middleware") or ctx["pin"]).tracer
112+
tracer = ctx.get_item("tracer") or (ctx.get_item("middleware") or ctx["pin"]).tracer
113113
distributed_headers_config = ctx.get_item("distributed_headers_config")
114114
if distributed_headers_config:
115115
trace_utils.activate_distributed_headers(
116-
tracer, int_config=distributed_headers_config, request_headers=ctx["distributed_headers"]
116+
tracer,
117+
int_config=distributed_headers_config,
118+
request_headers=ctx["distributed_headers"],
119+
override=ctx.get_item("distributed_headers_config_override"),
117120
)
118121
distributed_context = ctx.get_item("distributed_context")
119122
if distributed_context and not call_trace:
@@ -126,6 +129,42 @@ def _start_span(ctx: core.ExecutionContext, call_trace: bool = True, **kwargs) -
126129
return span
127130

128131

132+
def _set_web_frameworks_tags(ctx, span, int_config):
133+
span.set_tag_str(COMPONENT, int_config.integration_name)
134+
span.set_tag_str(SPAN_KIND, SpanKind.SERVER)
135+
span.set_tag(_SPAN_MEASURED_KEY)
136+
137+
analytics_enabled = ctx.get_item("analytics_enabled")
138+
analytics_sample_rate = ctx.get_item("analytics_sample_rate", True)
139+
140+
# Configure trace search sample rate
141+
if (config._analytics_enabled and analytics_enabled is not False) or analytics_enabled is True:
142+
span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, analytics_sample_rate)
143+
144+
145+
def _on_web_framework_start_request(ctx, int_config):
146+
request_span = ctx.get_item("req_span")
147+
_set_web_frameworks_tags(ctx, request_span, int_config)
148+
149+
150+
def _on_web_framework_finish_request(
151+
span, int_config, method, url, status_code, query, req_headers, res_headers, route, finish
152+
):
153+
trace_utils.set_http_meta(
154+
span=span,
155+
integration_config=int_config,
156+
method=method,
157+
url=url,
158+
status_code=status_code,
159+
query=query,
160+
request_headers=req_headers,
161+
response_headers=res_headers,
162+
route=route,
163+
)
164+
if finish:
165+
span.finish()
166+
167+
129168
def _on_traced_request_context_started_flask(ctx):
130169
current_span = ctx["pin"].tracer.current_span()
131170
if not ctx["pin"].enabled or not current_span:
@@ -761,6 +800,10 @@ def listen():
761800
core.on("azure.functions.request_call_modifier", _on_azure_functions_request_span_modifier)
762801
core.on("azure.functions.start_response", _on_azure_functions_start_response)
763802

803+
# web frameworks general handlers
804+
core.on("web.request.start", _on_web_framework_start_request)
805+
core.on("web.request.finish", _on_web_framework_finish_request)
806+
764807
core.on("test_visibility.enable", _on_test_visibility_enable)
765808
core.on("test_visibility.disable", _on_test_visibility_disable)
766809
core.on("test_visibility.is_enabled", _on_test_visibility_is_enabled, "is_enabled")
@@ -769,6 +812,14 @@ def listen():
769812
core.on("rq.queue.enqueue_job", _propagate_context)
770813

771814
for context_name in (
815+
# web frameworks
816+
"aiohttp.request",
817+
"bottle.request",
818+
"cherrypy.request",
819+
"falcon.request",
820+
"molten.request",
821+
"pyramid.request",
822+
"sanic.request",
772823
"flask.call",
773824
"flask.jsonify",
774825
"flask.render_template",
@@ -779,6 +830,7 @@ def listen():
779830
"django.template.render",
780831
"django.process_exception",
781832
"django.func.wrapped",
833+
# non web frameworks
782834
"botocore.instrumented_api_call",
783835
"botocore.instrumented_lib_function",
784836
"botocore.patched_kinesis_api_call",

ddtrace/appsec/_constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class APPSEC(metaclass=Constant_Class):
9090
AUTO_LOGIN_EVENTS_FAILURE_MODE: Literal[
9191
"_dd.appsec.events.users.login.failure.auto.mode"
9292
] = "_dd.appsec.events.users.login.failure.auto.mode"
93+
AUTO_LOGIN_EVENTS_COLLECTION_MODE: Literal["_dd.appsec.user.collection_mode"] = "_dd.appsec.user.collection_mode"
9394
BLOCKED: Literal["appsec.blocked"] = "appsec.blocked"
9495
EVENT: Literal["appsec.event"] = "appsec.event"
9596
AUTO_USER_INSTRUMENTATION_MODE: Literal[

ddtrace/appsec/_iast/_evidence_redaction/_sensitive_handler.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
from .._utils import _get_source_index
88
from ..constants import VULN_CMDI
9+
from ..constants import VULN_CODE_INJECTION
910
from ..constants import VULN_HEADER_INJECTION
1011
from ..constants import VULN_SQL_INJECTION
1112
from ..constants import VULN_SSRF
1213
from .command_injection_sensitive_analyzer import command_injection_sensitive_analyzer
14+
from .default_sensitive_analyzer import default_sensitive_analyzer
1315
from .header_injection_sensitive_analyzer import header_injection_sensitive_analyzer
1416
from .sql_sensitive_analyzer import sql_sensitive_analyzer
1517
from .url_sensitive_analyzer import url_sensitive_analyzer
@@ -19,6 +21,7 @@
1921

2022
REDACTED_SOURCE_BUFFER = string.ascii_letters + string.digits
2123
LEN_SOURCE_BUFFER = len(REDACTED_SOURCE_BUFFER)
24+
VALUE_MAX_LENGHT = 45
2225

2326

2427
def get_redacted_source(length):
@@ -42,6 +45,7 @@ def __init__(self):
4245
VULN_SQL_INJECTION: sql_sensitive_analyzer,
4346
VULN_SSRF: url_sensitive_analyzer,
4447
VULN_HEADER_INJECTION: header_injection_sensitive_analyzer,
48+
VULN_CODE_INJECTION: default_sensitive_analyzer,
4549
}
4650

4751
@staticmethod
@@ -288,7 +292,7 @@ def to_redacted_json(self, evidence_value, sensitive, tainted_ranges, sources):
288292
return {"redacted_value_parts": value_parts, "redacted_sources": redacted_sources}
289293

290294
def redact_source(self, sources, redacted_sources, redacted_sources_context, source_index, start, end):
291-
if source_index is not None:
295+
if source_index is not None and source_index < len(sources):
292296
if not sources[source_index].redacted:
293297
redacted_sources.append(source_index)
294298
sources[source_index].pattern = get_redacted_source(len(sources[source_index].value))
@@ -303,8 +307,10 @@ def write_value_part(self, value_parts, value, source_index=None):
303307
if value:
304308
if source_index is not None:
305309
value_parts.append({"value": value, "source": source_index})
306-
else:
310+
elif len(value) < VALUE_MAX_LENGHT:
307311
value_parts.append({"value": value})
312+
else:
313+
value_parts.append({"redacted": True})
308314

309315
def write_redacted_value_part(
310316
self,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from ddtrace.internal.logger import get_logger
2+
3+
4+
log = get_logger(__name__)
5+
6+
7+
def default_sensitive_analyzer(evidence, name_pattern, value_pattern):
8+
if name_pattern.search(evidence.value) or value_pattern.search(evidence.value):
9+
return [{"start": 0, "end": len(evidence.value)}]
10+
11+
return []

ddtrace/appsec/_iast/_handlers.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def _on_django_patch():
153153
functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER),
154154
)
155155
)
156+
156157
# we instrument those sources on _on_django_func_wrapped
157158
_set_metric_iast_instrumented_source(OriginType.HEADER_NAME)
158159
_set_metric_iast_instrumented_source(OriginType.HEADER)

ddtrace/appsec/_trace_utils.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,10 @@ def track_user_login_success_event(
133133
return
134134
if real_mode == LOGIN_EVENTS_MODE.ANON and isinstance(user_id, str):
135135
user_id = _hash_user_id(user_id)
136-
136+
span.set_tag_str(APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE, real_mode)
137137
if login_events_mode != LOGIN_EVENTS_MODE.SDK:
138138
span.set_tag_str(APPSEC.USER_LOGIN_USERID, str(user_id))
139-
set_user(tracer, user_id, name, email, scope, role, session_id, propagate, span)
139+
set_user(tracer, user_id, name, email, scope, role, session_id, propagate, span, may_block=False)
140140
if in_asm_context():
141141
res = call_waf_callback(
142142
custom_data={
@@ -185,6 +185,7 @@ def track_user_login_failure_event(
185185
if login_events_mode != LOGIN_EVENTS_MODE.SDK:
186186
span.set_tag_str(APPSEC.USER_LOGIN_USERID, str(user_id))
187187
span.set_tag_str("%s.failure.%s" % (APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC, user.ID), str(user_id))
188+
span.set_tag_str(APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE, real_mode)
188189
# if called from the SDK, set the login, email and name
189190
if login_events_mode in (LOGIN_EVENTS_MODE.SDK, LOGIN_EVENTS_MODE.AUTO):
190191
if login:
@@ -376,5 +377,57 @@ def _on_django_auth(result_user, mode, kwargs, pin, info_retriever, django_confi
376377
return False, None
377378

378379

380+
def _on_django_process(result_user, mode, kwargs, pin, info_retriever, django_config):
381+
if (not asm_config._asm_enabled) or mode == LOGIN_EVENTS_MODE.DISABLED:
382+
return
383+
userid_list = info_retriever.possible_user_id_fields + info_retriever.possible_login_fields
384+
385+
for possible_key in userid_list:
386+
if possible_key in kwargs:
387+
user_id = kwargs[possible_key]
388+
break
389+
else:
390+
user_id = None
391+
392+
user_id_found, user_extra = info_retriever.get_user_info(
393+
login=django_config.include_user_login,
394+
email=django_config.include_user_email,
395+
name=django_config.include_user_realname,
396+
)
397+
if user_extra.get("login") is None:
398+
user_extra["login"] = user_id
399+
user_id = user_id_found or user_id
400+
if result_user and result_user.is_authenticated:
401+
span = pin.tracer.current_root_span()
402+
if mode == LOGIN_EVENTS_MODE.ANON and isinstance(user_id, str):
403+
hash_id = _hash_user_id(user_id)
404+
span.set_tag_str(APPSEC.USER_LOGIN_USERID, hash_id)
405+
span.set_tag_str(APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE, mode)
406+
set_user(pin.tracer, hash_id, propagate=True, may_block=False, span=span)
407+
elif mode == LOGIN_EVENTS_MODE.IDENT:
408+
span.set_tag_str(APPSEC.USER_LOGIN_USERID, str(user_id))
409+
span.set_tag_str(APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE, mode)
410+
set_user(
411+
pin.tracer,
412+
str(user_id),
413+
propagate=True,
414+
email=user_extra.get("email"),
415+
name=user_extra.get("name"),
416+
may_block=False,
417+
span=span,
418+
)
419+
if in_asm_context():
420+
real_mode = mode if mode != LOGIN_EVENTS_MODE.AUTO else asm_config._user_event_mode
421+
custom_data = {
422+
"REQUEST_USER_ID": str(user_id) if user_id else None,
423+
"REQUEST_USERNAME": user_extra.get("login"),
424+
"LOGIN_SUCCESS": real_mode,
425+
}
426+
res = call_waf_callback(custom_data=custom_data, force_sent=True)
427+
if res and any(action in [WAF_ACTIONS.BLOCK_ACTION, WAF_ACTIONS.REDIRECT_ACTION] for action in res.actions):
428+
raise BlockingException(get_blocked())
429+
430+
379431
core.on("django.login", _on_django_login)
380432
core.on("django.auth", _on_django_auth, "user")
433+
core.on("django.process_request", _on_django_process)

ddtrace/appsec/trace_utils/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Public API for User events"""
2+
13
from ddtrace.appsec._trace_utils import block_request # noqa: F401
24
from ddtrace.appsec._trace_utils import block_request_if_user_blocked # noqa: F401
35
from ddtrace.appsec._trace_utils import should_block_user # noqa: F401

ddtrace/contrib/internal/aiohttp/middlewares.py

Lines changed: 49 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@
22
from aiohttp.web_urldispatcher import SystemRoute
33

44
from ddtrace import config
5-
from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY
6-
from ddtrace.constants import _SPAN_MEASURED_KEY
7-
from ddtrace.constants import SPAN_KIND
8-
from ddtrace.contrib import trace_utils
95
from ddtrace.contrib.asyncio import context_provider
10-
from ddtrace.ext import SpanKind
116
from ddtrace.ext import SpanTypes
127
from ddtrace.ext import http
13-
from ddtrace.internal.constants import COMPONENT
8+
from ddtrace.internal import core
149
from ddtrace.internal.schema import schematize_url_operation
1510
from ddtrace.internal.schema.span_attribute_schema import SpanDirection
1611

@@ -35,47 +30,42 @@ async def attach_context(request):
3530
# application configs
3631
tracer = app[CONFIG_KEY]["tracer"]
3732
service = app[CONFIG_KEY]["service"]
38-
distributed_tracing = app[CONFIG_KEY]["distributed_tracing_enabled"]
39-
# Create a new context based on the propagated information.
40-
trace_utils.activate_distributed_headers(
41-
tracer,
42-
int_config=config.aiohttp,
43-
request_headers=request.headers,
44-
override=distributed_tracing,
45-
)
46-
47-
# trace the handler
48-
request_span = tracer.trace(
49-
schematize_url_operation("aiohttp.request", protocol="http", direction=SpanDirection.INBOUND),
50-
service=service,
51-
span_type=SpanTypes.WEB,
52-
)
53-
request_span.set_tag(_SPAN_MEASURED_KEY)
54-
55-
request_span.set_tag_str(COMPONENT, config.aiohttp.integration_name)
56-
57-
# set span.kind tag equal to type of request
58-
request_span.set_tag_str(SPAN_KIND, SpanKind.SERVER)
59-
60-
# Configure trace search sample rate
6133
# DEV: aiohttp is special case maintains separate configuration from config api
6234
analytics_enabled = app[CONFIG_KEY]["analytics_enabled"]
63-
if (config._analytics_enabled and analytics_enabled is not False) or analytics_enabled is True:
64-
request_span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, app[CONFIG_KEY].get("analytics_sample_rate", True))
65-
66-
# attach the context and the root span to the request; the Context
67-
# may be freely used by the application code
68-
request[REQUEST_CONTEXT_KEY] = request_span.context
69-
request[REQUEST_SPAN_KEY] = request_span
70-
request[REQUEST_CONFIG_KEY] = app[CONFIG_KEY]
71-
try:
72-
response = await handler(request)
73-
if isinstance(response, web.StreamResponse):
74-
request.task.add_done_callback(lambda _: finish_request_span(request, response))
75-
return response
76-
except Exception:
77-
request_span.set_traceback()
78-
raise
35+
# Create a new context based on the propagated information.
36+
37+
with core.context_with_data(
38+
"aiohttp.request",
39+
span_name=schematize_url_operation("aiohttp.request", protocol="http", direction=SpanDirection.INBOUND),
40+
span_type=SpanTypes.WEB,
41+
service=service,
42+
tags={},
43+
tracer=tracer,
44+
distributed_headers=request.headers,
45+
distributed_headers_config=config.aiohttp,
46+
distributed_headers_config_override=app[CONFIG_KEY]["distributed_tracing_enabled"],
47+
headers_case_sensitive=True,
48+
analytics_enabled=analytics_enabled,
49+
analytics_sample_rate=app[CONFIG_KEY].get("analytics_sample_rate", True),
50+
) as ctx:
51+
req_span = ctx.span
52+
53+
ctx.set_item("req_span", req_span)
54+
core.dispatch("web.request.start", (ctx, config.aiohttp))
55+
56+
# attach the context and the root span to the request; the Context
57+
# may be freely used by the application code
58+
request[REQUEST_CONTEXT_KEY] = req_span.context
59+
request[REQUEST_SPAN_KEY] = req_span
60+
request[REQUEST_CONFIG_KEY] = app[CONFIG_KEY]
61+
try:
62+
response = await handler(request)
63+
if isinstance(response, web.StreamResponse):
64+
request.task.add_done_callback(lambda _: finish_request_span(request, response))
65+
return response
66+
except Exception:
67+
req_span.set_traceback()
68+
raise
7969

8070
return attach_context
8171

@@ -122,19 +112,22 @@ def finish_request_span(request, response):
122112
# SystemRoute objects exist to throw HTTP errors and have no path
123113
route = aiohttp_route.resource.canonical
124114

125-
trace_utils.set_http_meta(
126-
request_span,
127-
config.aiohttp,
128-
method=request.method,
129-
url=str(request.url), # DEV: request.url is a yarl's URL object
130-
status_code=response.status,
131-
request_headers=request.headers,
132-
response_headers=response.headers,
133-
route=route,
115+
core.dispatch(
116+
"web.request.finish",
117+
(
118+
request_span,
119+
config.aiohttp,
120+
request.method,
121+
str(request.url), # DEV: request.url is a yarl's URL object
122+
response.status,
123+
None, # query arg = None
124+
request.headers,
125+
response.headers,
126+
route,
127+
True,
128+
),
134129
)
135130

136-
request_span.finish()
137-
138131

139132
async def on_prepare(request, response):
140133
"""

0 commit comments

Comments
 (0)