From e8a40ea18ec5a089022652357e57ddb2882c6bd4 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Mon, 21 Jul 2025 16:13:03 +0200 Subject: [PATCH] Update .NET DataProtection config to be resilient against container restarts (#493) --- .../LoggingConfiguration.cs | 4 -- .../DataProtectionExtensions.cs | 44 +++++++++++++------ install.sh | 4 +- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/apps/server/Utilities/AliasVault.Logging/LoggingConfiguration.cs b/apps/server/Utilities/AliasVault.Logging/LoggingConfiguration.cs index 655042208..283f7f3a1 100644 --- a/apps/server/Utilities/AliasVault.Logging/LoggingConfiguration.cs +++ b/apps/server/Utilities/AliasVault.Logging/LoggingConfiguration.cs @@ -58,13 +58,9 @@ public static class LoggingConfiguration // Log all warning and above to database via EF core except for: // - Microsoft.EntityFrameworkCore logsas this would create a loop. - // - Microsoft.AspNetCore.Antiforgery logs - // - Microsoft.AspNetCore.DataProtection logs .WriteTo.Logger(lc => lc .Filter.ByIncludingOnly(evt => evt.Level >= LogEventLevel.Warning) .Filter.ByExcluding(Matching.FromSource("Microsoft.EntityFrameworkCore")) - .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore.Antiforgery")) - .Filter.ByExcluding(Matching.FromSource("Microsoft.AspNetCore.DataProtection")) .WriteTo.Sink(new DatabaseSink(CultureInfo.InvariantCulture, () => services.BuildServiceProvider().GetRequiredService>(), applicationName))) .CreateLogger()); diff --git a/apps/server/Utilities/Cryptography/AliasVault.Cryptography.Server/DataProtectionExtensions.cs b/apps/server/Utilities/Cryptography/AliasVault.Cryptography.Server/DataProtectionExtensions.cs index 6a7199549..f9f1b041a 100644 --- a/apps/server/Utilities/Cryptography/AliasVault.Cryptography.Server/DataProtectionExtensions.cs +++ b/apps/server/Utilities/Cryptography/AliasVault.Cryptography.Server/DataProtectionExtensions.cs @@ -18,7 +18,7 @@ using Microsoft.Extensions.DependencyInjection; public static class DataProtectionExtensions { /// - /// Setup .NET DataProtection to use common AliasVault settings with self-signed certificate. + /// Setup .NET DataProtection to use common AliasVault settings. /// /// Services. /// Application name. @@ -36,25 +36,41 @@ public static class DataProtectionExtensions certPath = Path.Combine(AppContext.BaseDirectory, $"{applicationName}.DataProtection.Development.pfx"); } - var certificateFlags = X509KeyStorageFlags.MachineKeySet | - X509KeyStorageFlags.PersistKeySet | - X509KeyStorageFlags.Exportable; + // Use different protection methods for containerized environments + var isContainer = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true" || + Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Production"; - X509Certificate2 cert; - if (!File.Exists(certPath)) + var dataProtectionBuilder = services.AddDataProtection() + .PersistKeysToDbContext() + .SetApplicationName(applicationName); + + if (isContainer) { - cert = CertificateGenerator.GeneratePfx($"{applicationName}.DataProtection", certPassword); - CertificateGenerator.SaveCertificateToFile(cert, certPassword, certPath); + // When running in containers, don't use certificate-based key protection due to Linux keystore limitations + dataProtectionBuilder.UseCryptographicAlgorithms(new Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorConfiguration() + { + EncryptionAlgorithm = Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.EncryptionAlgorithm.AES_256_CBC, + ValidationAlgorithm = Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ValidationAlgorithm.HMACSHA256, + }); } else { - cert = X509CertificateLoader.LoadPkcs12FromFile(certPath, certPassword, certificateFlags); - } + // Use certificate-based protection for development + var certificateFlags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable; - services.AddDataProtection() - .PersistKeysToDbContext() - .ProtectKeysWithCertificate(cert) - .SetApplicationName(applicationName); + X509Certificate2 cert; + if (!File.Exists(certPath)) + { + cert = CertificateGenerator.GeneratePfx($"{applicationName}.DataProtection", certPassword); + CertificateGenerator.SaveCertificateToFile(cert, certPassword, certPath); + } + else + { + cert = X509CertificateLoader.LoadPkcs12FromFile(certPath, certPassword, certificateFlags); + } + + dataProtectionBuilder.ProtectKeysWithCertificate(cert); + } return services; } diff --git a/install.sh b/install.sh index be5718f6d..e08bba09f 100755 --- a/install.sh +++ b/install.sh @@ -1242,7 +1242,7 @@ print_install_success_message() { # Function to recreate (restart) Docker containers recreate_docker_containers() { - printf "${CYAN}ℹ (Re)creating Docker containers...${NC} " + printf "${CYAN}ℹ (Re)creating Docker containers...${NC}\n" if [ "$VERBOSE" = true ]; then printf "\b${NC}\n" @@ -1454,7 +1454,7 @@ handle_build() { printf "\n${YELLOW}+++ Building and starting services +++${NC}\n" - printf "${CYAN}ℹ Building Docker Compose stack...${NC} " + printf "${CYAN}ℹ Building Docker Compose stack...${NC}\n" if [ "$VERBOSE" = true ]; then printf "\b${NC}\n" if ! $(get_docker_compose_command) build; then