From 935deec6e659e1a81d68be1043ca009d8b192895 Mon Sep 17 00:00:00 2001 From: Flavien Darche <11708575+e-n-0@users.noreply.github.com> Date: Wed, 17 Jul 2024 19:08:08 +0200 Subject: [PATCH] [ASM] Standalone Billing (part 2: Propagation) (#5743) ## Summary of changes Part 2 of the implementation of [Standalone ASM billing](https://docs.google.com/document/d/12NBx-nD-IoQEMiCRnJXneq4Be7cbtSc6pJLOFUWTpNE/edit?pli=1#heading=h.hs091bhdmugz). Part 1 was #5565 This PR adds the propagated span tag `_dd.p.appsec: 1` allowing to propagate to downstream services the information that the current distributed trace is containing at least one ASM security event. ## Test coverage This fully pass the system tests for ASM Standalone. --- tracer/src/Datadog.Trace/AppSec/Security.cs | 1 + tracer/src/Datadog.Trace/Iast/IastModule.cs | 4 ++++ .../Propagators/DatadogContextPropagator.cs | 1 - .../Propagators/SpanContextPropagator.cs | 6 ++++++ tracer/src/Datadog.Trace/Tags.cs | 6 ++++++ tracer/src/Datadog.Trace/Tracer.cs | 20 +++++++++++++++---- 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/tracer/src/Datadog.Trace/AppSec/Security.cs b/tracer/src/Datadog.Trace/AppSec/Security.cs index 2f2e6578b113..3e2ca10fa6ad 100644 --- a/tracer/src/Datadog.Trace/AppSec/Security.cs +++ b/tracer/src/Datadog.Trace/AppSec/Security.cs @@ -482,6 +482,7 @@ internal void SetTraceSamplingPriority(Span span) else if (_rateLimiter?.Allowed(span) ?? false) { span.Context.TraceContext?.SetSamplingPriority(SamplingPriorityValues.UserKeep, SamplingMechanism.Asm); + span.Context.TraceContext?.Tags.SetTag(Tags.Propagated.AppSec, "1"); } } diff --git a/tracer/src/Datadog.Trace/Iast/IastModule.cs b/tracer/src/Datadog.Trace/Iast/IastModule.cs index 37ad24dce841..4a1405c80f37 100644 --- a/tracer/src/Datadog.Trace/Iast/IastModule.cs +++ b/tracer/src/Datadog.Trace/Iast/IastModule.cs @@ -562,6 +562,7 @@ private static IastModuleResponse AddWebVulnerability(string? evidenceValue, Int { traceContext.IastRequestContext?.AddVulnerability(vulnerability); traceContext.SetSamplingPriority(SamplingPriorityValues.UserKeep, SamplingMechanism.Asm); + traceContext.Tags.SetTag(Tags.Propagated.AppSec, "1"); return IastModuleResponse.Vulnerable; } @@ -636,6 +637,8 @@ private static IastModuleResponse GetScope(string evidenceValue, IntegrationId i if (isRequest) { traceContext?.SetSamplingPriority(SamplingPriorityValues.UserKeep, SamplingMechanism.Asm); + traceContext?.Tags.SetTag(Tags.Propagated.AppSec, "1"); + traceContext?.IastRequestContext?.AddVulnerability(vulnerability); return IastModuleResponse.Vulnerable; } @@ -691,6 +694,7 @@ private static IastModuleResponse AddVulnerabilityAsSingleSpan(Tracer tracer, In scope.Span.Type = SpanTypes.IastVulnerability; tracer.TracerManager.Telemetry.IntegrationGeneratedSpan(integrationId); scope.Span.Context.TraceContext?.SetSamplingPriority(SamplingPriorityValues.UserKeep, SamplingMechanism.Asm); + scope.Span.Context.TraceContext?.Tags.SetTag(Tags.Propagated.AppSec, "1"); return new IastModuleResponse(scope); } diff --git a/tracer/src/Datadog.Trace/Propagators/DatadogContextPropagator.cs b/tracer/src/Datadog.Trace/Propagators/DatadogContextPropagator.cs index 5a88cd32a803..97fb7db9fb3e 100644 --- a/tracer/src/Datadog.Trace/Propagators/DatadogContextPropagator.cs +++ b/tracer/src/Datadog.Trace/Propagators/DatadogContextPropagator.cs @@ -43,7 +43,6 @@ public void Inject(SpanContext context, TCarrier carri } var propagatedTagsHeader = context.PrepareTagsHeaderForPropagation(); - if (!string.IsNullOrEmpty(propagatedTagsHeader)) { carrierSetter.Set(carrier, HttpHeaderNames.PropagatedTags, propagatedTagsHeader!); diff --git a/tracer/src/Datadog.Trace/Propagators/SpanContextPropagator.cs b/tracer/src/Datadog.Trace/Propagators/SpanContextPropagator.cs index 34e85d9542f5..9b27ed793385 100644 --- a/tracer/src/Datadog.Trace/Propagators/SpanContextPropagator.cs +++ b/tracer/src/Datadog.Trace/Propagators/SpanContextPropagator.cs @@ -115,6 +115,12 @@ internal void Inject(SpanContext context, TCarrier car if (context == null!) { ThrowHelper.ThrowArgumentNullException(nameof(context)); } if (carrier == null) { ThrowHelper.ThrowArgumentNullException(nameof(carrier)); } + // If appsec standalone is enabled and appsec propagation is disabled (no ASM events) -> stop propagation + if (context.TraceContext?.Tracer.Settings?.AppsecStandaloneEnabledInternal == true && context.TraceContext.Tags.GetTag(Tags.Propagated.AppSec) != "1") + { + return; + } + // trigger a sampling decision if it hasn't happened yet _ = context.GetOrMakeSamplingDecision(); diff --git a/tracer/src/Datadog.Trace/Tags.cs b/tracer/src/Datadog.Trace/Tags.cs index 5923a725eba6..672c34f6b215 100644 --- a/tracer/src/Datadog.Trace/Tags.cs +++ b/tracer/src/Datadog.Trace/Tags.cs @@ -725,6 +725,12 @@ internal static class Propagated /// lower-case hexadecimal string with no zero-padding or `0x` prefix. /// internal const string TraceIdUpper = "_dd.p.tid"; + + /// + /// A boolean allowing the propagation to downstream services the information that the current distributed trace + /// is containing at least one ASM security event, no matter its type (threats, business logic events, IAST, etc.). + /// + internal const string AppSec = "_dd.p.appsec"; } } } diff --git a/tracer/src/Datadog.Trace/Tracer.cs b/tracer/src/Datadog.Trace/Tracer.cs index a4c87591a071..4b3fd18d304f 100644 --- a/tracer/src/Datadog.Trace/Tracer.cs +++ b/tracer/src/Datadog.Trace/Tracer.cs @@ -419,13 +419,25 @@ internal SpanContext CreateSpanContext(ISpanContext parent = null, string servic if (traceContext == null) { - // If parent is SpanContext but its TraceContext is null, then it was extracted from - // propagation headers. Create a new TraceContext (this will start a new trace) and initialize + var propagatedTags = parentSpanContext.PropagatedTags; + var samplingPriority = parentSpanContext.SamplingPriority; + + // When in appsec standalone mode, only distributed traces with the `_dd.p.appsec` tag + // are propagated downstream, however we need 1 trace per minute sent to the backend, so + // we unset sampling priority so the rate limiter decides. + if (Settings?.AppsecStandaloneEnabledInternal == true) + { + // If the trace has appsec propagation tag, the default priority is user keep + samplingPriority = propagatedTags?.GetTag(Tags.Propagated.AppSec) == "1" ? SamplingPriorityValues.UserKeep : null; + } + + // If parent is SpanContext but its TraceContext is null, then it was extracted from propagation headers. + // Create a new TraceContext (this will start a new trace) and initialize // it with the propagated values (sampling priority, origin, tags, W3C trace state, etc). - traceContext = new TraceContext(this, parentSpanContext.PropagatedTags); + traceContext = new TraceContext(this, propagatedTags); TelemetryFactory.Metrics.RecordCountTraceSegmentCreated(MetricTags.TraceContinuation.Continued); - var samplingPriority = parentSpanContext.SamplingPriority ?? DistributedTracer.Instance.GetSamplingPriority(); + samplingPriority ??= DistributedTracer.Instance.GetSamplingPriority(); traceContext.SetSamplingPriority(samplingPriority); traceContext.Origin = parentSpanContext.Origin; traceContext.AdditionalW3CTraceState = parentSpanContext.AdditionalW3CTraceState;