Description
This issue is inspired by an email from @wiktork. From the email:
I’m [working] on dotnet-monitor. The tool uses Asp.net Core to host a web server that acts as a control plane for diagnostic tooling. We are currently working on the security work for this tool, in particular supporting https.
I have a scenario where I want to do the following:
Notice one is https, and the other is http.
Note the two endpoints serve different information (sensitive data over https, non-sensitive data over http).
If there is no default certificate (one generated by dotnet dev-certs) or one specified via Kestrel:Certificates:Default, I would like to successfully bind and listen on the http endpoint, but gracefully fail on the https endpoint.
My initial take on this was to use ConfigureKestrel:
//Simplified code ConfigureKestrel((context, options) => { try { //If https options.ListenLocalhost(url.Port, listenOptions => listenOptions.UseHttps() ) } catch (InvalidOperationException e) { //This binding failure is typically due to missing default certificate console.Error.WriteLine($"Unable to bind to {url}. Dotnet-monitor functionality will be limited."); console.Error.WriteLine(e.Message); }This allows me to selectively UseHttps on an endpoint, but this doesn’t work because the default certificate has not been loaded yet. From my understanding of the code, that happens post configuration:
- All configuration occurs (my UseHttps code always throws, indicating there is no default certificate)
- KestrelConfigurationLoader.Load will actually load the default certificate into CertificateManager
- Binding/Running server
At this point I could pass along the certificate myself if it’s present to UseHttps, but I don’t really want to replicate all the default certificate logic from asp.net core. Do you guys have any suggestions on how to accomplish this?
The workaround I provided in the email thread was as follows:
It's too bad that UseHttps() doesn't automatically use the default certificate from config. UseHttps() will use the installed development certificate if there's one available. I would love to fix it, but this is the unfortunate side-effect of being able change the config section Kestrel reads from in the ConfigureKestrel callback. Fortunately, since you can load the configuration early yourself inside the ConfigureKestrel callback, the fix should be a one-liner. If you add
options.Configure(context.Configuration.GetSection("Kestrel")).Load();
before
options.ListenLocalhost(url.Port, listenOptions => listenOptions.UseHttps());
does that fix your issue?
While I expect this workaround of manually loading config at the start of the ConfigureKestrel callback works, it's not at all obvious that this should be necessary and is an overall bad experience.
If we instead preload default Kestrel config before running configureKestrel's callback should allow the KestrelServerOptions.Listen() and ListenOptions.UseHttps() methods to pick up "Default"/"Development" certificates and "EndpointDefaults" from config. Since ConfigureWebDefaults is responsible for binding the KestrelConfigurationLoader the the "Kestrel" subsection of config here, this is also where we should do the preloading.
This is a behavioral breaking change, but maybe we can make this less risky by loading just the defaults, and not the endpoints before ConfigureKestrel's callback is run.