Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kestrel SNI from config #24286

Merged
merged 24 commits into from
Aug 12, 2020
Merged

Kestrel SNI from config #24286

merged 24 commits into from
Aug 12, 2020

Conversation

halter73
Copy link
Member

@halter73 halter73 commented Jul 24, 2020

{
  "Kestrel": {
    "Endpoints": {
      "EndpointName": {
        "Url": "https://*",
        "Sni": {
          "a.example.org": {
            "Protocols": "Http1AndHttp2",
            "SslProtocols": [ "Tls11", "Tls12", "Tls13"],
            "Certificate": {
              "Path": "testCert.pfx",
              "Password": "testPassword"
            },
            "ClientCertificateMode" : "NoCertificate"
          },
          "*.example.org": {
            "Certificate": {
              "Path": "testCert2.pfx",
              "Password": "testPassword"
            }
          },
          "*": {
            // At least one subproperty needs to exist per SNI section or it
            // cannot be discovered via IConfiguration
            "Protocols": "Http1",
          }
        }
      }
    }
  }
}
  • What is this feature?

    • Adding support for configuring Kestrel's endpoint-specific options, most notably certs, via config (e.g. appsettings.json). The complete list of configurable options are currently as follows:
      • Certificate
      • ClientCertficateMode
      • SslProtocols
      • HttpProtocols
  • What is supported?

    • Server name (or host name)
      • Exact match (StringComparision.OrdinalIgnoreCase)
        • Exact matches will always be selected before any wildcard matches
      • Wildcard prefix
        • If the server name specified in config starts with "*.", and the requested server name ends with the string that fallows (again StringComparision.OrdinalIgnoreCase), that's considered a match. The longest match will be taken in the event there are
      • Full wildcard
        • If specified, any requested server name that doesn't have an exact match or wildcard prefix match will match this. This will be matched even if the client doesn't request any server name (i.e. doesn't use SNI).
  • Where/How it can fail?

    • If the client requests a server name via SNI that doesn't match any part of SNI config (this means that no "full wildcard" config was specified), the connection will be refused.
    • If the client requests no server name (i.e. doesn't use SNI) and no "full wildcard" config was specified, the connection will be refused.
    • Certs for all configurations are now loaded at startup. If a cert isn't found or fails to load for a specific SNI endpoint, the server will fail to start like it does today for an endpoint configured without SNI.
    • If a cert isn't found or fails to load after configuration changes at runtime, the server will log critically like it does today for an endpoint configured without SNI.
  • What APIs are exposed (if any)?

    • No APIs are exposed yet. Something similar to internal static ListenOptions UseHttps(this ListenOptions listenOptions, ServerOptionsSelectionCallback serverOptionsCallback, object state = null) could be made public once we figure out some kinks like how the handshake timeout can be configured with that overload.
      • Do we want a new options object that has only a few properties like a ServerOptionsSelectionCallback and a HandshakeTimeout? We could add convenience methods that make it easy to set common configuration like the server certificate without needing to manually replace or wrap the ServerOptionsSelectionCallback. The convenience methods would likely wrap the ServerOptionsSelectionCallback internally. A downside to this approach is things like a server certificate getter would be difficult or impossible.
      • Do we want to add an additional parameter for the handshake timeout? This risks creating too many UseHttps() overloads if there wind up being too many other things that cannot be configured via the ServerOptionsSelectionCallback.
      • Do we want to expand the HttpsConnectionAdapterOptions used by the existing UseHttps() method? This avoids adding a new overload to UseHttps(), but it makes HttpsConnectionAdapterOptions more complicated and confusing that it already does. It becomes unclear if a ServerOptionsSelectionCallback overrides OnAuthenticate, ServerCertificate, etc...
  • How it composes with anything we already have built in? (What the fallbacks are)

    • The SNI section only affects HTTPS endpoints.
    • Configuration specified directly in the SNI section overrides all other configuration when that SNI section is matched.
    • If an Endpoint does have its own SNI section, that will take precedents over the "EndpointDefaults" SNI section. No merging of the two SNI sections will occur.
    • If an Endpoint doesn't have its own SNI section, but an "EndpointDefaults" SNI section does exist, the configuration logic behaves as if the SNI section was defined directly on the endpoint. This means that a "Certificate" defined directly on the endpoint could be overridden by a certificate defined in an "EndpointDefaults" SNI section.
    • If no relevant configuration exists in the selected SNI section for a given property (e.g. Certificate, ClientCertficateMode, SslProtocols, HttpProtocols), named-endpoint-wide configuration will be used instead.
      • Named-endpoint-wide configuration can be overridden as always via public KestrelConfigurationLoader Endpoint(string name, Action<EndpointConfiguration> configureOptions). Since there's no public API currently exposing SNI-specific config yet, only named-endpoint-wide configuration, non SNI-specific configuration" can be overridden this way.
    • If no relevant configuration exists in the selected SNI or named endpoint section for a given property, "EndpointDefaults" and/or "Certificates:Default/Development" configuration will be used instead.
    • If no relevant configuration exists in the selected SNI or named endpoint section for a given property, and no defaults are configured via "EndpointDefaults" and/or "Certificates:Default/Development", configuration will fallback to what was configured via KSO.ConfigureHttpsDefaults(Action<HttpsConnectionAdapterOptions> configureOptions) or KSO.ConfigureEndpointDefaults(Action<ListenOptions> configureOptions).
  • We decided against adding an SNI section to "EndpointDefaults", because certificates defined in "EndpointDefaults:SNI" it had the weird behavior of overriding "Certificate"s defined directly on endpoints or via ListenOptions.UseHttps() since certs defined via SNI config is preferred over normal certs.

    // The following was considered but decided against.
    //
    // EndpointDefaults will only be used only for endpoints without their own SNI configuration.
    // SNI configuration will always be preferred even over endpoint-wide non-SNI endpoint-specific
    // Configuration, but if no equivalent setting exists in the SNI configuration for
    // the given property, it will fallback to endpoint specific configuration, then code-configured endpoint/HTTPS defualts,
    // then non-SNI configuration EndpointDefaults.
    "EndpointDefaults": {
      "Sni": {
        "a.example.org": {
          "Certificate": {
            "Path": "testCert3.pfx",
            "Password": "testPassword"
          },
        },
        "b.example.org": {
          "Certificate": {
            "Path": "testCert4.pfx",
            "Password": "testPassword"
          }
        }
      }
    }

Addresses #15144

@jkotalik
Copy link
Contributor

Glad I didn't bet that you would get this done.

src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs Outdated Show resolved Hide resolved
src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs Outdated Show resolved Hide resolved
src/Servers/Kestrel/Core/src/KestrelConfigurationLoader.cs Outdated Show resolved Hide resolved
/// <param name="serverOptionsCallback">Callback to configure HTTPS options.</param>
/// <param name="state">State for the <see cref="ServerOptionsSelectionCallback" />.</param>
/// <returns>The <see cref="ListenOptions"/>.</returns>
internal static ListenOptions UseHttps(this ListenOptions listenOptions, ServerOptionsSelectionCallback serverOptionsCallback, object state = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider making this public. We want the config to use a consistent API with whatever the eventual public API for this callback is.

Copy link
Member

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@halter73 I'd like a full description of the behavior in the PR description:

  • What is this feature?
  • What is supported?
    • Wildcards (how matching works)
    • Explicit host names
    • etc.
  • Where/How it can fail?
  • What APIs are exposed (if any)?
  • How it composes with anything we already have built in?
  • What the fallbacks are (I saw a discussion about falling back to the default certificate in some cases).

@halter73 halter73 marked this pull request as draft July 25, 2020 01:20
Copy link
Member

@Tratcher Tratcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only minor feedback at the moment.

The big items that still need to be addressed:

  • Should there be a global SNI config? E.g. so you can have 10 endpoints all with the same SNI settings.
  • Making the new ServerOptionsSelectionCallback accessible in code.

@JamesNK
Copy link
Member

JamesNK commented Jul 31, 2020

Should there be a global SNI config? E.g. so you can have 10 endpoints all with the same SNI settings.

So make it available in EndpointDefaults?

@Tratcher
Copy link
Member

Should there be a global SNI config? E.g. so you can have 10 endpoints all with the same SNI settings.

So make it available in EndpointDefaults?

Right

Copy link
Member

@Tratcher Tratcher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good. What's left on the draft?

@halter73 halter73 marked this pull request as ready for review August 6, 2020 04:50
@halter73 halter73 force-pushed the halter73/15144 branch 2 times, most recently from 8e59bf2 to b6fbeed Compare August 11, 2020 01:59
- Hopefully this is it!
@halter73 halter73 merged commit 292cb9c into master Aug 12, 2020
@halter73 halter73 deleted the halter73/15144 branch August 12, 2020 02:18
@Tratcher Tratcher added this to the 5.0.0-rc1 milestone Aug 27, 2020
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Jun 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants