Skip to content

* Add AcceptablePolicyErrors to TlsSettings #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions RabbitMQ.AMQP.Client/IConnectionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,25 @@ public interface ITlsSettings
/// <summary>
/// Supported protocols to use.
/// </summary>
SslProtocols Protocols { get; }
SslProtocols Protocols { get; set; }

/// <summary>
/// Acceptable TLS/SSL errors when connecting.
/// </summary>
SslPolicyErrors AcceptablePolicyErrors { get; set; }

/// <summary>
/// Specifies whether certificate revocation should be performed during handshake.
/// </summary>
bool CheckCertificateRevocation { get; }
bool CheckCertificateRevocation { get; set; }

/// <summary>
/// Gets or sets a certificate validation callback to validate remote certificate.
/// </summary>
RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; }
RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; set; }

/// <summary>
/// Gets or sets a local certificate selection callback to select the certificate which should be used for authentication.
/// </summary>
LocalCertificateSelectionCallback? LocalCertificateSelectionCallback { get; }
LocalCertificateSelectionCallback? LocalCertificateSelectionCallback { get; set; }
}
58 changes: 28 additions & 30 deletions RabbitMQ.AMQP.Client/Impl/AmqpConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,6 @@

namespace RabbitMQ.AMQP.Client.Impl;

internal class Visitor(AmqpManagement management) : IVisitor
{
private AmqpManagement Management { get; } = management;

public async Task VisitQueues(IEnumerable<QueueSpec> queueSpec)
{
foreach (QueueSpec spec in queueSpec)
{
Trace.WriteLine(TraceLevel.Information, $"Recovering queue {spec.Name}");
try
{
await Management.Queue(spec).Declare()
.ConfigureAwait(false);
}
catch (Exception e)
{
Trace.WriteLine(TraceLevel.Error,
$"Error recovering queue {spec.Name}. Error: {e}. Management Status: {Management}");
}
}
}
}

