From 9a9cc91cb32a7b9cac120c1a346e3d8c35a4589e Mon Sep 17 00:00:00 2001 From: dhhoang Date: Sat, 12 Aug 2023 16:17:35 +0700 Subject: [PATCH] [OTLP] Implement TLS certificates integration tests --- .../IntegrationTest/.gitignore | 7 +- .../IntegrationTest/IntegrationTests.cs | 199 +++++++++++++----- .../IntegrationTest/create-cert.sh | 42 ++-- .../otel-collector-config.yaml | 24 ++- 4 files changed, 195 insertions(+), 77 deletions(-) diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore index a18c9aae62c..9fbcfae3843 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/.gitignore @@ -1,6 +1,7 @@ # Self-signed cert generated by integration test otel-collector.crt otel-collector.key -otel-client-cert.pem -otel-client-key.pem -otel-ca-cert.pem +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 ac93bd6e6ca..a59c5944042 100755 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/create-cert.sh +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/IntegrationTest/create-cert.sh @@ -11,21 +11,7 @@ cp /otel-collector.crt /otel-collector.key /cfg chmod 644 /cfg/otel-collector.key -# Generate CA and client cert for mTLS -echo "\ -basicConstraints = CA:FALSE -nsCertType = server -nsComment = "OpenSSL Generated CA Certificate" -subjectKeyIdentifier = hash -authorityKeyIdentifier = keyid,issuer:always -keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment -extendedKeyUsage = serverAuth -" > /ca_cert_ext.cnf - -openssl ecparam -genkey -name prime256v1 -out /otel-ca-key.pem -openssl req -new -sha256 -key /otel-ca-key.pem -out /otel-ca-csr.pem -subj "/CN=otel-test-ca" -openssl x509 -req -in /otel-ca-csr.pem -sha256 -days 365 -signkey /otel-ca-key.pem -out /otel-ca-cert.pem -extfile /ca_cert_ext.cnf - +# Generate client certificate for mTLS echo "\ basicConstraints = CA:FALSE nsCertType = client, email @@ -34,14 +20,28 @@ subjectKeyIdentifier = hash authorityKeyIdentifier = keyid,issuer keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment extendedKeyUsage = clientAuth, emailProtection -" > client_cert_ext.cnf +" > /client_ext.cnf -openssl ecparam -genkey -name prime256v1 -out /otel-client-key.pem -openssl req -new -key /otel-client-key.pem -out /otel-client-csr.pem -subj "/CN=otel-test-client" -openssl x509 -req -in /otel-client-csr.pem -CA /otel-ca-cert.pem -CAkey /otel-ca-key.pem -out /otel-client-cert.pem -CAcreateserial -days 365 -sha256 -extfile /client_cert_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-ca-cert.pem /otel-client-cert.pem /otel-client-key.pem /cfg -cp /otel-ca-cert.pem /usr/local/share/ca-certificates/otel-ca-cert.pem +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 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 c00d0d36767..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,20 +23,32 @@ receivers: tls: cert_file: /cfg/otel-collector.crt key_file: /cfg/otel-collector.key - otlp/mtls: + 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-ca-cert.pem + client_ca_file: /cfg/otel-collector.crt http: - endpoint: 0.0.0.0:6318 + endpoint: 0.0.0.0:7318 tls: cert_file: /cfg/otel-collector.crt key_file: /cfg/otel-collector.key - client_ca_file: /cfg/otel-ca-cert.pem + client_ca_file: /cfg/otel-collector.crt exporters: logging: @@ -45,8 +57,8 @@ exporters: service: pipelines: traces: - receivers: [otlp, otlp/tls, otlp/mtls] + receivers: [otlp, otlp/tls, otlp/untrustedtls, otlp/mtls] exporters: [logging] metrics: - receivers: [otlp, otlp/tls, otlp/mtls] + receivers: [otlp, otlp/tls, otlp/untrustedtls, otlp/mtls] exporters: [logging]