Skip to content

Commit 889915f

Browse files
authored
[ASM][ATO] Collect session id at all times (#6623)
## Summary of changes Collects session id in any case: - .net core: on endpoint matched - net framework: try access it through httpcontext User doesn't need to be authenticated anymore for it to collect the session id. ## Reason for change Session id should always be collected, not only when user is authenticated. ## Implementation details there are guards for netcore as accessing Session when it's not been setup just throws. So this way we make sure it's setup for the webapp. ## Test coverage Change all snapshots. Scrubbing pitfalls: - scrub cookies and session id for aspnet mvc because if there's a session id it will always change the cookies - scrub cookies and session id for aspnet core when user is authenticated / logged in (as it will keep changing) - other cases in aspnet core: just scrub session fingerprint ## Other details <!-- Fixes #{issue} --> <!-- ⚠️ Note: where possible, please obtain 2 approvals prior to merging. Unless CODEOWNERS specifies otherwise, for external teams it is typically best to have one review from a team member, and one review from apm-dotnet. Trivial changes do not require 2 reviews. -->
1 parent 20f2bf1 commit 889915f

File tree

103 files changed

+371
-165
lines changed

Some content is hidden

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

103 files changed

+371
-165
lines changed

tracer/src/Datadog.Trace/AppSec/Coordinator/SecurityCoordinator.Framework.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,8 @@ private SecurityCoordinator(Security security, Span span, HttpTransport transpor
278278
/// </summary>
279279
internal void BlockAndReport(Dictionary<string, object> args, bool lastWafCall = false, bool isInHttpTracingModule = false)
280280
{
281-
var result = RunWaf(args, lastWafCall);
281+
var sessionId = _httpTransport.Context.Session?.SessionID;
282+
var result = RunWaf(args, lastWafCall, sessionId: sessionId);
282283
if (result is not null)
283284
{
284285
var reporting = Reporter.MakeReportingFunction(result);

tracer/src/Datadog.Trace/AppSec/Coordinator/SecurityCoordinator.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ internal readonly partial struct SecurityCoordinator
4949

5050
public bool IsAdditiveContextDisposed() => _httpTransport.IsAdditiveContextDisposed();
5151

52-
public IResult? RunWaf(Dictionary<string, object> args, bool lastWafCall = false, bool runWithEphemeral = false, bool isRasp = false)
52+
public IResult? RunWaf(Dictionary<string, object> args, bool lastWafCall = false, bool runWithEphemeral = false, bool isRasp = false, string? sessionId = null)
5353
{
5454
SecurityReporter.LogAddressIfDebugEnabled(args);
5555
IResult? result = null;
@@ -63,6 +63,12 @@ internal readonly partial struct SecurityCoordinator
6363
return null;
6464
}
6565

66+
var shouldAddSessionId = additiveContext.ShouldRunWithSession(_security, sessionId);
67+
if (shouldAddSessionId)
68+
{
69+
args[AddressesConstants.UserSessionId] = sessionId!;
70+
}
71+
6672
_security.ApiSecurity.ShouldAnalyzeSchema(lastWafCall, _localRootSpan, args, _httpTransport.StatusCode?.ToString(), _httpTransport.RouteData);
6773

6874
// run the WAF and execute the results
@@ -105,7 +111,7 @@ internal readonly partial struct SecurityCoordinator
105111
try
106112
{
107113
var additiveContext = GetOrCreateAdditiveContext();
108-
if (additiveContext?.ShouldRunWith(_security, userId, userLogin, userSessionId, fromSdk) is { Count: > 0 } userAddresses)
114+
if (additiveContext?.FilterAddresses(_security, userId, userLogin, userSessionId, fromSdk) is { Count: > 0 } userAddresses)
109115
{
110116
if (otherTags is not null)
111117
{

tracer/src/Datadog.Trace/AppSec/Coordinator/SecurityCoordinatorHelpers.Core.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
#if !NETFRAMEWORK
88
using System;
99
using System.Collections.Generic;
10+
using Datadog.Trace.AppSec.Waf;
1011
using Datadog.Trace.Logging;
1112
using Microsoft.AspNetCore.Http;
13+
using Microsoft.AspNetCore.Http.Features;
1214
using Microsoft.AspNetCore.Mvc.Abstractions;
1315
using Microsoft.AspNetCore.Routing;
1416

@@ -69,7 +71,7 @@ internal static void CheckReturnedHeaders(this Security security, Span span, IHe
6971
}
7072
}
7173

72-
internal static void CheckPathParams(this Security security, HttpContext context, Span span, IDictionary<string, object> pathParams)
74+
internal static void CheckPathParamsAndSessionId(this Security security, HttpContext context, Span span, IDictionary<string, object> pathParams)
7375
{
7476
if (security.AppsecEnabled)
7577
{
@@ -78,7 +80,17 @@ internal static void CheckPathParams(this Security security, HttpContext context
7880
{
7981
var securityCoordinator = SecurityCoordinator.Get(security, span, transport);
8082
var args = new Dictionary<string, object> { { AddressesConstants.RequestPathParams, pathParams } };
81-
var result = securityCoordinator.RunWaf(args);
83+
IResult? result;
84+
// we need to check context.Features.Get<ISessionFeature> as accessing the Session item if session has not been configured for the application is throwing InvalidOperationException
85+
if (context.Features.Get<ISessionFeature>() is { Session.IsAvailable: true } feature)
86+
{
87+
result = securityCoordinator.RunWaf(args, sessionId: feature.Session.Id);
88+
}
89+
else
90+
{
91+
result = securityCoordinator.RunWaf(args);
92+
}
93+
8294
securityCoordinator.BlockAndReport(result);
8395
}
8496
}

tracer/src/Datadog.Trace/AppSec/Waf/Context.cs

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -63,35 +63,29 @@ private Context(IntPtr contextHandle, IWaf waf, IWafLibraryInvoker wafLibraryInv
6363
public IResult? RunWithEphemeral(IDictionary<string, object> ephemeralAddressData, ulong timeoutMicroSeconds, bool isRasp)
6464
=> RunInternal(null, ephemeralAddressData, timeoutMicroSeconds, isRasp);
6565

66-
public Dictionary<string, object> ShouldRunWith(IDatadogSecurity security, string? userId = null, string? userLogin = null, string? userSessionId = null, bool fromSdk = false)
66+
public Dictionary<string, object> FilterAddresses(IDatadogSecurity security, string? userId = null, string? userLogin = null, string? userSessionId = null, bool fromSdk = false)
6767
{
6868
var addresses = new Dictionary<string, object>();
69-
ShouldRun(_userEventsState.Id, userId, AddressesConstants.UserId);
70-
ShouldRun(_userEventsState.Login, userLogin, AddressesConstants.UserLogin);
71-
ShouldRun(_userEventsState.SessionId, userSessionId, AddressesConstants.UserSessionId);
72-
return addresses;
69+
if (ShouldRunWith(security, _userEventsState.Id, userId, AddressesConstants.UserId, fromSdk))
70+
{
71+
addresses[AddressesConstants.UserId] = userId!;
72+
}
7373

74-
void ShouldRun(UserEventsState.UserRecord? userRecord, string? value, string address)
74+
if (ShouldRunWith(security, _userEventsState.Login, userLogin, AddressesConstants.UserLogin, fromSdk))
7575
{
76-
string? previousValue = null;
77-
var cameFromSdk = false;
78-
if (userRecord.HasValue)
79-
{
80-
previousValue = userRecord.Value.Value;
81-
cameFromSdk = userRecord.Value.FromSdk;
82-
}
76+
addresses[AddressesConstants.UserLogin] = userLogin!;
77+
}
8378

84-
if (value is not null && security.AddressEnabled(address))
85-
{
86-
var differentValue = string.Compare(previousValue, value, StringComparison.OrdinalIgnoreCase) is not 0;
87-
if (differentValue && (fromSdk || !cameFromSdk))
88-
{
89-
addresses[address] = value;
90-
}
91-
}
79+
if (ShouldRunWith(security, _userEventsState.SessionId, userSessionId, AddressesConstants.UserSessionId, fromSdk))
80+
{
81+
addresses[AddressesConstants.UserSessionId] = userSessionId!;
9282
}
83+
84+
return addresses;
9385
}
9486

87+
public bool ShouldRunWithSession(IDatadogSecurity security, string? userSessionId = null, bool fromSdk = false) => ShouldRunWith(security, _userEventsState.SessionId, userSessionId, AddressesConstants.UserSessionId, fromSdk);
88+
9589
public void CommitUserRuns(Dictionary<string, object> addresses, bool fromSdk)
9690
{
9791
if (addresses.TryGetValue(AddressesConstants.UserId, out var address))
@@ -110,6 +104,24 @@ public void CommitUserRuns(Dictionary<string, object> addresses, bool fromSdk)
110104
}
111105
}
112106

107+
private bool ShouldRunWith(IDatadogSecurity security, UserEventsState.UserRecord? previousUserRecord, string? value, string address, bool fromSdk)
108+
{
109+
if (value is null || !security.AddressEnabled(address))
110+
{
111+
return false;
112+
}
113+
114+
if (!previousUserRecord.HasValue)
115+
{
116+
return true;
117+
}
118+
119+
var previousValue = previousUserRecord.Value.Value;
120+
var previousValueFromSdk = previousUserRecord.Value.FromSdk;
121+
var differentValue = string.Compare(previousValue, value, StringComparison.OrdinalIgnoreCase) is not 0;
122+
return differentValue && (fromSdk || !previousValueFromSdk);
123+
}
124+
113125
private unsafe IResult? RunInternal(IDictionary<string, object>? persistentAddressData, IDictionary<string, object>? ephemeralAddressData, ulong timeoutMicroSeconds, bool isRasp = false)
114126
{
115127
DdwafResultStruct retNative = default;

tracer/src/Datadog.Trace/AppSec/Waf/IContext.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,26 @@ internal interface IContext : IDisposable
1616

1717
IResult? RunWithEphemeral(IDictionary<string, object> ephemeralAddressData, ulong timeoutMicroSeconds, bool isRasp);
1818

19-
Dictionary<string, object> ShouldRunWith(IDatadogSecurity security, string? userId = null, string? userLogin = null, string? userSessionId = null, bool fromSdk = false);
19+
/// <summary>
20+
/// Here we compare with potential previous runs, and make sure we don't override previous values provided by the SDK
21+
/// </summary>
22+
/// <param name="security">security main instance</param>
23+
/// <param name="userId">user id</param>
24+
/// <param name="userLogin">user login</param>
25+
/// <param name="userSessionId">user session id</param>
26+
/// <param name="fromSdk">is this coming from sdk</param>
27+
/// <returns>filtered addresses, potentially empty if nothing should precede over sdk or same values than before</returns>
28+
Dictionary<string, object> FilterAddresses(IDatadogSecurity security, string? userId = null, string? userLogin = null, string? userSessionId = null, bool fromSdk = false);
29+
30+
/// <summary>
31+
/// Special method called by RunWaf to make sure we don't override a session id provided by the sdk or run with the same value.
32+
/// It's similar as FilterAddresses but only for session id, behind the scenes they both call ShouldRunWith()
33+
/// </summary>
34+
/// <param name="security">security main instance</param>
35+
/// <param name="userSessionId">user session id</param>
36+
/// <param name="fromSdk">is this coming from sdk</param>
37+
/// <returns>whether we should add this address to the waf run</returns>
38+
bool ShouldRunWithSession(IDatadogSecurity security, string? userSessionId = null, bool fromSdk = false);
2039

2140
void CommitUserRuns(Dictionary<string, object> addresses, bool fromSdk);
2241
}

tracer/src/Datadog.Trace/DiagnosticListeners/AspNetCoreDiagnosticObserver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,7 @@ private void OnRoutingEndpointMatched(object arg)
564564
tags.HttpRoute = normalizedRoute;
565565
}
566566

567-
CurrentSecurity.CheckPathParams(httpContext, rootSpan, routeValues);
567+
CurrentSecurity.CheckPathParamsAndSessionId(httpContext, rootSpan, routeValues);
568568

569569
if (CurrentIast.Settings.Enabled)
570570
{

tracer/src/Datadog.Trace/SpanExtensions.Core.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,23 @@ namespace Datadog.Trace
1717
/// </summary>
1818
public static partial class SpanExtensions
1919
{
20-
private static void RunBlockingCheck(Span span, string userId)
20+
private static void RunBlockingCheck(Span span, string userId, string userSession)
2121
{
2222
var security = Security.Instance;
2323

2424
if (security.AppsecEnabled && AspNetCoreAvailabilityChecker.IsAspNetCoreAvailable())
2525
{
26-
RunBlockingCheckUnsafe(security, span, userId);
26+
RunBlockingCheckUnsafe(security, span, userId, userSession);
2727
}
2828

2929
// Don't inline this, so we don't load the aspnetcore types if they're not available
3030
[MethodImpl(MethodImplOptions.NoInlining)]
31-
static void RunBlockingCheckUnsafe(Security security, Span span, string userId)
31+
static void RunBlockingCheckUnsafe(Security security, Span span, string userId, string userSession)
3232
{
3333
if (CoreHttpContextStore.Instance.Get() is { } httpContext)
3434
{
3535
var securityCoordinator = SecurityCoordinator.Get(security, span, httpContext);
36-
var result = securityCoordinator.RunWafForUser(userId: userId, fromSdk: true);
36+
var result = securityCoordinator.RunWafForUser(userId: userId, userSessionId: userSession, fromSdk: true);
3737
securityCoordinator.BlockAndReport(result);
3838
}
3939
}

tracer/src/Datadog.Trace/SpanExtensions.Framework.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ namespace Datadog.Trace
2020
/// </summary>
2121
public static partial class SpanExtensions
2222
{
23-
private static void RunBlockingCheck(Span span, string userId)
23+
private static void RunBlockingCheck(Span span, string userId, string userSessionId)
2424
{
2525
var security = Security.Instance;
2626

@@ -32,7 +32,7 @@ private static void RunBlockingCheck(Span span, string userId)
3232
return;
3333
}
3434

35-
var result = securityCoordinator.Value.RunWafForUser(userId, fromSdk: true);
35+
var result = securityCoordinator.Value.RunWafForUser(userId, userSessionId, fromSdk: true);
3636
securityCoordinator.Value.BlockAndReport(result);
3737
}
3838
}

tracer/src/Datadog.Trace/SpanExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ internal static void SetUserInternal(this ISpan span, UserDetails userDetails)
8686

8787
if (spanClass != null)
8888
{
89-
RunBlockingCheck(spanClass, userDetails.Id);
89+
RunBlockingCheck(spanClass, userDetails.Id, userDetails.SessionId);
9090
}
9191
}
9292

tracer/test/Datadog.Trace.Security.IntegrationTests/ApiSecurity/AspNetCoreApiSecurity.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ protected AspNetCoreApiSecurity(AspNetCoreTestFixture fixture, ITestOutputHelper
3232
_fixture.SetOutput(outputHelper);
3333
Directory.CreateDirectory(LogDirectory);
3434
EnvironmentHelper.CustomEnvironmentVariables.Add(ConfigurationKeys.AppSec.Rules, Path.Combine("ApiSecurity", "ruleset-with-block.json"));
35-
// necessary as the developer middleware prevents the right blocking response
36-
EnvironmentHelper.CustomEnvironmentVariables.Add("ASPNETCORE_ENVIRONMENT", "Production");
3735
SetEnvironmentVariable(ConfigurationKeys.LogDirectory, LogDirectory);
3836
SetEnvironmentVariable(ConfigurationKeys.AppSec.ApiSecurityEnabled, enableApiSecurity.ToString());
3937
}

0 commit comments

Comments
 (0)