diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/net462/PublicAPI.Unshipped.txt index d64c20118ee..4a4727c0c18 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/net462/PublicAPI.Unshipped.txt @@ -1,3 +1,9 @@ +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 OpenTelemetry.Logs.OtlpLogExporterHelperExtensions static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action configureExporterAndProcessor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/net6.0/PublicAPI.Unshipped.txt index d64c20118ee..4a4727c0c18 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/net6.0/PublicAPI.Unshipped.txt @@ -1,3 +1,9 @@ +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 OpenTelemetry.Logs.OtlpLogExporterHelperExtensions static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action configureExporterAndProcessor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/netstandard2.0/PublicAPI.Unshipped.txt index d64c20118ee..4a4727c0c18 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,3 +1,9 @@ +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 OpenTelemetry.Logs.OtlpLogExporterHelperExtensions static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action configureExporterAndProcessor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/netstandard2.1/PublicAPI.Unshipped.txt index d64c20118ee..4a4727c0c18 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/.publicApi/Stable/netstandard2.1/PublicAPI.Unshipped.txt @@ -1,3 +1,9 @@ +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 OpenTelemetry.Logs.OtlpLogExporterHelperExtensions static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions static OpenTelemetry.Logs.OtlpLogExporterHelperExtensions.AddOtlpExporter(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions loggerOptions, System.Action configureExporterAndProcessor) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs index f8669fc1dd1..25968e972ff 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptions.cs @@ -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[] StandardHeaders = new KeyValuePair[] { @@ -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( 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 /// Note: This only applies when exporting traces. public BatchExportProcessorOptions BatchExportProcessorOptions { get; set; } +#if NET6_0_OR_GREATER + /// + /// Gets or sets the trusted certificate to use when verifying a server's TLS credentials. + /// +#else + /// + /// Gets or sets the trusted certificate to use when verifying a server's TLS credentials. + /// + /// + /// This option does not affect protocol. + /// +#endif + public string CertificateFile { get; set; } + +#if NET6_0_OR_GREATER + /// + /// Gets or sets the path to the private key to use in mTLS communication in PEM format. + /// +#else + /// + /// Gets or sets the path to the private key to use in mTLS communication in PEM format. + /// + /// + /// This option does not affect protocol. + /// +#endif + public string ClientKeyFile { get; set; } + +#if NET6_0_OR_GREATER + /// + /// Gets or sets the path to the certificate/chain trust for clients private key to use in mTLS communication in PEM format. + /// +#else + /// + /// Gets or sets the path to the certificate/chain trust for clients private key to use in mTLS communication in PEM format. + /// + /// + /// This option does not affect protocol. + /// +#endif + public string ClientCertificateFile { get; set; } + /// /// Gets or sets the factory function called to create the instance that will be used at runtime to @@ -205,6 +268,54 @@ internal static void RegisterOtlpExporterOptionsFactory(IServiceCollection servi sp.GetRequiredService>().Get(name))); } + internal HttpClientHandler CreateDefaultHttpMessageHandler() + { + var handler = new HttpClientHandler(); + + if (this.CertificateFile != null) + { +#if NET6_0_OR_GREATER + var trustedCA = LoadCertificateFromPemFile(this.CertificateFile, null); + + handler.ServerCertificateCustomValidationCallback = (message, serverCert, chain, policyErrs) => + { + chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + chain.ChainPolicy.CustomTrustStore.Add(trustedCA); + var buildResult = chain.Build(serverCert); + return buildResult; + }; +#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 diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs index 5f8758408a9..64b85787e3b 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/OtlpExporterOptionsExtensions.cs @@ -43,12 +43,30 @@ public static Channel CreateChannel(this OtlpExporterOptions options) } #if NETSTANDARD2_1 || NET6_0_OR_GREATER - return GrpcChannel.ForAddress(options.Endpoint); + var channelOptions = new GrpcChannelOptions(); + + if (options.ClientCertificateFile != null || options.CertificateFile != null) + { + // Setting certificate in SslCredentials won't work: https://aka.ms/aspnet/grpc/certauth + channelOptions.HttpHandler = channelOptions.HttpHandler = options.CreateDefaultHttpMessageHandler(); + channelOptions.DisposeHttpClient = true; + } + + return GrpcChannel.ForAddress(options.Endpoint, channelOptions); #else + var rootCertificate = options.CertificateFile is null ? null : File.ReadAllText(options.CertificateFile); + KeyCertificatePair clientCertificatePair = options.ClientCertificateFile switch + { + null => null, + string f => new KeyCertificatePair(File.ReadAllText(f), options.ClientKeyFile == null ? null : File.ReadAllText(options.ClientKeyFile)), + }; + ChannelCredentials channelCredentials; if (options.Endpoint.Scheme == Uri.UriSchemeHttps) { - channelCredentials = new SslCredentials(); + channelCredentials = new SslCredentials( + rootCertificates: rootCertificate, + keyCertificatePair: clientCertificatePair); } else { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore index feada150421..9fbcfae3843 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore @@ -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 diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs index 63e28e501a2..d4b3d1e3014 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/IntegrationTests.cs @@ -56,10 +56,8 @@ public void Dispose() [InlineData(OtlpExportProtocol.HttpProtobuf, ":5318/v1/traces", ExportProcessorType.Simple, true, "https")] [Trait("CategoryName", "CollectorIntegrationTests")] [SkipUnlessEnvVarFoundTheory(CollectorHostnameEnvVarName)] - public void TraceExportResultIsSuccess(OtlpExportProtocol protocol, string endpoint, ExportProcessorType exportProcessorType, bool forceFlush, string scheme = "http") + public void ExportTrace_ReturnsSucceedResult(OtlpExportProtocol protocol, string endpoint, ExportProcessorType exportProcessorType, bool forceFlush, string scheme = "http") { - using EventWaitHandle handle = new ManualResetEvent(false); - var exporterOptions = new OtlpExporterOptions { Endpoint = new Uri($"{scheme}://{CollectorHostname}{endpoint}"), @@ -71,60 +69,107 @@ public void TraceExportResultIsSuccess(OtlpExportProtocol protocol, string endpo }, }; - DelegatingExporter delegatingExporter = null; - var exportResults = new List(); + this.VerifyTraceExportResult(exporterOptions, forceFlush, ExportResult.Success); + } - var activitySourceName = "otlp.collector.test"; + [InlineData(OtlpExportProtocol.Grpc, ":6317", ExportProcessorType.Simple, true)] + [InlineData(OtlpExportProtocol.HttpProtobuf, ":6318/v1/traces", ExportProcessorType.Simple, true)] + [Trait("CategoryName", "CollectorIntegrationTests")] + [Trait("CategoryName", "CollectorIntegrationTests")] + [SkipUnlessEnvVarFoundTheory(CollectorHostnameEnvVarName)] + public void ExportTrace_UntrustedRoot_CustomCATrustStore_ReturnsSucceedResult( + OtlpExportProtocol protocol, + string endpoint, + ExportProcessorType exportProcessorType, + bool forceFlush) + { + var exporterOptions = new OtlpExporterOptions + { + Endpoint = new Uri($"https://{CollectorHostname}{endpoint}"), + Protocol = protocol, + ExportProcessorType = exportProcessorType, + BatchExportProcessorOptions = new() + { + ScheduledDelayMilliseconds = ExportIntervalMilliseconds, + }, + CertificateFile = "/cfg/otel-untrusted-collector.crt", + }; - var builder = Sdk.CreateTracerProviderBuilder() - .AddSource(activitySourceName); + this.VerifyTraceExportResult(exporterOptions, forceFlush, ExportResult.Success); + } - builder.AddProcessor(OtlpTraceExporterHelperExtensions.BuildOtlpExporterProcessor( - exporterOptions, - DefaultSdkLimitOptions, - serviceProvider: null, - configureExporterInstance: otlpExporter => + [InlineData(OtlpExportProtocol.Grpc, ":6317", ExportProcessorType.Simple)] + [InlineData(OtlpExportProtocol.HttpProtobuf, ":6318/v1/traces", ExportProcessorType.Simple)] + [Trait("CategoryName", "CollectorIntegrationTests")] + [Trait("CategoryName", "CollectorIntegrationTests")] + [SkipUnlessEnvVarFoundTheory(CollectorHostnameEnvVarName)] + public void ExportTrace_UntrustedRoot_NoCertificateSet_ReturnsFailureResult( + OtlpExportProtocol protocol, + string endpoint, + ExportProcessorType exportProcessorType) + { + var exporterOptions = new OtlpExporterOptions + { + Endpoint = new Uri($"https://{CollectorHostname}{endpoint}"), + Protocol = protocol, + ExportProcessorType = exportProcessorType, + BatchExportProcessorOptions = new() { - delegatingExporter = new DelegatingExporter - { - OnExportFunc = (batch) => - { - var result = otlpExporter.Export(batch); - exportResults.Add(result); - handle.Set(); - return result; - }, - }; - return delegatingExporter; - })); + ScheduledDelayMilliseconds = ExportIntervalMilliseconds, + }, + }; - using (var tracerProvider = builder.Build()) + this.VerifyTraceExportResult(exporterOptions, true, ExportResult.Failure); + } + + [InlineData(OtlpExportProtocol.Grpc, ":7317", ExportProcessorType.Simple)] + [InlineData(OtlpExportProtocol.HttpProtobuf, ":7318/v1/traces", ExportProcessorType.Simple)] + [Trait("CategoryName", "CollectorIntegrationTests")] + [Trait("CategoryName", "CollectorIntegrationTests")] + [SkipUnlessEnvVarFoundTheory(CollectorHostnameEnvVarName)] + public void ExportTrace_MTLS_WithClientCertificate_ReturnsSucceedResult( + OtlpExportProtocol protocol, + string endpoint, + ExportProcessorType exportProcessorType) + { + var exporterOptions = new OtlpExporterOptions { - using var source = new ActivitySource(activitySourceName); - var activity = source.StartActivity($"{protocol} Test Activity"); - activity?.Stop(); + Endpoint = new Uri($"https://{CollectorHostname}{endpoint}"), + Protocol = protocol, + ExportProcessorType = exportProcessorType, + BatchExportProcessorOptions = new() + { + ScheduledDelayMilliseconds = ExportIntervalMilliseconds, + }, + ClientCertificateFile = "/cfg/otel-client.crt", + ClientKeyFile = "/cfg/otel-client.key", + }; - Assert.NotNull(delegatingExporter); + this.VerifyTraceExportResult(exporterOptions, true, ExportResult.Success); + } - if (forceFlush) - { - Assert.True(tracerProvider.ForceFlush()); - Assert.Single(exportResults); - Assert.Equal(ExportResult.Success, exportResults[0]); - } - else if (exporterOptions.ExportProcessorType == ExportProcessorType.Batch) + [InlineData(OtlpExportProtocol.Grpc, ":7317", ExportProcessorType.Simple)] + [InlineData(OtlpExportProtocol.HttpProtobuf, ":7318/v1/traces", ExportProcessorType.Simple)] + [Trait("CategoryName", "CollectorIntegrationTests")] + [Trait("CategoryName", "CollectorIntegrationTests")] + [SkipUnlessEnvVarFoundTheory(CollectorHostnameEnvVarName)] + public void ExportTrace_MTLS_NoClientCertificate_ReturnsFailureResult( + OtlpExportProtocol protocol, + string endpoint, + ExportProcessorType exportProcessorType) + { + var exporterOptions = new OtlpExporterOptions + { + Endpoint = new Uri($"https://{CollectorHostname}{endpoint}"), + Protocol = protocol, + ExportProcessorType = exportProcessorType, + BatchExportProcessorOptions = new() { - Assert.True(handle.WaitOne(ExportIntervalMilliseconds * 2)); - Assert.Single(exportResults); - Assert.Equal(ExportResult.Success, exportResults[0]); - } - } + ScheduledDelayMilliseconds = ExportIntervalMilliseconds, + }, + }; - if (!forceFlush && exportProcessorType == ExportProcessorType.Simple) - { - Assert.Single(exportResults); - Assert.Equal(ExportResult.Success, exportResults[0]); - } + this.VerifyTraceExportResult(exporterOptions, true, ExportResult.Failure); } [InlineData(OtlpExportProtocol.Grpc, ":4317", false, false)] @@ -237,6 +282,66 @@ public void ConstructingGrpcExporterFailsWhenHttp2UnencryptedSupportIsDisabledFo } } + private void VerifyTraceExportResult(OtlpExporterOptions exporterOptions, bool forceFlush, ExportResult expectedResult) + { + using EventWaitHandle handle = new ManualResetEvent(false); + + DelegatingExporter delegatingExporter = null; + var exportResults = new List(); + + var activitySourceName = "otlp.collector.test"; + + var builder = Sdk.CreateTracerProviderBuilder() + .AddSource(activitySourceName); + + builder.AddProcessor(OtlpTraceExporterHelperExtensions.BuildOtlpExporterProcessor( + exporterOptions, + DefaultSdkLimitOptions, + serviceProvider: null, + configureExporterInstance: otlpExporter => + { + delegatingExporter = new DelegatingExporter + { + OnExportFunc = (batch) => + { + var result = otlpExporter.Export(batch); + exportResults.Add(result); + handle.Set(); + return result; + }, + }; + return delegatingExporter; + })); + + using (var tracerProvider = builder.Build()) + { + using var source = new ActivitySource(activitySourceName); + var activity = source.StartActivity($"{exporterOptions.Protocol} Test Activity"); + activity?.Stop(); + + Assert.NotNull(delegatingExporter); + + if (forceFlush) + { + Assert.True(tracerProvider.ForceFlush()); + Assert.Single(exportResults); + Assert.Equal(expectedResult, exportResults[0]); + } + else if (exporterOptions.ExportProcessorType == ExportProcessorType.Batch) + { + Assert.True(handle.WaitOne(ExportIntervalMilliseconds * 2)); + Assert.Single(exportResults); + Assert.Equal(expectedResult, exportResults[0]); + } + } + + if (!forceFlush && exporterOptions.ExportProcessorType == ExportProcessorType.Simple) + { + Assert.Single(exportResults); + Assert.Equal(expectedResult, exportResults[0]); + } + } + private sealed class OpenTelemetryEventListener : EventListener { private readonly ITestOutputHelper outputHelper; diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/create-cert.sh b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/create-cert.sh index 24c6fe319cd..a59c5944042 100755 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/create-cert.sh +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/create-cert.sh @@ -11,6 +11,38 @@ cp /otel-collector.crt /otel-collector.key /cfg chmod 644 /cfg/otel-collector.key +# Generate client certificate for mTLS +echo "\ +basicConstraints = CA:FALSE +nsCertType = client, email +nsComment = "OpenSSL Generated Client Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +extendedKeyUsage = clientAuth, emailProtection +" > /client_ext.cnf + +openssl req -new -newkey rsa:2048 -days 365 -nodes \ + -subj "/CN=otel-client" \ + -keyout /otel-client.key -out /otel-client.csr + +openssl x509 -req -in /otel-client.csr \ + -CA /otel-collector.crt -CAkey /otel-collector.key \ + -out /otel-client.crt -CAcreateserial -days 365 -sha256 \ + -extfile ./client_ext.cnf + +cp /otel-client.crt /otel-client.key /cfg +chmod 644 /cfg/otel-client.key + +# Generate an self-signed certificate that is NOT included in the test runner's trust store +# Generate self-signed certificate for the collector +openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \ + -subj "/CN=otel-collector" \ + -keyout /otel-untrusted-collector.key -out /otel-untrusted-collector.crt + +cp /otel-untrusted-collector.crt /otel-untrusted-collector.key /cfg +chmod 644 /cfg/otel-untrusted-collector.key + # The integration test is run via docker-compose with the --exit-code-from # option. The --exit-code-from option implies --abort-on-container-exit # which means when any container exits then all containers are stopped. diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/otel-collector-config.yaml b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/otel-collector-config.yaml index 8dc68e98793..1d056f1f497 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/otel-collector-config.yaml +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/otel-collector-config.yaml @@ -23,6 +23,32 @@ receivers: tls: cert_file: /cfg/otel-collector.crt key_file: /cfg/otel-collector.key + otlp/untrustedtls: + protocols: + grpc: + endpoint: 0.0.0.0:6317 + tls: + cert_file: /cfg/otel-untrusted-collector.crt + key_file: /cfg/otel-untrusted-collector.key + http: + endpoint: 0.0.0.0:6318 + tls: + cert_file: /cfg/otel-untrusted-collector.crt + key_file: /cfg/otel-untrusted-collector.key + otlp/mtls: + protocols: + grpc: + endpoint: 0.0.0.0:7317 + tls: + cert_file: /cfg/otel-collector.crt + key_file: /cfg/otel-collector.key + client_ca_file: /cfg/otel-collector.crt + http: + endpoint: 0.0.0.0:7318 + tls: + cert_file: /cfg/otel-collector.crt + key_file: /cfg/otel-collector.key + client_ca_file: /cfg/otel-collector.crt exporters: logging: @@ -31,8 +57,8 @@ exporters: service: pipelines: traces: - receivers: [otlp, otlp/tls] + receivers: [otlp, otlp/tls, otlp/untrustedtls, otlp/mtls] exporters: [logging] metrics: - receivers: [otlp, otlp/tls] + receivers: [otlp, otlp/tls, otlp/untrustedtls, otlp/mtls] exporters: [logging] diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj index 98dc1773d3b..0b1f8d515d2 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests.csproj @@ -47,4 +47,20 @@ + + + + + + + + + + diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs index 798cab42667..892af9ab5c0 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsExtensionsTests.cs @@ -168,4 +168,25 @@ public void AppendPathIfNotPresent_TracesPath_AppendsCorrectly(string inputUri, Assert.Equal(expectedUri, resultUri.AbsoluteUri); } + + [Fact] + public void CreateGrpcChannel_WithCertificates_ReturnsChannelWithoutException() + { + var trustedCACertPath = Path.Combine(AppContext.BaseDirectory, "otel-test-ca-cert.pem"); + var certPath = Path.Combine(AppContext.BaseDirectory, "otel-test-client-cert.pem"); + var pKeyPath = Path.Combine(AppContext.BaseDirectory, "otel-test-client-key.pem"); + + var otlpOptions = new OtlpExporterOptions + { + CertificateFile = trustedCACertPath, + ClientCertificateFile = certPath, + ClientKeyFile = pKeyPath, + }; + +#if NETSTANDARD2_1 || NET6_0_OR_GREATER + using var channel = OtlpExporterOptionsExtensions.CreateChannel(otlpOptions); +#else + _ = OtlpExporterOptionsExtensions.CreateChannel(otlpOptions); +#endif + } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs index 472d3b87171..ae3e1f83b04 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpExporterOptionsTests.cs @@ -16,6 +16,10 @@ using Microsoft.Extensions.Configuration; using Xunit; +#if NET6_0_OR_GREATER +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +#endif namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests; @@ -63,6 +67,9 @@ public void OtlpExporterOptions_EnvironmentVariableOverride() Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, "A=2,B=3"); Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, "2000"); Environment.SetEnvironmentVariable(OtlpExporterOptions.ProtocolEnvVarName, "http/protobuf"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.ClientCertificateFileEnvVarName, "/path/to/my/certificate.pem"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.ClientKeyFileEnvVarName, "/path/to/my/key.pem"); + Environment.SetEnvironmentVariable(OtlpExporterOptions.CertificateFileEnvVarName, "/path/to/my/ca.pem"); var options = new OtlpExporterOptions(); @@ -70,6 +77,9 @@ public void OtlpExporterOptions_EnvironmentVariableOverride() Assert.Equal("A=2,B=3", options.Headers); Assert.Equal(2000, options.TimeoutMilliseconds); Assert.Equal(OtlpExportProtocol.HttpProtobuf, options.Protocol); + Assert.Equal("/path/to/my/certificate.pem", options.ClientCertificateFile); + Assert.Equal("/path/to/my/key.pem", options.ClientKeyFile); + Assert.Equal("/path/to/my/ca.pem", options.CertificateFile); } [Fact] @@ -81,6 +91,9 @@ public void OtlpExporterOptions_UsingIConfiguration() [OtlpExporterOptions.HeadersEnvVarName] = "A=2,B=3", [OtlpExporterOptions.TimeoutEnvVarName] = "2000", [OtlpExporterOptions.ProtocolEnvVarName] = "http/protobuf", + [OtlpExporterOptions.ClientCertificateFileEnvVarName] = "/path/to/my/certificate.pem", + [OtlpExporterOptions.ClientKeyFileEnvVarName] = "/path/to/my/key.pem", + [OtlpExporterOptions.CertificateFileEnvVarName] = "/path/to/my/ca.pem", }; var configuration = new ConfigurationBuilder() @@ -93,6 +106,9 @@ public void OtlpExporterOptions_UsingIConfiguration() Assert.Equal("A=2,B=3", options.Headers); Assert.Equal(2000, options.TimeoutMilliseconds); Assert.Equal(OtlpExportProtocol.HttpProtobuf, options.Protocol); + Assert.Equal("/path/to/my/certificate.pem", options.ClientCertificateFile); + Assert.Equal("/path/to/my/key.pem", options.ClientKeyFile); + Assert.Equal("/path/to/my/ca.pem", options.CertificateFile); } [Fact] @@ -160,11 +176,177 @@ public void OtlpExporterOptions_EnvironmentVariableNames() Assert.Equal("OTEL_EXPORTER_OTLP_PROTOCOL", OtlpExporterOptions.ProtocolEnvVarName); } + [Theory] +#if NET6_0_OR_GREATER + [InlineData(OtlpExportProtocol.HttpProtobuf)] +#endif + [InlineData(OtlpExportProtocol.Grpc)] + public void OtlpExporterOptions_ClientCertificateDoesNotExist_ThrowsException(OtlpExportProtocol protocol) + { + var certPath = Path.Combine(AppContext.BaseDirectory, "no", "such", "cert.pem"); + var pKeyPath = Path.Combine(AppContext.BaseDirectory, "no", "such", "key.pem"); + + var options = new OtlpExporterOptions + { + Protocol = protocol, + ClientCertificateFile = certPath, + ClientKeyFile = pKeyPath, + }; + + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetLogExportClient(options)); + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetMetricsExportClient(options)); + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetTraceExportClient(options)); + } + + [Theory] +#if NET6_0_OR_GREATER + [InlineData(OtlpExportProtocol.HttpProtobuf)] +#endif + [InlineData(OtlpExportProtocol.Grpc)] + public void OtlpExporterOptions_CertificateDoesNotExist_ThrowsException(OtlpExportProtocol protocol) + { + var caPath = Path.Combine(AppContext.BaseDirectory, "no", "such", "ca.pem"); + + var options = new OtlpExporterOptions + { + Protocol = protocol, + CertificateFile = caPath, + }; + + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetLogExportClient(options)); + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetMetricsExportClient(options)); + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetTraceExportClient(options)); + } + +#if !NET6_0_OR_GREATER + [Fact] + public void OtlpExporterOptions_ClientCertificateWithHttpProtobuf_ThrowsPlatformNotSupportedException() + { + var certPath = Path.Combine(AppContext.BaseDirectory, "otel-test-client-cert.pem"); + var pKeyPath = Path.Combine(AppContext.BaseDirectory, "otel-test-client-key.pem"); + + var options = new OtlpExporterOptions + { + Protocol = OtlpExportProtocol.HttpProtobuf, + ClientCertificateFile = certPath, + ClientKeyFile = pKeyPath, + }; + + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetLogExportClient(options)); + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetMetricsExportClient(options)); + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetTraceExportClient(options)); + } + + [Fact] + public void OtlpExporterOptions_CACertificateWithHttpProtobuf_ThrowsPlatformNotSupportedException() + { + var caPath = Path.Combine(AppContext.BaseDirectory, "my", "ca.pem"); + + var options = new OtlpExporterOptions + { + Protocol = OtlpExportProtocol.HttpProtobuf, + CertificateFile = caPath, + }; + + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetLogExportClient(options)); + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetMetricsExportClient(options)); + Assert.ThrowsAny(() => OtlpExporterOptionsExtensions.GetTraceExportClient(options)); + } +#endif + + [Fact] + public void OtlpExporterOptions_NoCertificate_DefaultHttpClientDoesnotHaveCertificate() + { + var values = new Dictionary() + { + [OtlpExporterOptions.EndpointEnvVarName] = "http://test:8888", + [OtlpExporterOptions.HeadersEnvVarName] = "A=2,B=3", + [OtlpExporterOptions.TimeoutEnvVarName] = "2000", + [OtlpExporterOptions.ProtocolEnvVarName] = "http/protobuf", + }; + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(values) + .Build(); + + var options = new OtlpExporterOptions(configuration, new()); + using var defaultHandler = options.CreateDefaultHttpMessageHandler(); + + Assert.Empty(defaultHandler.ClientCertificates); + } + +#if NET6_0_OR_GREATER + [Fact] + public void OtlpExporterOptions_WithCACertificate_AddsToHttpMessageHandlerTrustStore() + { + var caPath = Path.Combine(AppContext.BaseDirectory, "otel-test-ca-cert.pem"); + + using var serverCert = X509Certificate2.CreateFromPemFile( + certPemFilePath: Path.Combine(AppContext.BaseDirectory, "otel-test-server-cert.pem"), + keyPemFilePath: Path.Combine(AppContext.BaseDirectory, "otel-test-server-key.pem")); + + var values = new Dictionary() + { + [OtlpExporterOptions.EndpointEnvVarName] = "http://test:8888", + [OtlpExporterOptions.HeadersEnvVarName] = "A=2,B=3", + [OtlpExporterOptions.TimeoutEnvVarName] = "2000", + [OtlpExporterOptions.ProtocolEnvVarName] = "http/protobuf", + [OtlpExporterOptions.CertificateFileEnvVarName] = caPath, + }; + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(values) + .Build(); + + var options = new OtlpExporterOptions(configuration, new()); + using var defaultHandler = options.CreateDefaultHttpMessageHandler(); + + using var chain = new X509Chain(); + chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreWrongUsage; + + var serverCertValidationResult = defaultHandler.ServerCertificateCustomValidationCallback.Invoke( + new HttpRequestMessage(), serverCert, chain, SslPolicyErrors.RemoteCertificateChainErrors); + + Assert.True(serverCertValidationResult); + } + + [Fact] + public void OtlpExporterOptions_WithClientCertificate_PassesCertificateToDefaultHttpClient() + { + var certPath = Path.Combine(AppContext.BaseDirectory, "otel-test-client-cert.pem"); + var pKeyPath = Path.Combine(AppContext.BaseDirectory, "otel-test-client-key.pem"); + + var values = new Dictionary() + { + [OtlpExporterOptions.EndpointEnvVarName] = "http://test:8888", + [OtlpExporterOptions.HeadersEnvVarName] = "A=2,B=3", + [OtlpExporterOptions.TimeoutEnvVarName] = "2000", + [OtlpExporterOptions.ProtocolEnvVarName] = "http/protobuf", + [OtlpExporterOptions.ClientCertificateFileEnvVarName] = certPath, + [OtlpExporterOptions.ClientKeyFileEnvVarName] = pKeyPath, + }; + + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(values) + .Build(); + + var options = new OtlpExporterOptions(configuration, new()); + using var defaultHandler = options.CreateDefaultHttpMessageHandler(); + + Assert.Single(defaultHandler.ClientCertificates); + Assert.Contains("CN=otel-test-client", (defaultHandler.ClientCertificates[0] as X509Certificate2).Subject); + } +#endif + private static void ClearEnvVars() { Environment.SetEnvironmentVariable(OtlpExporterOptions.EndpointEnvVarName, null); Environment.SetEnvironmentVariable(OtlpExporterOptions.HeadersEnvVarName, null); Environment.SetEnvironmentVariable(OtlpExporterOptions.TimeoutEnvVarName, null); Environment.SetEnvironmentVariable(OtlpExporterOptions.ProtocolEnvVarName, null); + Environment.SetEnvironmentVariable(OtlpExporterOptions.CertificateFileEnvVarName, null); + Environment.SetEnvironmentVariable(OtlpExporterOptions.ClientCertificateFileEnvVarName, null); + Environment.SetEnvironmentVariable(OtlpExporterOptions.ClientKeyFileEnvVarName, null); } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.ps1 b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.ps1 new file mode 100644 index 00000000000..8c177463f69 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.ps1 @@ -0,0 +1,90 @@ +using namespace System.Security.Cryptography; +using namespace System.Security.Cryptography.X509Certificates; + +param ( + [string] $OutDir +) + +function Write-Certificate { + param ( + [X509Certificate2] $Cert, + [string] $Name, + [string] $Dir + ) + + # write cert content + $certPem = $Cert.ExportCertificatePem(); + $certPemPath = Join-Path $Dir -ChildPath "$Name-cert.pem"; + [System.IO.File]::WriteAllText($certPemPath, $certPem); + + # write pkey + [AsymmetricAlgorithm] $pkey = [RSACertificateExtensions]::GetRSAPrivateKey($Cert); + [string] $pkeyPem = $null; + + if ($null -ne $pkey) { + $pkeyPem = $pkey.ExportRSAPrivateKeyPem(); + } + + if ($null -eq $pkey) { + $pkey = [ECDsaCertificateExtensions]::GetECDsaPrivateKey($Cert); + $pkeyPem = $pkey.ExportECPrivateKeyPem(); + } + + if ($null -eq $pkeyPem) { + return; + } + + + $pKeyPath = Join-Path $Dir -ChildPath "$Name-key.pem"; + [System.IO.File]::WriteAllText($pKeyPath, $pkeyPem); +} + +$ca = New-SelfSignedCertificate -CertStoreLocation 'Cert:\CurrentUser\My' ` + -DnsName "otel-test-ca" ` + -NotAfter (Get-Date).AddYears(20) ` + -FriendlyName "otel-test-ca" ` + -KeyAlgorithm ECDSA_nistP256 ` + -KeyExportPolicy Exportable ` + -KeyUsageProperty All -KeyUsage CertSign, CRLSign, DigitalSignature; + + +try { + Write-Certificate -Cert $ca -Name "otel-test-ca" -Dir $OutDir; + $serverCert = New-SelfSignedCertificate -CertStoreLocation 'Cert:\CurrentUser\My' ` + -DnsName "otel-test-server" ` + -Signer $ca ` + -NotAfter (Get-Date).AddYears(20) ` + -FriendlyName "otel-test-server" ` + -KeyAlgorithm ECDSA_nistP256 ` + -KeyUsageProperty All ` + -KeyExportPolicy Exportable ` + -KeyUsage CertSign, CRLSign, DigitalSignature ` + -TextExtension @("2.5.29.19={text}CA=1&pathlength=1", "2.5.29.37={text}1.3.6.1.5.5.7.3.1"); + + try { + Write-Certificate -Cert $serverCert -Name "otel-test-server" -Dir $OutDir; + + $clientCert = New-SelfSignedCertificate -CertStoreLocation 'Cert:\CurrentUser\My' ` + -DnsName "otel-test-client" ` + -Signer $ca ` + -NotAfter (Get-Date).AddYears(20) ` + -FriendlyName "otel-test-client" ` + -KeyAlgorithm ECDSA_nistP256 ` + -KeyUsageProperty All ` + -KeyExportPolicy Exportable ` + -KeyUsage CertSign, CRLSign, DigitalSignature ` + -TextExtension @("2.5.29.19={text}CA=1&pathlength=1", "2.5.29.37={text}1.3.6.1.5.5.7.3.2"); + try { + Write-Certificate -Cert $clientCert -Name "otel-test-client" -Dir $OutDir; + } + finally { + Get-Item -Path "Cert:\CurrentUser\My\$($clientCert.Thumbprint)" | Remove-Item; + } + } + finally { + Get-Item -Path "Cert:\CurrentUser\My\$($serverCert.Thumbprint)" | Remove-Item; + } +} +finally { + Get-Item -Path "Cert:\CurrentUser\My\$($ca.Thumbprint)" | Remove-Item; +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.sh b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.sh new file mode 100644 index 00000000000..de69a4590f5 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/gen_test_cert.sh @@ -0,0 +1,46 @@ +# ca +openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \ + -subj "/CN=otel-test-ca" \ + -keyout $1/otel-test-ca-key.pem -out $1/otel-test-ca-cert.pem + +# server cert +echo "\ +basicConstraints = CA:FALSE +nsCertType = server +nsComment = "OpenSSL Generated Server Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +extendedKeyUsage = serverAuth +" > $1/server_cert_ext.cnf; + +openssl req -new -newkey rsa:2048 -sha256 \ + -keyout $1/otel-test-server-key.pem -out $1/otel-test-server-csr.pem -nodes \ + -subj "/CN=otel-test-server" + +openssl x509 -req -in $1/otel-test-server-csr.pem \ + -extfile $1/server_cert_ext.cnf \ + -CA $1/otel-test-ca-cert.pem -CAkey $1/otel-test-ca-key.pem -CAcreateserial \ + -out $1/otel-test-server-cert.pem \ + -days 3650 -sha256 + +# client cert +echo "\ +basicConstraints = CA:FALSE +nsCertType = client, email +nsComment = "OpenSSL Generated Client Certificate" +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment +extendedKeyUsage = clientAuth, emailProtection +" > $1/client_cert_ext.cnf; + +openssl req -new -newkey rsa:2048 -sha256 \ + -keyout $1/otel-test-client-key.pem -out $1/otel-test-client-csr.pem -nodes \ + -subj "/CN=otel-test-client" + +openssl x509 -req -in $1/otel-test-client-csr.pem \ + -extfile $1/client_cert_ext.cnf \ + -CA $1/otel-test-server-cert.pem -CAkey $1/otel-test-server-key.pem -CAcreateserial \ + -out $1/otel-test-client-cert.pem \ + -days 3650 -sha256