Skip to content

Commit

Permalink
Move existing ServerCertificateValidationHandler to Shared (#1640)
Browse files Browse the repository at this point in the history
  • Loading branch information
joegoldman2 authored Apr 23, 2024
1 parent d1a7283 commit 99f4fb1
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 42 deletions.
3 changes: 3 additions & 0 deletions opentelemetry-dotnet-contrib.sln
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E
src\Shared\DiagnosticSourceSubscriber.cs = src\Shared\DiagnosticSourceSubscriber.cs
src\Shared\ExceptionExtensions.cs = src\Shared\ExceptionExtensions.cs
src\Shared\Guard.cs = src\Shared\Guard.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
Expand All @@ -285,6 +286,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{1FCC8E
src\Shared\RedactionHelper.cs = src\Shared\RedactionHelper.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\SpanAttributeConstants.cs = src\Shared\SpanAttributeConstants.cs
src\Shared\SpanHelper.cs = src\Shared\SpanHelper.cs
EndProjectSection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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, AWSResourcesEventSource.Log);

if (credentials == null || !IsEKSProcess(credentials, httpClientHandler))
{
Expand Down
36 changes: 30 additions & 6 deletions src/OpenTelemetry.ResourceDetectors.AWS/AWSResourcesEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@
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();

private const int EventIdFailedToExtractAttributes = 1;
private const int EventIdFailedToValidateCertificate = 2;
private const int EventIdFailedToCreateHttpHandler = 3;
private const int EventIdFailedCertificateFileNotExists = 4;
private const int EventIdFailedToLoadCertificateInStorage = 5;

[NonEvent]
public void ResourceAttributesExtractException(string format, Exception ex)
{
Expand All @@ -21,15 +27,33 @@ public void ResourceAttributesExtractException(string format, Exception ex)
}
}

[Event(1, Message = "Failed to extract resource attributes in '{0}'.", Level = EventLevel.Warning)]
[Event(EventIdFailedToExtractAttributes, Message = "Failed to extract resource attributes in '{0}'.", Level = EventLevel.Warning)]
public void FailedToExtractResourceAttributes(string format, string exception)
{
this.WriteEvent(1, format, exception);
this.WriteEvent(EventIdFailedToExtractAttributes, format, exception);
}

[Event(EventIdFailedToValidateCertificate, Message = "Failed to validate certificate. Details: '{0}'", Level = EventLevel.Warning)]
public void FailedToValidateCertificate(string error)
{
this.WriteEvent(EventIdFailedToValidateCertificate, error);
}

[Event(EventIdFailedToCreateHttpHandler, Message = "Failed to create HTTP handler. Exception: '{0}'", Level = EventLevel.Warning)]
public void FailedToCreateHttpHandler(Exception exception)
{
this.WriteEvent(EventIdFailedToCreateHttpHandler, exception.ToInvariantString());
}

[Event(EventIdFailedCertificateFileNotExists, Message = "Certificate file does not exist. File: '{0}'", Level = EventLevel.Warning)]
public void CertificateFileDoesNotExist(string filename)
{
this.WriteEvent(EventIdFailedCertificateFileNotExists, filename);
}

[Event(2, Message = "Failed to validate certificate in format: '{0}', error: '{1}'.", Level = EventLevel.Warning)]
public void FailedToValidateCertificate(string format, string error)
[Event(EventIdFailedToLoadCertificateInStorage, Message = "Failed to load certificate in trusted storage. File: '{0}'", Level = EventLevel.Warning)]
public void FailedToLoadCertificateInTrustedStorage(string filename)
{
this.WriteEvent(2, format, error);
this.WriteEvent(EventIdFailedToLoadCertificateInStorage, filename);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
<ItemGroup>
<Compile Include="$(RepoRoot)\src\Shared\ExceptionExtensions.cs" Link="Includes\ExceptionExtensions.cs" />
<Compile Include="$(RepoRoot)\src\Shared\Guard.cs" Link="Includes\Guard.cs" />
<Compile Include="$(RepoRoot)\src\Shared\IServerCertificateValidationEventSource.cs" Link="Includes\IServerCertificateValidationEventSource.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ServerCertificateValidationHandler.cs" Link="Includes\ServerCertificateValidationHandler.cs" />
<Compile Include="$(RepoRoot)\src\Shared\ServerCertificateValidationProvider.cs" Link="Includes\ServerCertificateValidationProvider.cs" />
</ItemGroup>

</Project>
17 changes: 17 additions & 0 deletions src/Shared/IServerCertificateValidationEventSource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System;

namespace OpenTelemetry.ResourceDetectors;

internal interface IServerCertificateValidationEventSource
{
public void FailedToValidateCertificate(string error);

public void FailedToCreateHttpHandler(Exception exception);

public void CertificateFileDoesNotExist(string filename);

public void FailedToLoadCertificateInTrustedStorage(string filename);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,36 @@
using System;
using System.Net.Http;

namespace OpenTelemetry.ResourceDetectors.AWS.Http;
namespace OpenTelemetry.ResourceDetectors;

internal class Handler
internal static class ServerCertificateValidationHandler
{
public static HttpClientHandler? Create(string certificateFile)
public static HttpClientHandler? Create(string certificateFile, IServerCertificateValidationEventSource log)
{
try
{
ServerCertificateValidationProvider? serverCertificateValidationProvider =
ServerCertificateValidationProvider.FromCertificateFile(certificateFile);
ServerCertificateValidationProvider? serverCertificateValidationProvider = ServerCertificateValidationProvider.FromCertificateFile(certificateFile, log);

if (serverCertificateValidationProvider == null)
{
AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(Handler), "Failed to Load the certificate file into trusted collection");
return null;
return new HttpClientHandler();
}

var clientHandler = new HttpClientHandler();
clientHandler.ServerCertificateCustomValidationCallback =
var clientHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
(sender, x509Certificate2, x509Chain, sslPolicyErrors) =>
serverCertificateValidationProvider.ValidationCallback(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);
log.FailedToCreateHttpHandler(ex);
}

return null;
}
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,39 @@
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

namespace OpenTelemetry.ResourceDetectors.AWS.Http;
namespace OpenTelemetry.ResourceDetectors;

internal class ServerCertificateValidationProvider
{
private readonly X509Certificate2Collection trustedCertificates;
private readonly IServerCertificateValidationEventSource log;

private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates)
private ServerCertificateValidationProvider(X509Certificate2Collection trustedCertificates, IServerCertificateValidationEventSource log)
{
this.trustedCertificates = trustedCertificates;
this.ValidationCallback = (_, cert, chain, errors) =>
this.ValidateCertificate(cert != null ? new X509Certificate2(cert) : null, chain, errors);
this.log = log;
}

public RemoteCertificateValidationCallback ValidationCallback { get; }

public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile)
public static ServerCertificateValidationProvider? FromCertificateFile(string certificateFile, IServerCertificateValidationEventSource log)
{
if (!File.Exists(certificateFile))
{
AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate File does not exist");
log.CertificateFileDoesNotExist(certificateFile);
return null;
}

var trustedCertificates = new X509Certificate2Collection();
if (!LoadCertificateToTrustedCollection(trustedCertificates, certificateFile))
{
AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Failed to load certificate in trusted collection");
log.FailedToLoadCertificateInTrustedStorage(certificateFile);
return null;
}

return new ServerCertificateValidationProvider(trustedCertificates);
return new ServerCertificateValidationProvider(trustedCertificates, log);
}

private static bool LoadCertificateToTrustedCollection(X509Certificate2Collection collection, string certFileName)
Expand Down Expand Up @@ -84,24 +86,24 @@ private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPo
{
if ((errors | SslPolicyErrors.RemoteCertificateNotAvailable) == errors)
{
AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "SslPolicyError RemoteCertificateNotAvailable occurred");
this.log.FailedToValidateCertificate("SslPolicyError RemoteCertificateNotAvailable occurred");
}

if ((errors | SslPolicyErrors.RemoteCertificateNameMismatch) == errors)
{
AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "SslPolicyError RemoteCertificateNameMismatch occurred");
this.log.FailedToValidateCertificate("SslPolicyError RemoteCertificateNameMismatch occurred");
}
}

