Skip to content

Preserve explicit Kestrel certificate configuration - #20133

Merged
Karol Zadora-Przylecki (karolz-ms) merged 1 commit into
mainfrom
dev/karolz/issue-20019
Sep 15, 2026
Merged

Karol Zadora-Przylecki (karolz-ms) merged 1 commit into
mainfrom
dev/karolz/issue-20019

Conversation

@karolz-ms

Copy link
Copy Markdown
Contributor

Description

Confirmed root cause

WithHttpsCertificateConfiguration() appends a callback annotation. HttpsCertificateExecutionConfigurationGatherer.GatherAsync() invokes all such callbacks in registration order against one shared environment dictionary.

The PEM mapping shown in the issue, also used by YARP, writes:

Kestrel__Certificates__Default__Path    = <certificate>.crt
Kestrel__Certificates__Default__KeyPath = <certificate>.key

When registered afterward, the built-in WithProjectDefaults() callback replaces Path with ctx.PfxPath and updates or removes Password. It does not remove or otherwise reconcile KeyPath. The result is a mixed PFX/PEM configuration.

Kestrel chooses the separate-key loading branch whenever KeyPath is non-null, including an empty string. That branch rejects the PFX file as the certificate portion of a PEM pair and throws The provided key file is missing or invalid. The reproduction uses valid files; the failure is not evidence of a missing or corrupt private key.

The default PFX mapping was introduced by [#18481](#18481), commit 16783f098082a62f7b31d06fdca2f6c7d84870c1. Its comments intentionally prefer PFX to avoid platform-specific PEM key-handling differences.

Agreed behavior

  1. Keep the existing TLS-endpoint condition.

  2. At certificate callback execution time, check for the resource environment names Kestrel__Certificates__Default__Path, Kestrel__Certificates__Default__KeyPath, or Kestrel__Certificates__Default__Subject.

  3. If any selection key is present, leave the entire existing certificate configuration untouched. In particular, do not overwrite Path, replace or remove Password, or add another representation.

  4. Compare the known environment names case-insensitively using the repository's StringComparisons.EnvironmentVariableName convention. Do not change the dictionary's comparer or rewrite the caller's keys.

  5. Use key presence, not eager value resolution or file validation. Empty, incomplete, or deferred explicit selections remain the application's responsibility; Kestrel may combine them with other providers.

  6. If no selection key exists, retain the current PFX assignment and password handling exactly. A password by itself, or supporting store settings without Subject, does not suppress defaults.

  7. Preserve callback registration and execution order. Later explicit callbacks retain their current ability to override defaults.

Fixes

#20019

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

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

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/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 20133

Or

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

@github-actions github-actions Bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Sep 15, 2026
@aspire-repo-bot
aspire-repo-bot Bot requested a balanced review from Copilot September 15, 2026 00:26
@github-actions

Copy link
Copy Markdown
Contributor

Tests selector

55 / 99 PR test projects · 4 PR jobs, from 3 changed files.

Selected PR test projects (55 / 99)

Aspire.Cli.EndToEnd.Tests, Aspire.Cli.Tests, Aspire.Dashboard.Components.Tests, Aspire.Dashboard.Tests, Aspire.Hosting.Analyzers.Tests, Aspire.Hosting.Azure.Kubernetes.Tests, Aspire.Hosting.Azure.Kusto.Tests, Aspire.Hosting.Azure.Tests, Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Browsers.Tests, Aspire.Hosting.CodeGeneration.Go.Tests, Aspire.Hosting.CodeGeneration.Java.Tests, Aspire.Hosting.CodeGeneration.Python.Tests, Aspire.Hosting.CodeGeneration.Rust.Tests, Aspire.Hosting.CodeGeneration.TypeScript.Tests, Aspire.Hosting.Containers.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.DotnetTool.Tests, Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Garnet.Tests, Aspire.Hosting.GitHub.Models.Tests, Aspire.Hosting.Go.Tests, Aspire.Hosting.Java.Tests, Aspire.Hosting.JavaScript.Tests, Aspire.Hosting.Kafka.Tests, Aspire.Hosting.Keycloak.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Maui.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.MongoDB.Tests, Aspire.Hosting.MySql.Tests, Aspire.Hosting.Nats.Tests, Aspire.Hosting.OpenAI.Tests, Aspire.Hosting.Oracle.Tests, Aspire.Hosting.Orleans.Tests, Aspire.Hosting.PostgreSQL.Tests, Aspire.Hosting.Python.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.RabbitMQ.Tests, Aspire.Hosting.Radius.Tests, Aspire.Hosting.Redis.Tests, Aspire.Hosting.RemoteHost.Tests, Aspire.Hosting.Rust.Tests, Aspire.Hosting.Seq.Tests, Aspire.Hosting.SqlServer.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Tests, Aspire.Hosting.Valkey.Tests, Aspire.Hosting.Yarp.Tests, Aspire.Managed.Tests, Aspire.Playground.Tests, Aspire.Templates.Tests

Selected PR jobs (4)

cli-starter-validation, extension-e2e, polyglot, typescript-api-compat


How these were chosen — grouped by what changed

⚠️ 40 of the 55 selected test projects come from a single change — src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs.

🔧 src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs (changed source)
40 via the project graph

show 40

Aspire.Hosting.Analyzers.Tests (2 hops), Aspire.Hosting.Azure.Kubernetes.Tests (2 hops), Aspire.Hosting.Azure.Kusto.Tests (2 hops), Aspire.Hosting.Azure.Tests, Aspire.Hosting.Browsers.Tests (2 hops), Aspire.Hosting.CodeGeneration.Go.Tests, Aspire.Hosting.CodeGeneration.Java.Tests, Aspire.Hosting.CodeGeneration.Python.Tests, Aspire.Hosting.CodeGeneration.Rust.Tests, Aspire.Hosting.CodeGeneration.TypeScript.Tests, Aspire.Hosting.DevTunnels.Tests (2 hops), Aspire.Hosting.Docker.Tests (2 hops), Aspire.Hosting.DotnetTool.Tests (2 hops), Aspire.Hosting.EntityFrameworkCore.Tests (2 hops), Aspire.Hosting.Foundry.Tests (2 hops), Aspire.Hosting.Garnet.Tests (2 hops), Aspire.Hosting.Go.Tests (2 hops), Aspire.Hosting.Java.Tests (2 hops), Aspire.Hosting.Kafka.Tests (2 hops), Aspire.Hosting.Keycloak.Tests (2 hops), Aspire.Hosting.Kubernetes.Tests (2 hops), Aspire.Hosting.Maui.Tests, Aspire.Hosting.Milvus.Tests (2 hops), Aspire.Hosting.MongoDB.Tests (2 hops), Aspire.Hosting.MySql.Tests (2 hops), Aspire.Hosting.Nats.Tests (2 hops), Aspire.Hosting.Oracle.Tests (2 hops), Aspire.Hosting.Orleans.Tests (2 hops), Aspire.Hosting.PostgreSQL.Tests (2 hops), Aspire.Hosting.Python.Tests (2 hops), Aspire.Hosting.Qdrant.Tests (2 hops), Aspire.Hosting.RabbitMQ.Tests (2 hops), Aspire.Hosting.Redis.Tests (2 hops), Aspire.Hosting.RemoteHost.Tests, Aspire.Hosting.Rust.Tests (2 hops), Aspire.Hosting.Seq.Tests (2 hops), Aspire.Hosting.SqlServer.Tests (2 hops), Aspire.Hosting.Valkey.Tests (2 hops), Aspire.Hosting.Yarp.Tests (2 hops), Aspire.Playground.Tests

🔧 src/Shared/KnownAspNetCoreConfigNames.cs (changed source)
9 via the project graph: Aspire.Cli.Tests, Aspire.Dashboard.Components.Tests, Aspire.Dashboard.Tests, Aspire.Hosting.Containers.Tests, Aspire.Hosting.GitHub.Models.Tests, Aspire.Hosting.JavaScript.Tests, Aspire.Hosting.OpenAI.Tests, Aspire.Hosting.Testing.Tests, Aspire.Managed.Tests (2 hops)

🧪 tests/Aspire.Hosting.Tests/ProjectResourceBuilderExtensionTests.cs (changed test)
1 directly: Aspire.Hosting.Tests
3 via the project graph: Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.Radius.Tests

📦 affected project Aspire.Dashboard
1 test: Aspire.Templates.Tests

📦 affected project Aspire.Hosting
1 test: Aspire.Cli.EndToEnd.Tests

Job reasons

Job Triggered by
cli-starter-validation affected project Aspire.Cli
extension-e2e src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs
• affected project Aspire.Hosting
polyglot affected project Aspire.Hosting
typescript-api-compat affected project Aspire.Hosting

Selection computed for commit babd524.

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.

🟢 Approval recommended

The implementation matches the agreed behavior and includes focused regression coverage.

Pull request overview

Preserves explicit Kestrel certificate configuration while retaining the default PFX fallback.

Changes:

  • Detects explicit Path, KeyPath, or Subject settings case-insensitively.
  • Preserves callback ordering and password behavior.
  • Adds comprehensive regression coverage.
File summaries
File Description
src/Aspire.Hosting/ProjectResourceBuilderExtensions.cs Preserves explicit certificate selections.
src/Shared/KnownAspNetCoreConfigNames.cs Adds KeyPath and Subject constants.
tests/Aspire.Hosting.Tests/ProjectResourceBuilderExtensionTests.cs Covers ordering, modes, casing, and certificate formats.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.6 milestone Sep 15, 2026
@karolz-ms
Karol Zadora-Przylecki (karolz-ms) merged commit e01d15d into main Sep 15, 2026
964 of 985 checks passed
@karolz-ms
Karol Zadora-Przylecki (karolz-ms) deleted the dev/karolz/issue-20019 branch September 15, 2026 17:47
aspire-repo-bot Bot added a commit to microsoft/aspire.dev that referenced this pull request Sep 15, 2026
Adds a note to the certificate-configuration doc explaining that
WithProjectDefaults preserves an existing explicit Kestrel certificate
selection (Path, KeyPath, or Subject) instead of overwriting it with
the default PFX mapping, per microsoft/aspire#20133.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@aspire-repo-bot

Copy link
Copy Markdown
Contributor

Pull request created: #1669

Generated by PR Documentation Check · auto · 85.5 AIC · ⌖ 9.4 AIC · ⊞ 18.5K

@aspire-repo-bot

Copy link
Copy Markdown
Contributor

📝 Documentation has been drafted in microsoft/aspire.dev#1669 targeting release/13.6.

Added a note to src/frontend/src/content/docs/app-host/certificate-configuration.mdx (in the "Customize certificate configuration" section) documenting that WithProjectDefaults now preserves an explicit Kestrel certificate configuration (Kestrel__Certificates__Default__Path, KeyPath, or Subject) instead of overwriting it with the default PFX mapping, and explains why this avoids Kestrel rejecting a mixed PFX/PEM configuration.

  • File modified: src/frontend/src/content/docs/app-host/certificate-configuration.mdx

Note

This draft PR needs human review before merging.

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ CI Failure Analysis: Possible Flaky Test(s)

The CI build failed due to test failure(s) that appear unrelated to the PR changes. These may be flaky tests.

Suspected flaky test(s):

  • Aspire.Cli.Tests.Acquisition.PeerInstallProbeTests.ProbeAsync_PeerEmitsArrayWithNonObjectFirstElement_FallsBackToVersion(doctorStdout: "[\"string\"]", kind: "string") in job Tests / No-package tests (regular, Aspire.Cli.Tests, Cli, Cli, tests/Aspire.Cli.Tests/Aspire.Cli.Tests.cs... / Cli (windows-latest)
    • Error: Expected PeerProbeResult.Ok, got PeerProbeResult.Failed. Reason:
      Peer produced no usable output (and --version fallback).
    • Stack Trace (first frames):
at Aspire.Cli.Tests.Acquisition.PeerInstallProbeTests.AssertProbeOk(PeerProbeResult result) in D:\a\aspire\aspire\tests\Aspire.Cli.Tests\Acquisition\PeerInstallProbeTests.cs:line 38
   at Aspire.Cli.Tests.Acquisition.PeerInstallProbeTests.ProbeAsync_PeerEmitsArrayWithNonObjectFirstElement_FallsBackToVersion(String doctorStdout, String kind) in D:\a\aspire\aspire\tests\Aspire.Cli.Tests\Acquisition\PeerInstallProbeTests.cs:line 352
--- End of stack trace from previous location ---
  • Why likely flaky: The test exercises CLI peer-install probe fallback logic via a generated Windows batch script; this code path and test file are not touched by the PR, which only modifies Kestrel certificate handling. The batch script's --version fallback appears to have not been invoked as expected by the CMD interpreter, an environment/timing related issue on Windows CI rather than a PR-induced regression.
  • Aspire dynamic debug configuration E2E "after each" hook for "does not launch an AppHost when the ambiguous AppHost picker is dismissed" in job Tests / Run VS Code extension E2E tests / VS Code extension E2E (Windows, dynamic-debug-configuration)
    • Error: Dynamic debug configuration E2E teardown failed.
  1. Error: EBUSY: resource busy or locked, rmdir 'C:\Users\runneradmin\AppData\Local\Temp\aev-YmuFqx\workspace.e2e-dynamic-debug\ambiguous'
  • Stack Trace (first frames):
AggregateError: Dynamic debug configuration E2E teardown failed.
1. Error: EBUSY: resource busy or locked, rmdir 'C:\Users\runneradmin\AppData\Local\Temp\aev-YmuFqx\workspace\.e2e-dynamic-debug\ambiguous'
    at runE2eTeardown (out\test-e2e\test-e2e\helpers\fixtures.js:217:15)
    at async Context.<anonymous> (out\test-e2e\test-e2e\dynamicDebugConfiguration.e2e.test.js:62:9)
  • Why likely flaky: EBUSY on rmdir of a Windows temp workspace during test teardown is a well-documented recurring flaky failure for this specific E2E suite (matches prior causes 'vscode-e2e-windows-teardown-ebusy-rmdir' and 'vscode-e2e-windows-dynamic-debug-configuration-flaky' with many prior occurrences). PR does not modify any extension/ code.

Suggested actions:

  • Re-run the failed CI jobs to confirm if the failure is intermittent
  • If the test continues to fail, consider quarantining it using /quarantine-test <test name> <issue URL>
  • Search existing issues to see if this test is already known to be flaky

You can re-run the failed jobs from the workflow run page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants