From 3c82179d312839f1496b8bb1907ba41a10f32009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20R=C3=A4tzel?= Date: Sat, 23 Mar 2024 17:02:26 +0000 Subject: [PATCH] Work around problems with asynchronous SSL certificate arrival Work around the problem with ASP.NET crashing because SSL certificates arrive ordered via LetsEncrypt arrive after starting the app: Disable HTTPS for a first start, if we find the certificate is not available. Later, if the HTTPS certificate has arrived, restart the ASP.NET host with the HTTPS URLS. For discussion of this issue, see: + + Also, update the LetsEncrypt library and Certes to integrate various recent upstream improvements. --- .../Platform/WebService/PublicAppState.cs | 28 ++-- .../WebService/StartupAdminInterface.cs | 143 +++++++++++++++--- implement/elm-time/Program.cs | 2 +- implement/elm-time/elm-time.csproj | 12 +- .../lib/FluffySpoon.AspNet.EncryptWeMust.dll | Bin 0 -> 70656 bytes implement/test-elm-time/test-elm-time.csproj | 6 + 6 files changed, 151 insertions(+), 40 deletions(-) create mode 100644 implement/lib/FluffySpoon.AspNet.EncryptWeMust.dll diff --git a/implement/elm-time/Platform/WebService/PublicAppState.cs b/implement/elm-time/Platform/WebService/PublicAppState.cs index 22b6abdda..993729dc6 100644 --- a/implement/elm-time/Platform/WebService/PublicAppState.cs +++ b/implement/elm-time/Platform/WebService/PublicAppState.cs @@ -88,6 +88,7 @@ public PublicAppState( public WebApplication Build( WebApplicationBuilder appBuilder, + ILogger logger, IHostEnvironment env, IReadOnlyList publicWebHostUrls, bool? disableLetsEncrypt, @@ -99,16 +100,11 @@ public WebApplication Build( logging.AddDebug(); }); - using var loggerFactory = LoggerFactory.Create(logging => - { - logging.AddConsole(); - logging.AddDebug(); - }); - - var logger = loggerFactory.CreateLogger(); + var enableUseFluffySpoonLetsEncrypt = + serverAndElmAppConfig.ServerConfig?.letsEncryptOptions is not null && !(disableLetsEncrypt ?? false); var canUseHttps = - serverAndElmAppConfig.ServerConfig?.letsEncryptOptions is not null && !(disableLetsEncrypt ?? false); + enableUseFluffySpoonLetsEncrypt; var publicWebHostUrlsFilteredForHttps = canUseHttps && !disableHttps @@ -119,9 +115,17 @@ public WebApplication Build( .Where(url => !url.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) .ToImmutableArray(); - logger.LogInformation("disableLetsEncrypt: {disableLetsEncrypt}", disableLetsEncrypt?.ToString() ?? "null"); - logger.LogInformation("canUseHttps: {canUseHttps}", canUseHttps); - logger.LogInformation("disableHttps: {disableHttps}", disableHttps); + logger.LogInformation( + "disableLetsEncrypt: {disableLetsEncrypt}", disableLetsEncrypt?.ToString() ?? "null"); + + logger.LogInformation( + "enableUseFluffySpoonLetsEncrypt: {enableUseFluffySpoonLetsEncrypt}", enableUseFluffySpoonLetsEncrypt); + + logger.LogInformation( + "canUseHttps: {canUseHttps}", canUseHttps); + + logger.LogInformation( + "disableHttps: {disableHttps}", disableHttps); var webHostBuilder = appBuilder.WebHost @@ -142,7 +146,7 @@ public WebApplication Build( app.UseDeveloperExceptionPage(); } - if (canUseHttps) + if (enableUseFluffySpoonLetsEncrypt) { app.UseFluffySpoonLetsEncrypt(); } diff --git a/implement/elm-time/Platform/WebService/StartupAdminInterface.cs b/implement/elm-time/Platform/WebService/StartupAdminInterface.cs index 1a23dd719..b31e81843 100644 --- a/implement/elm-time/Platform/WebService/StartupAdminInterface.cs +++ b/implement/elm-time/Platform/WebService/StartupAdminInterface.cs @@ -19,7 +19,7 @@ using ApiRouteMethodConfig = System.Func< Microsoft.AspNetCore.Http.HttpContext, - ElmTime.Platform.WebService.StartupAdminInterface.PublicHostConfiguration?, + ElmTime.Platform.WebService.StartupAdminInterface.PublicHostProcess?, System.Threading.Tasks.Task>; namespace ElmTime.Platform.WebService; @@ -93,9 +93,13 @@ public static void ConfigureServices(IServiceCollection services) services.AddSingleton(getDateTimeOffset); } - public record PublicHostConfiguration( - PersistentProcessLiveRepresentation processLiveRepresentation, - IHost webHost); + public record PublicHostProcess( + PersistentProcessLiveRepresentation ProcessLiveRepresentation, + AspHostConfig AspHostConfig, + IHost WebHost); + + public record AspHostConfig( + bool DisableHttps); public void Configure( IApplicationBuilder app, @@ -128,7 +132,7 @@ public void Configure( var processStoreFileStore = processStoreForFileStore.fileStore; - PublicHostConfiguration? publicAppHost = null; + PublicHostProcess? publicAppHost = null; void stopPublicApp() { @@ -139,9 +143,9 @@ void stopPublicApp() logger.LogInformation("Begin to stop the public app."); - publicAppHost?.webHost?.StopAsync(TimeSpan.FromSeconds(10)).Wait(); - publicAppHost?.webHost?.Dispose(); - publicAppHost?.processLiveRepresentation?.Dispose(); + publicAppHost?.WebHost?.StopAsync(TimeSpan.FromSeconds(10)).Wait(); + publicAppHost?.WebHost?.Dispose(); + publicAppHost?.ProcessLiveRepresentation?.Dispose(); publicAppHost = null; } } @@ -155,6 +159,85 @@ void stopPublicApp() processStoreFileStore); void startPublicApp() + { + var aspHostConfig = BuildCurrentAspHostConfig(logger); + + startPublicAppLessRestartForHttps(aspHostConfig); + + if (aspHostConfig.DisableHttps) + { + /* + * Workaround for the issue of ASP.NET crashing discovered during the incident in March. + * TODO: Review: Consider moving the certificate out of the versioned web service config. + * */ + + System.Threading.Tasks.Task.Run(() => + { + System.Threading.Thread.Sleep(TimeSpan.FromSeconds(100)); + + logger.LogInformation( + "Last start of public interface had HTTPS disabled. Checking if we can enable HTTPS now..."); + + var newConfig = BuildCurrentAspHostConfig(logger); + + if (!newConfig.DisableHttps) + { + logger.LogInformation( + "Looks like we can enable HTTPS now, restarting the public interface..."); + + startPublicAppLessRestartForHttps( + aspHostConfig + with + { + DisableHttps = false + }); + } + }); + } + } + + AspHostConfig BuildCurrentAspHostConfig(ILogger? logger) + { + var letsEncryptRenewalServiceCertificateCompleted = + FluffySpoon.AspNet.EncryptWeMust.Certes.LetsEncryptRenewalService.Certificate is { }; + + logger?.LogInformation( + "letsEncryptRenewalServiceCertificateCompleted: {completed}", + letsEncryptRenewalServiceCertificateCompleted); + + var aspWouldCrashOnConfiguringHttpsEndpoint = + /* + * Adapt to crashes observed 2024-03-23: + * + warn: Microsoft.AspNetCore.Hosting.Diagnostics[15] + Overriding HTTP_PORTS '8080' and HTTPS_PORTS ''. Binding to values defined by URLS instead 'http://*;https://*'. + fail: Microsoft.Extensions.Hosting.Internal.Host[11] + Hosting failed to start + System.InvalidOperationException: Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date. + To generate a developer certificate run 'dotnet dev-certs https'. To trust the certificate (Windows and macOS only) run 'dotnet dev-certs https --trust'. + For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054. + at Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(ListenOptions listenOptions, Action`1 configureOptions) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindAsync(ListenOptions[] listenOptions, AddressBindContext context, Func`2 useHttps, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.Internal.Host.b__15_1(IHostedService service, CancellationToken token) + at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) + + For discussion of crashes following race condition like this, see: + https://github.com/natemcmaster/LettuceEncrypt/issues/293 + + * */ + !letsEncryptRenewalServiceCertificateCompleted; + + var aspHostConfig = new AspHostConfig( + DisableHttps: disableHttps || aspWouldCrashOnConfiguringHttpsEndpoint); + + return aspHostConfig; + } + + void startPublicAppLessRestartForHttps(AspHostConfig aspHostConfig) { lock (avoidConcurrencyLock) { @@ -221,6 +304,7 @@ void maintainStoreReductions() WebApplication buildWebApplication( ProcessAppConfig processAppConfig, + AspHostConfig aspHostConfig, IReadOnlyList publicWebHostUrls) { var appConfigTree = @@ -272,35 +356,46 @@ WebApplication buildWebApplication( var appBuilder = WebApplication.CreateBuilder(); + using var loggerFactory = LoggerFactory.Create(logging => + { + logging.AddConsole(); + logging.AddDebug(); + }); + + var logger = loggerFactory.CreateLogger(); + var app = publicAppState.Build( appBuilder, + logger, env, publicWebHostUrls: publicWebHostUrls, disableLetsEncrypt: disableLetsEncrypt, - disableHttps: disableHttps); + disableHttps: aspHostConfig.DisableHttps); publicAppState.ProcessEventTimeHasArrived(); return app; } - if (processLiveRepresentation?.lastAppConfig != null) + if (processLiveRepresentation?.lastAppConfig is { } lastAppConfig) { var publicWebHostUrls = configuration.GetSettingPublicWebHostUrls(); var webHost = buildWebApplication( - processLiveRepresentation.lastAppConfig, + lastAppConfig, + aspHostConfig, publicWebHostUrls: publicWebHostUrls); webHost.StartAsync(appLifetime.ApplicationStopping).Wait(); logger.LogInformation( - "Started the public app at '{urls}'.", string.Join(",", webHost.Urls)); + "Started the public app at '{urls}'.", string.Join(", ", webHost.Urls)); - publicAppHost = new PublicHostConfiguration( - processLiveRepresentation: processLiveRepresentation, - webHost: webHost); + publicAppHost = new PublicHostProcess( + ProcessLiveRepresentation: processLiveRepresentation, + AspHostConfig: aspHostConfig, + WebHost: webHost); } return 0; @@ -342,7 +437,7 @@ private static RequestDelegate AdminInterfaceRun( IFileStore processStoreFileStore, IProcessStoreWriter processStoreWriter, string? adminPassword, - Func getPublicAppHost, + Func getPublicAppHost, object avoidConcurrencyLock, Action startPublicApp, Action stopPublicApp) @@ -457,7 +552,7 @@ async System.Threading.Tasks.Task deployElmApp(bool initElmAppState) return Result>.err( "No application deployed."); - return publicAppHost.processLiveRepresentation.ListDatabaseFunctions(); + return publicAppHost.ProcessLiveRepresentation.ListDatabaseFunctions(); } Result applyDatabaseFunction( @@ -469,7 +564,7 @@ async System.Threading.Tasks.Task deployElmApp(bool initElmAppState) return Result.err( "No application deployed."); - return publicAppHost.processLiveRepresentation.ApplyFunctionOnMainBranch(storeWriter: processStoreWriter, request); + return publicAppHost.ProcessLiveRepresentation.ApplyFunctionOnMainBranch(storeWriter: processStoreWriter, request); } } @@ -506,7 +601,7 @@ async System.Threading.Tasks.Task deployElmApp(bool initElmAppState) methods : ImmutableDictionary.Empty .Add("get", async (context, publicAppHost) => { - var appConfig = publicAppHost?.processLiveRepresentation?.lastAppConfig.appConfigComponent; + var appConfig = publicAppHost?.ProcessLiveRepresentation?.lastAppConfig.appConfigComponent; if (appConfig == null) { @@ -548,7 +643,7 @@ async System.Threading.Tasks.Task deployElmApp(bool initElmAppState) return; } - var processLiveRepresentation = publicAppHost?.processLiveRepresentation; + var processLiveRepresentation = publicAppHost?.ProcessLiveRepresentation; var components = new List(); @@ -592,10 +687,10 @@ async System.Threading.Tasks.Task deployElmApp(bool initElmAppState) var elmAppStateToSet = await System.Text.Json.JsonSerializer.DeserializeAsync(context.Request.Body); var setAppStateResult = - Result.ok(publicAppHost) + Result.ok(publicAppHost) .AndThen(maybeNull => Maybe.NothingFromNull(maybeNull).ToResult("Not possible because there is no app (state).")) .AndThen(publicAppHost => - publicAppHost.processLiveRepresentation.SetStateOnMainBranch( + publicAppHost.ProcessLiveRepresentation.SetStateOnMainBranch( storeWriter: processStoreWriter, elmAppStateToSet)) .Map(compositionLogEventAndResponse => @@ -818,7 +913,7 @@ TruncateProcessHistoryReport truncateProcessHistory(TimeSpan productionBlockDura var storeReductionReport = getPublicAppHost() - ?.processLiveRepresentation?.StoreReductionRecordForCurrentState(processStoreWriter).report; + ?.ProcessLiveRepresentation?.StoreReductionRecordForCurrentState(processStoreWriter).report; storeReductionStopwatch.Stop(); @@ -960,7 +1055,7 @@ TruncateProcessHistoryReport truncateProcessHistory(TimeSpan productionBlockDura var storeReductionReport = getPublicAppHost() - ?.processLiveRepresentation?.StoreReductionRecordForCurrentState(processStoreWriter).report; + ?.ProcessLiveRepresentation?.StoreReductionRecordForCurrentState(processStoreWriter).report; storeReductionStopwatch.Stop(); diff --git a/implement/elm-time/Program.cs b/implement/elm-time/Program.cs index d794f7d1c..5f8de73b5 100644 --- a/implement/elm-time/Program.cs +++ b/implement/elm-time/Program.cs @@ -18,7 +18,7 @@ namespace ElmTime; public class Program { - public static string AppVersionId => "2024-03-22"; + public static string AppVersionId => "2024-03-23"; private static int AdminInterfaceDefaultPort => 4000; diff --git a/implement/elm-time/elm-time.csproj b/implement/elm-time/elm-time.csproj index f97319218..d743218f2 100644 --- a/implement/elm-time/elm-time.csproj +++ b/implement/elm-time/elm-time.csproj @@ -5,8 +5,8 @@ net8.0 ElmTime elm-time - 2024.0322.0.0 - 2024.0322.0.0 + 2024.0323.0.0 + 2024.0323.0.0 enable true @@ -38,7 +38,7 @@ - + @@ -59,4 +59,10 @@ + + + ./../lib/FluffySpoon.AspNet.EncryptWeMust.dll + + + diff --git a/implement/lib/FluffySpoon.AspNet.EncryptWeMust.dll b/implement/lib/FluffySpoon.AspNet.EncryptWeMust.dll new file mode 100644 index 0000000000000000000000000000000000000000..3faca4924795bf95d3c43a68dc93b2cf76df02b8 GIT binary patch literal 70656 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 L<=-NC`zY|gjY?SI literal 0 HcmV?d00001 diff --git a/implement/test-elm-time/test-elm-time.csproj b/implement/test-elm-time/test-elm-time.csproj index 34c87f730..a567ca090 100644 --- a/implement/test-elm-time/test-elm-time.csproj +++ b/implement/test-elm-time/test-elm-time.csproj @@ -29,4 +29,10 @@ + + + ./../lib/FluffySpoon.AspNet.EncryptWeMust.dll + + +