if (chain == null)
{
AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate chain is null.");
this.log.FailedToValidateCertificate("Certificate chain is null.");
return false;
}

if (cert == null)
{
AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), "Certificate is null.");
this.log.FailedToValidateCertificate("Certificate is null.");
return false;
}

Expand All @@ -123,7 +125,7 @@ private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPo
}
}

AWSResourcesEventSource.Log.FailedToValidateCertificate(nameof(ServerCertificateValidationProvider), chainErrors);
this.log.FailedToValidateCertificate(chainErrors);
}

// check if at least one certificate in the chain is in our trust list
Expand All @@ -142,12 +144,11 @@ private bool ValidateCertificate(X509Certificate2? cert, X509Chain? chain, SslPo
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}");
this.log.FailedToValidateCertificate($"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
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System;

namespace OpenTelemetry.ResourceDetectors.AWS.Tests.Http;

internal sealed class NoopServerCertificateValidationEventSource : IServerCertificateValidationEventSource
{
public static NoopServerCertificateValidationEventSource Instance { get; } = new NoopServerCertificateValidationEventSource();

public void FailedToValidateCertificate(string error)
{
}

public void FailedToCreateHttpHandler(Exception exception)
{
}

public void CertificateFileDoesNotExist(string filename)
{
}

public void FailedToLoadCertificateInTrustedStorage(string filename)
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@

#if !NETFRAMEWORK

using OpenTelemetry.ResourceDetectors.AWS.Http;
using Xunit;

namespace OpenTelemetry.ResourceDetectors.AWS.Tests.Http;

public class HandlerTests
public class ServerCertificateValidationHandlerTests
{
private const string INVALIDCRTNAME = "invalidcert";

Expand All @@ -20,15 +19,15 @@ public void TestValidHandler()
certificateUploader.Create();

// Validates if the handler created.
Assert.NotNull(Handler.Create(certificateUploader.FilePath));
Assert.NotNull(ServerCertificateValidationHandler.Create(certificateUploader.FilePath, NoopServerCertificateValidationEventSource.Instance));
}
}

[Fact]
public void TestInValidHandler()
{
// Validates if the handler created if no certificate is loaded into the trusted collection
Assert.Null(Handler.Create(INVALIDCRTNAME));
Assert.NotNull(ServerCertificateValidationHandler.Create(INVALIDCRTNAME, NoopServerCertificateValidationEventSource.Instance));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#if !NETFRAMEWORK

using System.Security.Cryptography.X509Certificates;
using OpenTelemetry.ResourceDetectors.AWS.Http;
using Xunit;

namespace OpenTelemetry.ResourceDetectors.AWS.Tests.Http;
Expand All @@ -20,7 +19,7 @@ public void TestValidCertificate()
certificateUploader.Create();

var serverCertificateValidationProvider =
ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath);
ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath, NoopServerCertificateValidationEventSource.Instance);

