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

[otlp] Add mTLS Support for OTLP Exporter #5918

Draft
wants to merge 28 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
00c903c
feat(): Add mTLS Support for OTLP Exporter
sandy2008 Oct 23, 2024
b10b3fa
Merge branch 'main' into main
sandy2008 Oct 23, 2024
0d0aa98
feat(): feat: Add certificate handling in HttpClientFactory using X50…
sandy2008 Oct 27, 2024
e7c6f5b
feat(): feat: Add certificate handling in HttpClientFactory using X50…
sandy2008 Oct 27, 2024
3b43fd3
feat(): feat: Add certificate handling in HttpClientFactory using X50…
sandy2008 Oct 27, 2024
4b6d3cd
feat(): feat: Add certificate handling in HttpClientFactory using X50…
sandy2008 Oct 27, 2024
5864dbf
feat(): feat: Add certificate handling in HttpClientFactory using X50…
sandy2008 Oct 27, 2024
0e38ba3
Merge branch 'main' into main
sandy2008 Oct 27, 2024
3733dee
Merge branch 'main' into main
sandy2008 Nov 5, 2024
f09a650
Merge branch 'main' into main
sandy2008 Nov 7, 2024
8630876
Merge branch 'main' into main
sandy2008 Nov 11, 2024
2e7e412
feat(): Add mTLS Support for OTLP Exporter
sandy2008 Nov 11, 2024
afc8df6
feat(): Add mTLS Support for OTLP Exporter
sandy2008 Nov 11, 2024
c5101b1
feat(): Add mTLS Support for OTLP Exporter
sandy2008 Nov 11, 2024
84a4d5b
feat(): Add mTLS Support for OTLP Exporter
sandy2008 Nov 11, 2024
716949c
feat(): Add mTLS Support for OTLP Exporter
sandy2008 Nov 11, 2024
2781534
feat(): Add mTLS Support for OTLP Exporter
sandy2008 Nov 11, 2024
31ef9aa
Merge branch 'main' into main
sandy2008 Nov 13, 2024
9df6f06
Merge branch 'main' into main
sandy2008 Nov 15, 2024
6e940d1
Merge branch 'main' into main
sandy2008 Nov 19, 2024
dc39de9
Merge branch 'main' into main
sandy2008 Nov 24, 2024
2006fbf
Merge branch 'main' into main
sandy2008 Nov 28, 2024
87737eb
Merge branch 'main' into main
sandy2008 Dec 11, 2024
4d56a9a
Merge branch 'main' into main
sandy2008 Dec 13, 2024
c4ec895
Merge branch 'main' into main
rajkumar-rangaraj Dec 16, 2024
0ad1e13
Merge branch 'main' into main
sandy2008 Dec 17, 2024
7a378e6
Merge branch 'main' into main
sandy2008 Dec 19, 2024
6bddb6b
Merge branch 'main' into main
sandy2008 Dec 25, 2024
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
@@ -0,0 +1,6 @@
OpenTelemetry.Exporter.OtlpExporterOptions.CertificateFile.get -> string!
OpenTelemetry.Exporter.OtlpExporterOptions.CertificateFile.set -> void
OpenTelemetry.Exporter.OtlpExporterOptions.ClientCertificateFile.get -> string!
OpenTelemetry.Exporter.OtlpExporterOptions.ClientCertificateFile.set -> void
OpenTelemetry.Exporter.OtlpExporterOptions.ClientKeyFile.get -> string!
OpenTelemetry.Exporter.OtlpExporterOptions.ClientKeyFile.set -> void
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
using OpenTelemetry.Internal;
using OpenTelemetry.Trace;
#if NET6_0_OR_GREATER
using System.Security.Cryptography.X509Certificates;
#endif

namespace OpenTelemetry.Exporter;

Expand All @@ -27,6 +30,9 @@ public class OtlpExporterOptions : IOtlpExporterOptions
internal const string DefaultGrpcEndpoint = "http://localhost:4317";
internal const string DefaultHttpEndpoint = "http://localhost:4318";
internal const OtlpExportProtocol DefaultOtlpExportProtocol = OtlpExportProtocol.Grpc;
internal const string CertificateFileEnvVarName = "OTEL_EXPORTER_OTLP_CERTIFICATE";
internal const string ClientKeyFileEnvVarName = "OTEL_EXPORTER_OTLP_CLIENT_KEY";
internal const string ClientCertificateFileEnvVarName = "OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE";

