-
Notifications
You must be signed in to change notification settings - Fork 769
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 grpc tls certificates #4731
Changes from 14 commits
3e773a1
22e80db
bcb71f3
48b1081
4249037
a4bab30
3250ab0
0cdac9b
d8d2d70
1183adb
12014f0
9a9cc91
b8064a6
5f4346f
ef6e2b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,9 @@ | |
|
||
using System.Diagnostics; | ||
using System.Reflection; | ||
#if NET6_0_OR_GREATER | ||
using System.Security.Cryptography.X509Certificates; | ||
#endif | ||
#if NETFRAMEWORK | ||
using System.Net.Http; | ||
#endif | ||
|
@@ -39,6 +42,9 @@ public class OtlpExporterOptions | |
internal const string HeadersEnvVarName = "OTEL_EXPORTER_OTLP_HEADERS"; | ||
internal const string TimeoutEnvVarName = "OTEL_EXPORTER_OTLP_TIMEOUT"; | ||
internal const string ProtocolEnvVarName = "OTEL_EXPORTER_OTLP_PROTOCOL"; | ||
internal const string ClientKeyFileEnvVarName = "OTEL_EXPORTER_OTLP_CLIENT_KEY"; | ||
internal const string ClientCertificateFileEnvVarName = "OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE"; | ||
internal const string CertificateFileEnvVarName = "OTEL_EXPORTER_OTLP_CERTIFICATE"; | ||
|
||
internal static readonly KeyValuePair<string, string>[] StandardHeaders = new KeyValuePair<string, string>[] | ||
{ | ||
|
@@ -84,6 +90,21 @@ internal OtlpExporterOptions( | |
this.TimeoutMilliseconds = timeout; | ||
} | ||
|
||
if (configuration.TryGetStringValue(ClientCertificateFileEnvVarName, out var clientCertificateFile)) | ||
{ | ||
this.ClientCertificateFile = clientCertificateFile; | ||
} | ||
|
||
if (configuration.TryGetStringValue(ClientKeyFileEnvVarName, out var clientKeyFile)) | ||
{ | ||
this.ClientKeyFile = clientKeyFile; | ||
} | ||
|
||
if (configuration.TryGetStringValue(CertificateFileEnvVarName, out var certificateFile)) | ||
{ | ||
this.CertificateFile = certificateFile; | ||
} | ||
|
||
if (configuration.TryGetValue<OtlpExportProtocol>( | ||
ProtocolEnvVarName, | ||
OtlpExportProtocolParser.TryParse, | ||
|
@@ -94,7 +115,7 @@ internal OtlpExporterOptions( | |
|
||
this.HttpClientFactory = this.DefaultHttpClientFactory = () => | ||
{ | ||
return new HttpClient | ||
return new HttpClient(this.CreateDefaultHttpMessageHandler(), disposeHandler: true) | ||
{ | ||
Timeout = TimeSpan.FromMilliseconds(this.TimeoutMilliseconds), | ||
}; | ||
|
@@ -159,6 +180,48 @@ public Uri Endpoint | |
/// <remarks>Note: This only applies when exporting traces.</remarks> | ||
public BatchExportProcessorOptions<Activity> BatchExportProcessorOptions { get; set; } | ||
|
||
#if NET6_0_OR_GREATER | ||
/// <summary> | ||
/// Gets or sets the trusted certificate to use when verifying a server's TLS credentials. | ||
/// </summary> | ||
#else | ||
/// <summary> | ||
/// Gets or sets the trusted certificate to use when verifying a server's TLS credentials. | ||
/// </summary> | ||
/// <remarks> | ||
/// This option does not affect <see cref="OtlpExportProtocol.HttpProtobuf"/> protocol. | ||
alanwest marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// </remarks> | ||
#endif | ||
public string CertificateFile { get; set; } | ||
|
||
#if NET6_0_OR_GREATER | ||
/// <summary> | ||
/// Gets or sets the path to the private key to use in mTLS communication in PEM format. | ||
/// </summary> | ||
#else | ||
/// <summary> | ||
/// Gets or sets the path to the private key to use in mTLS communication in PEM format. | ||
/// </summary> | ||
/// <remarks> | ||
/// This option does not affect <see cref="OtlpExportProtocol.HttpProtobuf"/> protocol. | ||
/// </remarks> | ||
#endif | ||
public string ClientKeyFile { get; set; } | ||
|
||
#if NET6_0_OR_GREATER | ||
/// <summary> | ||
/// Gets or sets the path to the certificate/chain trust for clients private key to use in mTLS communication in PEM format. | ||
/// </summary> | ||
#else | ||
/// <summary> | ||
/// Gets or sets the path to the certificate/chain trust for clients private key to use in mTLS communication in PEM format. | ||
/// </summary> | ||
/// <remarks> | ||
/// This option does not affect <see cref="OtlpExportProtocol.HttpProtobuf"/> protocol. | ||
/// </remarks> | ||
#endif | ||
public string ClientCertificateFile { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the factory function called to create the <see | ||
/// cref="HttpClient"/> instance that will be used at runtime to | ||
|
@@ -205,6 +268,54 @@ internal static void RegisterOtlpExporterOptionsFactory(IServiceCollection servi | |
sp.GetRequiredService<IOptionsMonitor<BatchExportActivityProcessorOptions>>().Get(name))); | ||
} | ||
|
||
internal HttpClientHandler CreateDefaultHttpMessageHandler() | ||
{ | ||
var handler = new HttpClientHandler(); | ||
|
||
if (this.CertificateFile != null) | ||
{ | ||
#if NET6_0_OR_GREATER | ||
var trustedCA = LoadCertificateFromPemFile(this.CertificateFile, null); | ||
alanwest marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
handler.ServerCertificateCustomValidationCallback = (message, serverCert, chain, policyErrs) => | ||
{ | ||
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; | ||
chain.ChainPolicy.CustomTrustStore.Add(trustedCA); | ||
var buildResult = chain.Build(serverCert); | ||
return buildResult; | ||
}; | ||
Comment on lines
+280
to
+286
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not an expert on these things, so still need to do some of my own research to understand if this is sufficient. One thing that leaves me a bit unsure here is that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're correct that The other validation criteria (e.g. expiration, revocation) is encoded in the |
||
#else | ||
throw new PlatformNotSupportedException("Client certificate option is not supported for HTTP/Protobuf protocol."); | ||
#endif | ||
} | ||
|
||
if (this.ClientCertificateFile != null) | ||
{ | ||
#if NET6_0_OR_GREATER | ||
handler.ClientCertificates.Add(LoadCertificateFromPemFile(this.ClientCertificateFile, this.ClientKeyFile)); | ||
#else | ||
throw new PlatformNotSupportedException("Trusted certificate option is not supported for HTTP/Protobuf protocol."); | ||
#endif | ||
} | ||
|
||
return handler; | ||
} | ||
|
||
#if NET6_0_OR_GREATER | ||
private static X509Certificate2 LoadCertificateFromPemFile(string certificatePath, string privateKeyPath) | ||
{ | ||
// API here is a bit tricky, apparently CreateFromPemFile requires private key, | ||
// while CreateFromPem can simply load the public cert | ||
using var pemCert = privateKeyPath is null | ||
? X509Certificate2.CreateFromPem(File.ReadAllText(certificatePath)) | ||
: X509Certificate2.CreateFromPemFile(certificatePath, privateKeyPath); | ||
|
||
// loading ephemeral pem files have problem on Windows | ||
// https://github.com/dotnet/runtime/issues/23749 | ||
return new X509Certificate2(pemCert.Export(X509ContentType.Pkcs12)); | ||
} | ||
#endif | ||
|
||
private static string GetUserAgentString() | ||
{ | ||
try | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
# Self-signed cert generated by integration test | ||
otel-collector.crt | ||
otel-collector.key | ||
otel-client.crt | ||
otel-client.key | ||
otel-untrusted-collector.crt | ||
otel-untrusted-collector.key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might make sense to add a
TryGetFilePath
method. Similar to theTryGetUriValue
it could validate that the string is a file path for an existing file and log a message if it isn't.opentelemetry-dotnet/src/Shared/Options/ConfigurationExtensions.cs
Lines 69 to 73 in 8f38400
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After trying this I think a
TryGetFilePath()
method doesn't make much sense here. TheTryGet...
methods simply check if the value is syntactically valid and not whether the resource actually exists (e.g. URI).If the certificate file with specified path doesn't exist then
TryGetFilePath()
would returnfalse
and the option would be set tonull
and treated as if no option has been set by the developer.I think from a developer PoV, if the file doesn't exist I'd prefer to be notified with a
FileNotFoundException
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Syntactic validation is what I had in mind - using something like
Path.GetInvalidPathChars
or something. Though file paths may be tricky to validate syntactically in a meaningful/helpful way, so I'm fine with not doing anything more in this PR.