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(asm): improve user blocking for django (auth middleware) #12069

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8a8add6
add blocking to django auth
christophe-papazian Jan 23, 2025
b27e320
add support for AuthentificationMiddleware to detect and block athent…
christophe-papazian Jan 24, 2025
3ee130c
lint
christophe-papazian Jan 24, 2025
3fd5496
Merge remote-tracking branch 'origin/main' into christophe-papazian/d…
christophe-papazian Jan 24, 2025
178feb7
ensure authentification
christophe-papazian Jan 24, 2025
cfd1d1e
remove additional tracing
christophe-papazian Jan 24, 2025
b05361f
restore django_auth
christophe-papazian Jan 24, 2025
be66632
small restore
christophe-papazian Jan 24, 2025
20db595
reenable listener
christophe-papazian Jan 24, 2025
88fa824
keep apm tools
christophe-papazian Jan 24, 2025
3d5e91a
revert line
christophe-papazian Jan 24, 2025
db21191
fix set_user
christophe-papazian Jan 24, 2025
d949d3d
remove debug code
christophe-papazian Jan 24, 2025
eb3738e
import ddwaf import mechanism
christophe-papazian Jan 27, 2025
5699920
Merge remote-tracking branch 'origin/main' into christophe-papazian/d…
christophe-papazian Jan 27, 2025
667be44
more log for waf initialisation
christophe-papazian Jan 27, 2025
28ff780
bypass Popen instrumentation for waf initialisation
christophe-papazian Jan 27, 2025
993afc9
bypass subprocess instrumentation for waf initialisation
christophe-papazian Jan 27, 2025
09f1ab2
bypass subprocess.wait instrumentation for waf initialisation
christophe-papazian Jan 27, 2025
aaedab9
chore: update changelog for version 2.19.2 (#12088)
Yun-Kim Jan 27, 2025
e3045a1
fix(profiling): fix SystemError when collecting memory profiler event…
nsrip-dd Jan 27, 2025
55767a7
chore(tracing): refactor web server integrations to use the core modu…
wconti27 Jan 28, 2025
16d5280
ci(tracer): make serverless test unrequired (#12121)
christophe-papazian Jan 28, 2025
4f0bcb5
chore(asm): clean libddwaf loading (#12102)
christophe-papazian Jan 28, 2025
c4448ea
fix(llmobs): propagate distributed headers via signal dispatching, no…
Yun-Kim Jan 28, 2025
5b98fdf
Merge remote-tracking branch 'origin/main' into christophe-papazian/d…
christophe-papazian Jan 29, 2025
894bb19
restore
christophe-papazian Jan 29, 2025
d3d715e
remove dispatch set_user
christophe-papazian Jan 29, 2025
2e20bca
ensure that we don't call the waf twice for the same event
christophe-papazian Jan 29, 2025
8fc13d9
Merge remote-tracking branch 'origin/3.x-staging' into christophe-pap…
christophe-papazian Jan 30, 2025
89baa22
revert main changes
christophe-papazian Jan 30, 2025
346e026
revert main changes
christophe-papazian Jan 30, 2025
48a53a3
add appsec.user.id for auto collection on already authenticated and u…
christophe-papazian Jan 30, 2025
b47ea60
fix span usage for _on_django_process
christophe-papazian Jan 30, 2025
a14acdb
add collection mode for on_django_process
christophe-papazian Jan 30, 2025
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
1 change: 1 addition & 0 deletions ddtrace/appsec/_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class APPSEC(metaclass=Constant_Class):
AUTO_LOGIN_EVENTS_FAILURE_MODE: Literal[
"_dd.appsec.events.users.login.failure.auto.mode"
] = "_dd.appsec.events.users.login.failure.auto.mode"
AUTO_LOGIN_EVENTS_COLLECTION_MODE: Literal["_dd.appsec.user.collection_mode"] = "_dd.appsec.user.collection_mode"
BLOCKED: Literal["appsec.blocked"] = "appsec.blocked"
EVENT: Literal["appsec.event"] = "appsec.event"
AUTO_USER_INSTRUMENTATION_MODE: Literal[
Expand Down
57 changes: 55 additions & 2 deletions ddtrace/appsec/_trace_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@ def track_user_login_success_event(
return
if real_mode == LOGIN_EVENTS_MODE.ANON and isinstance(user_id, str):
user_id = _hash_user_id(user_id)

span.set_tag_str(APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE, real_mode)
if login_events_mode != LOGIN_EVENTS_MODE.SDK:
span.set_tag_str(APPSEC.USER_LOGIN_USERID, str(user_id))
set_user(tracer, user_id, name, email, scope, role, session_id, propagate, span)
set_user(tracer, user_id, name, email, scope, role, session_id, propagate, span, may_block=False)
if in_asm_context():
res = call_waf_callback(
custom_data={
Expand Down Expand Up @@ -185,6 +185,7 @@ def track_user_login_failure_event(
if login_events_mode != LOGIN_EVENTS_MODE.SDK:
span.set_tag_str(APPSEC.USER_LOGIN_USERID, str(user_id))
span.set_tag_str("%s.failure.%s" % (APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC, user.ID), str(user_id))
span.set_tag_str(APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE, real_mode)
# if called from the SDK, set the login, email and name
if login_events_mode in (LOGIN_EVENTS_MODE.SDK, LOGIN_EVENTS_MODE.AUTO):
if login:
Expand Down Expand Up @@ -376,5 +377,57 @@ def _on_django_auth(result_user, mode, kwargs, pin, info_retriever, django_confi
return False, None


def _on_django_process(result_user, mode, kwargs, pin, info_retriever, django_config):
if (not asm_config._asm_enabled) or mode == LOGIN_EVENTS_MODE.DISABLED:
return
userid_list = info_retriever.possible_user_id_fields + info_retriever.possible_login_fields

for possible_key in userid_list:
if possible_key in kwargs:
user_id = kwargs[possible_key]
break
else:
user_id = None

user_id_found, user_extra = info_retriever.get_user_info(
login=django_config.include_user_login,
email=django_config.include_user_email,
name=django_config.include_user_realname,
)
if user_extra.get("login") is None:
user_extra["login"] = user_id
user_id = user_id_found or user_id
if result_user and result_user.is_authenticated:
span = pin.tracer.current_root_span()
if mode == LOGIN_EVENTS_MODE.ANON and isinstance(user_id, str):
hash_id = _hash_user_id(user_id)
span.set_tag_str(APPSEC.USER_LOGIN_USERID, hash_id)
span.set_tag_str(APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE, mode)
set_user(pin.tracer, hash_id, propagate=True, may_block=False, span=span)
elif mode == LOGIN_EVENTS_MODE.IDENT:
span.set_tag_str(APPSEC.USER_LOGIN_USERID, str(user_id))
span.set_tag_str(APPSEC.AUTO_LOGIN_EVENTS_COLLECTION_MODE, mode)
set_user(
pin.tracer,
str(user_id),
propagate=True,
email=user_extra.get("email"),
name=user_extra.get("name"),
may_block=False,
span=span,
)
if in_asm_context():
real_mode = mode if mode != LOGIN_EVENTS_MODE.AUTO else asm_config._user_event_mode
custom_data = {
"REQUEST_USER_ID": str(user_id) if user_id else None,
"REQUEST_USERNAME": user_extra.get("login"),
"LOGIN_SUCCESS": real_mode,
}
res = call_waf_callback(custom_data=custom_data, force_sent=True)
if res and any(action in [WAF_ACTIONS.BLOCK_ACTION, WAF_ACTIONS.REDIRECT_ACTION] for action in res.actions):
raise BlockingException(get_blocked())


core.on("django.login", _on_django_login)
core.on("django.auth", _on_django_auth, "user")
core.on("django.process_request", _on_django_process)
2 changes: 2 additions & 0 deletions ddtrace/appsec/trace_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Public API for User events"""

from ddtrace.appsec._trace_utils import block_request # noqa: F401
from ddtrace.appsec._trace_utils import block_request_if_user_blocked # noqa: F401
from ddtrace.appsec._trace_utils import should_block_user # noqa: F401
Expand Down
51 changes: 51 additions & 0 deletions ddtrace/contrib/internal/django/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,53 @@ def traced_authenticate(django, pin, func, instance, args, kwargs):
return result_user


@trace_utils.with_traced_module
def traced_process_request(django, pin, func, instance, args, kwargs):
tags = {COMPONENT: config.django.integration_name}
with core.context_with_data(
"django.func.wrapped",
span_name="django.middleware",
resource="django.contrib.auth.middleware.AuthenticationMiddleware.process_request",
tags=tags,
pin=pin,
) as ctx, ctx.span:
core.dispatch(
"django.func.wrapped",
(
args,
kwargs,
django.core.handlers.wsgi.WSGIRequest if hasattr(django.core.handlers, "wsgi") else object,
ctx,
None,
),
)
func(*args, **kwargs)
mode = asm_config._user_event_mode
if mode == "disabled":
return
try:
request = get_argument_value(args, kwargs, 0, "request")
if request:
if hasattr(request, "user") and hasattr(request.user, "_setup"):
request.user._setup()
request_user = request.user._wrapped
else:
request_user = request.user
core.dispatch(
"django.process_request",
(
request_user,
mode,
kwargs,
pin,
_DjangoUserInfoRetriever(request_user, credentials=kwargs),
config.django,
),
)
except Exception:
log.debug("Error while trying to trace Django AuthenticationMiddleware process_request", exc_info=True)


def unwrap_views(func, instance, args, kwargs):
"""
Django channels uses path() and re_path() to route asgi applications. This broke our initial
Expand Down Expand Up @@ -884,6 +931,10 @@ def _(m):
trace_utils.wrap(m, "login", traced_login(django))
trace_utils.wrap(m, "authenticate", traced_authenticate(django))

@when_imported("django.contrib.auth.middleware")
def _(m):
trace_utils.wrap(m, "AuthenticationMiddleware.process_request", traced_process_request(django))

# Only wrap get_asgi_application if get_response_async exists. Otherwise we will effectively double-patch
# because get_response and get_asgi_application will be used. We must rely on the version instead of coalescing
# with the previous patching hook because of circular imports within `django.core.asgi`.
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/contrib/internal/trace_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ def set_user(
session_id=None, # type: Optional[str]
propagate=False, # type bool
span=None, # type: Optional[Span]
may_block=True, # type: bool
):
# type: (...) -> None
"""Set user tags.
Expand Down Expand Up @@ -666,7 +667,7 @@ def set_user(
if session_id:
span.set_tag_str(user.SESSION_ID, session_id)

if asm_config._asm_enabled:
if may_block and asm_config._asm_enabled:
exc = core.dispatch_with_results("set_user_for_asm", [tracer, user_id]).block_user.exception
if exc:
raise exc
Expand Down
Loading