Add support for configuring TLS termination for supported resources#12506
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12506Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12506" |
There was a problem hiding this comment.
Pull Request Overview
This PR adds support for configuring TLS certificate key pairs on resources, allowing developers to specify custom certificates (including the ASP.NET Core developer certificate) for HTTPS endpoints. The changes enable both container and executable resources to use certificate key pairs with appropriate environment variables and file system mounts.
Key changes:
- Added three new public extension methods for certificate key pair configuration:
WithDeveloperCertificateKeyPair,WithCertificateKeyPair, andWithCertificateKeyPairConfiguration - Implemented infrastructure to export and mount certificates to containers and configure executables with certificate paths
- Refactored certificate trust configuration code by removing unused
CertificateTrustConfigurationPathsProviderclass and simplifying null-coalescing assignments
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Aspire.Hosting/ResourceBuilderExtensions.cs | Added three public extension methods for certificate key pair configuration on resources |
| src/Aspire.Hosting/Dcp/DcpExecutor.cs | Added methods to build and apply certificate key pair configuration for executables and containers; refactored null-coalescing assignments |
| src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs | Changed ResolveValueAsync from private static to internal extension method, made value parameter nullable |
| src/Aspire.Hosting/ApplicationModel/CertificateTrustConfigurationCallbackAnnotation.cs | Removed unused CertificateTrustConfigurationPathsProvider class |
| src/Aspire.Hosting/ApplicationModel/CertificateKeyPairConfigurationCallbackAnnotaion.cs | Added new annotation and context classes for certificate key pair configuration callbacks |
| src/Aspire.Hosting/ApplicationModel/CertificateKeyPairAnnotation.cs | Added new annotation class to associate certificate key pairs with resources |
| src/Aspire.Hosting.Yarp/YarpResourceExtensions.cs | Updated YARP resource to use HTTPS endpoint with developer certificate key pair configuration |
Comments suppressed due to low confidence (1)
src/Aspire.Hosting/ApplicationModel/CertificateKeyPairConfigurationCallbackAnnotaion.cs:1
- The example shows
Kestrel__Certificates__PathandKestrel__Certificates__KeyPath, but these are not valid Kestrel configuration keys. The correct keys should beKestrel__Certificates__Default__PathandKestrel__Certificates__Default__KeyPath(as used correctly in YarpResourceExtensions.cs lines 68-69).
// Licensed to the .NET Foundation under one or more agreements.
|
Can we expose this too #11534 |
I implemented a YARP config and have it working with the dev cert; for now I've only implemented PEM format certificate export, but it should be extendable to support PFX as well. |
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
| builder.AddRedis("redis1"); | ||
| builder.AddRedis("redis1") | ||
| // Disable certificate features to avoid extra arguments | ||
| .WithoutCertificateKeyPair() |
There was a problem hiding this comment.
Redis uses argument based config for TLS, which was screwing up the test this is for (runs tail on /dev/null instead of redis); turned them off so they wouldn’t try to contribute arguments.
There was a problem hiding this comment.
Are we turning default https on for everything or will it be opt in based on the endpoints added?
There was a problem hiding this comment.
Current design turns it on by default (and adds TLS/HTTPS endpoints automatically if a user doesn’t disable the feature). In the case of uvicorn, the server only supports a single endpoint, so I have to “upgrade” the existing endpoint to an https scheme.
| .WithOtlpExporter(); | ||
| .WithOtlpExporter() | ||
| .WithCertificateKeyPairConfiguration(ctx => | ||
| { |
There was a problem hiding this comment.
Seems like a lot of this applies to any .Net/Kestrel container. Could this be promoted to some kind of extension method that I could use on my own dotnet containers, rather than needing to re-implement what this block does?
There was a problem hiding this comment.
Initial release I'll keep this specific to YARP, but after it's been out in the wild and we've had a chance to identify and fix any issues that crop up we can consider adding something like a WithKestrelCertificateKeyPairConfiguration() extension method for containers.
| throw new ArgumentException("Cannot set both UseDeveloperCertificate and Certificate properties.", nameof(value)); | ||
| } | ||
|
|
||
| if (value?.HasPrivateKey == false) |
There was a problem hiding this comment.
Should this also check if the private key is exportable? (Which is presumably required to be able to inject it into a container).
There was a problem hiding this comment.
For the dev cert this shouldn't be an issue as it's always added exportable, but I'll open up a follow-up PR to double check.
There was a problem hiding this comment.
Meant to comment this on the dev certs service comment. I'll tackle both in a follow-up PR though as the defaults should work fine when using a dev cert and I'd expect a user to provide an exportable certificate when using custom configuration.
|
|
||
| _supportsTlsTermination = new Lazy<bool>(() => | ||
| { | ||
| var supportsTlsTermination = Certificates.Any(c => c.HasPrivateKey); |
There was a problem hiding this comment.
Should this also check that the key is exportable?
| /// <param name="builder">The resource builder.</param> | ||
| /// <returns>The <see cref="IResourceBuilder{TResource}"/>.</returns> | ||
| [Experimental("ASPIRECERTIFICATES001", UrlFormat = "https://aka.ms/aspire/diagnostics/{0}")] | ||
| public static IResourceBuilder<TResource> WithoutCertificateKeyPair<TResource>(this IResourceBuilder<TResource> builder) |
There was a problem hiding this comment.
Is WithCertificateKeyPair only for TLS certs, or can it be used to inject other certs. If the latter, what if I want to inject my custom certs, but not the dev cert? If hte former, should these methods be renamed With(out)TlsCertificateKeyPair()
| /// <summary> | ||
| /// A value provider that will resolve to a path to the certificate file. | ||
| /// </summary> | ||
| public required ReferenceExpression CertificatePath { get; init; } |
There was a problem hiding this comment.
With these being ReferenceExpressions, how would I put these into a config file. e.g. for sql server, which seems to require TLS be configured via /var/opt/mssql/mssql.conf
[network]
forceencryption = 1
tlscert = {CertificatePath}
tlskey = {KeyPath}
There was a problem hiding this comment.
Evaluate the reference expression
There was a problem hiding this comment.
Just tried this out and hit a few issues
.WithDeveloperCertificateKeyPair()
.WithCertificateKeyPairConfiguration(async ctx =>
{
builder.CreateResourceBuilder((SqlServerServerResource)ctx.Resource)
.WithContainerFiles("/var/opt/mssql/conf/", [
new ContainerFile {
Name = "mssql.conf",
Contents = $"""
[network]
forceencryption = 1
tlscert = {await ctx.CertificatePath.GetValueAsync(ctx.CancellationToken).ConfigureAwait(false)}
tlskey = {await ctx.KeyPath.GetValueAsync(ctx.CancellationToken).ConfigureAwait(false)}
"""
}
]);
//Need to set an env var or arg
//ctx.EnvironmentVariables["TLSFAKE"] = true;
});- This callback
WithCertificateKeyPairConfigurationis executed after container files are built, so theWithContainerFilescall above does nothing. After swapping the order ofBuildCreateFilesAsyncandBuildContainerCertificateKeyPairAsyncin DCP executor, I did get this to work. Although given that the current implementaiton does some special handling of env vars and args, I'm sort of assuming this "fix" causes a bunch of other problems - Because I set no env var or args, Aspire incorrectly logs "Resource 'sql1' does not have certificate key pair configuration defined. No TLS key pair override will be applied."
- The
builder.CreateResourceBuilder((SqlServerServerResource)ctx.Resource)syntax is a bit rough. is there a better way to add a container file?
There was a problem hiding this comment.
Main concern is that modifying annotations late (after BeforeStartEvent) isn't necessarily safe depending as there could be some other operation iterating over the annotations collection, hence why this (and the certificate trust API) use callback contexts with the same conventions as WithEnvironment and WithArgs.
For supporting applying custom file based configuration, it feels like making the certificate properties (both the trusted certificates and keypair related properties) available in the context of the WithContainerFiles callback may be the best approach to supporting that scenario as it's quite possible (if not likely) that the the config file would need access to more than just the TLS certs.
So something like:
.WithContainerFiles("/var/opt/mssql/conf/", (ctx, cancellationToken) => {
return Task.From<IEnumerable<ContainerFileSystemItem>>(new List<ContainerFileSystemItem>() {
new ContainerFile {
Name = "mssql.conf",
Contents = $"""
[network]
forceencryption = 1
tlscert = {await ctx.ServerAuthenticationCertificatePath.GetValueAsync(ctx.CancellationToken).ConfigureAwait(false)}
tlskey = {await ctx.ServerAuthenticationKeyPath.GetValueAsync(ctx.CancellationToken).ConfigureAwait(false)}
"""
},
});
});There was a problem hiding this comment.
As for that log entry if there's no config to apply; I've got a separate PR where I'm refactoring how the set of config callbacks are run that'll need to be updated to cover these new APIs as well and I'd like remove it there as it wouldn't make sense anymore.
There was a problem hiding this comment.
Adding the values within WithContainerFiles would make sense, although it would probably make more sense to provide the values as string there rather than as a ReferenceExpression - I'm not sure you could use a ReferenceExpression meaningfully more than a string in that context.
There was a problem hiding this comment.
The WithContainerFiles changes will likely come in a follow-up PR (no guarantees that PR would make it into 13.1 as we're trying for a pretty quick turnaround, but I'll ping you once it's open so you can try it out).
There was a problem hiding this comment.
FYI, I've now got tls cert injection "working" with SQL Server using TLS. It's not mergeable as it needs some test work and some workarounds for the fact that SQL server requires the use of 127.0.0.1 rather than localhost. But what I have should be good enough to prove the cert injection API works with the follow up PR.
There was a problem hiding this comment.
Definitely appreciate you taking a look at how this would need to work with SQL Server; I had to pick an arbitrary subset of integrations to implement in the initial release, so having more eyes looking at other integrations and trying things out is extremely helpful.
I expect we'll need to figure out a way to make 127.0.0.1 work for more than just SQL Server; I started looking at supporting TLS in Azurite, but some legacy decisions in the Azure Storage SDK mean that we need a certificate that's valid for either 127.0.0.1 or *.blob.localhost style domains (the SDK special cases IP addresses and specific ports for local access, any hostname is evaluated as a custom storage domain endpoint, with no allowance for localhost addresses).
|
@afscrome adding TLS to the API method names to avoid confusion makes sense; I do see this API as being specific to server TLS termination, with the potential for future API to cover additional certificate usage scenarios such as client authentication. |
@danegsta I feel slightly uneasy about putting "TLS" in the name. While it's arguably clear what it means now, it's technically not what the cert is attached to as the underlying security mechanism can change over time (e.g. SSL -> TLS). A more technically correct name would be something like |
The methods are intentionally marked experimental since we'll likely end up making tweaks before we stabilize them. It's probably reasonable to give ourselves some time to think about the naming and adjust in a subsequent release. |
|
I'm adding work items for the remaining feedback and will tackle them in follow up PRs. |
Description
Adds new experimental API for configuring HTTPS certificate key pairs for resources. This allows configuring a resource to use a particular certificate/private key pair to terminate TLS/HTTPS connections. This can be used to configure resources that otherwise wouldn't support it to secure traffic with ASP.NET Core developer certificate, such as apps running in containers. Certificate assets are provided in both PEM and PFX format with optional encryption of the certificate key; resources can choose to consume the the specific certificate assets required for their configuration (some resources only support encryption with PFX, for example, while others only support PEM format certificates).
This PR includes implementations for:
Additionally, the existing AspireWithNode playground project has been updated to use these new APIs.
Add a custom TLS keypair with password protection on the certificate:
Disable TLS for a resource:
Use the developer certificate with a password:
Fixes #6890
Fixes #9381
Checklist
<remarks />and<code />elements on your triple slash comments?doc-ideatemplatebreaking-changetemplatediagnostictemplate