From b7255501d1b35bef97b401d55e89660eec3b9316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gurhem?= Date: Fri, 13 Sep 2024 11:43:26 +0200 Subject: [PATCH 01/14] build: add tls for mongo in deployment --- terraform/main.tf | 4 ++ terraform/modules/compute_plane/inputs.tf | 4 ++ terraform/modules/compute_plane/main.tf | 8 +++ .../modules/monitoring/metrics/inputs.tf | 4 ++ terraform/modules/monitoring/metrics/main.tf | 8 +++ .../monitoring/partition_metrics/inputs.tf | 4 ++ .../monitoring/partition_metrics/main.tf | 8 +++ .../storage/database/mongo/certificates.tf | 69 +++++++++++++++++++ .../modules/storage/database/mongo/main.tf | 19 +++-- .../modules/storage/database/mongo/outputs.tf | 10 +++ .../storage/database/mongo/versions.tf | 4 ++ terraform/modules/submitter/inputs.tf | 4 ++ terraform/modules/submitter/main.tf | 8 +++ 13 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 terraform/modules/storage/database/mongo/certificates.tf diff --git a/terraform/main.tf b/terraform/main.tf index 56f9b4b2b..0a826ea8a 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -115,6 +115,7 @@ module "submitter" { generated_env_vars = local.environment log_driver = module.fluenbit.log_driver volumes = local.volumes + mounts = module.database.core_mounts } module "compute_plane" { @@ -129,6 +130,7 @@ module "compute_plane" { volumes = local.volumes network = docker_network.armonik.id log_driver = module.fluenbit.log_driver + mounts = module.database.core_mounts } module "metrics_exporter" { @@ -138,6 +140,7 @@ module "metrics_exporter" { network = docker_network.armonik.id generated_env_vars = local.environment log_driver = module.fluenbit.log_driver + mounts = module.database.core_mounts } module "partition_metrics_exporter" { @@ -148,6 +151,7 @@ module "partition_metrics_exporter" { generated_env_vars = local.environment metrics_env_vars = module.metrics_exporter.metrics_env_vars log_driver = module.fluenbit.log_driver + mounts = module.database.core_mounts } module "ingress" { diff --git a/terraform/modules/compute_plane/inputs.tf b/terraform/modules/compute_plane/inputs.tf index d599e9b0e..eba3a2bda 100644 --- a/terraform/modules/compute_plane/inputs.tf +++ b/terraform/modules/compute_plane/inputs.tf @@ -32,6 +32,10 @@ variable "volumes" { type = map(string) } +variable "mounts" { + type = map(string) +} + variable "replica_counter" { type = number } diff --git a/terraform/modules/compute_plane/main.tf b/terraform/modules/compute_plane/main.tf index 399f23190..b6202b1b6 100644 --- a/terraform/modules/compute_plane/main.tf +++ b/terraform/modules/compute_plane/main.tf @@ -74,5 +74,13 @@ resource "docker_container" "polling_agent" { } } + dynamic "upload" { + for_each = var.mounts + content { + source = upload.value + file = upload.key + } + } + depends_on = [docker_container.worker] } diff --git a/terraform/modules/monitoring/metrics/inputs.tf b/terraform/modules/monitoring/metrics/inputs.tf index 3f7d806aa..3dff6611d 100644 --- a/terraform/modules/monitoring/metrics/inputs.tf +++ b/terraform/modules/monitoring/metrics/inputs.tf @@ -14,6 +14,10 @@ variable "generated_env_vars" { type = map(string) } +variable "mounts" { + type = map(string) +} + variable "exposed_port" { type = number default = 5002 diff --git a/terraform/modules/monitoring/metrics/main.tf b/terraform/modules/monitoring/metrics/main.tf index 531674fbb..56e2e2b73 100644 --- a/terraform/modules/monitoring/metrics/main.tf +++ b/terraform/modules/monitoring/metrics/main.tf @@ -20,4 +20,12 @@ resource "docker_container" "metrics" { internal = 1080 external = var.exposed_port } + + dynamic "upload" { + for_each = var.mounts + content { + source = upload.value + file = upload.key + } + } } diff --git a/terraform/modules/monitoring/partition_metrics/inputs.tf b/terraform/modules/monitoring/partition_metrics/inputs.tf index 3fbf4c6a3..9724d39d5 100644 --- a/terraform/modules/monitoring/partition_metrics/inputs.tf +++ b/terraform/modules/monitoring/partition_metrics/inputs.tf @@ -19,6 +19,10 @@ variable "generated_env_vars" { type = map(string) } +variable "mounts" { + type = map(string) +} + variable "metrics_env_vars" { type = map(string) } diff --git a/terraform/modules/monitoring/partition_metrics/main.tf b/terraform/modules/monitoring/partition_metrics/main.tf index 87d3fede5..325fa6590 100644 --- a/terraform/modules/monitoring/partition_metrics/main.tf +++ b/terraform/modules/monitoring/partition_metrics/main.tf @@ -20,4 +20,12 @@ resource "docker_container" "partition_metrics" { internal = 1080 external = var.exposed_port } + + dynamic "upload" { + for_each = var.mounts + content { + source = upload.value + file = upload.key + } + } } diff --git a/terraform/modules/storage/database/mongo/certificates.tf b/terraform/modules/storage/database/mongo/certificates.tf new file mode 100644 index 000000000..71393660c --- /dev/null +++ b/terraform/modules/storage/database/mongo/certificates.tf @@ -0,0 +1,69 @@ +#------------------------------------------------------------------------------ +# Certificate Authority +#------------------------------------------------------------------------------ +resource "tls_private_key" "root_mongodb" { + algorithm = "RSA" + ecdsa_curve = "P384" + rsa_bits = "4096" +} + +resource "tls_self_signed_cert" "root_mongodb" { + private_key_pem = tls_private_key.root_mongodb.private_key_pem + is_ca_certificate = true + validity_period_hours = 100000 + allowed_uses = [ + "cert_signing", + "key_encipherment", + "digital_signature" + ] + subject { + organization = "ArmoniK mongodb Root (NonTrusted)" + common_name = "ArmoniK mongodb Root (NonTrusted) Private Certificate Authority" + country = "France" + } +} + +#------------------------------------------------------------------------------ +# Certificate +#------------------------------------------------------------------------------ +resource "tls_private_key" "mongodb_private_key" { + algorithm = "RSA" + ecdsa_curve = "P384" + rsa_bits = "4096" +} + +resource "tls_cert_request" "mongodb_cert_request" { + private_key_pem = tls_private_key.mongodb_private_key.private_key_pem + subject { + country = "France" + common_name = "127.0.0.1" + # organization = "127.0.0.1" + } + ip_addresses = ["127.0.0.1"] + dns_names = [var.mongodb_params.database_name] +} + +resource "tls_locally_signed_cert" "mongodb_certificate" { + cert_request_pem = tls_cert_request.mongodb_cert_request.cert_request_pem + ca_private_key_pem = tls_private_key.root_mongodb.private_key_pem + ca_cert_pem = tls_self_signed_cert.root_mongodb.cert_pem + validity_period_hours = 100000 + allowed_uses = [ + "key_encipherment", + "digital_signature", + "server_auth", + "client_auth", + "any_extended", + ] +} + +locals { + server_key = format("%s\n%s", tls_locally_signed_cert.mongodb_certificate.cert_pem, tls_private_key.mongodb_private_key.private_key_pem) +} + +resource "local_sensitive_file" "ca" { + content = tls_locally_signed_cert.mongodb_certificate.ca_cert_pem + filename = "${path.root}/generated/mongo/ca.pem" + file_permission = "0644" +} + diff --git a/terraform/modules/storage/database/mongo/main.tf b/terraform/modules/storage/database/mongo/main.tf index 388b9ca2d..9b1ab573a 100644 --- a/terraform/modules/storage/database/mongo/main.tf +++ b/terraform/modules/storage/database/mongo/main.tf @@ -7,7 +7,7 @@ resource "docker_container" "database" { name = var.mongodb_params.database_name image = docker_image.database.image_id - command = ["mongod", "--bind_ip_all", "--replSet", var.mongodb_params.replica_set_name] + command = ["mongod", "--bind_ip_all", "--replSet", var.mongodb_params.replica_set_name, "--tlsMode=requireTLS", "--tlsDisabledProtocols=TLS1_0", "--tlsCertificateKeyFile=/cert/key.pem", "--tlsCAFile=/cert/ca.pem", "--tlsAllowConnectionsWithoutCertificates"] networks_advanced { name = var.network @@ -23,12 +23,21 @@ resource "docker_container" "database" { dynamic "healthcheck" { for_each = var.mongodb_params.windows ? [] : [1] content { - test = ["CMD", "mongosh", "--quiet", "--eval", "db.runCommand('ping').ok"] + test = ["CMD", "mongosh", "--quiet", "--tls", "--tlsCAFile", "/cert/ca.pem", "--eval", "db.runCommand('ping').ok"] interval = "3s" retries = "2" - timeout = "2s" + timeout = "3s" } } + upload { + file = "/cert/key.pem" + content = local.server_key + } + + upload { + file = "/cert/ca.pem" + content = tls_locally_signed_cert.mongodb_certificate.ca_cert_pem + } } resource "time_sleep" "wait" { @@ -37,9 +46,9 @@ resource "time_sleep" "wait" { } locals { - linux_run = "docker run --net ${var.network} ${docker_image.database.image_id} mongosh mongodb://${docker_container.database.name}:27017/${var.mongodb_params.database_name}" + linux_run = "docker exec ${docker_container.database.name} mongosh mongodb://127.0.0.1:27017/${var.mongodb_params.database_name} --tls --tlsCAFile /cert/ca.pem" // mongosh is not installed in windows docker images so we need it to be installed locally - windows_run = "mongosh.exe mongodb://localhost:${var.mongodb_params.exposed_port}/${var.mongodb_params.database_name}" + windows_run = "mongosh.exe mongodb://localhost:${var.mongodb_params.exposed_port}/${var.mongodb_params.database_name} --tls --tlsCAFile ${local_sensitive_file.ca.filename}" prefix_run = var.mongodb_params.windows ? local.windows_run : local.linux_run } diff --git a/terraform/modules/storage/database/mongo/outputs.tf b/terraform/modules/storage/database/mongo/outputs.tf index 364c42c83..329f2d9ae 100644 --- a/terraform/modules/storage/database/mongo/outputs.tf +++ b/terraform/modules/storage/database/mongo/outputs.tf @@ -9,7 +9,17 @@ output "generated_env_vars" { "MongoDB__TableStorage__PollingDelayMax" = "${var.mongodb_params.max_polling_delay}" "MongoDB__DirectConnection" = "${var.mongodb_params.use_direct_connection}" "MongoDB__ReplicaSet" = "${var.mongodb_params.replica_set_name}" + "MongoDB__Tls" = "true" + "MongoDB__AllowInsecureTls" = "true" + "MongoDB__CAFile" = "/cert/ca.pem" + "MongoDB__ServerSelectionTimeout" = "00:00:20" } depends_on = [null_resource.partitions_in_db] } + +output "core_mounts" { + value = { + "/cert/ca.pem" = local_sensitive_file.ca.filename + } +} diff --git a/terraform/modules/storage/database/mongo/versions.tf b/terraform/modules/storage/database/mongo/versions.tf index 042090bbc..f007c46e4 100644 --- a/terraform/modules/storage/database/mongo/versions.tf +++ b/terraform/modules/storage/database/mongo/versions.tf @@ -8,5 +8,9 @@ terraform { source = "hashicorp/time" version = "0.12.0" } + tls = { + source = "hashicorp/tls" + version = ">= 4.0.4" + } } } diff --git a/terraform/modules/submitter/inputs.tf b/terraform/modules/submitter/inputs.tf index 0db8bf044..d16fe058f 100644 --- a/terraform/modules/submitter/inputs.tf +++ b/terraform/modules/submitter/inputs.tf @@ -18,6 +18,10 @@ variable "generated_env_vars" { type = map(string) } +variable "mounts" { + type = map(string) +} + variable "volumes" { type = map(string) } diff --git a/terraform/modules/submitter/main.tf b/terraform/modules/submitter/main.tf index 5f6f1c2e4..1ee304e61 100644 --- a/terraform/modules/submitter/main.tf +++ b/terraform/modules/submitter/main.tf @@ -34,4 +34,12 @@ resource "docker_container" "submitter" { source = mounts.key } } + + dynamic "upload" { + for_each = var.mounts + content { + source = upload.value + file = upload.key + } + } } From f8c4d423a5cfafec9dd90f08ccd01907689dcebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gurhem?= Date: Sat, 14 Sep 2024 21:43:26 +0200 Subject: [PATCH 02/14] fix: add localhost in certificate dns list --- terraform/modules/storage/database/mongo/certificates.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/modules/storage/database/mongo/certificates.tf b/terraform/modules/storage/database/mongo/certificates.tf index 71393660c..cce6c116f 100644 --- a/terraform/modules/storage/database/mongo/certificates.tf +++ b/terraform/modules/storage/database/mongo/certificates.tf @@ -40,7 +40,7 @@ resource "tls_cert_request" "mongodb_cert_request" { # organization = "127.0.0.1" } ip_addresses = ["127.0.0.1"] - dns_names = [var.mongodb_params.database_name] + dns_names = [var.mongodb_params.database_name, "localhost"] } resource "tls_locally_signed_cert" "mongodb_certificate" { From 75228bfb19657a7eb13480882a28392c35a95330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Gurhem?= Date: Mon, 16 Sep 2024 09:01:01 +0200 Subject: [PATCH 03/14] refactor: change certificates location --- terraform/modules/storage/database/mongo/main.tf | 10 +++++----- terraform/modules/storage/database/mongo/outputs.tf | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/terraform/modules/storage/database/mongo/main.tf b/terraform/modules/storage/database/mongo/main.tf index 9b1ab573a..fd0021a0d 100644 --- a/terraform/modules/storage/database/mongo/main.tf +++ b/terraform/modules/storage/database/mongo/main.tf @@ -7,7 +7,7 @@ resource "docker_container" "database" { name = var.mongodb_params.database_name image = docker_image.database.image_id - command = ["mongod", "--bind_ip_all", "--replSet", var.mongodb_params.replica_set_name, "--tlsMode=requireTLS", "--tlsDisabledProtocols=TLS1_0", "--tlsCertificateKeyFile=/cert/key.pem", "--tlsCAFile=/cert/ca.pem", "--tlsAllowConnectionsWithoutCertificates"] + command = ["mongod", "--bind_ip_all", "--replSet", var.mongodb_params.replica_set_name, "--tlsMode=requireTLS", "--tlsDisabledProtocols=TLS1_0", "--tlsCertificateKeyFile=/mongo-certificate/key.pem", "--tlsCAFile=/mongo-certificate/ca.pem", "--tlsAllowConnectionsWithoutCertificates"] networks_advanced { name = var.network @@ -23,19 +23,19 @@ resource "docker_container" "database" { dynamic "healthcheck" { for_each = var.mongodb_params.windows ? [] : [1] content { - test = ["CMD", "mongosh", "--quiet", "--tls", "--tlsCAFile", "/cert/ca.pem", "--eval", "db.runCommand('ping').ok"] + test = ["CMD", "mongosh", "--quiet", "--tls", "--tlsCAFile", "/mongo-certificate/ca.pem", "--eval", "db.runCommand('ping').ok"] interval = "3s" retries = "2" timeout = "3s" } } upload { - file = "/cert/key.pem" + file = "/mongo-certificate/key.pem" content = local.server_key } upload { - file = "/cert/ca.pem" + file = "/mongo-certificate/ca.pem" content = tls_locally_signed_cert.mongodb_certificate.ca_cert_pem } } @@ -46,7 +46,7 @@ resource "time_sleep" "wait" { } locals { - linux_run = "docker exec ${docker_container.database.name} mongosh mongodb://127.0.0.1:27017/${var.mongodb_params.database_name} --tls --tlsCAFile /cert/ca.pem" + linux_run = "docker exec ${docker_container.database.name} mongosh mongodb://127.0.0.1:27017/${var.mongodb_params.database_name} --tls --tlsCAFile /mongo-certificate/ca.pem" // mongosh is not installed in windows docker images so we need it to be installed locally windows_run = "mongosh.exe mongodb://localhost:${var.mongodb_params.exposed_port}/${var.mongodb_params.database_name} --tls --tlsCAFile ${local_sensitive_file.ca.filename}" prefix_run = var.mongodb_params.windows ? local.windows_run : local.linux_run diff --git a/terraform/modules/storage/database/mongo/outputs.tf b/terraform/modules/storage/database/mongo/outputs.tf index 329f2d9ae..b5b34844d 100644 --- a/terraform/modules/storage/database/mongo/outputs.tf +++ b/terraform/modules/storage/database/mongo/outputs.tf @@ -11,7 +11,7 @@ output "generated_env_vars" { "MongoDB__ReplicaSet" = "${var.mongodb_params.replica_set_name}" "MongoDB__Tls" = "true" "MongoDB__AllowInsecureTls" = "true" - "MongoDB__CAFile" = "/cert/ca.pem" + "MongoDB__CAFile" = "/mongo-certificate/ca.pem" "MongoDB__ServerSelectionTimeout" = "00:00:20" } @@ -20,6 +20,6 @@ output "generated_env_vars" { output "core_mounts" { value = { - "/cert/ca.pem" = local_sensitive_file.ca.filename + "/mongo-certificate/ca.pem" = local_sensitive_file.ca.filename } } From e089796fd7291a6bb70bebe386f3911d0de8c2df Mon Sep 17 00:00:00 2001 From: nico_dreylaq Date: Thu, 28 Nov 2024 17:59:07 +0100 Subject: [PATCH 04/14] fix: implem callback for tls caFile --- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 127 +++++++++++------- .../modules/storage/database/mongo/main.tf | 2 +- 2 files changed, 83 insertions(+), 46 deletions(-) diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index 9fa9e90a7..c78b8109c 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -16,6 +16,8 @@ // along with this program. If not, see . using System; +using System.Linq; +using System.Net.Security; using System.Security.Cryptography.X509Certificates; using ArmoniK.Api.Common.Utils; @@ -46,8 +48,8 @@ public static class ServiceCollectionExt { [PublicAPI] public static IServiceCollection AddMongoComponents(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { services.AddMongoClient(configuration, logger); @@ -58,8 +60,8 @@ public static IServiceCollection AddMongoComponents(this IServiceCollection serv [PublicAPI] public static IServiceCollection AddMongoStorages(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { logger.LogInformation("Configure MongoDB Components"); @@ -98,8 +100,8 @@ public static IServiceCollection AddMongoStorages(this IServiceCollection servic } public static IServiceCollection AddMongoClient(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { Options.MongoDB mongoOptions; services.AddOption(configuration, @@ -139,31 +141,7 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services { logger.LogTrace("No credentials provided"); } - - if (!string.IsNullOrEmpty(mongoOptions.CAFile)) - { - var localTrustStore = new X509Store(StoreName.Root); - var certificateCollection = new X509Certificate2Collection(); - try - { - certificateCollection.ImportFromPemFile(mongoOptions.CAFile); - localTrustStore.Open(OpenFlags.ReadWrite); - localTrustStore.AddRange(certificateCollection); - logger.LogTrace("Imported mongodb certificate from file {path}", - mongoOptions.CAFile); - } - catch (Exception ex) - { - logger.LogError("Root certificate import failed: {error}", - ex.Message); - throw; - } - finally - { - localTrustStore.Close(); - } - } - + string connectionString; if (string.IsNullOrEmpty(mongoOptions.User) || string.IsNullOrEmpty(mongoOptions.Password)) { @@ -190,20 +168,79 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services } 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.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; + settings.ReplicaSetName = mongoOptions.ReplicaSet; + if (!string.IsNullOrEmpty(mongoOptions.CAFile)) + { + // Find the authority certificate in the collection + var authority = new X509Certificate2(mongoOptions.CAFile); + + // Configure the SSL settings + settings.SslSettings = new SslSettings + { + ClientCertificates = new X509Certificate2Collection(), + CheckCertificateRevocation = false, + EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12, + ServerCertificateValidationCallback = (sender, certificate2, certChain, 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 (certChain!.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 + certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + + // Add unknown authority to the store + certChain.ChainPolicy.ExtraStore.Add(authority); + + // Check if the chain is valid for the actual server certificate (ie: trusted) + if (!certChain.Build(new X509Certificate2(certificate2!))) + { + logger.LogError("SSL chain validation failed."); + return false; + } + + // Check that the chain root is actually the specified authority (caCert) + bool isTrusted = certChain.ChainElements.Cast() + .Any(x => x.Certificate.Thumbprint == authority.Thumbprint); + + if (!isTrusted) + { + logger.LogError("Certificate chain root does not match the specified CA authority."); + } + + return isTrusted; + } + }; + + } + settings.ClusterConfigurator = cb => - { - //cb.Subscribe(e => logger.LogTrace("{CommandName} - {Command}", - // e.CommandName, - // e.Command.ToJson())); - cb.Subscribe(new DiagnosticsActivityEventSubscriber()); - }; + { + //cb.Subscribe(e => logger.LogTrace("{CommandName} - {Command}", + // e.CommandName, + // e.Command.ToJson())); + cb.Subscribe(new DiagnosticsActivityEventSubscriber()); + }; + + var client = new MongoClient(settings); @@ -229,7 +266,7 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IServiceCollection services, - ConfigurationManager configuration) + ConfigurationManager configuration) { var components = configuration.GetSection(Components.SettingSection); if (components[nameof(Components.AuthenticationStorage)] == "ArmoniK.Adapters.MongoDB.AuthenticationTable") @@ -250,7 +287,7 @@ public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IS /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthServices(this IServiceCollection services, - ConfigurationManager configuration, + ConfigurationManager configuration, out AuthenticationCache authCache) { authCache = new AuthenticationCache(); diff --git a/terraform/modules/storage/database/mongo/main.tf b/terraform/modules/storage/database/mongo/main.tf index fd0021a0d..361ca6008 100644 --- a/terraform/modules/storage/database/mongo/main.tf +++ b/terraform/modules/storage/database/mongo/main.tf @@ -54,7 +54,7 @@ locals { resource "null_resource" "init_replica" { provisioner "local-exec" { - command = "${local.prefix_run} --eval 'rs.initiate()'" + command = "${local.prefix_run} --eval 'rs.initiate({_id: \"${var.mongodb_params.replica_set_name}\"})'" } depends_on = [time_sleep.wait] } From a90827cd95705a13452de2d28b63637f34f36940 Mon Sep 17 00:00:00 2001 From: nico_dreylaq Date: Mon, 2 Dec 2024 14:07:18 +0100 Subject: [PATCH 05/14] fix: correct repset init for deploy --- .../modules/storage/database/mongo/main.tf | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/terraform/modules/storage/database/mongo/main.tf b/terraform/modules/storage/database/mongo/main.tf index 361ca6008..d145f0f01 100644 --- a/terraform/modules/storage/database/mongo/main.tf +++ b/terraform/modules/storage/database/mongo/main.tf @@ -24,11 +24,12 @@ resource "docker_container" "database" { for_each = var.mongodb_params.windows ? [] : [1] content { test = ["CMD", "mongosh", "--quiet", "--tls", "--tlsCAFile", "/mongo-certificate/ca.pem", "--eval", "db.runCommand('ping').ok"] - interval = "3s" + interval = "3s" retries = "2" timeout = "3s" } } + upload { file = "/mongo-certificate/key.pem" content = local.server_key @@ -39,7 +40,6 @@ resource "docker_container" "database" { content = tls_locally_signed_cert.mongodb_certificate.ca_cert_pem } } - resource "time_sleep" "wait" { create_duration = var.mongodb_params.windows ? "15s" : "0s" depends_on = [docker_container.database] @@ -54,7 +54,16 @@ locals { resource "null_resource" "init_replica" { provisioner "local-exec" { - command = "${local.prefix_run} --eval 'rs.initiate({_id: \"${var.mongodb_params.replica_set_name}\"})'" + command = < Date: Fri, 6 Dec 2024 12:11:25 +0100 Subject: [PATCH 06/14] chores: implem logs for tls in mongo --- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 162 ++++++++++--------- 1 file changed, 87 insertions(+), 75 deletions(-) diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index c78b8109c..cc30f94a2 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -18,6 +18,7 @@ using System; using System.Linq; using System.Net.Security; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using ArmoniK.Api.Common.Utils; @@ -48,8 +49,8 @@ public static class ServiceCollectionExt { [PublicAPI] public static IServiceCollection AddMongoComponents(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { services.AddMongoClient(configuration, logger); @@ -60,8 +61,8 @@ public static IServiceCollection AddMongoComponents(this IServiceCollection serv [PublicAPI] public static IServiceCollection AddMongoStorages(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { logger.LogInformation("Configure MongoDB Components"); @@ -100,8 +101,8 @@ public static IServiceCollection AddMongoStorages(this IServiceCollection servic } public static IServiceCollection AddMongoClient(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { Options.MongoDB mongoOptions; services.AddOption(configuration, @@ -141,7 +142,7 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services { logger.LogTrace("No credentials provided"); } - + string connectionString; if (string.IsNullOrEmpty(mongoOptions.User) || string.IsNullOrEmpty(mongoOptions.Password)) { @@ -168,78 +169,89 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services } 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.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; + settings.ReplicaSetName = mongoOptions.ReplicaSet; if (!string.IsNullOrEmpty(mongoOptions.CAFile)) { - // Find the authority certificate in the collection - var authority = new X509Certificate2(mongoOptions.CAFile); - - // Configure the SSL settings - settings.SslSettings = new SslSettings - { - ClientCertificates = new X509Certificate2Collection(), - CheckCertificateRevocation = false, - EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12, - ServerCertificateValidationCallback = (sender, certificate2, certChain, 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 (certChain!.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 - certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; - - // Add unknown authority to the store - certChain.ChainPolicy.ExtraStore.Add(authority); - - // Check if the chain is valid for the actual server certificate (ie: trusted) - if (!certChain.Build(new X509Certificate2(certificate2!))) - { - logger.LogError("SSL chain validation failed."); - return false; - } - - // Check that the chain root is actually the specified authority (caCert) - bool isTrusted = certChain.ChainElements.Cast() - .Any(x => x.Certificate.Thumbprint == authority.Thumbprint); - - if (!isTrusted) - { - logger.LogError("Certificate chain root does not match the specified CA authority."); - } - - return isTrusted; - } - }; - + logger.LogInformation("Starting X509 certificate ."); + + // Find the authority certificate in the collection + var authority = new X509Certificate2(mongoOptions.CAFile); + logger.LogInformation("CA certificate loaded.: " + authority); + + // Configure the SSL settings + settings.SslSettings = new SslSettings + { + ClientCertificates = new X509Certificate2Collection(), + CheckCertificateRevocation = false, + EnabledSslProtocols = SslProtocols.Tls12, + ServerCertificateValidationCallback = (sender, + certificate2, + certChain, + sslPolicyErrors) => + { + logger.LogInformation("Starting SSL certificate validation."); + + 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 (certChain!.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 + certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + + // Add unknown authority to the store + certChain.ChainPolicy.ExtraStore.Add(authority); + + // Check if the chain is valid for the actual server certificate (ie: trusted) + if (!certChain.Build(new X509Certificate2(certificate2!))) + { + logger.LogError("SSL chain validation failed."); + return false; + } + + // Check that the chain root is actually the specified authority (caCert) + var isTrusted = + certChain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint); + + if (!isTrusted) + { + logger.LogError("Certificate chain root does not match the specified CA authority."); + } + + return isTrusted; + }, + }; } settings.ClusterConfigurator = cb => - { - //cb.Subscribe(e => logger.LogTrace("{CommandName} - {Command}", - // e.CommandName, - // e.Command.ToJson())); - cb.Subscribe(new DiagnosticsActivityEventSubscriber()); - }; - + { + //cb.Subscribe(e => logger.LogTrace("{CommandName} - {Command}", + // e.CommandName, + // e.Command.ToJson())); + cb.Subscribe(new DiagnosticsActivityEventSubscriber()); + }; var client = new MongoClient(settings); @@ -266,7 +278,7 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IServiceCollection services, - ConfigurationManager configuration) + ConfigurationManager configuration) { var components = configuration.GetSection(Components.SettingSection); if (components[nameof(Components.AuthenticationStorage)] == "ArmoniK.Adapters.MongoDB.AuthenticationTable") @@ -287,7 +299,7 @@ public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IS /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthServices(this IServiceCollection services, - ConfigurationManager configuration, + ConfigurationManager configuration, out AuthenticationCache authCache) { authCache = new AuthenticationCache(); From fbdbdb6c1c15d26e7afbd3aa6e84bec6d3b393a3 Mon Sep 17 00:00:00 2001 From: nico_dreylaq Date: Mon, 9 Dec 2024 12:23:04 +0100 Subject: [PATCH 07/14] fix: removing the certchainStore for tls --- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 128 ++++++++----------- 1 file changed, 52 insertions(+), 76 deletions(-) diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index cc30f94a2..4ed53e9d7 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -16,7 +16,6 @@ // along with this program. If not, see . using System; -using System.Linq; using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; @@ -49,8 +48,8 @@ public static class ServiceCollectionExt { [PublicAPI] public static IServiceCollection AddMongoComponents(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { services.AddMongoClient(configuration, logger); @@ -61,8 +60,8 @@ public static IServiceCollection AddMongoComponents(this IServiceCollection serv [PublicAPI] public static IServiceCollection AddMongoStorages(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { logger.LogInformation("Configure MongoDB Components"); @@ -101,8 +100,8 @@ public static IServiceCollection AddMongoStorages(this IServiceCollection servic } public static IServiceCollection AddMongoClient(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { Options.MongoDB mongoOptions; services.AddOption(configuration, @@ -169,82 +168,59 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services } 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; + + // Configure the connection settings + 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; + settings.ReplicaSetName = mongoOptions.ReplicaSet; + if (!string.IsNullOrEmpty(mongoOptions.CAFile)) { - logger.LogInformation("Starting X509 certificate ."); + logger.LogInformation("Starting X509 certificate configuration."); - // Find the authority certificate in the collection + // Load the CA certificate var authority = new X509Certificate2(mongoOptions.CAFile); - logger.LogInformation("CA certificate loaded.: " + authority); + logger.LogInformation($"CA certificate loaded: {authority.Subject}"); - // Configure the SSL settings + // SSL Parameters configuration settings.SslSettings = new SslSettings - { - ClientCertificates = new X509Certificate2Collection(), - CheckCertificateRevocation = false, - EnabledSslProtocols = SslProtocols.Tls12, - ServerCertificateValidationCallback = (sender, - certificate2, - certChain, - sslPolicyErrors) => - { - logger.LogInformation("Starting SSL certificate validation."); - - 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 (certChain!.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 - certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; - - // Add unknown authority to the store - certChain.ChainPolicy.ExtraStore.Add(authority); - - // Check if the chain is valid for the actual server certificate (ie: trusted) - if (!certChain.Build(new X509Certificate2(certificate2!))) - { - logger.LogError("SSL chain validation failed."); - return false; - } - - // Check that the chain root is actually the specified authority (caCert) - var isTrusted = - certChain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint); - - if (!isTrusted) - { - logger.LogError("Certificate chain root does not match the specified CA authority."); - } - - return isTrusted; - }, - }; + { + ClientCertificates = new X509Certificate2Collection(authority), + EnabledSslProtocols = SslProtocols.Tls12, + ServerCertificateValidationCallback = (sender, + certificate, + chain, + sslPolicyErrors) => + { + logger.LogInformation("Validating server certificate."); + + + if (sslPolicyErrors == SslPolicyErrors.None) + { + logger.LogInformation("SSL validation successful: no errors."); + return true; + } + + logger.LogError($"SSL validation failed with errors: {sslPolicyErrors}"); + + // Refuse critical errors + if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0) + { + logger.LogError("Critical SSL errors detected."); + return false; + } + + logger.LogInformation("SSL validation succeeded despite minor chain errors."); + return true; + }, + }; } + settings.ClusterConfigurator = cb => { //cb.Subscribe(e => logger.LogTrace("{CommandName} - {Command}", @@ -278,7 +254,7 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IServiceCollection services, - ConfigurationManager configuration) + ConfigurationManager configuration) { var components = configuration.GetSection(Components.SettingSection); if (components[nameof(Components.AuthenticationStorage)] == "ArmoniK.Adapters.MongoDB.AuthenticationTable") @@ -299,7 +275,7 @@ public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IS /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthServices(this IServiceCollection services, - ConfigurationManager configuration, + ConfigurationManager configuration, out AuthenticationCache authCache) { authCache = new AuthenticationCache(); From 4a73c88d5591c263a2bc8647a899d11a27740349 Mon Sep 17 00:00:00 2001 From: nico_dreylaq Date: Tue, 10 Dec 2024 16:48:23 +0100 Subject: [PATCH 08/14] feat: add additional logging for better error visibility --- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 121 ++++++++++++++----- 1 file changed, 94 insertions(+), 27 deletions(-) diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index 4ed53e9d7..43185fc25 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -16,8 +16,11 @@ // along with this program. If not, see . using System; +using System.IO; +using System.Linq; using System.Net.Security; using System.Security.Authentication; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using ArmoniK.Api.Common.Utils; @@ -182,42 +185,106 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services { logger.LogInformation("Starting X509 certificate configuration."); - // Load the CA certificate - var authority = new X509Certificate2(mongoOptions.CAFile); - logger.LogInformation($"CA certificate loaded: {authority.Subject}"); + if (!File.Exists(mongoOptions.CAFile)) + { + throw new FileNotFoundException("CA certificate file not found", mongoOptions.CAFile); + } - // SSL Parameters configuration - settings.SslSettings = new SslSettings + // Load the CA certificate + try { - ClientCertificates = new X509Certificate2Collection(authority), - EnabledSslProtocols = SslProtocols.Tls12, - ServerCertificateValidationCallback = (sender, - certificate, - chain, - sslPolicyErrors) => - { - logger.LogInformation("Validating server certificate."); + var authority = new X509Certificate2(mongoOptions.CAFile); + logger.LogInformation("CA certificate loaded: {authority.Subject}", authority.Subject); + + // SSL Parameters configuration + settings.SslSettings = new SslSettings + { + ClientCertificates = new X509Certificate2Collection(authority), + EnabledSslProtocols = SslProtocols.Tls12, + ServerCertificateValidationCallback = (sender, + certificate, + certChain, + sslPolicyErrors) => + { - if (sslPolicyErrors == SslPolicyErrors.None) - { - logger.LogInformation("SSL validation successful: no errors."); + if (sslPolicyErrors == SslPolicyErrors.None) + { + logger.LogInformation("SSL validation successful: no errors."); + return true; + } + // var cert = new X509Certificate2(certificate!); + // var validHosts = new[] { mongoOptions.Host, "127.0.0.1", "localhost" }; //Adding localhost for local development + + // if (!validHosts.Any(host => cert.Subject.Contains($"CN={host}", StringComparison.OrdinalIgnoreCase))) + // { + // logger.LogError("MongoOptions Host: {Host}", mongoOptions.Host); + // logger.LogError("Certificate Subject: {Subject}", cert.Subject); + // logger.LogError("Certificate name mismatch. Expected one of: {ExpectedHosts}, but found: {ActualSubject}", string.Join(", ", validHosts), cert.Subject); + // return false; + // } + logger.LogError("SSL validation failed with errors: {sslPolicyErrors}", sslPolicyErrors); + + // No Critical errors + if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0) + { + logger.LogCritical("Critical SSL errors detected."); + return false; + } + + // Validation of the certificate chain + certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + certChain.ChainPolicy.ExtraStore.Add(authority); // add the CA certificate to the chain + + + if (!certChain.Build(new X509Certificate2(certificate!))) + { + logger.LogError("SSL chain validation failed."); + foreach (var status in certChain.ChainStatus) + { + logger.LogInformation("ChainStatus: {status.StatusInformation} ({status.Status})", status.StatusInformation, status.Status); + } + + return false; + } + + var isTrusted = + certChain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint); + + if (!isTrusted) + { + logger.LogError("Certificate chain root does not match the specified CA authority."); + return false; + } + return true; } + }; - logger.LogError($"SSL validation failed with errors: {sslPolicyErrors}"); + } + catch (CryptographicException e) + { + logger.LogError(e, + "Error loading CA certificate: {message}", + e.Message); + logger.LogError(e.InnerException, + "Inner exception: {message}", + e.InnerException?.Message); + logger.LogError("Stack trace: {stackTrace}", + e.StackTrace); + logger.LogError("Help link: {helpLink}", + e.HelpLink); + + logger.LogError("HResult: {hResult}", + e.HResult); + logger.LogError("Source: {source}", + e.Source); + logger.LogError("Exception Data: {Data}", e.Data); + throw; + } - // Refuse critical errors - if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0) - { - logger.LogError("Critical SSL errors detected."); - return false; - } - logger.LogInformation("SSL validation succeeded despite minor chain errors."); - return true; - }, - }; } From 2e0a6745d00f32a7f497263cb125b8e3fa0adc92 Mon Sep 17 00:00:00 2001 From: nico_dreylaq Date: Thu, 12 Dec 2024 14:37:55 +0100 Subject: [PATCH 09/14] fix: implem validhosts & logging --- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 191 +++++++++++-------- 1 file changed, 108 insertions(+), 83 deletions(-) diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index 43185fc25..13ea1a990 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -51,8 +51,8 @@ public static class ServiceCollectionExt { [PublicAPI] public static IServiceCollection AddMongoComponents(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { services.AddMongoClient(configuration, logger); @@ -63,8 +63,8 @@ public static IServiceCollection AddMongoComponents(this IServiceCollection serv [PublicAPI] public static IServiceCollection AddMongoStorages(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { logger.LogInformation("Configure MongoDB Components"); @@ -103,8 +103,8 @@ public static IServiceCollection AddMongoStorages(this IServiceCollection servic } public static IServiceCollection AddMongoClient(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { Options.MongoDB mongoOptions; services.AddOption(configuration, @@ -173,13 +173,13 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString)); // Configure the connection settings - settings.AllowInsecureTls = mongoOptions.AllowInsecureTls; - settings.UseTls = mongoOptions.Tls; - settings.DirectConnection = mongoOptions.DirectConnection; - settings.Scheme = ConnectionStringScheme.MongoDB; - settings.MaxConnectionPoolSize = mongoOptions.MaxConnectionPoolSize; + 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; + settings.ReplicaSetName = mongoOptions.ReplicaSet; if (!string.IsNullOrEmpty(mongoOptions.CAFile)) { @@ -187,81 +187,107 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services if (!File.Exists(mongoOptions.CAFile)) { - throw new FileNotFoundException("CA certificate file not found", mongoOptions.CAFile); + throw new FileNotFoundException("CA certificate file not found", + mongoOptions.CAFile); } + logger.LogInformation("CA file found at path: {path}", + mongoOptions.CAFile); + logger.LogInformation("Raw CA file path: {path}", + mongoOptions.CAFile); + // Load the CA certificate try { var authority = new X509Certificate2(mongoOptions.CAFile); - logger.LogInformation("CA certificate loaded: {authority.Subject}", authority.Subject); + logger.LogInformation("CA certificate loaded: {authority.Subject}", + authority.Subject); // SSL Parameters configuration settings.SslSettings = new SslSettings - { - ClientCertificates = new X509Certificate2Collection(authority), - EnabledSslProtocols = SslProtocols.Tls12, - ServerCertificateValidationCallback = (sender, - certificate, - certChain, - sslPolicyErrors) => - { - - - if (sslPolicyErrors == SslPolicyErrors.None) - { - logger.LogInformation("SSL validation successful: no errors."); - return true; - } - // var cert = new X509Certificate2(certificate!); - // var validHosts = new[] { mongoOptions.Host, "127.0.0.1", "localhost" }; //Adding localhost for local development - - // if (!validHosts.Any(host => cert.Subject.Contains($"CN={host}", StringComparison.OrdinalIgnoreCase))) - // { - // logger.LogError("MongoOptions Host: {Host}", mongoOptions.Host); - // logger.LogError("Certificate Subject: {Subject}", cert.Subject); - // logger.LogError("Certificate name mismatch. Expected one of: {ExpectedHosts}, but found: {ActualSubject}", string.Join(", ", validHosts), cert.Subject); - // return false; - // } - logger.LogError("SSL validation failed with errors: {sslPolicyErrors}", sslPolicyErrors); - - // No Critical errors - if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0) - { - logger.LogCritical("Critical SSL errors detected."); - return false; - } - - // Validation of the certificate chain - certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; - certChain.ChainPolicy.ExtraStore.Add(authority); // add the CA certificate to the chain - - - if (!certChain.Build(new X509Certificate2(certificate!))) - { - logger.LogError("SSL chain validation failed."); - foreach (var status in certChain.ChainStatus) - { - logger.LogInformation("ChainStatus: {status.StatusInformation} ({status.Status})", status.StatusInformation, status.Status); - } - - return false; - } - - var isTrusted = - certChain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint); - - if (!isTrusted) - { - logger.LogError("Certificate chain root does not match the specified CA authority."); - return false; - } - - return true; - } - }; - + { + ClientCertificates = new X509Certificate2Collection(authority), + EnabledSslProtocols = SslProtocols.Tls12, + ServerCertificateValidationCallback = (sender, + certificate, + certChain, + sslPolicyErrors) => + { + if (sslPolicyErrors == SslPolicyErrors.None) + { + logger.LogInformation("SSL validation successful: no errors."); + return true; + } + + logger.LogError("SSL validation failed with errors: {sslPolicyErrors}", + sslPolicyErrors); + + if (certificate == null) + { + logger.LogError("Certificate is null!"); + return false; + } + + var cert = new X509Certificate2(certificate); + if (certChain == null) + { + logger.LogError("Certificate chain is null!"); + return false; + } + + certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + certChain.ChainPolicy.VerificationFlags = + X509VerificationFlags.AllowUnknownCertificateAuthority; + // Build the chain + if (!certChain.Build(cert)) + { + logger.LogError("SSL chain validation failed."); + foreach (var status in certChain.ChainStatus) + { + logger.LogError("ChainStatus: {status.StatusInformation} ({status.Status})", + status.StatusInformation, + status.Status); + } + + return false; + } + + // Verification of the chain root + if (authority != null) + { + certChain.ChainPolicy.ExtraStore.Add(authority); + logger.LogInformation("Added CA certificate to chain policy."); + var isTrusted = + certChain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint); + + if (!isTrusted) + { + logger.LogError("Certificate chain root does not match the specified CA authority."); + return false; + } + } + + + var validHosts = new[] + { + mongoOptions.Host, + "127.0.0.1", + "localhost" + }; + if (!validHosts.Any(host => cert.Subject.Contains($"CN={host}", + StringComparison.OrdinalIgnoreCase))) + { + logger + .LogError("Certificate host mismatch. Expected one of: {ValidHosts}, but found: {CertSubject}", + validHosts, + cert.Subject); + return false; + } + + logger.LogInformation("SSL validation successful."); + return true; + } + }; } catch (CryptographicException e) { @@ -280,11 +306,10 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services e.HResult); logger.LogError("Source: {source}", e.Source); - logger.LogError("Exception Data: {Data}", e.Data); + logger.LogError("Exception Data: {Data}", + e.Data); throw; } - - } @@ -321,7 +346,7 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IServiceCollection services, - ConfigurationManager configuration) + ConfigurationManager configuration) { var components = configuration.GetSection(Components.SettingSection); if (components[nameof(Components.AuthenticationStorage)] == "ArmoniK.Adapters.MongoDB.AuthenticationTable") @@ -342,7 +367,7 @@ public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IS /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthServices(this IServiceCollection services, - ConfigurationManager configuration, + ConfigurationManager configuration, out AuthenticationCache authCache) { authCache = new AuthenticationCache(); From e2a937d84f4d7047a551ad049257117abb58efb8 Mon Sep 17 00:00:00 2001 From: nico_dreylaq Date: Fri, 13 Dec 2024 10:10:13 +0100 Subject: [PATCH 10/14] feat: testing a potential fixed callback fix: implem fixed callback & terraform --- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 213 +++++++++--------- .../modules/storage/database/mongo/main.tf | 13 +- 2 files changed, 113 insertions(+), 113 deletions(-) diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index 13ea1a990..11d11446d 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -49,10 +49,33 @@ namespace ArmoniK.Core.Adapters.MongoDB; public static class ServiceCollectionExt { + public static void LogObjectProperties(ILogger logger, + T obj, + string objectName) + { + if (obj == null) + { + logger.LogError("{ObjectName} is null", + objectName); + return; + } + + var properties = typeof(T).GetProperties(); + foreach (var property in properties) + { + var value = property.GetValue(obj, + null); + logger.LogInformation("{ObjectName}.{Property} = {Value}", + objectName, + property.Name, + value); + } + } + [PublicAPI] public static IServiceCollection AddMongoComponents(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { services.AddMongoClient(configuration, logger); @@ -63,8 +86,8 @@ public static IServiceCollection AddMongoComponents(this IServiceCollection serv [PublicAPI] public static IServiceCollection AddMongoStorages(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { logger.LogInformation("Configure MongoDB Components"); @@ -103,14 +126,13 @@ public static IServiceCollection AddMongoStorages(this IServiceCollection servic } public static IServiceCollection AddMongoClient(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { Options.MongoDB mongoOptions; services.AddOption(configuration, Options.MongoDB.SettingSection, out mongoOptions); - using var _ = logger.BeginNamedScope("MongoDB configuration", ("host", mongoOptions.Host), ("port", mongoOptions.Port)); @@ -173,13 +195,13 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString)); // Configure the connection settings - settings.AllowInsecureTls = mongoOptions.AllowInsecureTls; - settings.UseTls = mongoOptions.Tls; - settings.DirectConnection = mongoOptions.DirectConnection; - settings.Scheme = ConnectionStringScheme.MongoDB; - settings.MaxConnectionPoolSize = mongoOptions.MaxConnectionPoolSize; + 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; + settings.ReplicaSetName = mongoOptions.ReplicaSet; if (!string.IsNullOrEmpty(mongoOptions.CAFile)) { @@ -199,95 +221,82 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services // Load the CA certificate try { - var authority = new X509Certificate2(mongoOptions.CAFile); - logger.LogInformation("CA certificate loaded: {authority.Subject}", - authority.Subject); - + var fileInfo = new FileInfo(mongoOptions.CAFile); + LogObjectProperties(logger, + fileInfo, + nameof(fileInfo)); + var content = File.ReadAllLines(mongoOptions.CAFile); + LogObjectProperties(logger, + content, + nameof(content)); + var authority = new X509Certificate2(mongoOptions.CAFile, + "", + X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); + logger.LogInformation("CA certificate loaded: {authority}", + authority); // SSL Parameters configuration settings.SslSettings = new SslSettings - { - ClientCertificates = new X509Certificate2Collection(authority), - EnabledSslProtocols = SslProtocols.Tls12, - ServerCertificateValidationCallback = (sender, - certificate, - certChain, - sslPolicyErrors) => - { - if (sslPolicyErrors == SslPolicyErrors.None) - { - logger.LogInformation("SSL validation successful: no errors."); - return true; - } - - logger.LogError("SSL validation failed with errors: {sslPolicyErrors}", - sslPolicyErrors); - - if (certificate == null) - { - logger.LogError("Certificate is null!"); - return false; - } - - var cert = new X509Certificate2(certificate); - if (certChain == null) - { - logger.LogError("Certificate chain is null!"); - return false; - } - - certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - certChain.ChainPolicy.VerificationFlags = - X509VerificationFlags.AllowUnknownCertificateAuthority; - // Build the chain - if (!certChain.Build(cert)) - { - logger.LogError("SSL chain validation failed."); - foreach (var status in certChain.ChainStatus) - { - logger.LogError("ChainStatus: {status.StatusInformation} ({status.Status})", - status.StatusInformation, - status.Status); - } - - return false; - } - - // Verification of the chain root - if (authority != null) - { - certChain.ChainPolicy.ExtraStore.Add(authority); - logger.LogInformation("Added CA certificate to chain policy."); - var isTrusted = - certChain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint); - - if (!isTrusted) - { - logger.LogError("Certificate chain root does not match the specified CA authority."); - return false; - } - } - - - var validHosts = new[] - { - mongoOptions.Host, - "127.0.0.1", - "localhost" - }; - if (!validHosts.Any(host => cert.Subject.Contains($"CN={host}", - StringComparison.OrdinalIgnoreCase))) - { - logger - .LogError("Certificate host mismatch. Expected one of: {ValidHosts}, but found: {CertSubject}", - validHosts, - cert.Subject); - return false; - } - - logger.LogInformation("SSL validation successful."); - return true; - } - }; + { + ClientCertificates = new X509Certificate2Collection(authority), + EnabledSslProtocols = SslProtocols.Tls12, + ServerCertificateValidationCallback = (sender, + certificate, + certChain, + sslPolicyErrors) => + + { + logger.LogInformation("SSL validation callback called."); + if (sslPolicyErrors == SslPolicyErrors.None) + { + logger.LogInformation("SSL validation successful: no errors."); + return true; + } + + if (certificate == null) + { + logger.LogInformation("Certificate is null!"); + return false; + } + + if (certChain == null) + { + logger.LogInformation("Certificate chain is null!"); + return false; + } + + logger.LogError("SSL validation failed with errors: {sslPolicyErrors}", + sslPolicyErrors.ToString()); + + + var cert = new X509Certificate2(certificate); + + + if (mongoOptions.AllowInsecureTls) + { + certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + certChain.ChainPolicy.VerificationFlags = + X509VerificationFlags.AllowUnknownCertificateAuthority; + } + + certChain.ChainPolicy.ExtraStore.Add(authority); + if (!certChain.Build(cert)) + { + logger.LogError("SSL chain validation failed."); + foreach (var status in certChain.ChainStatus) + { + logger.LogError("ChainStatus: {status.StatusInformation} ({status.Status})", + status.StatusInformation, + status.Status); + } + + return false; + } + + logger.LogError("SSL validation failed with errors: {sslPolicyErrors}", + sslPolicyErrors.ToString()); + return false; + } + }; } catch (CryptographicException e) { @@ -346,7 +355,7 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IServiceCollection services, - ConfigurationManager configuration) + ConfigurationManager configuration) { var components = configuration.GetSection(Components.SettingSection); if (components[nameof(Components.AuthenticationStorage)] == "ArmoniK.Adapters.MongoDB.AuthenticationTable") @@ -367,7 +376,7 @@ public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IS /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthServices(this IServiceCollection services, - ConfigurationManager configuration, + ConfigurationManager configuration, out AuthenticationCache authCache) { authCache = new AuthenticationCache(); diff --git a/terraform/modules/storage/database/mongo/main.tf b/terraform/modules/storage/database/mongo/main.tf index d145f0f01..38a8373ab 100644 --- a/terraform/modules/storage/database/mongo/main.tf +++ b/terraform/modules/storage/database/mongo/main.tf @@ -46,7 +46,7 @@ resource "time_sleep" "wait" { } locals { - linux_run = "docker exec ${docker_container.database.name} mongosh mongodb://127.0.0.1:27017/${var.mongodb_params.database_name} --tls --tlsCAFile /mongo-certificate/ca.pem" + linux_run = "docker exec ${docker_container.database.name} mongosh mongodb://localhost:27017/${var.mongodb_params.database_name} --tls --tlsCAFile /mongo-certificate/ca.pem" // mongosh is not installed in windows docker images so we need it to be installed locally windows_run = "mongosh.exe mongodb://localhost:${var.mongodb_params.exposed_port}/${var.mongodb_params.database_name} --tls --tlsCAFile ${local_sensitive_file.ca.filename}" prefix_run = var.mongodb_params.windows ? local.windows_run : local.linux_run @@ -54,16 +54,7 @@ locals { resource "null_resource" "init_replica" { provisioner "local-exec" { - command = < Date: Tue, 17 Dec 2024 12:12:38 +0100 Subject: [PATCH 11/14] fix: implem condition for linux/windows and correction on initrepset for tls --- .../monitoring/partition_metrics/main.tf | 2 +- .../modules/storage/database/mongo/main.tf | 30 ++++++++++++++----- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/terraform/modules/monitoring/partition_metrics/main.tf b/terraform/modules/monitoring/partition_metrics/main.tf index 325fa6590..b75542fcf 100644 --- a/terraform/modules/monitoring/partition_metrics/main.tf +++ b/terraform/modules/monitoring/partition_metrics/main.tf @@ -21,7 +21,7 @@ resource "docker_container" "partition_metrics" { external = var.exposed_port } - dynamic "upload" { + dynamic "upload" { for_each = var.mounts content { source = upload.value diff --git a/terraform/modules/storage/database/mongo/main.tf b/terraform/modules/storage/database/mongo/main.tf index 38a8373ab..f164cb68e 100644 --- a/terraform/modules/storage/database/mongo/main.tf +++ b/terraform/modules/storage/database/mongo/main.tf @@ -24,7 +24,7 @@ resource "docker_container" "database" { for_each = var.mongodb_params.windows ? [] : [1] content { test = ["CMD", "mongosh", "--quiet", "--tls", "--tlsCAFile", "/mongo-certificate/ca.pem", "--eval", "db.runCommand('ping').ok"] - interval = "3s" + interval = "3s" retries = "2" timeout = "3s" } @@ -34,6 +34,10 @@ resource "docker_container" "database" { file = "/mongo-certificate/key.pem" content = local.server_key } + upload { + file = "/mongo-init.js" + content = local.mongo_init_repset + } upload { file = "/mongo-certificate/ca.pem" @@ -41,24 +45,36 @@ resource "docker_container" "database" { } } resource "time_sleep" "wait" { - create_duration = var.mongodb_params.windows ? "15s" : "0s" + create_duration = var.mongodb_params.windows ? "30s" : "0s" depends_on = [docker_container.database] } - locals { - linux_run = "docker exec ${docker_container.database.name} mongosh mongodb://localhost:27017/${var.mongodb_params.database_name} --tls --tlsCAFile /mongo-certificate/ca.pem" + linux_run = "docker exec ${docker_container.database.name} mongosh mongodb://127.0.0.1:27017/${var.mongodb_params.database_name} --tls --tlsCAFile /mongo-certificate/ca.pem" // mongosh is not installed in windows docker images so we need it to be installed locally - windows_run = "mongosh.exe mongodb://localhost:${var.mongodb_params.exposed_port}/${var.mongodb_params.database_name} --tls --tlsCAFile ${local_sensitive_file.ca.filename}" - prefix_run = var.mongodb_params.windows ? local.windows_run : local.linux_run + windows_run = "mongosh.exe mongodb://127.0.0.1:${var.mongodb_params.exposed_port}/${var.mongodb_params.database_name} --tls --tlsCAFile ${local_sensitive_file.ca.filename}" + prefix_run = var.mongodb_params.windows ? local.windows_run : local.linux_run + mongo_init_repset = < Date: Tue, 17 Dec 2024 14:22:07 +0100 Subject: [PATCH 12/14] fix: implem functionnal worker for mongo --- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 32 +++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index 11d11446d..ac85ebe32 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -229,9 +229,7 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services LogObjectProperties(logger, content, nameof(content)); - var authority = new X509Certificate2(mongoOptions.CAFile, - "", - X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet); + var authority = new X509Certificate2(mongoOptions.CAFile); logger.LogInformation("CA certificate loaded: {authority}", authority); // SSL Parameters configuration @@ -252,6 +250,12 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services return true; } + // If there is any error other than untrusted root or partial chain, fail the validation + if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0) + { + return false; + } + if (certificate == null) { logger.LogInformation("Certificate is null!"); @@ -263,21 +267,22 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services logger.LogInformation("Certificate chain is null!"); return false; } + // If there is any error other than untrusted root or partial chain, fail the validation + if (certChain.ChainStatus.Any(status => status.Status is not X509ChainStatusFlags.UntrustedRoot and not X509ChainStatusFlags.PartialChain)) + { + return false; + } logger.LogError("SSL validation failed with errors: {sslPolicyErrors}", - sslPolicyErrors.ToString()); + sslPolicyErrors); var cert = new X509Certificate2(certificate); + certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + certChain.ChainPolicy.VerificationFlags = + X509VerificationFlags.AllowUnknownCertificateAuthority; - if (mongoOptions.AllowInsecureTls) - { - certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - certChain.ChainPolicy.VerificationFlags = - X509VerificationFlags.AllowUnknownCertificateAuthority; - } - certChain.ChainPolicy.ExtraStore.Add(authority); if (!certChain.Build(cert)) { @@ -292,9 +297,8 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services return false; } - logger.LogError("SSL validation failed with errors: {sslPolicyErrors}", - sslPolicyErrors.ToString()); - return false; + return certChain.ChainElements.Cast() + .Any(x => x.Certificate.Thumbprint == authority.Thumbprint); ; } }; } From fdabc799ed68574513d84b33ec1a918250f57591 Mon Sep 17 00:00:00 2001 From: nico_dreylaq Date: Tue, 17 Dec 2024 14:59:07 +0100 Subject: [PATCH 13/14] refactor: remove useless logs/code & format --- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 221 ++++++------------- 1 file changed, 72 insertions(+), 149 deletions(-) diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index ac85ebe32..347758651 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -20,7 +20,6 @@ using System.Linq; using System.Net.Security; using System.Security.Authentication; -using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using ArmoniK.Api.Common.Utils; @@ -49,33 +48,10 @@ namespace ArmoniK.Core.Adapters.MongoDB; public static class ServiceCollectionExt { - public static void LogObjectProperties(ILogger logger, - T obj, - string objectName) - { - if (obj == null) - { - logger.LogError("{ObjectName} is null", - objectName); - return; - } - - var properties = typeof(T).GetProperties(); - foreach (var property in properties) - { - var value = property.GetValue(obj, - null); - logger.LogInformation("{ObjectName}.{Property} = {Value}", - objectName, - property.Name, - value); - } - } - [PublicAPI] public static IServiceCollection AddMongoComponents(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { services.AddMongoClient(configuration, logger); @@ -86,8 +62,8 @@ public static IServiceCollection AddMongoComponents(this IServiceCollection serv [PublicAPI] public static IServiceCollection AddMongoStorages(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { logger.LogInformation("Configure MongoDB Components"); @@ -126,8 +102,8 @@ public static IServiceCollection AddMongoStorages(this IServiceCollection servic } public static IServiceCollection AddMongoClient(this IServiceCollection services, - ConfigurationManager configuration, - ILogger logger) + ConfigurationManager configuration, + ILogger logger) { Options.MongoDB mongoOptions; services.AddOption(configuration, @@ -195,134 +171,81 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString)); // Configure the connection settings - settings.AllowInsecureTls = mongoOptions.AllowInsecureTls; - settings.UseTls = mongoOptions.Tls; - settings.DirectConnection = mongoOptions.DirectConnection; - settings.Scheme = ConnectionStringScheme.MongoDB; - settings.MaxConnectionPoolSize = mongoOptions.MaxConnectionPoolSize; + 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; + settings.ReplicaSetName = mongoOptions.ReplicaSet; if (!string.IsNullOrEmpty(mongoOptions.CAFile)) { - logger.LogInformation("Starting X509 certificate configuration."); - if (!File.Exists(mongoOptions.CAFile)) { throw new FileNotFoundException("CA certificate file not found", mongoOptions.CAFile); } - logger.LogInformation("CA file found at path: {path}", - mongoOptions.CAFile); - logger.LogInformation("Raw CA file path: {path}", - mongoOptions.CAFile); - // Load the CA certificate - try - { - var fileInfo = new FileInfo(mongoOptions.CAFile); - LogObjectProperties(logger, - fileInfo, - nameof(fileInfo)); - var content = File.ReadAllLines(mongoOptions.CAFile); - LogObjectProperties(logger, - content, - nameof(content)); - var authority = new X509Certificate2(mongoOptions.CAFile); - logger.LogInformation("CA certificate loaded: {authority}", - authority); - // SSL Parameters configuration - settings.SslSettings = new SslSettings - { - ClientCertificates = new X509Certificate2Collection(authority), - EnabledSslProtocols = SslProtocols.Tls12, - ServerCertificateValidationCallback = (sender, - certificate, - certChain, - sslPolicyErrors) => - - { - logger.LogInformation("SSL validation callback called."); - if (sslPolicyErrors == SslPolicyErrors.None) - { - logger.LogInformation("SSL validation successful: no errors."); - return true; - } - - // If there is any error other than untrusted root or partial chain, fail the validation - if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0) - { - return false; - } - - if (certificate == null) - { - logger.LogInformation("Certificate is null!"); - return false; - } - - if (certChain == null) - { - logger.LogInformation("Certificate chain is null!"); - return false; - } - // If there is any error other than untrusted root or partial chain, fail the validation - if (certChain.ChainStatus.Any(status => status.Status is not X509ChainStatusFlags.UntrustedRoot and not X509ChainStatusFlags.PartialChain)) - { - return false; - } - - logger.LogError("SSL validation failed with errors: {sslPolicyErrors}", - sslPolicyErrors); - - - var cert = new X509Certificate2(certificate); - certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - certChain.ChainPolicy.VerificationFlags = - X509VerificationFlags.AllowUnknownCertificateAuthority; - - - certChain.ChainPolicy.ExtraStore.Add(authority); - if (!certChain.Build(cert)) - { - logger.LogError("SSL chain validation failed."); - foreach (var status in certChain.ChainStatus) - { - logger.LogError("ChainStatus: {status.StatusInformation} ({status.Status})", - status.StatusInformation, - status.Status); - } - - return false; - } - - return certChain.ChainElements.Cast() - .Any(x => x.Certificate.Thumbprint == authority.Thumbprint); ; - } - }; - } - catch (CryptographicException e) - { - logger.LogError(e, - "Error loading CA certificate: {message}", - e.Message); - logger.LogError(e.InnerException, - "Inner exception: {message}", - e.InnerException?.Message); - logger.LogError("Stack trace: {stackTrace}", - e.StackTrace); - logger.LogError("Help link: {helpLink}", - e.HelpLink); - - logger.LogError("HResult: {hResult}", - e.HResult); - logger.LogError("Source: {source}", - e.Source); - logger.LogError("Exception Data: {Data}", - e.Data); - throw; - } + var authority = new X509Certificate2(mongoOptions.CAFile); + logger.LogInformation("CA certificate loaded: {authority}", + authority); + // SSL Parameters configuration + settings.SslSettings = new SslSettings + { + ClientCertificates = new X509Certificate2Collection(authority), + EnabledSslProtocols = SslProtocols.Tls12, + ServerCertificateValidationCallback = (sender, + certificate, + certChain, + sslPolicyErrors) => + + { + // If there is any error other than untrusted root or partial chain, fail the validation + if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0) + { + logger.LogDebug("SSL validation failed with errors: {sslPolicyErrors}", + sslPolicyErrors); + return false; + } + + if (certificate == null) + { + logger.LogDebug("Certificate is null!"); + return false; + } + + if (certChain == null) + { + logger.LogDebug("Certificate chain is null!"); + return false; + } + + // If there is any error other than untrusted root or partial chain, fail the validation + if (certChain.ChainStatus.Any(status + => status.Status is not X509ChainStatusFlags.UntrustedRoot and + not X509ChainStatusFlags.PartialChain)) + { + logger.LogDebug("SSL validation failed with chain status: {chainStatus}", + certChain.ChainStatus); + return false; + } + + var cert = new X509Certificate2(certificate); + certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + + certChain.ChainPolicy.ExtraStore.Add(authority); + if (!certChain.Build(cert)) + { + return false; + } + + return certChain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint); + ; + }, + }; } @@ -359,7 +282,7 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IServiceCollection services, - ConfigurationManager configuration) + ConfigurationManager configuration) { var components = configuration.GetSection(Components.SettingSection); if (components[nameof(Components.AuthenticationStorage)] == "ArmoniK.Adapters.MongoDB.AuthenticationTable") @@ -380,7 +303,7 @@ public static IServiceCollection AddClientSubmitterAuthenticationStorage(this IS /// Services [PublicAPI] public static IServiceCollection AddClientSubmitterAuthServices(this IServiceCollection services, - ConfigurationManager configuration, + ConfigurationManager configuration, out AuthenticationCache authCache) { authCache = new AuthenticationCache(); From d06d5e42f9feeea2ebefb64ee35036ba3077a722 Mon Sep 17 00:00:00 2001 From: nico_dreylaq Date: Tue, 31 Dec 2024 11:34:33 +0100 Subject: [PATCH 14/14] refactor: implem factory and generic callback for tls mongo --- Adaptors/MongoDB/src/ServiceCollectionExt.cs | 71 ++--------- Utils/src/ServerCertificateValidator.cs | 124 +++++++++++++++++++ 2 files changed, 135 insertions(+), 60 deletions(-) create mode 100644 Utils/src/ServerCertificateValidator.cs diff --git a/Adaptors/MongoDB/src/ServiceCollectionExt.cs b/Adaptors/MongoDB/src/ServiceCollectionExt.cs index 347758651..22491ef57 100644 --- a/Adaptors/MongoDB/src/ServiceCollectionExt.cs +++ b/Adaptors/MongoDB/src/ServiceCollectionExt.cs @@ -17,8 +17,6 @@ using System; using System.IO; -using System.Linq; -using System.Net.Security; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; @@ -44,6 +42,8 @@ using MongoDB.Driver.Core.Configuration; using MongoDB.Driver.Core.Extensions.DiagnosticSources; +using static ArmoniK.Core.Utils.CertificateValidator; + namespace ArmoniK.Core.Adapters.MongoDB; public static class ServiceCollectionExt @@ -183,72 +183,23 @@ public static IServiceCollection AddMongoClient(this IServiceCollection services { if (!File.Exists(mongoOptions.CAFile)) { - throw new FileNotFoundException("CA certificate file not found", + logger.LogError("CA certificate Mongo file not found at {path}", + mongoOptions.CAFile); + throw new FileNotFoundException("CA certificate Mongo file not found", mongoOptions.CAFile); } - // Load the CA certificate - var authority = new X509Certificate2(mongoOptions.CAFile); - logger.LogInformation("CA certificate loaded: {authority}", - authority); - // SSL Parameters configuration + var (validationCallback, authority) = CertificateValidatorFactory.CreateCallback(mongoOptions.CAFile, + logger); + settings.SslSettings = new SslSettings { - ClientCertificates = new X509Certificate2Collection(authority), - EnabledSslProtocols = SslProtocols.Tls12, - ServerCertificateValidationCallback = (sender, - certificate, - certChain, - sslPolicyErrors) => - - { - // If there is any error other than untrusted root or partial chain, fail the validation - if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0) - { - logger.LogDebug("SSL validation failed with errors: {sslPolicyErrors}", - sslPolicyErrors); - return false; - } - - if (certificate == null) - { - logger.LogDebug("Certificate is null!"); - return false; - } - - if (certChain == null) - { - logger.LogDebug("Certificate chain is null!"); - return false; - } - - // If there is any error other than untrusted root or partial chain, fail the validation - if (certChain.ChainStatus.Any(status - => status.Status is not X509ChainStatusFlags.UntrustedRoot and - not X509ChainStatusFlags.PartialChain)) - { - logger.LogDebug("SSL validation failed with chain status: {chainStatus}", - certChain.ChainStatus); - return false; - } - - var cert = new X509Certificate2(certificate); - certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; - certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; - - certChain.ChainPolicy.ExtraStore.Add(authority); - if (!certChain.Build(cert)) - { - return false; - } - - return certChain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint); - ; - }, + ClientCertificates = new X509Certificate2Collection(authority), + EnabledSslProtocols = SslProtocols.Tls12, + ServerCertificateValidationCallback = validationCallback, }; } - settings.ClusterConfigurator = cb => { //cb.Subscribe(e => logger.LogTrace("{CommandName} - {Command}", diff --git a/Utils/src/ServerCertificateValidator.cs b/Utils/src/ServerCertificateValidator.cs new file mode 100644 index 000000000..fccbbf374 --- /dev/null +++ b/Utils/src/ServerCertificateValidator.cs @@ -0,0 +1,124 @@ +// This file is part of the ArmoniK project +// +// Copyright (C) ANEO, 2021-2024. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +using System.IO; +using System.Linq; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; + +using Microsoft.Extensions.Logging; + +namespace ArmoniK.Core.Utils; + +public static class CertificateValidator +{ + public static RemoteCertificateValidationCallback ValidationCallback(ILogger logger, + X509Certificate2 authority) + => (sender, + certificate, + chain, + sslPolicyErrors) => + { + if (certificate == null || chain == null) + { + logger.LogWarning("Certificate or certificate chain is null"); + return false; + } + + return ValidateServerCertificate(sender, + certificate, + chain, + sslPolicyErrors, + authority, + logger); + }; + + + public static bool ValidateServerCertificate(object sender, + X509Certificate certificate, + X509Chain certChain, + SslPolicyErrors sslPolicyErrors, + X509Certificate2 authority, + ILogger logger) + { + // If there is any error other than untrusted root or partial chain, fail the validation + if ((sslPolicyErrors & ~SslPolicyErrors.RemoteCertificateChainErrors) != 0) + { + logger.LogDebug("SSL validation failed with errors: {sslPolicyErrors}", + sslPolicyErrors); + return false; + } + + if (certificate == null) + { + logger.LogDebug("Certificate is null!"); + return false; + } + + if (certChain == null) + { + logger.LogDebug("Certificate chain is null!"); + return false; + } + + // If there is any error other than untrusted root or partial chain, fail the validation + if (certChain.ChainStatus.Any(status => status.Status is not X509ChainStatusFlags.UntrustedRoot and not X509ChainStatusFlags.PartialChain)) + { + logger.LogDebug("SSL validation failed with chain status: {chainStatus}", + certChain.ChainStatus); + return false; + } + + var cert = new X509Certificate2(certificate); + certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; + certChain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; + + certChain.ChainPolicy.ExtraStore.Add(authority); + if (!certChain.Build(cert)) + { + return false; + } + + var isTrusted = certChain.ChainElements.Any(x => x.Certificate.Thumbprint == authority.Thumbprint); + if (isTrusted) + { + logger.LogInformation("SSL validation succeeded"); + } + else + { + logger.LogInformation("SSL validation failed with errors: {sslPolicyErrors}", + sslPolicyErrors); + } + + return isTrusted; + } + + public static class CertificateValidatorFactory + { + public static (RemoteCertificateValidationCallback, X509Certificate2) CreateCallback(string caFilePath, + ILogger logger) + { + var content = File.ReadAllText(caFilePath); + var authority = X509Certificate2.CreateFromPem(content); + logger.LogInformation("Loaded CA certificate from file {path}", + caFilePath); + var callback = ValidationCallback(logger, + authority); + return (callback, authority); + } + } +}