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

242 Views Asked by At

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

1

There are 1 best solutions below

0
Brian Boyington On BEST ANSWER

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