Skip to content

Commit

Permalink
[Instrumentation.AspNet][Instrumentation.Owin] Redact query parameters (
Browse files Browse the repository at this point in the history
  • Loading branch information
vishweshbankwar authored Apr 17, 2024
1 parent 3085772 commit 113e5b8
Show file tree
Hide file tree
Showing 14 changed files with 356 additions and 111 deletions.
3 changes: 2 additions & 1 deletion opentelemetry-dotnet-contrib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E
src\Shared\ListenerHandler.cs = src\Shared\ListenerHandler.cs
src\Shared\MultiTypePropertyFetcher.cs = src\Shared\MultiTypePropertyFetcher.cs
src\Shared\NullableAttributes.cs = src\Shared\NullableAttributes.cs
src\Shared\PropertyFetcher.cs = src\Shared\PropertyFetcher.cs
src\Shared\PropertyFetcher.AOT.cs = src\Shared\PropertyFetcher.AOT.cs
src\Shared\PropertyFetcher.cs = src\Shared\PropertyFetcher.cs
src\Shared\RedactionHelper.cs = src\Shared\RedactionHelper.cs
src\Shared\ResourceSemanticConventions.cs = src\Shared\ResourceSemanticConventions.cs
src\Shared\SemanticConventions.cs = src\Shared\SemanticConventions.cs
src\Shared\SpanAttributeConstants.cs = src\Shared\SpanAttributeConstants.cs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Diagnostics;
using System.Web;
using OpenTelemetry.Instrumentation.AspNet.Implementation;

namespace OpenTelemetry.Instrumentation.AspNet;

Expand All @@ -12,6 +13,27 @@ namespace OpenTelemetry.Instrumentation.AspNet;
/// </summary>
public class AspNetTraceInstrumentationOptions
{
private const string DisableQueryRedactionEnvVar = "OTEL_DOTNET_EXPERIMENTAL_ASPNET_DISABLE_URL_QUERY_REDACTION";

/// <summary>
/// Initializes a new instance of the <see cref="AspNetTraceInstrumentationOptions"/> class.
/// </summary>
public AspNetTraceInstrumentationOptions()
{
try
{
var disableQueryRedaction = Environment.GetEnvironmentVariable(DisableQueryRedactionEnvVar);
if (disableQueryRedaction != null && disableQueryRedaction.Equals("true", StringComparison.OrdinalIgnoreCase))
{
this.DisableUrlQueryRedaction = true;
}
}
catch (Exception ex)
{
AspNetInstrumentationEventSource.Log.FailedToReadEnvironmentVariable(DisableQueryRedactionEnvVar, ex);
}
}

/// <summary>
/// Gets or sets a filter callback function that determines on a per
/// request basis whether or not to collect telemetry.
Expand Down Expand Up @@ -46,4 +68,14 @@ public class AspNetTraceInstrumentationOptions
/// See: <see href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md"/>.
/// </remarks>
public bool RecordException { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the url query value should be redacted or not.
/// </summary>
/// <remarks>
/// The query parameter values are redacted with value set as Redacted.
/// e.g. `?key1=value1` is set as `?key1=Redacted`.
/// The redaction can be disabled by setting this property to <see langword="true" />.
/// </remarks>
internal bool DisableUrlQueryRedaction { get; set; }
}
12 changes: 11 additions & 1 deletion src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Changelog

## Unreleased
## 1.8.0-beta.2

Released 2024-Apr-17

* **Breaking Change**: Fixed tracing instrumentation so that by default any
values detected in the query string component of requests are replaced with
the text `Redacted` when building the `url.query` attribute. For example,
`?key1=value1&key2=value2` becomes `?key1=Redacted&key2=Redacted`. You can
disable this redaction by setting the environment variable
`OTEL_DOTNET_EXPERIMENTAL_ASPNET_DISABLE_URL_QUERY_REDACTION` to `true`.
([#1656](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1656))

## 1.8.0-beta.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ public void EnrichmentException(string eventName, Exception ex)
}
}

[NonEvent]
public void FailedToReadEnvironmentVariable(string envVarName, Exception ex)
{
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
{
this.EnrichmentException(envVarName, ex.ToInvariantString());
}
}

[Event(1, Message = "Request is filtered out and will not be collected. Operation='{0}'", Level = EventLevel.Verbose)]
public void RequestIsFilteredOut(string operationName)
{
Expand All @@ -50,4 +59,10 @@ public void EnrichmentException(string eventName, string exception)
{
this.WriteEvent(3, eventName, exception);
}

[Event(4, Message = "Failed to read environment variable='{0}': {1}", Level = EventLevel.Error)]
public void FailedToReadEnvironmentVariable(string envVarName, string exception)
{
this.WriteEvent(4, envVarName, exception);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,8 @@ private void OnStartActivity(Activity activity, HttpContext context)
var query = url.Query;
if (!string.IsNullOrEmpty(query))
{
if (query.StartsWith("?", StringComparison.InvariantCulture))
{
activity.SetTag(SemanticConventions.AttributeUrlQuery, query.Substring(1));
}
else
{
activity.SetTag(SemanticConventions.AttributeUrlQuery, query);
}
var queryString = query.StartsWith("?", StringComparison.InvariantCulture) ? query.Substring(1) : query;
activity.SetTag(SemanticConventions.AttributeUrlQuery, this.options.DisableUrlQueryRedaction ? queryString : RedactionHelper.GetRedactedQueryString(queryString));
}

var userAgent = request.UserAgent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="$(RepoRoot)\src\Shared\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
<Compile Include="$(RepoRoot)\src\Shared\PropertyFetcher.AOT.cs" Link="Includes\PropertyFetcher.AOT.cs" />
<Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\SemanticConventions.cs" Link="Includes\SemanticConventions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\SpanHelper.cs" Link="Includes\SpanHelper.cs" />
</ItemGroup>
Expand Down
12 changes: 11 additions & 1 deletion src/OpenTelemetry.Instrumentation.Owin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Changelog

## Unreleased
## 1.0.0-rc.5

Released 2024-Apr-17

* **Breaking Change**: Fixed tracing instrumentation so that by default any
values detected in the query string component of requests are replaced with
the text `Redacted` when building the `http.url` tag. For example,
`?key1=value1&key2=value2` becomes `?key1=Redacted&key2=Redacted`. You can
disable this redaction by setting the environment variable
`OTEL_DOTNET_EXPERIMENTAL_OWIN_DISABLE_URL_QUERY_REDACTION` to `true`.
([#1656](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1656))

* `ActivitySource.Version` and `Meter.Version` are set to NuGet package version.
([#1624](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1624))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Owin;
using OpenTelemetry.Context.Propagation;
using OpenTelemetry.Instrumentation.Owin.Implementation;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;

namespace OpenTelemetry.Instrumentation.Owin;
Expand Down Expand Up @@ -114,7 +115,7 @@ private static void BeginRequest(IOwinContext owinContext)

activity.SetTag(SemanticConventions.AttributeHttpMethod, request.Method);
activity.SetTag(SemanticConventions.AttributeHttpTarget, request.Uri.AbsolutePath);
activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUriTagValueFromRequestUri(request.Uri));
activity.SetTag(SemanticConventions.AttributeHttpUrl, GetUriTagValueFromRequestUri(request.Uri, OwinInstrumentationActivitySource.Options.DisableUrlQueryRedaction));

if (request.Headers.TryGetValue("User-Agent", out string[] userAgent) && userAgent.Length > 0)
{
Expand Down Expand Up @@ -228,13 +229,15 @@ private static void RequestEnd(IOwinContext owinContext, Exception? exception, l
/// </summary>
/// <param name="uri"><see cref="Uri"/>.</param>
/// <returns>Span uri value.</returns>
private static string GetUriTagValueFromRequestUri(Uri uri)
private static string GetUriTagValueFromRequestUri(Uri uri, bool disableQueryRedaction)
{
if (string.IsNullOrEmpty(uri.UserInfo))
if (string.IsNullOrEmpty(uri.UserInfo) && disableQueryRedaction)
{
return uri.ToString();
return uri.OriginalString;
}

return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.PathAndQuery, uri.Fragment);
var query = disableQueryRedaction ? uri.Query : RedactionHelper.GetRedactedQueryString(uri.Query);

return string.Concat(uri.Scheme, Uri.SchemeDelimiter, uri.Authority, uri.AbsolutePath, query, uri.Fragment);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,32 @@ public void EnrichmentException(Exception exception)
}
}

[NonEvent]
public void FailedToReadEnvironmentVariable(string envVarName, Exception ex)
{
if (this.IsEnabled(EventLevel.Error, EventKeywords.All))
{
this.FailedToReadEnvironmentVariable(envVarName, ex.ToInvariantString());
}
}

[Event(EventIds.EnrichmentException, Message = "Enrichment threw exception. Exception {0}.", Level = EventLevel.Error)]
public void EnrichmentException(string exception)
{
this.WriteEvent(EventIds.EnrichmentException, exception);
}

[Event(EventIds.FailedToReadEnvironmentVariable, Message = "Failed to read environment variable='{0}': {1}", Level = EventLevel.Error)]
public void FailedToReadEnvironmentVariable(string envVarName, string exception)
{
this.WriteEvent(4, envVarName, exception);
}

private class EventIds
{
public const int RequestIsFilteredOut = 1;
public const int RequestFilterException = 2;
public const int EnrichmentException = 3;
public const int FailedToReadEnvironmentVariable = 4;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<Compile Include="$(RepoRoot)\src\Shared\AssemblyVersionExtensions.cs" Link="Includes\AssemblyVersionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
<Compile Include="$(RepoRoot)\src\Shared\RedactionHelper.cs" Link="Includes\RedactionHelper.cs" />
<Compile Include="$(RepoRoot)\src\Shared\SemanticConventions.cs" Link="Includes\SemanticConventions.cs"/>
<Compile Include="$(RepoRoot)\src\Shared\SpanHelper.cs" Link="Includes\SpanHelper.cs" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ namespace OpenTelemetry.Instrumentation.Owin;
/// </summary>
public class OwinInstrumentationOptions
{
private const string DisableQueryRedactionEnvVar = "OTEL_DOTNET_EXPERIMENTAL_OWIN_DISABLE_URL_QUERY_REDACTION";

/// <summary>
/// Initializes a new instance of the <see cref="OwinInstrumentationOptions"/> class.
/// </summary>
public OwinInstrumentationOptions()
{
try
{
var disableQueryRedaction = Environment.GetEnvironmentVariable(DisableQueryRedactionEnvVar);
if (disableQueryRedaction != null && disableQueryRedaction.Equals("true", StringComparison.OrdinalIgnoreCase))
{
this.DisableUrlQueryRedaction = true;
}
}
catch (Exception ex)
{
OwinInstrumentationEventSource.Log.FailedToReadEnvironmentVariable(DisableQueryRedactionEnvVar, ex);
}
}

/// <summary>
/// Gets or sets a Filter function that determines whether or not to collect telemetry about requests on a per request basis.
/// The Filter gets the <see cref="IOwinContext"/>, and should return a boolean.
Expand All @@ -32,4 +53,14 @@ public class OwinInstrumentationOptions
/// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/exceptions.md.
/// </remarks>
public bool RecordException { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the url query value should be redacted or not.
/// </summary>
/// <remarks>
/// The query parameter values are redacted with value set as Redacted.
/// e.g. `?key1=value1` is set as `?key1=Redacted`.
/// The redaction can be disabled by setting this property to <see langword="true" />.
/// </remarks>
internal bool DisableUrlQueryRedaction { get; set; }
}
59 changes: 59 additions & 0 deletions src/Shared/RedactionHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#nullable enable

using System.Text;

namespace OpenTelemetry.Internal;

internal sealed class RedactionHelper
{
private const string RedactedText = "Redacted";

public static string? GetRedactedQueryString(string query)
{
int length = query.Length;
int index = 0;

// Preallocate some size to avoid re-sizing multiple times.
// Since the size will increase, allocating twice as much.
// TODO: Check to see if we can borrow from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/ValueStringBuilder.cs
// to improve perf.
StringBuilder queryBuilder = new(2 * length);
while (index < query.Length)
{
// Check if the character is = for redacting value.
if (query[index] == '=')
{
// Append =
queryBuilder.Append('=');
index++;

// Append redactedText in place of original value.
queryBuilder.Append(RedactedText);

// Move until end of this key/value pair.
while (index < length && query[index] != '&')
{
index++;
}

// End of key/value.
if (index < length && query[index] == '&')
{
queryBuilder.Append(query[index]);
}
}
else
{
// Keep adding to the result
queryBuilder.Append(query[index]);
}

index++;
}

return queryBuilder.ToString();
}
}
Loading

0 comments on commit 113e5b8

Please sign in to comment.