ssl-certificate.net-6.0startupproductionkestrel

Can Kestrel be configured programmatically to not require a dev cert on a production environment?


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


Solution

  • 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!);
                  
              });
          });
      });