Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,7 @@
<type fullname="System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs" />
<type fullname="System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptionsAttribute" />
<type fullname="System.Runtime.InteropServices.ComVisibleAttribute" />
<type fullname="System.Runtime.InteropServices.ExternalException" />
<type fullname="System.Runtime.InteropServices.GCHandle" />
<type fullname="System.Runtime.InteropServices.GCHandleType" />
<type fullname="System.Runtime.InteropServices.InAttribute" />
Expand Down
13 changes: 9 additions & 4 deletions tracer/src/Datadog.Trace/Agent/AgentTransportStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ internal static class AgentTransportStrategy
/// <param name="getBaseEndpoint">A func that returns the endpoint to send requests to for a given "base" endpoint.
/// The base endpoint will be <see cref="ExporterSettings.AgentUri" /> for TCP requests and
/// http://localhost/ for named pipes/UDS if null, the default base endpoint is used</param>
/// <param name="allowAutoRedirect">Whether HTTP handlers may follow redirects. Stream transports never follow them.</param>
public static IApiRequestFactory Get(
ExporterSettings settings,
string productName,
TimeSpan? tcpTimeout,
HttpHeaderHelperBase httpHeaderHelper,
Func<Uri, Uri>? getBaseEndpoint = null)
Func<Uri, Uri>? getBaseEndpoint = null,
bool allowAutoRedirect = true)
{
var strategy = settings.TracesTransport;

Expand All @@ -56,7 +58,8 @@ public static IApiRequestFactory Get(
return new SocketHandlerRequestFactory(
new UnixDomainSocketStreamFactory(settings.TracesUnixDomainSocketPath),
httpHeaderHelper.DefaultHeaders,
getBaseEndpoint?.Invoke(Localhost) ?? Localhost);
getBaseEndpoint?.Invoke(Localhost) ?? Localhost,
allowAutoRedirect: allowAutoRedirect);
#elif NETCOREAPP3_1_OR_GREATER
Log.Information<string, string?, int>("Using " + nameof(UnixDomainSocketStreamFactory) + " for {ProductName} transport, with Unix Domain Sockets path {TracesUnixDomainSocketPath} and timeout {TracesPipeTimeoutMs}ms.", productName, settings.TracesUnixDomainSocketPath, settings.TracesPipeTimeoutMs);
return new HttpStreamRequestFactory(
Expand All @@ -74,13 +77,15 @@ public static IApiRequestFactory Get(
return new HttpClientRequestFactory(
getBaseEndpoint?.Invoke(settings.AgentUri) ?? settings.AgentUri,
httpHeaderHelper.DefaultHeaders,
timeout: tcpTimeout);
timeout: tcpTimeout,
allowAutoRedirect: allowAutoRedirect);
#else
Log.Information("Using " + nameof(ApiWebRequestFactory) + " for {ProductName} transport.", productName);
return new ApiWebRequestFactory(
getBaseEndpoint?.Invoke(settings.AgentUri) ?? settings.AgentUri,
httpHeaderHelper.DefaultHeaders,
timeout: tcpTimeout);
timeout: tcpTimeout,
allowAutoRedirect: allowAutoRedirect);
#endif
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ internal sealed class ApiWebRequestFactory : IApiRequestFactory
{
private readonly KeyValuePair<string, string>[] _defaultHeaders;
private readonly Uri _baseEndpoint;
private readonly bool _allowAutoRedirect;
private WebProxy _proxy;
private NetworkCredential _credential;
private TimeSpan? _timeout;

public ApiWebRequestFactory(Uri baseEndpoint, KeyValuePair<string, string>[] defaultHeaders, TimeSpan? timeout = null)
public ApiWebRequestFactory(Uri baseEndpoint, KeyValuePair<string, string>[] defaultHeaders, TimeSpan? timeout = null, bool allowAutoRedirect = true)
{
_baseEndpoint = baseEndpoint;
_defaultHeaders = defaultHeaders;
_timeout = timeout;
_allowAutoRedirect = allowAutoRedirect;
}

internal bool AllowAutoRedirect => _allowAutoRedirect;

public string Info(Uri endpoint)
{
return endpoint.ToString();
Expand All @@ -38,6 +42,7 @@ public string Info(Uri endpoint)
public IApiRequest Create(Uri endpoint)
{
var request = WebRequest.CreateHttp(endpoint);
request.AllowAutoRedirect = _allowAutoRedirect;
if (_proxy is not null)
{
request.Proxy = _proxy;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="HttpClientRequestFactory.cs" company="Datadog">
// <copyright file="HttpClientRequestFactory.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
Expand All @@ -22,9 +22,9 @@ internal sealed class HttpClientRequestFactory : IApiRequestFactory
private readonly HttpMessageHandler _handler;
private readonly Uri _baseEndpoint;

public HttpClientRequestFactory(Uri baseEndpoint, KeyValuePair<string, string>[] defaultHeaders, HttpMessageHandler handler = null, TimeSpan? timeout = null)
public HttpClientRequestFactory(Uri baseEndpoint, KeyValuePair<string, string>[] defaultHeaders, HttpMessageHandler handler = null, TimeSpan? timeout = null, bool allowAutoRedirect = true)
{
_handler = handler ?? new HttpClientHandler();
_handler = handler ?? new HttpClientHandler { AllowAutoRedirect = allowAutoRedirect };
_client = new HttpClient(_handler);
_baseEndpoint = baseEndpoint;
if (timeout.HasValue)
Expand All @@ -41,6 +41,15 @@ public HttpClientRequestFactory(Uri baseEndpoint, KeyValuePair<string, string>[]
_client.DefaultRequestHeaders.ConnectionClose = true;
}

internal bool AllowAutoRedirect => _handler switch
{
HttpClientHandler handler => handler.AllowAutoRedirect,
#if NET5_0_OR_GREATER
SocketsHttpHandler handler => handler.AllowAutoRedirect,
#endif
_ => true,
};

public Uri GetEndpoint(string relativePath) => relativePath is null ? _baseEndpoint : UriHelpers.Combine(_baseEndpoint, relativePath);

#if NET5_0_OR_GREATER // in .NET 6 we derive a SocketHandlerRequestFactory
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="SocketHandlerRequestFactory.cs" company="Datadog">
// <copyright file="SocketHandlerRequestFactory.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
Expand All @@ -15,7 +15,7 @@ internal sealed class SocketHandlerRequestFactory : HttpClientRequestFactory
{
private readonly IStreamFactory _streamFactory;

public SocketHandlerRequestFactory(IStreamFactory streamFactory, KeyValuePair<string, string>[] defaultHeaders, Uri baseEndpoint, TimeSpan? timeout = null)
public SocketHandlerRequestFactory(IStreamFactory streamFactory, KeyValuePair<string, string>[] defaultHeaders, Uri baseEndpoint, TimeSpan? timeout = null, bool allowAutoRedirect = true)
: base(
// HttpClient requires a "valid" host header, and will only accept http:// or https:// schemes
// The host part of the endpoint is irrelevant, as we're using the UDS socket/named pipe
Expand All @@ -25,6 +25,7 @@ public SocketHandlerRequestFactory(IStreamFactory streamFactory, KeyValuePair<st
timeout: timeout,
handler: new SocketsHttpHandler
{
AllowAutoRedirect = allowAutoRedirect,
ConnectCallback = async (_, token) => await streamFactory.GetBidirectionalStreamAsync(token).ConfigureAwait(false)
})
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,28 +78,12 @@ public static bool TryCreate(string? site, string? baseUrl, [NotNullWhen(true)]
var configured = baseUrl?.Trim();
if (StringUtil.IsNullOrEmpty(configured))
{
var trimmedSite = site?.Trim();
if (StringUtil.IsNullOrEmpty(trimmedSite))
if (!TryNormalizeSite(site, out var normalizedSite, out error))
{
error = "No Datadog site is configured";
return false;
}

// The site is concatenated into a host, so every character that can change what a URL means
// has to be rejected before that happens. "@" is the dangerous one: it would make the rest of
// the value the real host, and the API key would be sent there. "/", "?" and "#" would start a
// path, query or fragment, and ":" a port or a scheme. Uri.TryCreate accepts several of these,
// so it cannot be relied on to catch them. The other tracers reject the same set.
foreach (var character in trimmedSite)
{
if (char.IsWhiteSpace(character) || character is '/' or '?' or '#' or '@' or ':')
{
error = "The configured Datadog site is not valid";
return false;
}
}

var managedHost = ManagedHostPrefix + StringUtil.ToLowerInvariant(trimmedSite);
var managedHost = ManagedHostPrefix + normalizedSite;

if (!Uri.TryCreate($"https://{managedHost}{DefaultPath}", UriKind.Absolute, out var managedUri))
{
Expand Down Expand Up @@ -144,6 +128,84 @@ public static bool TryCreate(string? site, string? baseUrl, [NotNullWhen(true)]
return true;
}

/// <summary>
/// Validates and normalizes a Datadog site before it is appended to a managed hostname.
/// Configuration and event delivery use this same method so credentials cannot be routed by
/// two subtly different parsers.
/// </summary>
internal static bool TryNormalizeSite(string? site, out string normalizedSite, out string? error)
{
normalizedSite = string.Empty;
error = null;

var trimmedSite = site?.Trim();
if (StringUtil.IsNullOrEmpty(trimmedSite))
{
error = "No Datadog site is configured";
return false;
}

// The complete managed host must remain below the DNS limit once either the configuration
// or event-intake prefix is applied.
if (trimmedSite.Length > 230)
{
error = "The configured Datadog site is not valid";
return false;
}

// Accept only DNS label characters before concatenating the site into a managed host.
// Uri parsing alone accepts delimiters such as '@' that can redirect the API key to a
// different host, as well as '/', '?', '#', and ':' that change the URL's meaning.
var labelLength = 0;
var previousWasHyphen = false;
foreach (var character in trimmedSite)
{
if (character == '.')
{
if (labelLength == 0 || previousWasHyphen)
{
error = "The configured Datadog site is not valid";
return false;
}

labelLength = 0;
previousWasHyphen = false;
continue;
}

if (character is >= 'A' and <= 'Z' or >= 'a' and <= 'z' or >= '0' and <= '9')
{
labelLength++;
previousWasHyphen = false;
}
else if (character == '-' && labelLength > 0)
{
labelLength++;
previousWasHyphen = true;
}
else
{
error = "The configured Datadog site is not valid";
return false;
}

if (labelLength > 63)
{
error = "The configured Datadog site is not valid";
return false;
}
}

if (labelLength == 0 || previousWasHyphen)
{
error = "The configured Datadog site is not valid";
return false;
}

normalizedSite = StringUtil.ToLowerInvariant(trimmedSite);
return true;
}

/// <summary>
/// Returns the URI to request configuration for <paramref name="env"/>. The environment is
/// added as a query parameter rather than baked into the endpoint, because it can change while
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// <copyright file="FeatureFlagsEvpHeaderHelper.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System.Collections.Generic;
using Datadog.Trace.HttpOverStreams;

namespace Datadog.Trace.FeatureFlags.Evp;

/// <summary>
/// Headers used when a Feature Flags event is delivered through a local EVP relay.
/// </summary>
internal sealed class FeatureFlagsEvpHeaderHelper : HttpHeaderHelperBase
{
internal const string EvpSubdomainHeader = "X-Datadog-EVP-Subdomain";
internal const string EvpSubdomain = "event-platform-intake";
internal const string EvpOriginHeader = "DD-EVP-ORIGIN";
internal const string EvpOrigin = "dd-trace-dotnet";
internal const string EvpOriginVersionHeader = "DD-EVP-ORIGIN-VERSION";

public static readonly FeatureFlagsEvpHeaderHelper Instance = new();

private FeatureFlagsEvpHeaderHelper()
{
DefaultHeaders =
[
.. AgentHttpHeaderNames.MinimalHeaders,
new(EvpSubdomainHeader, EvpSubdomain),
new(EvpOriginHeader, EvpOrigin),
new(EvpOriginVersionHeader, TracerConstants.ThreePartVersion),
];
HttpSerializedDefaultHeaders =
$"{AgentHttpHeaderNames.HttpSerializedMinimalHeaders}" +
$"{EvpSubdomainHeader}: {EvpSubdomain}{DatadogHttpValues.CrLf}" +
$"{EvpOriginHeader}: {EvpOrigin}{DatadogHttpValues.CrLf}" +
$"{EvpOriginVersionHeader}: {TracerConstants.ThreePartVersion}{DatadogHttpValues.CrLf}";
}

public override KeyValuePair<string, string>[] DefaultHeaders { get; }

protected override string HttpSerializedDefaultHeaders { get; }
}
Loading
Loading