/// <summary>
/// AmqpConnection is the concrete implementation of <see cref="IConnection"/>
/// It is a wrapper around the AMQP.Net Lite <see cref="Connection"/> class
Expand All @@ -38,7 +15,7 @@ public class AmqpConnection : AbstractLifeCycle, IConnection
{
private const string ConnectionNotRecoveredCode = "CONNECTION_NOT_RECOVERED";
private const string ConnectionNotRecoveredMessage = "Connection not recovered";
private readonly SemaphoreSlim _semaphoreClose = new(1, 1);
private readonly SemaphoreSlim _semaphoreClose = new(1, 1); // TODO this needs to be Disposed

// The native AMQP.Net Lite connection
private Connection? _nativeConnection;
Expand All @@ -55,7 +32,7 @@ private void ChangeEntitiesStatus(State state, Error? error)

private void ChangePublishersStatus(State state, Error? error)
{
foreach (var publisher1 in Publishers.Values)
foreach (IPublisher publisher1 in Publishers.Values)
{
var publisher = (AmqpPublisher)publisher1;
publisher.ChangeStatus(state, error);
Expand All @@ -64,7 +41,7 @@ private void ChangePublishersStatus(State state, Error? error)

private void ChangeConsumersStatus(State state, Error? error)
{
foreach (var consumer1 in Consumers.Values)
foreach (IConsumer consumer1 in Consumers.Values)
{
var consumer = (AmqpConsumer)consumer1;
consumer.ChangeStatus(state, error);
Expand All @@ -79,7 +56,7 @@ private async Task ReconnectEntities()

private async Task ReconnectPublishers()
{
foreach (var publisher1 in Publishers.Values)
foreach (IPublisher publisher1 in Publishers.Values)
{
var publisher = (AmqpPublisher)publisher1;
await publisher.Reconnect().ConfigureAwait(false);
Expand All @@ -88,7 +65,7 @@ private async Task ReconnectPublishers()

private async Task ReconnectConsumers()
{
foreach (var consumer1 in Consumers.Values)
foreach (IConsumer consumer1 in Consumers.Values)
{
var consumer = (AmqpConsumer)consumer1;
await consumer.Reconnect().ConfigureAwait(false);
Expand Down Expand Up @@ -419,7 +396,6 @@ public IPublisherBuilder PublisherBuilder()
return publisherBuilder;
}


public override async Task CloseAsync()
{
await _semaphoreClose.WaitAsync()
Expand Down Expand Up @@ -459,10 +435,32 @@ await ConnectionCloseTaskCompletionSource.Task.WaitAsync(TimeSpan.FromSeconds(10
OnNewStatus(State.Closed, null);
}


public override string ToString()
{
string info = $"AmqpConnection{{ConnectionSettings='{_connectionSettings}', Status='{State.ToString()}'}}";
return info;
}
}

internal class Visitor(AmqpManagement management) : IVisitor
{
private AmqpManagement Management { get; } = management;

public async Task VisitQueues(IEnumerable<QueueSpec> queueSpec)
{
foreach (QueueSpec spec in queueSpec)
{
Trace.WriteLine(TraceLevel.Information, $"Recovering queue {spec.Name}");
try
{
await Management.Queue(spec).Declare()
.ConfigureAwait(false);
}
catch (Exception e)
{
Trace.WriteLine(TraceLevel.Error,
$"Error recovering queue {spec.Name}. Error: {e}. Management Status: {Management}");
}
}
}
}
30 changes: 12 additions & 18 deletions RabbitMQ.AMQP.Client/Impl/ConnectionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,40 +358,34 @@ public override string ToString()
public class TlsSettings : ITlsSettings
{
internal const SslProtocols DefaultSslProtocols = SslProtocols.None;

private readonly SslProtocols _protocols;
private readonly X509CertificateCollection _clientCertificates;
private readonly bool _checkCertificateRevocation = false;
private readonly RemoteCertificateValidationCallback? _remoteCertificateValidationCallback;
private readonly LocalCertificateSelectionCallback? _localCertificateSelectionCallback;
private readonly X509CertificateCollection _clientCertificates = new X509CertificateCollection();

public TlsSettings() : this(DefaultSslProtocols)
{
}

public TlsSettings(SslProtocols protocols)
{
_protocols = protocols;
_clientCertificates = new X509CertificateCollection();
_remoteCertificateValidationCallback = trustEverythingCertValidationCallback;
_localCertificateSelectionCallback = null;
Protocols = protocols;
RemoteCertificateValidationCallback = trustEverythingCertValidationCallback;
LocalCertificateSelectionCallback = null;
}

public SslProtocols Protocols => _protocols;
public SslProtocols Protocols { get; set; }

public SslPolicyErrors AcceptablePolicyErrors { get; set; } = SslPolicyErrors.None;

public X509CertificateCollection ClientCertificates => _clientCertificates;

public bool CheckCertificateRevocation => _checkCertificateRevocation;
public bool CheckCertificateRevocation { get; set; } = false;

public RemoteCertificateValidationCallback? RemoteCertificateValidationCallback
=> _remoteCertificateValidationCallback;
public RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get; set; }

public LocalCertificateSelectionCallback? LocalCertificateSelectionCallback
=> _localCertificateSelectionCallback;
public LocalCertificateSelectionCallback? LocalCertificateSelectionCallback { get; set; }

private static bool trustEverythingCertValidationCallback(object sender, X509Certificate? certificate,
private bool trustEverythingCertValidationCallback(object sender, X509Certificate? certificate,
X509Chain? chain, SslPolicyErrors sslPolicyErrors)
{
return true;
return (sslPolicyErrors & ~AcceptablePolicyErrors) == SslPolicyErrors.None;
}
}
12 changes: 12 additions & 0 deletions RabbitMQ.AMQP.Client/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -416,11 +416,17 @@ RabbitMQ.AMQP.Client.Impl.RecoveryConfiguration.IsActivate() -> bool
RabbitMQ.AMQP.Client.Impl.RecoveryConfiguration.IsTopologyActive() -> bool
RabbitMQ.AMQP.Client.Impl.RecoveryConfiguration.Topology(bool activated) -> RabbitMQ.AMQP.Client.IRecoveryConfiguration!
RabbitMQ.AMQP.Client.Impl.TlsSettings
RabbitMQ.AMQP.Client.Impl.TlsSettings.AcceptablePolicyErrors.get -> System.Net.Security.SslPolicyErrors
RabbitMQ.AMQP.Client.Impl.TlsSettings.AcceptablePolicyErrors.set -> void
RabbitMQ.AMQP.Client.Impl.TlsSettings.CheckCertificateRevocation.get -> bool
RabbitMQ.AMQP.Client.Impl.TlsSettings.CheckCertificateRevocation.set -> void
RabbitMQ.AMQP.Client.Impl.TlsSettings.ClientCertificates.get -> System.Security.Cryptography.X509Certificates.X509CertificateCollection!
RabbitMQ.AMQP.Client.Impl.TlsSettings.LocalCertificateSelectionCallback.get -> System.Net.Security.LocalCertificateSelectionCallback?
RabbitMQ.AMQP.Client.Impl.TlsSettings.LocalCertificateSelectionCallback.set -> void
RabbitMQ.AMQP.Client.Impl.TlsSettings.Protocols.get -> System.Security.Authentication.SslProtocols
RabbitMQ.AMQP.Client.Impl.TlsSettings.Protocols.set -> void
RabbitMQ.AMQP.Client.Impl.TlsSettings.RemoteCertificateValidationCallback.get -> System.Net.Security.RemoteCertificateValidationCallback?
RabbitMQ.AMQP.Client.Impl.TlsSettings.RemoteCertificateValidationCallback.set -> void
RabbitMQ.AMQP.Client.Impl.TlsSettings.TlsSettings() -> void
RabbitMQ.AMQP.Client.Impl.TlsSettings.TlsSettings(System.Security.Authentication.SslProtocols protocols) -> void
RabbitMQ.AMQP.Client.Impl.Utils
Expand Down Expand Up @@ -485,11 +491,17 @@ RabbitMQ.AMQP.Client.IStreamSpecification.MaxAge(System.TimeSpan maxAge) -> Rabb
RabbitMQ.AMQP.Client.IStreamSpecification.MaxSegmentSizeBytes(RabbitMQ.AMQP.Client.ByteCapacity! maxSegmentSize) -> RabbitMQ.AMQP.Client.IStreamSpecification!
RabbitMQ.AMQP.Client.IStreamSpecification.Queue() -> RabbitMQ.AMQP.Client.IQueueSpecification!
RabbitMQ.AMQP.Client.ITlsSettings
RabbitMQ.AMQP.Client.ITlsSettings.AcceptablePolicyErrors.get -> System.Net.Security.SslPolicyErrors
RabbitMQ.AMQP.Client.ITlsSettings.AcceptablePolicyErrors.set -> void
RabbitMQ.AMQP.Client.ITlsSettings.CheckCertificateRevocation.get -> bool
RabbitMQ.AMQP.Client.ITlsSettings.CheckCertificateRevocation.set -> void
RabbitMQ.AMQP.Client.ITlsSettings.ClientCertificates.get -> System.Security.Cryptography.X509Certificates.X509CertificateCollection!
RabbitMQ.AMQP.Client.ITlsSettings.LocalCertificateSelectionCallback.get -> System.Net.Security.LocalCertificateSelectionCallback?
RabbitMQ.AMQP.Client.ITlsSettings.LocalCertificateSelectionCallback.set -> void
RabbitMQ.AMQP.Client.ITlsSettings.Protocols.get -> System.Security.Authentication.SslProtocols
RabbitMQ.AMQP.Client.ITlsSettings.Protocols.set -> void
RabbitMQ.AMQP.Client.ITlsSettings.RemoteCertificateValidationCallback.get -> System.Net.Security.RemoteCertificateValidationCallback?
RabbitMQ.AMQP.Client.ITlsSettings.RemoteCertificateValidationCallback.set -> void
RabbitMQ.AMQP.Client.ITopologyListener
RabbitMQ.AMQP.Client.ITopologyListener.Clear() -> void
RabbitMQ.AMQP.Client.ITopologyListener.QueueCount() -> int
Expand Down
76 changes: 1 addition & 75 deletions Tests/ConnectionTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using EasyNetQ.Management.Client;
using EasyNetQ.Management.Client.Model;
using System.Threading.Tasks;
using RabbitMQ.AMQP.Client;
using RabbitMQ.AMQP.Client.Impl;
using Xunit;
Expand Down Expand Up @@ -78,75 +73,6 @@ public void ValidateBuilderWithSslOptions()
Assert.Equal("amqps", connectionSettings.Scheme);
}

[Fact]
public async Task ConnectUsingTlsAndUserPassword()
{
ConnectionSettings connectionSettings = ConnectionSettingBuilder.Create()
.Host("localhost")
.Scheme("amqps")
.Build();

Assert.True(connectionSettings.UseSsl);
Assert.Equal("localhost", connectionSettings.Host);
Assert.Equal(5671, connectionSettings.Port);
Assert.Equal("guest", connectionSettings.User);
Assert.Equal("guest", connectionSettings.Password);
Assert.Equal("/", connectionSettings.VirtualHost);
Assert.Equal("amqps", connectionSettings.Scheme);

IConnection connection = await AmqpConnection.CreateAsync(connectionSettings);
Assert.Equal(State.Open, connection.State);
await connection.CloseAsync();
Assert.Equal(State.Closed, connection.State);
}

[Fact]
public async Task ConnectUsingTlsAndClientCertificate()
{
string cwd = Directory.GetCurrentDirectory();

string clientCertFile = Path.GetFullPath(Path.Join(cwd, "../../../../.ci/certs/client_localhost.p12"));
if (false == File.Exists(clientCertFile))
{
clientCertFile = Path.GetFullPath(Path.Join(cwd, "../../../../../.ci/certs/client_localhost.p12"));
}
Assert.True(File.Exists(clientCertFile));

var cert = new X509Certificate2(clientCertFile, "grapefruit");
string userName = cert.Subject.Trim().Replace(" ", string.Empty);

var managementUri = new Uri("http://localhost:15672");
using var managementClient = new ManagementClient(managementUri, "guest", "guest");
var userInfo = new UserInfo(null, null, []);
await managementClient.CreateUserAsync(userName, userInfo);

var permissionInfo = new PermissionInfo();
await managementClient.CreatePermissionAsync("/", userName, permissionInfo);

ConnectionSettings connectionSettings = ConnectionSettingBuilder.Create()
.Host("localhost")
.Scheme("amqps")
.SaslMechanism(SaslMechanism.External)
.Build();

Assert.NotNull(connectionSettings.TlsSettings);
connectionSettings.TlsSettings.ClientCertificates.Add(cert);

Assert.True(connectionSettings.UseSsl);
Assert.Equal("localhost", connectionSettings.Host);
Assert.Equal(5671, connectionSettings.Port);
Assert.Null(connectionSettings.User);
Assert.Null(connectionSettings.Password);
Assert.Equal("/", connectionSettings.VirtualHost);
Assert.Equal("amqps", connectionSettings.Scheme);
Assert.Equal(SaslMechanism.External, connectionSettings.SaslMechanism);

IConnection connection = await AmqpConnection.CreateAsync(connectionSettings);
Assert.Equal(State.Open, connection.State);
await connection.CloseAsync();
Assert.Equal(State.Closed, connection.State);
}

[Fact]
public async Task RaiseErrorsIfTheParametersAreNotValid()
{
Expand Down
Loading