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

Make TLS & QUIC Pay-for-Play (Redux) #47454

Merged
merged 9 commits into from Apr 14, 2023
Merged

Make TLS & QUIC Pay-for-Play (Redux) #47454

merged 9 commits into from Apr 14, 2023

Conversation

amcasey
Copy link
Member

@amcasey amcasey commented Mar 27, 2023

Based on the feedback on #47209, I've prepared this substantially different approach. Whereas #47209 focuses on providing a describable set of restrictions and guardrails to prevent their violation, this PR focuses on making a best effort to improve trimmed size while breaking as few scenarios as possible.

What's disabled when you create a slim builder? You'll find out at bind-time if you crossed the line and a helpful error message will tell you the one-liner (either IWebHostBuilder.UseHttpsConfiguration or IWebHostBuilder.UseQuic) to add to fix it. What does that mean, in practice? Kestrel won't call UseHttps on your behalf - in particular, setting your URL to https won't "just work" - and there won't be any QUIC transports.

Presently, IWebHostBuilder.UseQuic doesn't imply IWebHostBuilder.UseHttpsConfiguration, but it could if we're willing to slightly clutter the public API (i.e. with a marker service in Connection.Abstractions).

There are exactly two new DI services, both internal:

  1. IHttpsConfigurationHelper is always present, but throws until it is explicitly enabled.
  2. IEnabler is a declarative way to enable IHttpsConfigurationHelper (vs a method call). I suspect there's a better way to do this in DI, but I'm not familiar enough with the available APIs.

There are two new public APIs (plus overloads):

  1. UseKestrelSlim (name TBD) doesn't call UseQuic or IHttpsConfigurationHelper.Enable.
  2. UseHttpsConfiguration (effectively) calls IHttpsConfiguration.Enable.

I have more validation to do, but the trimmed size is as in #47209 and the tests pass.

Fixes #47567
Fixes #46681

@amcasey
Copy link
Member Author

amcasey commented Mar 27, 2023

FYI @halter73 @JamesNK This potentially replaces #47209. I believe it incorporates the feedback provided on that PR.

@amcasey
Copy link
Member Author

amcasey commented Mar 28, 2023

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@amcasey
Copy link
Member Author

amcasey commented Mar 29, 2023

I did manage to get the template tests to run locally, but they pass. I'll keep investigating, but I don't expect a substantial product change (i.e. I think it's safe to review).

Edit: I don't know what it was doing, but it wasn't running the tests. I debugged in CI and now the tests are green.

@amcasey
Copy link
Member Author

amcasey commented Mar 29, 2023

974b528 fixed most tests. The issue was that ConfigureWebDefaults was effectively calling UseKestrelSlim, rather than UseKestrel. I'm not sure why some tests are still failing, but I've added more instrumentation to investigate.

@amcasey
Copy link
Member Author

amcasey commented Mar 30, 2023

The remaining test failure appears to be induced by my logging code, so I'm ignoring it for now.

Edit: It was. Resolved.

@amcasey
Copy link
Member Author

amcasey commented Mar 31, 2023

Force push drops all debugging commits and, hopefully, fixes CI.

@adityamandaleeka adityamandaleeka added the blog-candidate Consider mentioning this in the release blog post label Apr 5, 2023
@ghost
Copy link

ghost commented Apr 5, 2023

@amcasey, this change will be considered for inclusion in the blog post for the release it'll ship in. Nice work!

Please ensure that the original comment in this thread contains a clear explanation of what the change does, why it's important (what problem does it solve?), and, if relevant, include things like code samples and/or performance numbers.

This content may not be exactly what goes into the blog post, but it will help the team putting together the announcement.

Thanks!

Comment on lines +85 to +117
public void ApplyHttpsConfiguration(
HttpsConnectionAdapterOptions httpsOptions,
EndpointConfig endpoint,
KestrelServerOptions serverOptions,
CertificateConfig? defaultCertificateConfig,
ConfigurationReader configurationReader)
{
throw new NotImplementedException(); // Not actually required by this impl
}

public ListenOptions UseHttpsWithSni(ListenOptions listenOptions, HttpsConnectionAdapterOptions httpsOptions, EndpointConfig endpoint)
{
throw new NotImplementedException(); // Not actually required by this impl
}

public CertificateAndConfig? LoadDefaultCertificate(ConfigurationReader configurationReader)
{
throw new NotImplementedException(); // Not actually required by this impl
}
}
Copy link
Member

Choose a reason for hiding this comment

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

I feel like I'm missing something. This is saying that these methods aren't called when KestrelServer is newed up manually?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct, KestrelServer and KestrelServerImpl only pass the IHttpsConfigurationService to TransportManager and AddressBinder, so they don't need the parts of the interface that are exclusively for KestrelConfigurationLoader.

