Skip to content

Add support for configuring TLS termination for supported resources#12506

Merged
danegsta merged 51 commits into
microsoft:mainfrom
danegsta:dev/danegsta/certificateKeyPair
Nov 19, 2025
Merged

Add support for configuring TLS termination for supported resources#12506
danegsta merged 51 commits into
microsoft:mainfrom
danegsta:dev/danegsta/certificateKeyPair

Conversation

@danegsta

@danegsta danegsta commented Oct 30, 2025

Copy link
Copy Markdown
Member

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:

  • YARP
  • Uvicorn
  • Keycloak
  • Redis Containers

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:

var passwordParam = builder.AddParameter("tlscertificate-password", secret: true)

builder.AddYarp("gateway")
    .WithCertificateKeyPair(certificate, password: passwordParam);

Disable TLS for a resource:

builder.AddYarp("gateway")
    .WithoutCertificateKeyPair();

Use the developer certificate with a password:

var passwordParam = builder.AddParameter("tlscertificate-password", secret: true)

builder.AddYarp("gateway")
    .WithDeveloperCertificateKeyPair(password: passwordParam);

Fixes #6890
Fixes #9381

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

Copilot AI review requested due to automatic review settings October 30, 2025 00:09
@github-actions

github-actions Bot commented Oct 30, 2025

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 12506

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 12506"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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, and WithCertificateKeyPairConfiguration
  • Implemented infrastructure to export and mount certificates to containers and configure executables with certificate paths
  • Refactored certificate trust configuration code by removing unused CertificateTrustConfigurationPathsProvider class 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__Path and Kestrel__Certificates__KeyPath, but these are not valid Kestrel configuration keys. The correct keys should be Kestrel__Certificates__Default__Path and Kestrel__Certificates__Default__KeyPath (as used correctly in YarpResourceExtensions.cs lines 68-69).
// Licensed to the .NET Foundation under one or more agreements.

Comment thread src/Aspire.Hosting/ResourceBuilderExtensions.cs Outdated
Comment thread src/Aspire.Hosting/ApplicationModel/CertificateKeyPairAnnotation.cs Outdated
Comment thread src/Aspire.Hosting/Dcp/DcpExecutor.cs Outdated
Comment thread src/Aspire.Hosting/Dcp/DcpExecutor.cs Outdated
@davidfowl

Copy link
Copy Markdown
Contributor

Can we expose this too #11534

@danegsta

Copy link
Copy Markdown
Member Author

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.

@danegsta danegsta requested a review from mitchdenny as a code owner October 30, 2025 09:44
@danegsta danegsta changed the title Add support for configuring cert key pair for https Add support for configuring TLS termination for supported resources Oct 31, 2025
@danegsta

Copy link
Copy Markdown
Member Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 2 pipeline(s).

@danegsta danegsta added this to the 13.1 milestone Nov 10, 2025
@danegsta danegsta self-assigned this Nov 10, 2025
builder.AddRedis("redis1");
builder.AddRedis("redis1")
// Disable certificate features to avoid extra arguments
.WithoutCertificateKeyPair()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do you need both?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are we turning default https on for everything or will it be opt in based on the endpoints added?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment thread src/Aspire.Hosting.Yarp/ConfigurationBuilder/YarpCluster.cs Outdated
Comment thread src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs
.WithOtlpExporter();
.WithOtlpExporter()
.WithCertificateKeyPairConfiguration(ctx =>
{

@afscrome afscrome Nov 18, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Should this also check if the private key is exportable? (Which is presumably required to be able to inject it into a container).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Comment thread src/Aspire.Hosting/ApplicationModel/ExpressionResolver.cs

_supportsTlsTermination = new Lazy<bool>(() =>
{
var supportsTlsTermination = Certificates.Any(c => c.HasPrivateKey);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Evaluate the reference expression

@afscrome afscrome Nov 18, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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;
});
  1. This callback WithCertificateKeyPairConfiguration is executed after container files are built, so the WithContainerFiles call above does nothing. After swapping the order of BuildCreateFilesAsync and BuildContainerCertificateKeyPairAsync in 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
  2. 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."
  3. The builder.CreateResourceBuilder((SqlServerServerResource)ctx.Resource) syntax is a bit rough. is there a better way to add a container file?

See https://github.com/danegsta/aspire/compare/dev/danegsta/certificateKeyPair...afscrome:sqltls?expand=1

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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)}
                        """
        },
    });
});

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

https://github.com/danegsta/aspire/compare/dev/danegsta/certificateKeyPair...afscrome:sqltls?expand=1

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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).

@danegsta

Copy link
Copy Markdown
Member Author

@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.

@DamianEdwards

Copy link
Copy Markdown
Member

@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 WithServerAuthenticationCertificateKeyPair, but I acknowledge that name is.... long.

@danegsta

Copy link
Copy Markdown
Member Author

@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 WithServerAuthenticationCertificateKeyPair, but I acknowledge that name is.... long.

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.

@danegsta danegsta enabled auto-merge (squash) November 18, 2025 20:59
@danegsta

Copy link
Copy Markdown
Member Author

I'm adding work items for the remaining feedback and will tackle them in follow up PRs.

@danegsta

Copy link
Copy Markdown
Member Author

Added #13037 and #13040 to track follow-up work.

@danegsta danegsta disabled auto-merge November 18, 2025 22:24
@danegsta danegsta enabled auto-merge (squash) November 19, 2025 00:49
@danegsta danegsta merged commit 0908a6a into microsoft:main Nov 19, 2025
580 of 583 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Dec 19, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

6 participants