From 823efa6896b350ed9a7775d81a3df9c5bcfaf896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gurhem?= Date: Thu, 4 Apr 2024 12:39:00 +0200 Subject: [PATCH] feat: support for client cert and multiple host connection string for mongo adapter --- Adaptors/MongoDB/src/Options/MongoDB.cs | 14 +++-- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 57 ++++++++++--------- Adaptors/MongoDB/tests/InjectionTests.cs | 54 +++++++++++++----- .../modules/storage/database/mongo/outputs.tf | 3 +- 4 files changed, 78 insertions(+), 50 deletions(-) diff --git a/Adaptors/MongoDB/src/Options/MongoDB.cs b/Adaptors/MongoDB/src/Options/MongoDB.cs index 67bc737d8..36e9c9990 100644 --- a/Adaptors/MongoDB/src/Options/MongoDB.cs +++ b/Adaptors/MongoDB/src/Options/MongoDB.cs @@ -16,9 +16,12 @@ // along with this program. If not, see . using System; +using System.Collections.Generic; using JetBrains.Annotations; +using MongoDB.Driver.Core.Configuration; + // ReSharper disable InconsistentNaming namespace ArmoniK.Core.Adapters.MongoDB.Options; @@ -36,12 +39,12 @@ public class MongoDB public string ReplicaSet { get; set; } = ""; - public string Host { get; set; } = ""; - - public int Port { get; set; } + public List Hosts { get; set; } = new(); public string CAFile { get; set; } = ""; + public List ClientCertificateFiles { get; set; } = new(); + public string CredentialsPath { get; set; } = ""; public string User { get; set; } = ""; @@ -60,6 +63,7 @@ public class MongoDB public QueueStorage QueueStorage { get; set; } = new(); - public int MaxConnectionPoolSize { get; set; } = 500; - public TimeSpan ServerSelectionTimeout { get; set; } = TimeSpan.FromMinutes(2); + public int MaxConnectionPoolSize { get; set; } = 500; + public TimeSpan ServerSelectionTimeout { get; set; } = TimeSpan.FromMinutes(2); + public ConnectionStringScheme Scheme { get; set; } = ConnectionStringScheme.MongoDB; } diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index c555bcfdc..6834b5ac9 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -16,6 +16,7 @@ // along with this program. If not, see . using System; +using System.Linq; using System.Security.Cryptography.X509Certificates; using ArmoniK.Api.Common.Utils; @@ -37,7 +38,6 @@ using Microsoft.Extensions.Logging; using MongoDB.Driver; -using MongoDB.Driver.Core.Configuration; using MongoDB.Driver.Core.Extensions.DiagnosticSources; namespace ArmoniK.Core.Adapters.MongoDB; @@ -107,13 +107,12 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services out mongoOptions); using var _ = logger.BeginNamedScope("MongoDB configuration", - ("host", mongoOptions.Host), - ("port", mongoOptions.Port)); + ("host", mongoOptions.Hosts)); - if (string.IsNullOrEmpty(mongoOptions.Host)) + if (!mongoOptions.Hosts.Any()) { throw new ArgumentOutOfRangeException(Options.MongoDB.SettingSection, - $"{nameof(Options.MongoDB.Host)} is not defined."); + $"{nameof(Options.MongoDB.Hosts)} is not defined."); } if (string.IsNullOrEmpty(mongoOptions.DatabaseName)) @@ -164,34 +163,29 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services } } - string connectionString; - if (string.IsNullOrEmpty(mongoOptions.User) || string.IsNullOrEmpty(mongoOptions.Password)) + var url = new MongoUrlBuilder + { + Servers = mongoOptions.Hosts.Select(MongoServerAddress.Parse), + }; + if (!string.IsNullOrEmpty(mongoOptions.User)) { - var template = "mongodb://{0}:{1}/{2}"; - connectionString = string.Format(template, - mongoOptions.Host, - mongoOptions.Port, - mongoOptions.DatabaseName); + url.Username = mongoOptions.User; } - else + + if (!string.IsNullOrEmpty(mongoOptions.Password)) { - var template = "mongodb://{0}:{1}@{2}:{3}/{4}"; - connectionString = string.Format(template, - mongoOptions.User, - mongoOptions.Password, - mongoOptions.Host, - mongoOptions.Port, - mongoOptions.DatabaseName); + url.Password = mongoOptions.Password; } - var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString)); - settings.AllowInsecureTls = mongoOptions.AllowInsecureTls; - settings.UseTls = mongoOptions.Tls; - settings.DirectConnection = mongoOptions.DirectConnection; - settings.Scheme = ConnectionStringScheme.MongoDB; - settings.MaxConnectionPoolSize = mongoOptions.MaxConnectionPoolSize; - settings.ServerSelectionTimeout = mongoOptions.ServerSelectionTimeout; - settings.ReplicaSetName = mongoOptions.ReplicaSet; + url.Scheme = mongoOptions.Scheme; + url.DirectConnection = mongoOptions.DirectConnection; + url.UseTls = mongoOptions.Tls; + url.AllowInsecureTls = mongoOptions.AllowInsecureTls; + url.MaxConnectionPoolSize = mongoOptions.MaxConnectionPoolSize; + url.ReplicaSetName = mongoOptions.ReplicaSet; + url.ServerSelectionTimeout = mongoOptions.ServerSelectionTimeout; + + var settings = MongoClientSettings.FromUrl(url.ToMongoUrl()); settings.ClusterConfigurator = cb => { //cb.Subscribe(e => logger.LogTrace("{CommandName} - {Command}", @@ -199,6 +193,13 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services // e.Command.ToJson())); cb.Subscribe(new DiagnosticsActivityEventSubscriber()); }; + if (mongoOptions.ClientCertificateFiles.Any()) + { + settings.SslSettings = new SslSettings + { + ClientCertificates = mongoOptions.ClientCertificateFiles.Select(s => X509Certificate2.CreateFromPemFile(s)), + }; + } var client = new MongoClient(settings); diff --git a/Adaptors/MongoDB/tests/InjectionTests.cs b/Adaptors/MongoDB/tests/InjectionTests.cs index d46387388..7eb768138 100644 --- a/Adaptors/MongoDB/tests/InjectionTests.cs +++ b/Adaptors/MongoDB/tests/InjectionTests.cs @@ -18,6 +18,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using ArmoniK.Core.Adapters.MongoDB.Options; using ArmoniK.Core.Common.Storage; @@ -37,6 +41,23 @@ internal class InjectionTests [SetUp] public void SetUp() { + var certReq = new CertificateRequest(new X500DistinguishedName("CN=test"), + RSA.Create(2048), + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + + var cert0 = certReq.CreateSelfSigned(DateTimeOffset.UtcNow, + DateTimeOffset.Now.AddDays(10)); + + var path0 = Path.Combine(Path.GetTempPath(), + "file0.pem"); + var path1 = Path.Combine(Path.GetTempPath(), + "file1.pem"); + File.WriteAllText(path0, + cert0.ExportCertificatePem() + "\n" + cert0.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem()); + File.WriteAllText(path1, + cert0.ExportCertificatePem() + "\n" + cert0.GetRSAPrivateKey()!.ExportRSAPrivateKeyPem()); + Dictionary baseConfig = new() { { @@ -49,10 +70,7 @@ public void SetUp() "Components:LeaseProvider", "ArmoniK.Adapters.MongoDB.LeaseProvider" }, { - $"{Options.MongoDB.SettingSection}:{nameof(Options.MongoDB.Host)}", "localhost" - }, - { - $"{Options.MongoDB.SettingSection}:{nameof(Options.MongoDB.Port)}", "3232" + $"{Options.MongoDB.SettingSection}:{nameof(Options.MongoDB.Hosts)}:0", "localhost:3232" }, { $"{Options.MongoDB.SettingSection}:{nameof(Options.MongoDB.Tls)}", "true" @@ -90,6 +108,12 @@ public void SetUp() { $"{Options.MongoDB.SettingSection}:{nameof(Options.MongoDB.ObjectStorage)}:ChunkSize", "100000" }, + { + $"{Options.MongoDB.SettingSection}:{nameof(Options.MongoDB.ClientCertificateFiles)}:0", path0 + }, + { + $"{Options.MongoDB.SettingSection}:{nameof(Options.MongoDB.ClientCertificateFiles)}:1", path1 + }, }; var logger = NullLogger.Instance; @@ -132,8 +156,17 @@ public void ReadMongoDbHost() { var options = provider_!.GetRequiredService(); - Assert.AreEqual("localhost", - options.Host); + Assert.AreEqual("localhost:3232", + options.Hosts.Single()); + } + + [Test] + public void ReadClientCertificateFiles() + { + var options = provider_!.GetRequiredService(); + + Assert.AreEqual(2, + options.ClientCertificateFiles.Count); } [Test] @@ -172,15 +205,6 @@ public void ReadMongoDbCaFile() options.CAFile); } - [Test] - public void ReadMongoDbPort() - { - var options = provider_!.GetRequiredService(); - - Assert.AreEqual(3232, - options.Port); - } - [Test] public void ReadMongoDbTls() { diff --git a/terraform/modules/storage/database/mongo/outputs.tf b/terraform/modules/storage/database/mongo/outputs.tf index 364c42c83..eeb4a2d32 100644 --- a/terraform/modules/storage/database/mongo/outputs.tf +++ b/terraform/modules/storage/database/mongo/outputs.tf @@ -1,8 +1,7 @@ output "generated_env_vars" { value = { "Components__TableStorage" = "ArmoniK.Adapters.MongoDB.TableStorage" - "MongoDB__Host" = docker_container.database.name - "MongoDB__Port" = "${var.mongodb_params.exposed_port}" + "MongoDB__Hosts__0" = "${docker_container.database.name}:${var.mongodb_params.exposed_port}" "MongoDB__DatabaseName" = docker_container.database.name "MongoDB__MaxConnectionPoolSize" = "${var.mongodb_params.max_connection_pool_size}" "MongoDB__TableStorage__PollingDelayMin" = "${var.mongodb_params.min_polling_delay}"