Assert.NotNull(serverCertificateValidationProvider);

Expand All @@ -38,7 +37,7 @@ public void TestValidCertificate()
public void TestInValidCertificate()
{
var serverCertificateValidationProvider =
ServerCertificateValidationProvider.FromCertificateFile(InvalidCertificateName);
ServerCertificateValidationProvider.FromCertificateFile(InvalidCertificateName, NoopServerCertificateValidationEventSource.Instance);

Assert.Null(serverCertificateValidationProvider);
}
Expand All @@ -50,7 +49,7 @@ public void TestTestCallbackWithNullCertificate()
certificateUploader.Create();

var serverCertificateValidationProvider =
ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath);
ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath, NoopServerCertificateValidationEventSource.Instance);

Assert.NotNull(serverCertificateValidationProvider);
Assert.False(serverCertificateValidationProvider.ValidationCallback(this, null, new X509Chain(), default));
Expand All @@ -63,7 +62,7 @@ public void TestCallbackWithNullChain()
certificateUploader.Create();

var serverCertificateValidationProvider =
ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath);
ServerCertificateValidationProvider.FromCertificateFile(certificateUploader.FilePath, NoopServerCertificateValidationEventSource.Instance);

Assert.NotNull(serverCertificateValidationProvider);
Assert.False(serverCertificateValidationProvider.ValidationCallback(this, new X509Certificate2(certificateUploader.FilePath), null, default));
Expand Down

0 comments on commit 99f4fb1

Please sign in to comment.