internal static readonly KeyValuePair<string, string>[] StandardHeaders = new KeyValuePair<string, string>[]
{
Expand Down Expand Up @@ -68,13 +74,80 @@ internal OtlpExporterOptions(

this.DefaultHttpClientFactory = () =>
{
#if NET6_0_OR_GREATER
// Create a new handler
var handler = new HttpClientHandler();

// Load server certificate
if (!string.IsNullOrEmpty(this.CertificateFile))
{
var trustedCertificate = X509Certificate2.CreateFromPemFile(this.CertificateFile);

handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want users of this API being able to add their custom validation callbacks next to this one?

{
if (cert != null && chain != null)
{
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(trustedCertificate);
return chain.Build(cert);
}

return false;
};
}

// Load client certificate for mTLS
if (!string.IsNullOrEmpty(this.ClientCertificateFile) && !string.IsNullOrEmpty(this.ClientKeyFile))
{
var clientCertificate = X509Certificate2.CreateFromPemFile(this.ClientCertificateFile, this.ClientKeyFile);
handler.ClientCertificates.Add(clientCertificate);
}

// Create and return the HttpClient
return new HttpClient(handler)
{
Timeout = TimeSpan.FromMilliseconds(this.TimeoutMilliseconds),
};
#else
// For earlier .NET versions
return new HttpClient
{
Timeout = TimeSpan.FromMilliseconds(this.TimeoutMilliseconds),
};
#endif
};

this.BatchExportProcessorOptions = defaultBatchOptions!;

// Load CertificateFile from environment variable
if (Environment.GetEnvironmentVariable(CertificateFileEnvVarName) is string certificateFile)
{
this.CertificateFile = certificateFile;
}
else
{
this.CertificateFile = string.Empty;
}

// Load ClientKeyFile from environment variable
if (Environment.GetEnvironmentVariable(ClientKeyFileEnvVarName) is string clientKeyFile)
{
this.ClientKeyFile = clientKeyFile;
}
else
{
this.ClientKeyFile = string.Empty;
}

// Load ClientCertificateFile from environment variable
if (Environment.GetEnvironmentVariable(ClientCertificateFileEnvVarName) is string clientCertificateFile)
{
this.ClientCertificateFile = clientCertificateFile;
}
else
{
this.ClientCertificateFile = string.Empty;
}
}

/// <inheritdoc/>
Expand Down Expand Up @@ -142,6 +215,21 @@ public Func<HttpClient> HttpClientFactory
}
}

/// <summary>
/// Gets or sets the trusted certificate to use when verifying a server's TLS credentials.
/// </summary>
public string CertificateFile { get; set; }

/// <summary>
/// Gets or sets the path to the private key to use in mTLS communication in PEM format.
/// </summary>
public string ClientKeyFile { get; set; }

/// <summary>
/// Gets or sets the path to the certificate/chain trust for client's private key to use in mTLS communication in PEM format.
/// </summary>
public string ClientCertificateFile { get; set; }

/// <summary>
/// Gets a value indicating whether or not the signal-specific path should
/// be appended to <see cref="Endpoint"/>.
Expand Down Expand Up @@ -220,6 +308,45 @@ internal OtlpExporterOptions ApplyDefaults(OtlpExporterOptions defaultExporterOp
return this;
}

internal HttpClient AddCertificatesToHttpClient(HttpClientHandler handler)
{
#if NET6_0_OR_GREATER
// Set up server certificate validation if CertificateFile is provided
if (!string.IsNullOrEmpty(this.CertificateFile))
{
// Load the certificate from the file
var trustedCertificate = X509Certificate2.CreateFromPemFile(this.CertificateFile);

// Set custom server certificate validation callback
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
if (cert != null && chain != null)
{
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(trustedCertificate);
return chain.Build(cert);
}

return false;
};
}

// Add client certificate if both files are provided
if (!string.IsNullOrEmpty(this.ClientCertificateFile) && !string.IsNullOrEmpty(this.ClientKeyFile))
{
// Load the client certificate from PEM files
var clientCertificate = X509Certificate2.CreateFromPemFile(this.ClientCertificateFile, this.ClientKeyFile);
handler.ClientCertificates.Add(clientCertificate);
}

// Create and return an HttpClient with the modified handler
return new HttpClient(handler);
#else
// Handle alternative methods for earlier .NET versions
throw new PlatformNotSupportedException("mTLS support requires .NET 6.0 or later.");
#endif
}

private static string GetUserAgentString()
{
var assembly = typeof(OtlpExporterOptions).Assembly;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
using LogOtlpCollector = OpenTelemetry.Proto.Collector.Logs.V1;
using MetricsOtlpCollector = OpenTelemetry.Proto.Collector.Metrics.V1;
using TraceOtlpCollector = OpenTelemetry.Proto.Collector.Trace.V1;
#if NET6_0_OR_GREATER
using System.Security.Cryptography.X509Certificates;
#endif

namespace OpenTelemetry.Exporter;

Expand All @@ -33,7 +36,41 @@ public static Channel CreateChannel(this OtlpExporterOptions options)
throw new NotSupportedException($"Endpoint URI scheme ({options.Endpoint.Scheme}) is not supported. Currently only \"http\" and \"https\" are supported.");
}

#if NETSTANDARD2_1 || NET
#if NET6_0_OR_GREATER
var handler = new HttpClientHandler();

// Set up custom certificate validation if CertificateFile is provided
if (!string.IsNullOrEmpty(options.CertificateFile))
{
var trustedCertificate = X509Certificate2.CreateFromPemFile(options.CertificateFile);
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
if (cert != null && chain != null)
{
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(trustedCertificate);
return chain.Build(cert);
}

return false;
};
}

// Set up client certificate if provided
if (!string.IsNullOrEmpty(options.ClientCertificateFile) && !string.IsNullOrEmpty(options.ClientKeyFile))
{
var clientCertificate = X509Certificate2.CreateFromPemFile(options.ClientCertificateFile, options.ClientKeyFile);
handler.ClientCertificates.Add(clientCertificate);
}

var grpcChannelOptions = new GrpcChannelOptions
{
HttpHandler = handler,
DisposeHttpClient = true,
};

return GrpcChannel.ForAddress(options.Endpoint, grpcChannelOptions);
#elif NETSTANDARD2_1 || NET
return GrpcChannel.ForAddress(options.Endpoint);
#else
ChannelCredentials channelCredentials;
Expand Down
Loading