aspnet/KestrelHttpServer

Make it easier to configure Kestrel endpoints from config

Tratcher opened this issue ยท 20 comments

#1280 added new KestrelServerOptions.Listen APIs for directly configuring kestrel to listen on IPs and ports. However, it's non-trivial to parse these values from config so they can be passed to the new methods. @shirhatti @DamianEdwards

Basic scenarios:

  1. http://localhost:port/ (IPv4 and IPv6 loopback, ANCM)
  2. http://*:port/ (IPv6Any)
  3. http://IP:port/
  4. https://*:port/ with given SSL cert & password
  5. https://IP:port/ with given SSL cert & password
  6. 2 and 4 at the same time.
  7. A list of IPs and ports

Update the samples to use values passed in via config rather than hardcoded values.

Current API/Sample:

options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
       listenOptions.UseHttps("testCert.pfx", "testPassword");
});

Proposals:

  • It's easy enough for the user to infer the scheme from the presence of cert information.
    • if (!string.IsNullOrEmpty(config["certPath"])) { listenOptions.UseHttps(config["certPath"], config["certPassword"]); }
  • If the app only expects to be loopback or *, they only need to pass in a port. Adding Loopback and Any versions with string overloads that parse the port would help.
    • options.ListenLoopback(int.Parse(config["port"], CultureInfo.InvariantCulture)); vs options.ListenLoopback(config["port"]);
    • options.ListenAny(int.Parse(config["port"], CultureInfo.InvariantCulture)); vs options.ListenAny(config["port"]);
  • For an IP and port, IPEndpoint does not have a Parse method. If we don't provide one then the config needs to store the parameters separately.
    • options.Listen(IPAddress.Parse(config["IP"]), int.Parse(config["port"], CultureInfo.InvariantCulture)); vs options.Listen(config["IPandPort"]);
  • Most of the above do not scale well if you need to pass in multiple values, especially if the count varies.
    • options.Listen(config["IPandPort1"]); options.Listen(config["IPandPort2"]); options.Listen(config["IPandPort3"]);
    • The user may be able to do a string split on semicolon and call Listen in a loop.
    • config["IPsAndPorts"].Split(';').ForEach(endpoint => options.Listen(endpoint));

Removing from Backlog as @danroth27 asked about scenarios (4) and (5). We'll need to discuss this.

We need to be able to configure SSL for a couple of our templates:

  • Setting auth with Azure AD requires an HTTPS endpoint to receive the token
  • Identity as a service requires HTTPS for the metadata endpoint and for receiving the token

@glennc @javiercn @mlorbetske @blowdart

@danroth27 You realize none of the above works with IIS right?

@davidfowl True, but configuring https in IIS is a reasonably addressed problem.

@davidfowl Also, SSL, port etc should be easier to configure if Kestrel becomes an "edge" server.

In my mind the key thing here is to allow WebHostBuilderContext to flow to the UseKestrel extension method(s). You can add WebHostBuilderContext to UseKestrel without any of the options stuff taking an explicit dependency on Hosting or Configuration. Which seems like the right thing to do at the moment.

@danroth27 Could you showcase the template changes for this one? @davidfowl wants to see this before committing to a design :)

I think we would add code that looks something like this:

webHostBuilder.UseKestrel((context, options) =>
{
    options.UseHttps(context.Configuration.GetSection("KestrelHttps");
});

And then we would add the SSL certificate to use for development in appsettings.Development.json.

Also, we'd like to be able to specify the certificate from a certificate store in configuration. The current user store in particular is supported cross platform in .NET Core. The recommendation from @blowdart is to specify the certificate you want by subject name and then pick the matching cert whose expiry date is furthest in the future.

Looks like this issue requires aspnet/Hosting#1014

@danroth27 That code sample is sufficiently vague. It doesn't actually show how kestrel is configured. We can do the bare minimum and expose access to configuration and then talk about next steps.

Chatted with @DamianEdwards and we think Kestrel should just bind its options to the config in DI by default: #1703

Reopening as default config was cut from 2.0.

Note the https config experience regressed in 2.0: #1851

We also need to consider how to configure kestrel from VS, especially the port and path base. Today it's setting ASPNETCORE_URLS but https and path base have been disallowed. See https://github.com/aspnet/Hosting/issues/1120

options.ListenAnyIP(configuration.GetValue<int>("PORT")) Is a pretty good combo.
However, there's no config type converter for IPAddress or IPEndPoint.

2.1 Improvements made so far:

  1. http://localhost:port/ (IPv4 and IPv6 loopback, ANCM)

options.ListenLocalhost(configuration.GetValue<int>("PORT"))

  1. http://*:port/ (IPv6Any)

options.ListenAnyIP(configuration.GetValue<int>("PORT"))

  1. http://IP:port/

No direct improvement: options.Listen(IPAddress.Parse(configuration["IP"]), configuration.GetValue<int>("PORT"))
But the URLS config parameter handles this, as does the new config file Endpoints { "MyEndpoint": { "Url": "http://IP:port/" ...

  "Kestrel": {
	"Endpoints": {
	   "HTTP": { "Url": "http://192.168.1.1:5000" },
          }
    }
  1. https://*:port/ with given SSL cert & password
options.ListenAnyIP(configuration.GetValue<int>("PORT"), listenOptions =>
{
  listenOptions.UseHttps(configuration["certPath"], configuration["certPassword"]);
})

Or full config file:

  "Kestrel": {
	"Endpoints": {
           "HTTPS": {
              "Url": "https://*:5463",
              "Certificate": {
                  "Path": "testCert.pfx",
                  "Password": "testPassword"
              }
          }
    }
  1. https://IP:port/ with given SSL cert & password

See 3. and 4. above.

  1. 2 and 4 at the same time.
options.ListenAnyIP(configuration.GetValue<int>("PORT"))
options.ListenAnyIP(configuration.GetValue<int>("HTTPS_PORT"), listenOptions =>
{
  listenOptions.UseHttps(configuration["certPath"], configuration["certPassword"]);
})

Or config:

  "Kestrel": {
	"Endpoints": {
	   "HTTP": { "Url": "http://*:5000" },
           "HTTPS": {
              "Url": "https://*:5463",
              "Certificate": {
                  "Path": "testCert.pfx",
                  "Password": "testPassword"
              }
          }
    }
  1. A list of IPs and ports

No code improvements, but better in config:

  "Kestrel": {
	"Endpoints": {
	   "HTTP1": { "Url": "http://192.168.1.1:5000" },
	   "HTTP2": { "Url": "http://192.168.1.2:5000" },
	   "HTTP3": { "Url": "http://192.168.1.3:5000" },
	   "HTTP4": { "Url": "http://192.168.1.4:5000" },
          }
    }

Or the existing:
"Urls": "http://192.168.1.1:5000;http://192.168.1.2:5000;http://192.168.1.3:5000;http://192.168.1.4:5000

We're going to call this good for 2.1. Please open new issues if there are still painful gaps.