aspnetcore
aspnetcore copied to clipboard
Kestrel config behaves differently when set using env vars vs. in-code
I have a Web API application that I want to configure it to listen on an HTTPS port. The app is containerized and orchestrated using Docker compose.
I can configure it by setting the following env variable in docker-compose
, which works as expected.
services:
webapi:
environment:
- ASPNETCORE_URLS=https://+:443
However, when I use the following approach as an alternative to setting the environment variable, it fails with the following error message.
builder.WebHost.ConfigureKestrel(options =>
{
options.Listen(
address: IPAddress.Any,
port: 443,
configure: listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps();
});
});
Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found or is out of date.
When I provide the filename and password to the SSL certificate file as the following, it works as expected.
builder.WebHost.ConfigureKestrel(options =>
{
options.Listen(
address: IPAddress.Any,
port: 443,
configure: listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps("/root/.aspnet/https/cert.pfx", "Password");
});
});
I assume setting the environment variable ASPNETCORE_URLS
or calling ConfigureKestrel
are equivalent, though, I am not sure what I am missing in the second approach.
Update
I was previously getting the following error, which was related to mapping the port 443
to an incorrect port of the container.
Detected a TLS handshake to an endpoint that does not have TLS enabled.
The app is containerized and orchestrated using Docker compose.
I think this explains the difference. If you try environment variables vs code outside of the container they should work the same. Inside the container there are extra tooling steps to make the dev certificate available. I don't know if that is happening when you switch to the code version.
Detected a TLS handshake to an endpoint that does not have TLS enabled.
That's a weird error, you should only get that if you left out UseHttps. Even if it couldn't load the cert, it should fail to start instead.
Inside the container there are extra tooling steps to make the dev certificate available. I don't know if that is happening when you switch to the code version.
Is mounting the certs onto the container sufficient?! I guess so, because when the certificate file and its password are explicity provided, it works as expected.
volumes:
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro
Detected a TLS handshake to an endpoint that does not have TLS enabled.
That's a weird error, you should only get that if you left out UseHttps. Even if it couldn't load the cert, it should fail to start instead.
This was caused by an error on my part mapping the port 443
to incorrect container port. My apologies for the confusion; I updated my question.
@NCarlsonMSFT Can you take a look at the original post in this issue and see if there's something missing from the workflow here (why does it not work when the certificate path isn't specified)?
@qui8t for clarification, are you using the VS tools for running your compose app? If so, I believe we key off the value for ASPNETCORE_URLS to know whether to do our magic to enable HTTPS so that may be what is causing your strange behavior. If you don't want the value in your main compose file, you can specify it in docker-compose.override.yml located next your main docker-compose.yml file. As of now there is no other way to let the tools know that the service needs HTTPS support.
Alternatively you can add the volumes from your above comment, and export the dev certificate to %AppData%/ASP.NET/Https
Yes, I am using VS tools.
I already have the setup in docker-compose.override.yml
that mounts the directory containing the certificates onto the container. Though it seems it only works if I explicitly provide the certificate names and password.
@adityamandaleeka I've replicated the scenario and can confirm that despite setting the same user secret and providing the same .pfx file as the compose tooling, attempting to configure HTTPs using the above method results in an error. Looking at the code it appears that the UseHttps extension method does not attempt to call FindDeveloperCertificateFile so it can't use this fall-back mechanism.
@qui8t as a work-around you can at least update what you have to also use the user secret (and only be used in development):
if (builder.Environment.IsDevelopment())
{
listenOptions.UseHttps("/root/.aspnet/https/WebApplication1.pfx", builder.Configuration["Kestrel:Certificates:Development:Password"]);
}
I also modified my launch profile to manually use https as the scheme:
"Docker Compose": {
"commandName": "DockerCompose",
"commandVersion": "1.0",
"composeLaunchAction": "LaunchBrowser",
"composeLaunchServiceName": "webapplication1",
"composeLaunchUrl": "https://localhost:{ServicePort}",
"serviceActions": {
"webapplication1": "StartDebugging"
}
}
Bonus: The container tools won't regenerate the cert/secret for you w/o the Environment variable, but here is a script that when run in the Web project's folder will take care of doing the same steps
$password = [Guid]::NewGuid().ToString("N")
$projectName = [System.IO.Path]::GetFileNameWithoutExtension((Get-Item *.csproj)[0])
dotnet dev-certs https --trust --export-path "$Env:APPDATA\ASP.NET\Https\$projectName.pfx" -p $password
dotnet user-secrets init
dotnet user-secrets set "Kestrel:Certificates:Development:Password" "$password"
@NCarlsonMSFT Thank you for looking into this.
It appears that we have two pieces of code that try to load the development cert. There's the following which doesn't look at config at all.
https://github.com/dotnet/aspnetcore/blob/d2074d7181221021861de7492bc03449d5a424f2/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs#L283-L324
The above ends up being the only thing that tries to set the DefaultCertificate if KestrelConfigurationLoader.Load() never gets called, which would mean the following logic couldn't set it first:
https://github.com/dotnet/aspnetcore/blob/d2074d7181221021861de7492bc03449d5a424f2/src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs#L399-L451
I'm surprised KestrelConfigurationLoader.Load() isn't being called here in a WebApplicationBuilder-based app:
https://github.com/dotnet/aspnetcore/blob/d2074d7181221021861de7492bc03449d5a424f2/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs#L315
And `Options.ConfigurationLoader should always be set in WebApplicationBuilder-based apps by the following: https://github.com/dotnet/aspnetcore/blob/d2074d7181221021861de7492bc03449d5a424f2/src/DefaultBuilder/src/WebHost.cs#L225-L228
There are scenarios where we know KestrelServerOptions.ConfigurationLoader is not set though. I bet the logic for reading the development certificate from config was put into KestrelConfigurationLoader because that's about the only place Kestrel has access to the IConfiguration, and if it's not set, that means that the user didn't configure a "Kestrel" config section and therefore might be surprised to see something trying to read "Kestrel:Certificates:Development:Password".
It's unfortunate that our tooling tooling requires Kestrel to bind to the "Kestrel" section of config for the dev cert to work on docker. It might make sense to fix Kestrel to look there for the dev cert regardless of whether KestrelServerOptions.Configure(IConfiguration) was ever called if otherwise it would fail.
It looks like UseHttps() is probably calling KestrelServerOptions.ApplyDefaultCert() too early before KestrelConfigurationLoader.Load().
There are scenarios where we know KestrelServerOptions.ConfigurationLoader is not set though.
Such as?
There are scenarios where we know KestrelServerOptions.ConfigurationLoader is not set though.
Such as?
When you use a non-default builder. So ConfigureWebHost
instead of ConfigureWebHostDefaults
. It can also be overridden even if you are using the defaults.
This seems related to #28120 and #26258.
Even if we don't end up reviving #46296, it's probably worth salvaging the tests.
Possible path forward: can dotnet-monitor query for the existence of the cert(s) they care about and use the result to determine whether or not they attempt to use https?