Not sure if I am being stupid.
Using NET 6, I have a piece of code to start up Kestrel on a port I specify, and use an SSL certificate as specified in configuration. Basically this works fine on development machines, and the UAT server because they have a development certificate on them for localhost, and on UAT the correct certificate is loaded when the site is called externally.
When I move it out to production, without a development certificate, it will not start up with the "Unable to configure HTTPS endpoint. No server certificate was specified, and the default developer certificate could not be found..." error. I verified the reason by removing the developer certificate on UAT, and it then fails to start, in the same fashion, and when I re install the certificate it starts up ok again.
To me, this makes sense as it is trying to listen to all IP addresses including the loopback, but is there something I can do so that this can start up without installing a development certificate on the production server, which seems like an awful hack?
WebApplicationBuilder builder = WebApplication.CreateBuilder(options);
builder.Host.UseWindowsService(options => options.ServiceName = "Windows Kestrel Server");
builder.WebHost.ConfigureKestrel(serverOptions =>
{
int Port = builder.Configuration.GetValue<int>("Port");
serverOptions.ListenAnyIP(Port, listenOptions =>
{
listenOptions.UseHttps(httpsOptions =>
{
listenOptions.UseHttps((stream, clientHelloInfo, state, cancellationToken) =>
{
string? StoreName = builder.Configuration.GetSection("CertificateStore").Value;
string? CertName = builder.Configuration.GetSection("CertificateName").Value;
if (StoreName == null)
StoreName = "My";
if (string.IsNullOrEmpty(CertName))
CertName = clientHelloInfo.ServerName;
X509Certificate2? Cert = null;
try
{
Cert = CertificateLoader.LoadFromStoreCert(CertName, StoreName, StoreLocation.LocalMachine, allowInvalid: false);
}
catch { };
try
{
if (Cert == null)
Cert = CertificateLoader.LoadFromStoreCert(CertName, StoreName, StoreLocation.CurrentUser, allowInvalid: false);
}
catch { };
return new ValueTask<SslServerAuthenticationOptions>(new SslServerAuthenticationOptions { ServerCertificate = Cert});
}, state: null!);
});
});
});
I have tried various methods of setting the certificate to be that specified in configuration, but they all seem to suffer from this same fundamental problem
I had tried adding the defaults in configuration, that had not worked because I was using builder.WebHost.ConfigureKestrel, and I had tried configuring the defaults in the serverOptions.ListenAnyAPI, that had failed Because it was run too late.
All I needed to do was serverOptions.ConfigureHttpsDefaults in ConfigureKestral BEFORE I configured the listener!
I hope this helps somebody else who struggled with a similar deployment issue.
WebApplicationBuilder builder = WebApplication.CreateBuilder(options);
builder.Host.UseWindowsService(options => options.ServiceName = "Windows Kestrel Server");
builder.WebHost.ConfigureKestrel(serverOptions =>
{
int Port = builder.Configuration.GetValue<int>("Port");
// IPGlobalProperties IPP = IPGlobalProperties.GetIPGlobalProperties();
serverOptions.ConfigureHttpsDefaults(configureOptions =>
{
string? StoreName = builder.Configuration.GetSection("CertificateStore").Value;
string? CertName = builder.Configuration.GetSection("CertificateName").Value;
if (StoreName == null)
StoreName = "My";
if (string.IsNullOrEmpty(CertName))
CertName = "localhost";
X509Certificate2? DefaultCert = null;
try
{
DefaultCert = CertificateLoader.LoadFromStoreCert(CertName, StoreName, StoreLocation.LocalMachine, allowInvalid: false);
}
catch { };
try
{
if (DefaultCert == null)
DefaultCert = CertificateLoader.LoadFromStoreCert(CertName, StoreName, StoreLocation.CurrentUser, allowInvalid: false);
}
catch { };
configureOptions.ServerCertificate = DefaultCert;
});
serverOptions.ListenAnyIP(Port, listenOptions =>
{
listenOptions.UseHttps(httpsOptions =>
{
listenOptions.UseHttps((stream, clientHelloInfo, state, cancellationToken) =>
{
string? CertName = builder.Configuration.GetSection("CertificateName").Value;
string? StoreName = builder.Configuration.GetSection("CertificateStore").Value;
if (StoreName == null)
StoreName = "My";
if (string.IsNullOrEmpty(CertName))
CertName = clientHelloInfo.ServerName;
X509Certificate2? Cert = null;
try
{
Cert = CertificateLoader.LoadFromStoreCert(CertName, StoreName, StoreLocation.LocalMachine, allowInvalid: false);
}
catch { };
try
{
if (Cert == null)
Cert = CertificateLoader.LoadFromStoreCert(CertName, StoreName, StoreLocation.CurrentUser, allowInvalid: false);
}
catch { };
return new ValueTask<SslServerAuthenticationOptions>(new SslServerAuthenticationOptions { ServerCertificate = Cert});
}, state: null!);
});
});
});