Skip to content

Commit

Permalink
refactor: remove localTrustStore and implem sslConfig
Browse files Browse the repository at this point in the history
  • Loading branch information
Nico-dl05 committed Dec 4, 2024
1 parent 2e55acc commit 8d31538
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 96 deletions.
1 change: 1 addition & 0 deletions Adaptors/Amqp/src/ArmoniK.Core.Adapters.Amqp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<Private>false</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
<PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
</ItemGroup>

<ItemGroup>
Expand Down
98 changes: 77 additions & 21 deletions Adaptors/Amqp/src/QueueBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

using ArmoniK.Core.Base;
Expand All @@ -27,6 +28,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

using RabbitMQ.Client;

namespace ArmoniK.Core.Adapters.Amqp;

/// <summary>
Expand Down Expand Up @@ -67,26 +70,79 @@ public void Build(IServiceCollection serviceCollection,

if (!string.IsNullOrEmpty(amqpOptions.CaPath))
{
var localTrustStore = new X509Store(StoreName.Root);
var certificateCollection = new X509Certificate2Collection();
try
{
certificateCollection.ImportFromPemFile(amqpOptions.CaPath);
localTrustStore.Open(OpenFlags.ReadWrite);
localTrustStore.AddRange(certificateCollection);
logger.LogTrace("Imported AMQP certificate from file {path}",
amqpOptions.CaPath);
}
catch (Exception ex)
{
logger.LogError("Root certificate import failed: {error}",
ex.Message);
throw;
}
finally
{
localTrustStore.Close();
}
var authority = new X509Certificate2(amqpOptions.CaPath);

// Configure the SSL settings
var sslOption = new SslOption
{
Enabled = true,
ServerName = amqpOptions.Host,
Certs = new X509Certificate2Collection(),
AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateChainErrors,
CertificateValidationCallback = (sender,
certificate,
chain,
sslPolicyErrors) =>
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}

if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
logger.LogError("SSL validation failed: {errors}",
sslPolicyErrors);
return false;
}

// If there is any error other than untrusted root or partial chain, fail the validation
if (chain!.ChainStatus.Any(status => status.Status is not X509ChainStatusFlags.UntrustedRoot and
not X509ChainStatusFlags.PartialChain))
{
return false;
}

// Disable some extensive checks that would fail on the authority that is not in store
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;

// Add unknown authority to the store
chain.ChainPolicy.ExtraStore.Add(authority);

// Check if the chain is valid for the actual server certificate (ie: trusted)
if (!chain.Build(new X509Certificate2(certificate!)))
{
logger.LogError("SSL chain validation failed.");
return false;
}

// Check that the chain root is actually the specified authority (caCert)
var isTrusted = chain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint);

if (!isTrusted)
{
logger.LogError("Certificate chain root does not match the specified CA authority.");
}

return isTrusted;
},
};

// Apply the SSL settings to your RabbitMQ connection factory
var factory = new ConnectionFactory
{
HostName = amqpOptions.Host,
UserName = amqpOptions.User,
Password = amqpOptions.Password,
Ssl = sslOption,
};

serviceCollection.AddSingleton(factory);
}
else
{
logger.LogTrace("No CA path provided");
}

serviceCollection.AddSingletonWithHealthCheck<IConnectionAmqp, ConnectionAmqp>(nameof(IConnectionAmqp));
Expand Down
117 changes: 85 additions & 32 deletions Adaptors/RabbitMQ/src/QueueBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Linq;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

using ArmoniK.Core.Adapters.QueueCommon;
using ArmoniK.Core.Adapters.RabbitMQ;
using ArmoniK.Core.Base;
using ArmoniK.Core.Utils;

Expand All @@ -28,7 +29,9 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace ArmoniK.Core.Adapters.RabbitMQ;
using RabbitMQ.Client;

namespace ArmoniK.Core.Adapters.Amqp;

/// <summary>
/// Class for building RabbitMQ object and Queue interfaces through Dependency Injection
Expand All @@ -42,25 +45,22 @@ public void Build(IServiceCollection serviceCollection,
ConfigurationManager configuration,
ILogger logger)
{
logger.LogInformation("Configure RabbitMQ client");

logger.LogInformation("Configure Amqp client");

serviceCollection.AddOption(configuration,
Amqp.SettingSection,
out Amqp amqpOptions);

QueueCommon.Amqp.SettingSection,
out QueueCommon.Amqp amqpOptions);

if (!string.IsNullOrEmpty(amqpOptions.CredentialsPath))
{
configuration.AddJsonFile(amqpOptions.CredentialsPath,
false,
false);
logger.LogTrace("Loaded amqp credentials from file {path}",
amqpOptions.CredentialsPath);

serviceCollection.AddOption(configuration,
Amqp.SettingSection,
QueueCommon.Amqp.SettingSection,
out amqpOptions);
logger.LogTrace("Loaded amqp credentials from file {path}",
amqpOptions.CredentialsPath);
}
else
{
Expand All @@ -69,26 +69,79 @@ public void Build(IServiceCollection serviceCollection,

if (!string.IsNullOrEmpty(amqpOptions.CaPath))
{
var localTrustStore = new X509Store(StoreName.Root);
var certificateCollection = new X509Certificate2Collection();
try
{
certificateCollection.ImportFromPemFile(amqpOptions.CaPath);
localTrustStore.Open(OpenFlags.ReadWrite);
localTrustStore.AddRange(certificateCollection);
logger.LogTrace("Imported AMQP certificate from file {path}",
amqpOptions.CaPath);
}
catch (Exception ex)
{
logger.LogError("Root certificate import failed: {error}",
ex.Message);
throw;
}
finally
{
localTrustStore.Close();
}
var authority = new X509Certificate2(amqpOptions.CaPath);

// Configure the SSL settings
var sslOption = new SslOption
{
Enabled = true,
ServerName = amqpOptions.Host,
Certs = new X509Certificate2Collection(),
AcceptablePolicyErrors = SslPolicyErrors.RemoteCertificateChainErrors,
CertificateValidationCallback = (sender,
certificate,
chain,
sslPolicyErrors) =>
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}

if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0)
{
logger.LogError("SSL validation failed: {errors}",
sslPolicyErrors);
return false;
}

// If there is any error other than untrusted root or partial chain, fail the validation
if (chain!.ChainStatus.Any(status => status.Status is not X509ChainStatusFlags.UntrustedRoot and
not X509ChainStatusFlags.PartialChain))
{
return false;
}

// Disable some extensive checks that would fail on the authority that is not in store
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;

// Add unknown authority to the store
chain.ChainPolicy.ExtraStore.Add(authority);

// Check if the chain is valid for the actual server certificate (ie: trusted)
if (!chain.Build(new X509Certificate2(certificate!)))
{
logger.LogError("SSL chain validation failed.");
return false;
}

// Check that the chain root is actually the specified authority (caCert)
var isTrusted = chain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint);

if (!isTrusted)
{
logger.LogError("Certificate chain root does not match the specified CA authority.");
}

return isTrusted;
},
};

// Apply the SSL settings to your RabbitMQ connection factory
var factory = new ConnectionFactory
{
HostName = amqpOptions.Host,
UserName = amqpOptions.User,
Password = amqpOptions.Password,
Ssl = sslOption,
};

serviceCollection.AddSingleton(factory);
}
else
{
logger.LogTrace("No CA path provided");
}

serviceCollection.AddSingletonWithHealthCheck<IConnectionRabbit, ConnectionRabbit>(nameof(IConnectionRabbit));
Expand Down
Loading

0 comments on commit 8d31538

Please sign in to comment.