Comment on lines 287 to 295
var HttpsConfigurationService = ApplicationServices.GetRequiredService<IHttpsConfigurationService>();

if (!HttpsConfigurationService.IsInitialized)
{
var hostEnvironment = ApplicationServices.GetRequiredService<IHostEnvironment>();
var logger = ApplicationServices.GetRequiredService<ILogger<KestrelServer>>();
var httpsLogger = ApplicationServices.GetRequiredService<ILogger<HttpsConnectionMiddleware>>();
HttpsConfigurationService.Initialize(hostEnvironment, logger, httpsLogger);
}
Copy link
Member

Choose a reason for hiding this comment

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

Usually, there's a single call to some entry point service that resolved dependencies. This feels very manual. Is there a way to avoid these checks to IsInitialized or does that happen opportunistically in various places?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the pattern I came up with for making it possible to light up this functionality either during DI setup or afterwards (esp when calling ListenOptions.UseHttps). Alternatives welcome.

Copy link
Member Author

@amcasey amcasey Apr 5, 2023

Choose a reason for hiding this comment

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

To elaborate a bit, there's always an IHttpsConfigurationService, but all of its methods throw unless you either register an HttpsConfigurationService.IInitializer (as in UseHttpsConfiguration) or explicitly invoke Initialize (as here, in service of ListenOptions.UseHttps). Since UseHttps is called after the DI container is sealed, there needed to be a different mechanism for enabling the functionality in that code path. (Eric and Stephen both felt strongly that UseHttps should "just work".)

@amcasey
Copy link
Member Author

amcasey commented Apr 10, 2023

Looks like I was once again sabotaged by my local dev certs. I'll update the tests.

@amcasey
Copy link
Member Author

amcasey commented Apr 11, 2023

Rename "UseKestrelSlim" to "UseKestrelCore"?

`CreateSlimBuilder` now calls `UseKestrelSlim` (name TBD) to create a Kestrel server that doesn't automatically support TLS or QUIC.  If you invoke `ListenOptions.UseHttps`, TLS will "just work" but, if you want to enable https using `ASPNETCORE_URLS`, you'll need to invoke the new `WebHostBuilder.UseHttpsConfiguration` extension method.  Quic is enabled using the existing `WebHostBuilder.UseQuic` extension method.

Squash:

Break direct dependency of AddressBinder on ListenOptionsHttpsExtensions.UseHttps

Factor out the part of TransportManager that depends on https

Introduce KestrelServerOptions.HasServerCertificateOrSelector for convenience

Factor TlsConfigurationLoader out of KestrelConfigurationLoader

Introduce but don't consume IHttpsConfigurationHelper

Consume IHttpsConfigurationHelper - tests failing

Fix most tests

Fix KestrelServerTests

Fix remaining tests

Respect IHttpsConfigurationHelper in ApplyDefaultCertificate

Introduce UseKestrelSlim

Delete unused TryUseHttps

Enable HttpsConfiguration when UseHttps is called

Introduce UseHttpsConfiguration

Drop incomplete test implementation of IHttpsConfigurationHelper

Tidy up test diffs

Fix AOT trimming by moving enable call out of ctor

Fix some tests

Simplify HttpsConfigurationHelper ctor for more convenient testing

Improve error message

Don't declare Enabler transient

Fix tests other than KestrelConfigurationLoaderTests

Correct HttpsConfigurationHelper

Add IEnabler interface to break direct dependency

Restore UseKestrel call in WebHost.ConfigureWebDefaults

Stop registering an https address in ApiTemplateTest

boolean -> bool

HttpsConfigurationHelper -> HttpsConfigurationService

HttpsConfigurationService.Enable -> Initialize

ITlsConfigurationLoader.ApplyHttpsDefaults -> ApplyHttpsConfiguration

ITlsConfigurationLoader.UseHttps -> UseHttpsWithSni

IHttpsConfigurationService.UseHttps -> UseHttpsWithDefaults

Inline ITlsConfigurationLoader in IHttpsConfigurationService

Document IHttpsConfigurationService

Document new public APIs in WebHostBuilderKestrelExtensions

Clean up TODOs

Improve error text recommending UseQuic

Co-authored-by: James Newton-King <james@newtonking.com>

Add assert message

Clarify comment on assert

Fix typo in doc comment

Co-authored-by: Aditya Mandaleeka <adityamandaleeka@users.noreply.github.com>

Fix typo in doc comment

Co-authored-by: Aditya Mandaleeka <adityamandaleeka@users.noreply.github.com>

Fix typo in doc comment

Co-authored-by: Aditya Mandaleeka <adityamandaleeka@users.noreply.github.com>

Don't use regions

Correct casing

Replace record with readonly struct

Test AddressBinder exception

Test an endpoint address from config

Test certificate loading

Bonus: use dynamic ports to improve reliability

Test Quic with UseKestrelSlim

Test the interaction of UseHttps and UseHttpsConfiguration

Test different UseHttps overloads

Add more detail to doc comment

Set TestOverrideDefaultCertificate in the tests that expect it
@amcasey
Copy link
Member Author

amcasey commented Apr 12, 2023

Force push is a squash and a rebase on main (mostly a merge with #46834).

@JamesNK
Copy link
Member

JamesNK commented Apr 13, 2023

I played around a bit on a branch - 6e10637

MemberNotNull can definitely be used to remove some bangs.
Can TlsConfigurationLoad be passed from DI, or does that bring in types we don't want?
Is it possible to write a test to verify that TLS and friends are trimmed when not enabled? We have benchmarks that measure size, but something that will be caught in CI is needed. Otherwise, I think this will be too easy to regress accidentally.

Co-authored-by: James Newton-King <james@newtonking.com>
@amcasey
Copy link
Member Author

amcasey commented Apr 13, 2023

I played around a bit on a branch - 6e10637

I'll have a look.

Edit: I only see MemberNotNull and TlsConfigurationLoader - did I miss anything?

MemberNotNull can definitely be used to remove some bangs.

I didn't know about that attribute. Neat.

Edit: done.

Can TlsConfigurationLoad be passed from DI, or does that bring in types we don't want?

It was a DI service in #47209, but Stephen asked me not to clutter the service collection.

Edit: It's definitely cleaner, but we really need to finally land this. That won't affect the public surface area, so we can refactor in a follow-up PR if there's still appetite.

Is it possible to write a test to verify that TLS and friends are trimmed when not enabled? We have benchmarks that measure size, but something that will be caught in CI is needed. Otherwise, I think this will be too easy to regress accidentally.

It is ridiculously easy to regress this. I think @mitchdenny is working on AOT template tests that might provide this coverage?

@JamesNK
Copy link
Member

JamesNK commented Apr 13, 2023

You also need the Debug.Assert calls from my branch to fix the new warnings.

If it's very easy to undo this improvement, we need unit tests. @eerhardt does dotnet/runtime have a way to assert types aren't accidentally being brought in that we could use? Would publishing an app with native AOT and then doing Type.GetType(xxx) work?

Copy link
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

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

LGTM.

Need a way to automate testing this to prevent accidentally regressing the next time we need to change TLS cert logic.

This will be non-trivial. Can happen in a follow-up PR.

@amcasey amcasey enabled auto-merge (squash) April 14, 2023 00:09
@eerhardt
Copy link
Member

You also need the Debug.Assert calls from my branch to fix the new warnings.

If it's very easy to undo this improvement, we need unit tests. @eerhardt does dotnet/runtime have a way to assert types aren't accidentally being brought in that we could use? Would publishing an app with native AOT and then doing Type.GetType(xxx) work?

Yes, we do that in 2 places (that I know of) in dotnet/runtime:

Invariant Globalization:

https://github.com/dotnet/runtime/blob/e4743ec9474728f2b4e120fd70a3f729e9204c32/src/libraries/System.Runtime/tests/TrimmingTests/InvariantGlobalizationTrue.cs#L31-L67

System.Text.Json:

https://github.com/dotnet/runtime/blob/e4743ec9474728f2b4e120fd70a3f729e9204c32/src/libraries/System.Text.Json/tests/System.Text.Json.Tests/TrimmingTests/IsReflectionEnabledByDefaultFalse.cs#L54-L66

Note there is a bit of infrastructure to support this. We have a "TrimmingTests" leg that publishes all these EXE projects for trimming. And then we execute the published executable and ensure the exit code is 100. If it isn't, the test fails. That is all captured here:

https://github.com/dotnet/runtime/blob/e4743ec9474728f2b4e120fd70a3f729e9204c32/eng/testing/linker/trimmingTests.targets#L151

Note that ASP.NET has a backup system (https://github.com/aspnet/Benchmarks/tree/main/src/BenchmarksApps) which publish fully trimmed apps and measure the output size. We also have a regression bot that logs an issue if those apps increase by more than 2%.

@amcasey amcasey merged commit f1bbdd4 into dotnet:main Apr 14, 2023
26 checks passed
@ghost ghost added this to the 8.0-preview4 milestone Apr 14, 2023
@amcasey amcasey deleted the PayForPlay branch April 14, 2023 02:49
@eerhardt
Copy link
Member

image

image

@ghost

This comment was marked as off-topic.

@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 blog-candidate Consider mentioning this in the release blog post
Projects
None yet
6 participants