From bc33fc3238117197d7b81f5704f6d4d3e695d7dc Mon Sep 17 00:00:00 2001 From: Anoop Babu Date: Mon, 22 Jan 2024 18:52:29 -0500 Subject: [PATCH 1/5] Saving changes to switch to another branch --- Handler.cs | 40 +++++++ Http | 0 ServerCertificateValidationProvider.cs | 153 +++++++++++++++++++++++++ opentelemetry-dotnet-contrib.sln | 7 ++ 4 files changed, 200 insertions(+) create mode 100644 Handler.cs create mode 100644 Http create mode 100644 ServerCertificateValidationProvider.cs diff --git a/Handler.cs b/Handler.cs new file mode 100644 index 0000000000..65b396df75 --- /dev/null +++ b/Handler.cs @@ -0,0 +1,40 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if !NETFRAMEWORK + +using System; +using System.Net.Http; + +namespace OpenTelemetry.ResourceDetector; + +internal class Handler +{ + public static HttpClientHandler? Create(string certificateFile) + { + try + { + ServerCertificateValidationProvider? serverCertificateValidationProvider = + ServerCertificateValidationProvider.FromCertificateFile(certificateFile); + + if (serverCertificateValidationProvider == null) + { + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(Handler), "Failed to Load the certificate file into trusted collection"); + return null; + } + + var clientHandler = new HttpClientHandler(); + clientHandler.ServerCertificateCustomValidationCallback = + (sender, x509Certificate2, x509Chain, sslPolicyErrors) => + serverCertificateValidationProvider.ValidationCallback(sender, x509Certificate2, x509Chain, sslPolicyErrors); + return clientHandler; + } + catch (Exception ex) + { + AWSResourcesEventSource.Log.ResourceAttributesExtractException($"{nameof(Handler)} : Failed to create HttpClientHandler", ex); + } + + return null; + } +} +#endif diff --git a/Http b/Http new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ServerCertificateValidationProvider.cs b/ServerCertificateValidationProvider.cs new file mode 100644 index 0000000000..137c811610 --- /dev/null +++ b/ServerCertificateValidationProvider.cs @@ -0,0 +1,153 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if !NETFRAMEWORK + +using System; +using System.IO; +using System.Linq; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace OpenTelemetry.ResourceDetector; + +internal class ServerCertificateValidationProvider +{ + private readonly X509Certificate2Collection trustedCertificates; + + private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates) + { + this.trustedCertificates = trustedCertificates; + this.ValidationCallback = (_, cert, chain, errors) => + this.ValidateCertificate(cert != null ? new X509Certificate2(cert) : null, chain, errors); + } + + public RemoteCertificateValidationCallback ValidationCallback { get; } + + public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile) + { + if (!File.Exists(certificateFile)) + { + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate File does not exist"); + return null; + } + + var trustedCertificates = new X509Certificate2Collection(); + if (!LoadCertificateToTrustedCollection(trustedCertificates, certificateFile)) + { + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection"); + return null; + } + + return new ServerCertificateValidationProvider(trustedCertificates); + } + + private static bool LoadCertificateToTrustedCollection(X509Certificate2Collection collection, string certFileName) + { + try + { + collection.Import(certFileName); + return true; + } + catch (Exception) + { + return false; + } + } + + private static bool HasCommonCertificate(X509Chain chain, X509Certificate2Collection? collection) + { + if (collection == null) + { + return false; + } + + foreach (var chainElement in chain.ChainElements) + { + foreach (var certificate in collection) + { + if (chainElement.Certificate.GetPublicKey().SequenceEqual(certificate.GetPublicKey())) + { + return true; + } + } + } + + return false; + } + + private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) + { + var isSslPolicyPassed = errors == SslPolicyErrors.None || + errors == SslPolicyErrors.RemoteCertificateChainErrors; + if (!isSslPolicyPassed) + { + if ((errors | SslPolicyErrors.RemoteCertificateNotAvailable) == errors) + { + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNotAvailable"); + } + + if ((errors | SslPolicyErrors.RemoteCertificateNameMismatch) == errors) + { + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNameMismatch"); + } + } + + if (chain == null) + { + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate chain is null."); + return false; + } + + if (cert == null) + { + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate is null."); + return false; + } + + chain.ChainPolicy.ExtraStore.AddRange(this.trustedCertificates); + chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + + // building the chain to process basic validations e.g. signature, use, expiration, revocation + var isValidChain = chain.Build(cert); + + if (!isValidChain) + { + var chainErrors = string.Empty; + foreach (var element in chain.ChainElements) + { + foreach (var status in element.ChainElementStatus) + { + chainErrors += + $"\nCertificate [{element.Certificate.Subject}] Status [{status.Status}]: {status.StatusInformation}"; + } + } + + AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), $"Failed to validate certificate due to {chainErrors}"); + } + + // check if at least one certificate in the chain is in our trust list + var isTrusted = HasCommonCertificate(chain, this.trustedCertificates); + if (!isTrusted) + { + var serverCertificates = string.Empty; + foreach (var element in chain.ChainElements) + { + serverCertificates += " " + element.Certificate.Subject; + } + + var trustCertificates = string.Empty; + foreach (var trustCertificate in this.trustedCertificates) + { + trustCertificates += " " + trustCertificate.Subject; + } + + AWSResourcesEventSource.Log.FailedToValidateCertificate( + nameof(ServerCertificateValidationProvider), + $"Server Certificates Chain cannot be trusted. The chain doesn't match with the Trusted Certificates provided. Server Certificates:{serverCertificates}. Trusted Certificates:{trustCertificates}"); + } + + return isSslPolicyPassed && isValidChain && isTrusted; + } +} +#endif diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln index 09fac45779..3f8115e97b 100644 --- a/opentelemetry-dotnet-contrib.sln +++ b/opentelemetry-dotnet-contrib.sln @@ -341,6 +341,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.ResourceDetec EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.ResourceDetectors.Host.Tests", "test\OpenTelemetry.ResourceDetectors.Host.Tests\OpenTelemetry.ResourceDetectors.Host.Tests.csproj", "{36271347-2055-438E-9659-B71542A17A73}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http", "Http", "{C6D9B83F-4080-4CF9-A09E-2480B4420D20}" + ProjectSection(SolutionItems) = preProject + Handler.cs = Handler.cs + ServerCertificateValidationProvider.cs = ServerCertificateValidationProvider.cs + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -785,6 +791,7 @@ Global {A5EF701C-439E-407F-8BB4-394166000C6D} = {2097345F-4DD3-477D-BC54-A922F9B2B402} {033CA8D4-1529-413A-B244-07958D5F9A48} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63} {36271347-2055-438E-9659-B71542A17A73} = {2097345F-4DD3-477D-BC54-A922F9B2B402} + {C6D9B83F-4080-4CF9-A09E-2480B4420D20} = {1FCC8EEC-9E75-4FEA-AFCF-363DD33FF0B9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B0816796-CDB3-47D7-8C3C-946434DE3B66} From 5a5e782c3586842a3157bcff21150dd3b4ba43fb Mon Sep 17 00:00:00 2001 From: Anoop Babu Date: Thu, 25 Jan 2024 21:56:09 -0500 Subject: [PATCH 2/5] Fully adjusted move to shared --- Handler.cs | 40 ----- Http | 0 ServerCertificateValidationProvider.cs | 153 ------------------ opentelemetry-dotnet-contrib.sln | 10 +- .../AWSEKSResourceDetector.cs | 3 +- .../AWSResourcesEventSource.cs | 6 - .../Http/Handler.cs | 40 ----- .../ServerCertificateValidationProvider.cs | 153 ------------------ ...OpenTelemetry.ResourceDetectors.AWS.csproj | 3 + .../ServerCertificateValidationEventSource.cs | 28 ++++ .../ServerCertificateValidationHandler.cs | 42 +++++ .../ServerCertificateValidationProvider.cs | 153 ++++++++++++++++++ .../Http/HandlerTests.cs | 5 +- ...erverCertificateValidationProviderTests.cs | 1 - 14 files changed, 232 insertions(+), 405 deletions(-) delete mode 100644 Handler.cs delete mode 100644 Http delete mode 100644 ServerCertificateValidationProvider.cs delete mode 100644 src/OpenTelemetry.ResourceDetectors.AWS/Http/Handler.cs delete mode 100644 src/OpenTelemetry.ResourceDetectors.AWS/Http/ServerCertificateValidationProvider.cs create mode 100644 src/Shared/ServerCertificateValidationEventSource.cs create mode 100644 src/Shared/ServerCertificateValidationHandler.cs create mode 100644 src/Shared/ServerCertificateValidationProvider.cs diff --git a/Handler.cs b/Handler.cs deleted file mode 100644 index 65b396df75..0000000000 --- a/Handler.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if !NETFRAMEWORK - -using System; -using System.Net.Http; - -namespace OpenTelemetry.ResourceDetector; - -internal class Handler -{ - public static HttpClientHandler? Create(string certificateFile) - { - try - { - ServerCertificateValidationProvider? serverCertificateValidationProvider = - ServerCertificateValidationProvider.FromCertificateFile(certificateFile); - - if (serverCertificateValidationProvider == null) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(Handler), "Failed to Load the certificate file into trusted collection"); - return null; - } - - var clientHandler = new HttpClientHandler(); - clientHandler.ServerCertificateCustomValidationCallback = - (sender, x509Certificate2, x509Chain, sslPolicyErrors) => - serverCertificateValidationProvider.ValidationCallback(sender, x509Certificate2, x509Chain, sslPolicyErrors); - return clientHandler; - } - catch (Exception ex) - { - AWSResourcesEventSource.Log.ResourceAttributesExtractException($"{nameof(Handler)} : Failed to create HttpClientHandler", ex); - } - - return null; - } -} -#endif diff --git a/Http b/Http deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ServerCertificateValidationProvider.cs b/ServerCertificateValidationProvider.cs deleted file mode 100644 index 137c811610..0000000000 --- a/ServerCertificateValidationProvider.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if !NETFRAMEWORK - -using System; -using System.IO; -using System.Linq; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; - -namespace OpenTelemetry.ResourceDetector; - -internal class ServerCertificateValidationProvider -{ - private readonly X509Certificate2Collection trustedCertificates; - - private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates) - { - this.trustedCertificates = trustedCertificates; - this.ValidationCallback = (_, cert, chain, errors) => - this.ValidateCertificate(cert != null ? new X509Certificate2(cert) : null, chain, errors); - } - - public RemoteCertificateValidationCallback ValidationCallback { get; } - - public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile) - { - if (!File.Exists(certificateFile)) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate File does not exist"); - return null; - } - - var trustedCertificates = new X509Certificate2Collection(); - if (!LoadCertificateToTrustedCollection(trustedCertificates, certificateFile)) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection"); - return null; - } - - return new ServerCertificateValidationProvider(trustedCertificates); - } - - private static bool LoadCertificateToTrustedCollection(X509Certificate2Collection collection, string certFileName) - { - try - { - collection.Import(certFileName); - return true; - } - catch (Exception) - { - return false; - } - } - - private static bool HasCommonCertificate(X509Chain chain, X509Certificate2Collection? collection) - { - if (collection == null) - { - return false; - } - - foreach (var chainElement in chain.ChainElements) - { - foreach (var certificate in collection) - { - if (chainElement.Certificate.GetPublicKey().SequenceEqual(certificate.GetPublicKey())) - { - return true; - } - } - } - - return false; - } - - private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) - { - var isSslPolicyPassed = errors == SslPolicyErrors.None || - errors == SslPolicyErrors.RemoteCertificateChainErrors; - if (!isSslPolicyPassed) - { - if ((errors | SslPolicyErrors.RemoteCertificateNotAvailable) == errors) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNotAvailable"); - } - - if ((errors | SslPolicyErrors.RemoteCertificateNameMismatch) == errors) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNameMismatch"); - } - } - - if (chain == null) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate chain is null."); - return false; - } - - if (cert == null) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate is null."); - return false; - } - - chain.ChainPolicy.ExtraStore.AddRange(this.trustedCertificates); - chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; - - // building the chain to process basic validations e.g. signature, use, expiration, revocation - var isValidChain = chain.Build(cert); - - if (!isValidChain) - { - var chainErrors = string.Empty; - foreach (var element in chain.ChainElements) - { - foreach (var status in element.ChainElementStatus) - { - chainErrors += - $"\nCertificate [{element.Certificate.Subject}] Status [{status.Status}]: {status.StatusInformation}"; - } - } - - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), $"Failed to validate certificate due to {chainErrors}"); - } - - // check if at least one certificate in the chain is in our trust list - var isTrusted = HasCommonCertificate(chain, this.trustedCertificates); - if (!isTrusted) - { - var serverCertificates = string.Empty; - foreach (var element in chain.ChainElements) - { - serverCertificates += " " + element.Certificate.Subject; - } - - var trustCertificates = string.Empty; - foreach (var trustCertificate in this.trustedCertificates) - { - trustCertificates += " " + trustCertificate.Subject; - } - - AWSResourcesEventSource.Log.FailedToValidateCertificate( - nameof(ServerCertificateValidationProvider), - $"Server Certificates Chain cannot be trusted. The chain doesn't match with the Trusted Certificates provided. Server Certificates:{serverCertificates}. Trusted Certificates:{trustCertificates}"); - } - - return isSslPolicyPassed && isValidChain && isTrusted; - } -} -#endif diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln index 3f8115e97b..a15696ddda 100644 --- a/opentelemetry-dotnet-contrib.sln +++ b/opentelemetry-dotnet-contrib.sln @@ -283,6 +283,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E src\Shared\PropertyFetcher.cs = src\Shared\PropertyFetcher.cs src\Shared\ResourceSemanticConventions.cs = src\Shared\ResourceSemanticConventions.cs src\Shared\SemanticConventions.cs = src\Shared\SemanticConventions.cs + src\Shared\ServerCertificateValidationEventSource.cs = src\Shared\ServerCertificateValidationEventSource.cs + src\Shared\ServerCertificateValidationHandler.cs = src\Shared\ServerCertificateValidationHandler.cs + src\Shared\ServerCertificateValidationProvider.cs = src\Shared\ServerCertificateValidationProvider.cs src\Shared\ServiceProviderExtensions.cs = src\Shared\ServiceProviderExtensions.cs src\Shared\SpanAttributeConstants.cs = src\Shared\SpanAttributeConstants.cs src\Shared\SpanHelper.cs = src\Shared\SpanHelper.cs @@ -341,12 +344,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.ResourceDetec EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.ResourceDetectors.Host.Tests", "test\OpenTelemetry.ResourceDetectors.Host.Tests\OpenTelemetry.ResourceDetectors.Host.Tests.csproj", "{36271347-2055-438E-9659-B71542A17A73}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http", "Http", "{C6D9B83F-4080-4CF9-A09E-2480B4420D20}" - ProjectSection(SolutionItems) = preProject - Handler.cs = Handler.cs - ServerCertificateValidationProvider.cs = ServerCertificateValidationProvider.cs - EndProjectSection -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -791,7 +788,6 @@ Global {A5EF701C-439E-407F-8BB4-394166000C6D} = {2097345F-4DD3-477D-BC54-A922F9B2B402} {033CA8D4-1529-413A-B244-07958D5F9A48} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63} {36271347-2055-438E-9659-B71542A17A73} = {2097345F-4DD3-477D-BC54-A922F9B2B402} - {C6D9B83F-4080-4CF9-A09E-2480B4420D20} = {1FCC8EEC-9E75-4FEA-AFCF-363DD33FF0B9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B0816796-CDB3-47D7-8C3C-946434DE3B66} diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs index 82e4ea0170..b1a8611c5b 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Net.Http; using System.Text; -using OpenTelemetry.ResourceDetectors.AWS.Http; using OpenTelemetry.ResourceDetectors.AWS.Models; using OpenTelemetry.Resources; @@ -31,7 +30,7 @@ public sealed class AWSEKSResourceDetector : IResourceDetector public Resource Detect() { var credentials = GetEKSCredentials(AWSEKSCredentialPath); - using var httpClientHandler = Handler.Create(AWSEKSCertificatePath); + using var httpClientHandler = ServerCertificateValidationHandler.Create(AWSEKSCertificatePath); if (credentials == null || !IsEKSProcess(credentials, httpClientHandler)) { diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs index 4238241190..1b7eaad006 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs @@ -26,10 +26,4 @@ public void FailedToExtractResourceAttributes(string format, string exception) { this.WriteEvent(3, format, exception); } - - [Event(2, Message = "Failed to validate certificate in format: '{0}', error: '{1}'.", Level = EventLevel.Warning)] - public void FailedToValidateCertificate(string format, string error) - { - this.WriteEvent(4, format, error); - } } diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/Http/Handler.cs b/src/OpenTelemetry.ResourceDetectors.AWS/Http/Handler.cs deleted file mode 100644 index 5e709733b1..0000000000 --- a/src/OpenTelemetry.ResourceDetectors.AWS/Http/Handler.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if !NETFRAMEWORK - -using System; -using System.Net.Http; - -namespace OpenTelemetry.ResourceDetectors.AWS.Http; - -internal class Handler -{ - public static HttpClientHandler? Create(string certificateFile) - { - try - { - ServerCertificateValidationProvider? serverCertificateValidationProvider = - ServerCertificateValidationProvider.FromCertificateFile(certificateFile); - - if (serverCertificateValidationProvider == null) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(Handler), "Failed to Load the certificate file into trusted collection"); - return null; - } - - var clientHandler = new HttpClientHandler(); - clientHandler.ServerCertificateCustomValidationCallback = - (sender, x509Certificate2, x509Chain, sslPolicyErrors) => - serverCertificateValidationProvider.ValidationCallback(sender, x509Certificate2, x509Chain, sslPolicyErrors); - return clientHandler; - } - catch (Exception ex) - { - AWSResourcesEventSource.Log.ResourceAttributesExtractException($"{nameof(Handler)} : Failed to create HttpClientHandler", ex); - } - - return null; - } -} -#endif diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/Http/ServerCertificateValidationProvider.cs b/src/OpenTelemetry.ResourceDetectors.AWS/Http/ServerCertificateValidationProvider.cs deleted file mode 100644 index 1538215391..0000000000 --- a/src/OpenTelemetry.ResourceDetectors.AWS/Http/ServerCertificateValidationProvider.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if !NETFRAMEWORK - -using System; -using System.IO; -using System.Linq; -using System.Net.Security; -using System.Security.Cryptography.X509Certificates; - -namespace OpenTelemetry.ResourceDetectors.AWS.Http; - -internal class ServerCertificateValidationProvider -{ - private readonly X509Certificate2Collection trustedCertificates; - - private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates) - { - this.trustedCertificates = trustedCertificates; - this.ValidationCallback = (_, cert, chain, errors) => - this.ValidateCertificate(cert != null ? new X509Certificate2(cert) : null, chain, errors); - } - - public RemoteCertificateValidationCallback ValidationCallback { get; } - - public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile) - { - if (!File.Exists(certificateFile)) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate File does not exist"); - return null; - } - - var trustedCertificates = new X509Certificate2Collection(); - if (!LoadCertificateToTrustedCollection(trustedCertificates, certificateFile)) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection"); - return null; - } - - return new ServerCertificateValidationProvider(trustedCertificates); - } - - private static bool LoadCertificateToTrustedCollection(X509Certificate2Collection collection, string certFileName) - { - try - { - collection.Import(certFileName); - return true; - } - catch (Exception) - { - return false; - } - } - - private static bool HasCommonCertificate(X509Chain chain, X509Certificate2Collection? collection) - { - if (collection == null) - { - return false; - } - - foreach (var chainElement in chain.ChainElements) - { - foreach (var certificate in collection) - { - if (chainElement.Certificate.GetPublicKey().SequenceEqual(certificate.GetPublicKey())) - { - return true; - } - } - } - - return false; - } - - private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) - { - var isSslPolicyPassed = errors == SslPolicyErrors.None || - errors == SslPolicyErrors.RemoteCertificateChainErrors; - if (!isSslPolicyPassed) - { - if ((errors | SslPolicyErrors.RemoteCertificateNotAvailable) == errors) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNotAvailable"); - } - - if ((errors | SslPolicyErrors.RemoteCertificateNameMismatch) == errors) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNameMismatch"); - } - } - - if (chain == null) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate chain is null."); - return false; - } - - if (cert == null) - { - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate is null."); - return false; - } - - chain.ChainPolicy.ExtraStore.AddRange(this.trustedCertificates); - chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; - - // building the chain to process basic validations e.g. signature, use, expiration, revocation - var isValidChain = chain.Build(cert); - - if (!isValidChain) - { - var chainErrors = string.Empty; - foreach (var element in chain.ChainElements) - { - foreach (var status in element.ChainElementStatus) - { - chainErrors += - $"\nCertificate [{element.Certificate.Subject}] Status [{status.Status}]: {status.StatusInformation}"; - } - } - - AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), $"Failed to validate certificate due to {chainErrors}"); - } - - // check if at least one certificate in the chain is in our trust list - var isTrusted = HasCommonCertificate(chain, this.trustedCertificates); - if (!isTrusted) - { - var serverCertificates = string.Empty; - foreach (var element in chain.ChainElements) - { - serverCertificates += " " + element.Certificate.Subject; - } - - var trustCertificates = string.Empty; - foreach (var trustCertificate in this.trustedCertificates) - { - trustCertificates += " " + trustCertificate.Subject; - } - - AWSResourcesEventSource.Log.FailedToValidateCertificate( - nameof(ServerCertificateValidationProvider), - $"Server Certificates Chain cannot be trusted. The chain doesn't match with the Trusted Certificates provided. Server Certificates:{serverCertificates}. Trusted Certificates:{trustCertificates}"); - } - - return isSslPolicyPassed && isValidChain && isTrusted; - } -} -#endif diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj index b5ad0ad470..7800cccf4b 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj +++ b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj @@ -23,6 +23,9 @@ + + + diff --git a/src/Shared/ServerCertificateValidationEventSource.cs b/src/Shared/ServerCertificateValidationEventSource.cs new file mode 100644 index 0000000000..854e813a5c --- /dev/null +++ b/src/Shared/ServerCertificateValidationEventSource.cs @@ -0,0 +1,28 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if !NETFRAMEWORK + +using System.Diagnostics.Tracing; + +namespace OpenTelemetry.ResourceDetectors; + +[EventSource(Name = "OpenTelemetry-ResourceDetectors-Http")] +internal class ServerCertificateValidationEventSource : EventSource +{ + public static ServerCertificateValidationEventSource Log = new(); + + [Event(1, Message = "Failed to extract resource attributes in '{0}'.", Level = EventLevel.Warning)] + public void FailedToExtractResourceAttributes(string format, string exception) + { + this.WriteEvent(3, format, exception); + } + + [Event(2, Message = "Failed to validate certificate in format: '{0}', error: '{1}'.", Level = EventLevel.Warning)] + public void FailedToValidateCertificate(string format, string error) + { + this.WriteEvent(4, format, error); + } +} + +#endif diff --git a/src/Shared/ServerCertificateValidationHandler.cs b/src/Shared/ServerCertificateValidationHandler.cs new file mode 100644 index 0000000000..551b7b1645 --- /dev/null +++ b/src/Shared/ServerCertificateValidationHandler.cs @@ -0,0 +1,42 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if !NETFRAMEWORK + +using System; +using System.Net.Http; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.ResourceDetectors; + +internal class ServerCertificateValidationHandler +{ + public static HttpClientHandler? Create(string certificateFile) + { + try + { + ServerCertificateValidationProvider? serverCertificateValidationProvider = ServerCertificateValidationProvider.FromCertificateFile(certificateFile); + + if (serverCertificateValidationProvider == null) + { + ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationHandler), "Failed to Load the certificate file into trusted collection"); + return null; + } + + var clientHandler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = + (sender, x509Certificate2, x509Chain, sslPolicyErrors) => + serverCertificateValidationProvider.ValidationCallback(sender, x509Certificate2, x509Chain, sslPolicyErrors), + }; + return clientHandler; + } + catch (Exception ex) + { + ServerCertificateValidationEventSource.Log.FailedToExtractResourceAttributes($"{nameof(ServerCertificateValidationHandler)} : Failed to create HttpClientHandler", ex.ToInvariantString()); + } + + return null; + } +} +#endif diff --git a/src/Shared/ServerCertificateValidationProvider.cs b/src/Shared/ServerCertificateValidationProvider.cs new file mode 100644 index 0000000000..d036c9d5b9 --- /dev/null +++ b/src/Shared/ServerCertificateValidationProvider.cs @@ -0,0 +1,153 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if !NETFRAMEWORK + +using System; +using System.IO; +using System.Linq; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +namespace OpenTelemetry.ResourceDetectors; + +internal class ServerCertificateValidationProvider +{ + private readonly X509Certificate2Collection trustedCertificates; + + private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates) + { + this.trustedCertificates = trustedCertificates; + this.ValidationCallback = (_, cert, chain, errors) => + this.ValidateCertificate(cert != null ? new X509Certificate2(cert) : null, chain, errors); + } + + public RemoteCertificateValidationCallback ValidationCallback { get; } + + public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile) + { + if (!File.Exists(certificateFile)) + { + ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate File does not exist"); + return null; + } + + var trustedCertificates = new X509Certificate2Collection(); + if (!LoadCertificateToTrustedCollection(trustedCertificates, certificateFile)) + { + ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection"); + return null; + } + + return new ServerCertificateValidationProvider(trustedCertificates); + } + + private static bool LoadCertificateToTrustedCollection(X509Certificate2Collection collection, string certFileName) + { + try + { + collection.Import(certFileName); + return true; + } + catch (Exception) + { + return false; + } + } + + private static bool HasCommonCertificate(X509Chain chain, X509Certificate2Collection? collection) + { + if (collection == null) + { + return false; + } + + foreach (var chainElement in chain.ChainElements) + { + foreach (var certificate in collection) + { + if (chainElement.Certificate.GetPublicKey().SequenceEqual(certificate.GetPublicKey())) + { + return true; + } + } + } + + return false; + } + + private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) + { + var isSslPolicyPassed = errors == SslPolicyErrors.None || + errors == SslPolicyErrors.RemoteCertificateChainErrors; + if (!isSslPolicyPassed) + { + if ((errors | SslPolicyErrors.RemoteCertificateNotAvailable) == errors) + { + ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNotAvailable"); + } + + if ((errors | SslPolicyErrors.RemoteCertificateNameMismatch) == errors) + { + ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNameMismatch"); + } + } + + if (chain == null) + { + ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate chain is null."); + return false; + } + + if (cert == null) + { + ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate is null."); + return false; + } + + chain.ChainPolicy.ExtraStore.AddRange(this.trustedCertificates); + chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + + // building the chain to process basic validations e.g. signature, use, expiration, revocation + var isValidChain = chain.Build(cert); + + if (!isValidChain) + { + var chainErrors = string.Empty; + foreach (var element in chain.ChainElements) + { + foreach (var status in element.ChainElementStatus) + { + chainErrors += + $"\nCertificate [{element.Certificate.Subject}] Status [{status.Status}]: {status.StatusInformation}"; + } + } + + ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), $"Failed to validate certificate due to {chainErrors}"); + } + + // check if at least one certificate in the chain is in our trust list + var isTrusted = HasCommonCertificate(chain, this.trustedCertificates); + if (!isTrusted) + { + var serverCertificates = string.Empty; + foreach (var element in chain.ChainElements) + { + serverCertificates += " " + element.Certificate.Subject; + } + + var trustCertificates = string.Empty; + foreach (var trustCertificate in this.trustedCertificates) + { + trustCertificates += " " + trustCertificate.Subject; + } + + ServerCertificateValidationEventSource.Log.FailedToValidateCertificate( + nameof(ServerCertificateValidationProvider), + $"Server Certificates Chain cannot be trusted. The chain doesn't match with the Trusted Certificates provided. Server Certificates:{serverCertificates}. Trusted Certificates:{trustCertificates}"); + } + + return isSslPolicyPassed && isValidChain && isTrusted; + } +} +#endif diff --git a/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/HandlerTests.cs b/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/HandlerTests.cs index b7c16d64ca..a209ff07c3 100644 --- a/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/HandlerTests.cs +++ b/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/HandlerTests.cs @@ -3,7 +3,6 @@ #if !NETFRAMEWORK -using OpenTelemetry.ResourceDetectors.AWS.Http; using Xunit; namespace OpenTelemetry.ResourceDetectors.AWS.Tests.Http; @@ -20,7 +19,7 @@ public void TestValidHandler() certificateUploader.Create(); // Validates if the handler created. - Assert.NotNull(Handler.Create(certificateUploader.FilePath)); + Assert.NotNull(ServerCertificateValidationHandler.Create(certificateUploader.FilePath)); } } @@ -28,7 +27,7 @@ public void TestValidHandler() public void TestInValidHandler() { // Validates if the handler created if no certificate is loaded into the trusted collection - Assert.Null(Handler.Create(INVALIDCRTNAME)); + Assert.Null(ServerCertificateValidationHandler.Create(INVALIDCRTNAME)); } } diff --git a/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/ServerCertificateValidationProviderTests.cs b/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/ServerCertificateValidationProviderTests.cs index 008107b52e..81edbd8791 100644 --- a/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/ServerCertificateValidationProviderTests.cs +++ b/test/OpenTelemetry.ResourceDetectors.AWS.Tests/Http/ServerCertificateValidationProviderTests.cs @@ -4,7 +4,6 @@ #if !NETFRAMEWORK using System.Security.Cryptography.X509Certificates; -using OpenTelemetry.ResourceDetectors.AWS.Http; using Xunit; namespace OpenTelemetry.ResourceDetectors.AWS.Tests.Http; From 81ace52fc3e92bcf4664fa305eadb9958d040540 Mon Sep 17 00:00:00 2001 From: Anoop Babu Date: Mon, 29 Jan 2024 18:51:15 -0500 Subject: [PATCH 3/5] Removing shared event source entirely, and using invoked actions for logging instead --- opentelemetry-dotnet-contrib.sln | 1 - .../AWSEKSResourceDetector.cs | 3 +- .../AWSResourcesEventSource.cs | 6 + ...OpenTelemetry.ResourceDetectors.AWS.csproj | 1 - .../ServerCertificateValidationHandler.cs | 8 +- .../ServerCertificateValidationProvider.cs | 215 +++++++++--------- 6 files changed, 121 insertions(+), 113 deletions(-) diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln index a15696ddda..9f02511871 100644 --- a/opentelemetry-dotnet-contrib.sln +++ b/opentelemetry-dotnet-contrib.sln @@ -283,7 +283,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E src\Shared\PropertyFetcher.cs = src\Shared\PropertyFetcher.cs src\Shared\ResourceSemanticConventions.cs = src\Shared\ResourceSemanticConventions.cs src\Shared\SemanticConventions.cs = src\Shared\SemanticConventions.cs - src\Shared\ServerCertificateValidationEventSource.cs = src\Shared\ServerCertificateValidationEventSource.cs src\Shared\ServerCertificateValidationHandler.cs = src\Shared\ServerCertificateValidationHandler.cs src\Shared\ServerCertificateValidationProvider.cs = src\Shared\ServerCertificateValidationProvider.cs src\Shared\ServiceProviderExtensions.cs = src\Shared\ServiceProviderExtensions.cs diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs index b1a8611c5b..0c72ffebcf 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs @@ -30,7 +30,8 @@ public sealed class AWSEKSResourceDetector : IResourceDetector public Resource Detect() { var credentials = GetEKSCredentials(AWSEKSCredentialPath); - using var httpClientHandler = ServerCertificateValidationHandler.Create(AWSEKSCertificatePath); + using var httpClientHandler = ServerCertificateValidationHandler.Create( + AWSEKSCertificatePath, AWSResourcesEventSource.Log.FailedToValidateCertificate, AWSResourcesEventSource.Log.FailedToExtractResourceAttributes); if (credentials == null || !IsEKSProcess(credentials, httpClientHandler)) { diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs index 1b7eaad006..4238241190 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs @@ -26,4 +26,10 @@ public void FailedToExtractResourceAttributes(string format, string exception) { this.WriteEvent(3, format, exception); } + + [Event(2, Message = "Failed to validate certificate in format: '{0}', error: '{1}'.", Level = EventLevel.Warning)] + public void FailedToValidateCertificate(string format, string error) + { + this.WriteEvent(4, format, error); + } } diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj index 7800cccf4b..8b87636a88 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj +++ b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj @@ -25,7 +25,6 @@ - diff --git a/src/Shared/ServerCertificateValidationHandler.cs b/src/Shared/ServerCertificateValidationHandler.cs index 551b7b1645..aca8d4d29c 100644 --- a/src/Shared/ServerCertificateValidationHandler.cs +++ b/src/Shared/ServerCertificateValidationHandler.cs @@ -11,15 +11,15 @@ namespace OpenTelemetry.ResourceDetectors; internal class ServerCertificateValidationHandler { - public static HttpClientHandler? Create(string certificateFile) + public static HttpClientHandler? Create(string certificateFile, Action? logFailedToValidateCertificate = null, Action? logFailedToExtractResourceAttributes = null) { try { - ServerCertificateValidationProvider? serverCertificateValidationProvider = ServerCertificateValidationProvider.FromCertificateFile(certificateFile); + ServerCertificateValidationProvider? serverCertificateValidationProvider = ServerCertificateValidationProvider.FromCertificateFile(certificateFile, logFailedToValidateCertificate); if (serverCertificateValidationProvider == null) { - ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationHandler), "Failed to Load the certificate file into trusted collection"); + logFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationHandler), "Failed to Load the certificate file into trusted collection"); return null; } @@ -33,7 +33,7 @@ internal class ServerCertificateValidationHandler } catch (Exception ex) { - ServerCertificateValidationEventSource.Log.FailedToExtractResourceAttributes($"{nameof(ServerCertificateValidationHandler)} : Failed to create HttpClientHandler", ex.ToInvariantString()); + logFailedToExtractResourceAttributes?.Invoke($"{nameof(ServerCertificateValidationHandler)} : Failed to create HttpClientHandler", ex.ToInvariantString()); } return null; diff --git a/src/Shared/ServerCertificateValidationProvider.cs b/src/Shared/ServerCertificateValidationProvider.cs index d036c9d5b9..d58a42ae99 100644 --- a/src/Shared/ServerCertificateValidationProvider.cs +++ b/src/Shared/ServerCertificateValidationProvider.cs @@ -13,141 +13,144 @@ namespace OpenTelemetry.ResourceDetectors; internal class ServerCertificateValidationProvider { - private readonly X509Certificate2Collection trustedCertificates; + public static Action? LogFailedToValidateCertificate; - private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates) - { - this.trustedCertificates = trustedCertificates; - this.ValidationCallback = (_, cert, chain, errors) => - this.ValidateCertificate(cert != null ? new X509Certificate2(cert) : null, chain, errors); - } + private readonly X509Certificate2Collection trustedCertificates; - public RemoteCertificateValidationCallback ValidationCallback { get; } - - public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile) - { - if (!File.Exists(certificateFile)) + private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates, Action? logFailedToValidateCertificate = null) { - ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate File does not exist"); - return null; + this.trustedCertificates = trustedCertificates; + this.ValidationCallback = (_, cert, chain, errors) => + this.ValidateCertificate(cert != null ? new X509Certificate2(cert) : null, chain, errors); + LogFailedToValidateCertificate = logFailedToValidateCertificate; } - var trustedCertificates = new X509Certificate2Collection(); - if (!LoadCertificateToTrustedCollection(trustedCertificates, certificateFile)) + public RemoteCertificateValidationCallback ValidationCallback { get; } + + public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile, Action? failedToValidateCertificate = null) { - ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection"); - return null; - } + if (!File.Exists(certificateFile)) + { + failedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Certificate File does not exist"); + return null; + } - return new ServerCertificateValidationProvider(trustedCertificates); - } + var trustedCertificates = new X509Certificate2Collection(); + if (!LoadCertificateToTrustedCollection(trustedCertificates, certificateFile)) + { + failedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection"); + return null; + } - private static bool LoadCertificateToTrustedCollection(X509Certificate2Collection collection, string certFileName) - { - try - { - collection.Import(certFileName); - return true; + return new ServerCertificateValidationProvider(trustedCertificates, failedToValidateCertificate); } - catch (Exception) - { - return false; - } - } - private static bool HasCommonCertificate(X509Chain chain, X509Certificate2Collection? collection) - { - if (collection == null) + private static bool LoadCertificateToTrustedCollection(X509Certificate2Collection collection, string certFileName) { - return false; + try + { + collection.Import(certFileName); + return true; + } + catch (Exception) + { + return false; + } } - foreach (var chainElement in chain.ChainElements) + private static bool HasCommonCertificate(X509Chain chain, X509Certificate2Collection? collection) { - foreach (var certificate in collection) - { - if (chainElement.Certificate.GetPublicKey().SequenceEqual(certificate.GetPublicKey())) + if (collection == null) { - return true; + return false; } - } - } - return false; - } + foreach (var chainElement in chain.ChainElements) + { + foreach (var certificate in collection) + { + if (chainElement.Certificate.GetPublicKey().SequenceEqual(certificate.GetPublicKey())) + { + return true; + } + } + } - private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) - { - var isSslPolicyPassed = errors == SslPolicyErrors.None || - errors == SslPolicyErrors.RemoteCertificateChainErrors; - if (!isSslPolicyPassed) - { - if ((errors | SslPolicyErrors.RemoteCertificateNotAvailable) == errors) - { - ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNotAvailable"); - } - - if ((errors | SslPolicyErrors.RemoteCertificateNameMismatch) == errors) - { - ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNameMismatch"); - } + return false; } - if (chain == null) + private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPolicyErrors errors) { - ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate chain is null."); - return false; - } + var isSslPolicyPassed = errors == SslPolicyErrors.None || + errors == SslPolicyErrors.RemoteCertificateChainErrors; + if (!isSslPolicyPassed) + { + if ((errors | SslPolicyErrors.RemoteCertificateNotAvailable) == errors) + { + LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNotAvailable"); + } + + if ((errors | SslPolicyErrors.RemoteCertificateNameMismatch) == errors) + { + LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Failed to validate certificate due to RemoteCertificateNameMismatch"); + } + } - if (cert == null) - { - ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate is null."); - return false; - } + if (chain == null) + { + LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate chain is null."); + return false; + } - chain.ChainPolicy.ExtraStore.AddRange(this.trustedCertificates); - chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + if (cert == null) + { + LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Failed to validate certificate. Certificate is null."); + return false; + } - // building the chain to process basic validations e.g. signature, use, expiration, revocation - var isValidChain = chain.Build(cert); + chain.ChainPolicy.ExtraStore.AddRange(this.trustedCertificates); + chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; - if (!isValidChain) - { - var chainErrors = string.Empty; - foreach (var element in chain.ChainElements) - { - foreach (var status in element.ChainElementStatus) + // building the chain to process basic validations e.g. signature, use, expiration, revocation + var isValidChain = chain.Build(cert); + + if (!isValidChain) { - chainErrors += - $"\nCertificate [{element.Certificate.Subject}] Status [{status.Status}]: {status.StatusInformation}"; + var chainErrors = string.Empty; + foreach (var element in chain.ChainElements) + { + foreach (var status in element.ChainElementStatus) + { + chainErrors += + $"\nCertificate [{element.Certificate.Subject}] Status [{status.Status}]: {status.StatusInformation}"; + } + } + + LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), $"Failed to validate certificate due to {chainErrors}"); } - } - ServerCertificateValidationEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), $"Failed to validate certificate due to {chainErrors}"); - } + // check if at least one certificate in the chain is in our trust list + var isTrusted = HasCommonCertificate(chain, this.trustedCertificates); + if (!isTrusted) + { + var serverCertificates = string.Empty; + foreach (var element in chain.ChainElements) + { + serverCertificates += " " + element.Certificate.Subject; + } + + var trustCertificates = string.Empty; + foreach (var trustCertificate in this.trustedCertificates) + { + trustCertificates += " " + trustCertificate.Subject; + } + + LogFailedToValidateCertificate?.Invoke( + nameof(ServerCertificateValidationProvider), + $"Server Certificates Chain cannot be trusted. The chain doesn't match with the Trusted Certificates provided. Server Certificates:{serverCertificates}. Trusted Certificates:{trustCertificates}"); + } - // check if at least one certificate in the chain is in our trust list - var isTrusted = HasCommonCertificate(chain, this.trustedCertificates); - if (!isTrusted) - { - var serverCertificates = string.Empty; - foreach (var element in chain.ChainElements) - { - serverCertificates += " " + element.Certificate.Subject; - } - - var trustCertificates = string.Empty; - foreach (var trustCertificate in this.trustedCertificates) - { - trustCertificates += " " + trustCertificate.Subject; - } - - ServerCertificateValidationEventSource.Log.FailedToValidateCertificate( - nameof(ServerCertificateValidationProvider), - $"Server Certificates Chain cannot be trusted. The chain doesn't match with the Trusted Certificates provided. Server Certificates:{serverCertificates}. Trusted Certificates:{trustCertificates}"); + return isSslPolicyPassed && isValidChain && isTrusted; } - - return isSslPolicyPassed && isValidChain && isTrusted; - } } #endif From 5158730a1a67e419405c073ec5f3a8c9eb61558a Mon Sep 17 00:00:00 2001 From: Anoop Babu Date: Mon, 29 Jan 2024 19:21:07 -0500 Subject: [PATCH 4/5] Remove unncessary file --- .../ServerCertificateValidationEventSource.cs | 28 ------------------- 1 file changed, 28 deletions(-) delete mode 100644 src/Shared/ServerCertificateValidationEventSource.cs diff --git a/src/Shared/ServerCertificateValidationEventSource.cs b/src/Shared/ServerCertificateValidationEventSource.cs deleted file mode 100644 index 854e813a5c..0000000000 --- a/src/Shared/ServerCertificateValidationEventSource.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -#if !NETFRAMEWORK - -using System.Diagnostics.Tracing; - -namespace OpenTelemetry.ResourceDetectors; - -[EventSource(Name = "OpenTelemetry-ResourceDetectors-Http")] -internal class ServerCertificateValidationEventSource : EventSource -{ - public static ServerCertificateValidationEventSource Log = new(); - - [Event(1, Message = "Failed to extract resource attributes in '{0}'.", Level = EventLevel.Warning)] - public void FailedToExtractResourceAttributes(string format, string exception) - { - this.WriteEvent(3, format, exception); - } - - [Event(2, Message = "Failed to validate certificate in format: '{0}', error: '{1}'.", Level = EventLevel.Warning)] - public void FailedToValidateCertificate(string format, string error) - { - this.WriteEvent(4, format, error); - } -} - -#endif From ef63c273b4ea4b66ad2cb9a59581b2e28064663b Mon Sep 17 00:00:00 2001 From: Anoop Babu Date: Wed, 7 Feb 2024 17:08:49 -0500 Subject: [PATCH 5/5] Updated change to fix the AWS Event Source and changed delegate logger to an interface on the event source --- opentelemetry-dotnet-contrib.sln | 3 +-- .../AWSEKSResourceDetector.cs | 3 +-- .../AWSResourcesEventSource.cs | 2 +- ...OpenTelemetry.ResourceDetectors.AWS.csproj | 1 + ...IServerCertificateValidationEventSource.cs | 11 ++++++++ .../ServerCertificateValidationHandler.cs | 8 +++--- .../ServerCertificateValidationProvider.cs | 26 +++++++++---------- 7 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 src/Shared/IServerCertificateValidationEventSource.cs diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln index 9f02511871..f9393852cf 100644 --- a/opentelemetry-dotnet-contrib.sln +++ b/opentelemetry-dotnet-contrib.sln @@ -276,6 +276,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E src\Shared\ExceptionExtensions.cs = src\Shared\ExceptionExtensions.cs src\Shared\Guard.cs = src\Shared\Guard.cs src\Shared\InstrumentationEventSource.cs = src\Shared\InstrumentationEventSource.cs + src\Shared\IServerCertificateValidationEventSource.cs = src\Shared\IServerCertificateValidationEventSource.cs src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs src\Shared\ListenerHandler.cs = src\Shared\ListenerHandler.cs src\Shared\MultiTypePropertyFetcher.cs = src\Shared\MultiTypePropertyFetcher.cs @@ -283,8 +284,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E src\Shared\PropertyFetcher.cs = src\Shared\PropertyFetcher.cs src\Shared\ResourceSemanticConventions.cs = src\Shared\ResourceSemanticConventions.cs src\Shared\SemanticConventions.cs = src\Shared\SemanticConventions.cs - src\Shared\ServerCertificateValidationHandler.cs = src\Shared\ServerCertificateValidationHandler.cs - src\Shared\ServerCertificateValidationProvider.cs = src\Shared\ServerCertificateValidationProvider.cs src\Shared\ServiceProviderExtensions.cs = src\Shared\ServiceProviderExtensions.cs src\Shared\SpanAttributeConstants.cs = src\Shared\SpanAttributeConstants.cs src\Shared\SpanHelper.cs = src\Shared\SpanHelper.cs diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs index abf2f2de17..c8a616f2b7 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs @@ -30,8 +30,7 @@ public sealed class AWSEKSResourceDetector : IResourceDetector public Resource Detect() { var credentials = GetEKSCredentials(AWSEKSCredentialPath); - using var httpClientHandler = ServerCertificateValidationHandler.Create( - AWSEKSCertificatePath, AWSResourcesEventSource.Log.FailedToValidateCertificate, AWSResourcesEventSource.Log.FailedToExtractResourceAttributes); + using var httpClientHandler = ServerCertificateValidationHandler.Create(AWSEKSCertificatePath, AWSResourcesEventSource.Log); if (credentials == null || !IsEKSProcess(credentials, httpClientHandler)) { diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs index e34bb70909..b6e009089a 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs @@ -8,7 +8,7 @@ namespace OpenTelemetry.ResourceDetectors.AWS; [EventSource(Name = "OpenTelemetry-ResourceDetectors-AWS")] -internal sealed class AWSResourcesEventSource : EventSource +internal sealed class AWSResourcesEventSource : EventSource, IServerCertificateValidationEventSource { public static AWSResourcesEventSource Log = new(); diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj index 8b87636a88..2038cb34b7 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj +++ b/src/OpenTelemetry.ResourceDetectors.AWS/OpenTelemetry.ResourceDetectors.AWS.csproj @@ -23,6 +23,7 @@ + diff --git a/src/Shared/IServerCertificateValidationEventSource.cs b/src/Shared/IServerCertificateValidationEventSource.cs new file mode 100644 index 0000000000..c29acbb45c --- /dev/null +++ b/src/Shared/IServerCertificateValidationEventSource.cs @@ -0,0 +1,11 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +namespace OpenTelemetry.ResourceDetectors; + +internal interface IServerCertificateValidationEventSource +{ + public void FailedToExtractResourceAttributes(string format, string exception); + + public void FailedToValidateCertificate(string format, string error); +} diff --git a/src/Shared/ServerCertificateValidationHandler.cs b/src/Shared/ServerCertificateValidationHandler.cs index aca8d4d29c..b24cc93d1d 100644 --- a/src/Shared/ServerCertificateValidationHandler.cs +++ b/src/Shared/ServerCertificateValidationHandler.cs @@ -11,15 +11,15 @@ namespace OpenTelemetry.ResourceDetectors; internal class ServerCertificateValidationHandler { - public static HttpClientHandler? Create(string certificateFile, Action? logFailedToValidateCertificate = null, Action? logFailedToExtractResourceAttributes = null) + public static HttpClientHandler? Create(string certificateFile, IServerCertificateValidationEventSource? log = null) { try { - ServerCertificateValidationProvider? serverCertificateValidationProvider = ServerCertificateValidationProvider.FromCertificateFile(certificateFile, logFailedToValidateCertificate); + ServerCertificateValidationProvider? serverCertificateValidationProvider = ServerCertificateValidationProvider.FromCertificateFile(certificateFile, log); if (serverCertificateValidationProvider == null) { - logFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationHandler), "Failed to Load the certificate file into trusted collection"); + log?.FailedToValidateCertificate(nameof(ServerCertificateValidationHandler), "Failed to Load the certificate file into trusted collection"); return null; } @@ -33,7 +33,7 @@ internal class ServerCertificateValidationHandler } catch (Exception ex) { - logFailedToExtractResourceAttributes?.Invoke($"{nameof(ServerCertificateValidationHandler)} : Failed to create HttpClientHandler", ex.ToInvariantString()); + log?.FailedToExtractResourceAttributes($"{nameof(ServerCertificateValidationHandler)} : Failed to create HttpClientHandler", ex.ToInvariantString()); } return null; diff --git a/src/Shared/ServerCertificateValidationProvider.cs b/src/Shared/ServerCertificateValidationProvider.cs index 75b546c2a2..d270274376 100644 --- a/src/Shared/ServerCertificateValidationProvider.cs +++ b/src/Shared/ServerCertificateValidationProvider.cs @@ -13,36 +13,36 @@ namespace OpenTelemetry.ResourceDetectors; internal class ServerCertificateValidationProvider { - public static Action? LogFailedToValidateCertificate; + public static IServerCertificateValidationEventSource? Log; private readonly X509Certificate2Collection trustedCertificates; - private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates, Action? logFailedToValidateCertificate = null) + private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates, IServerCertificateValidationEventSource? log = null) { this.trustedCertificates = trustedCertificates; this.ValidationCallback = (_, cert, chain, errors) => this.ValidateCertificate(cert != null ? new X509Certificate2(cert) : null, chain, errors); - LogFailedToValidateCertificate = logFailedToValidateCertificate; + Log = log; } public RemoteCertificateValidationCallback ValidationCallback { get; } - public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile, Action? failedToValidateCertificate = null) + public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile, IServerCertificateValidationEventSource? log = null) { if (!File.Exists(certificateFile)) { - failedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Certificate File does not exist"); + log?.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate File does not exist"); return null; } var trustedCertificates = new X509Certificate2Collection(); if (!LoadCertificateToTrustedCollection(trustedCertificates, certificateFile)) { - failedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection"); + log?.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection"); return null; } - return new ServerCertificateValidationProvider(trustedCertificates, failedToValidateCertificate); + return new ServerCertificateValidationProvider(trustedCertificates, log); } private static bool LoadCertificateToTrustedCollection(X509Certificate2Collection collection, string certFileName) @@ -87,24 +87,24 @@ private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPo { if ((errors | SslPolicyErrors.RemoteCertificateNotAvailable) == errors) { - LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "SslPolicyError RemoteCertificateNotAvailable occurred"); + Log?.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "SslPolicyError RemoteCertificateNotAvailable occurred"); } if ((errors | SslPolicyErrors.RemoteCertificateNameMismatch) == errors) { - LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "SslPolicyError RemoteCertificateNameMismatch occurred"); + Log?.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "SslPolicyError RemoteCertificateNameMismatch occurred"); } } if (chain == null) { - LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Certificate chain is null."); + Log?.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate chain is null."); return false; } if (cert == null) { - LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), "Certificate is null."); + Log?.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate is null."); return false; } @@ -126,7 +126,7 @@ private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPo } } - LogFailedToValidateCertificate?.Invoke(nameof(ServerCertificateValidationProvider), chainErrors); + Log?.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), chainErrors); } // check if at least one certificate in the chain is in our trust list @@ -145,7 +145,7 @@ private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPo trustCertificates += " " + trustCertificate.Subject; } - LogFailedToValidateCertificate?.Invoke( + Log?.FailedToValidateCertificate( nameof(ServerCertificateValidationProvider), $"Server Certificates Chain cannot be trusted. The chain doesn't match with the Trusted Certificates provided. Server Certificates:{serverCertificates}. Trusted Certificates:{trustCertificates}"); }