Skip to content

Support DCP dev cert TLS files#17449

Merged
danegsta merged 5 commits into
mainfrom
danegsta/dcp-dev-certs
May 26, 2026
Merged

Support DCP dev cert TLS files#17449
danegsta merged 5 commits into
mainfrom
danegsta/dcp-dev-certs

Conversation

@danegsta
Copy link
Copy Markdown
Member

@danegsta danegsta commented May 24, 2026

Description

DCP recently added support for TLS certificate files, which lets Aspire use the existing ASP.NET Core developer certificate beyond the current Windows-only thumbprint path. This change makes that developer-certificate path the default for DCP TLS, while preserving ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE=false as an opt-out for users who need DCP's default ephemeral certificate behavior.

By default, Aspire now keeps the Windows behavior of passing --tls-cert-thumbprint. On macOS and Linux, Aspire asks the existing developer certificate service for cached certificate and key file paths, then starts DCP with --tls-cert-file, --tls-key-file, and --tls-cert-thumbprint so DCP can verify the loaded certificate.

Startup profiling also records a dedicated aspire.hosting.dcp.prepare_tls_certificate activity when this code path is active. The span tags capture whether the developer certificate path was enabled, the preparation result, whether a certificate was prepared, and the certificate mode (thumbprint or files) so we can measure the performance impact of this path.

User-facing usage

This behavior is enabled by default. Users can opt out with the existing environment variable:

ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE=false

Security considerations

This change uses the existing developer certificate cache for exported certificate and private key material on macOS and Linux. The cache is under the user's profile and the cached files are written with user-only Unix permissions. Security review should confirm the file location and permissions are appropriate for the default developer certificate scenario and explicit opt-out behavior.

Validation:

dotnet test --project tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj --no-launch-profile -- --filter-class "*.DcpHostNotificationTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"

Fixes # (issue)

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

Extend the opt-in DCP developer certificate path to macOS and Linux by exporting certificate and key files for DCP TLS while preserving the existing Windows thumbprint behavior. Add startup profiling telemetry and tests for the active code path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 24, 2026

🚀 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 -- 17449

Or

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

@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

Make the existing DCP developer certificate setting default to enabled while preserving the environment variable as an opt-out. Update tests to cover the default-on path and explicit opt-out.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/Aspire.Hosting/Dcp/DcpHost.cs Outdated

if (!string.IsNullOrWhiteSpace(_dcpTlsCertFile) && !string.IsNullOrWhiteSpace(_dcpTlsKeyFile))
{
arguments += $" --tls-cert-file \"{_dcpTlsCertFile}\" --tls-key-file \"{_dcpTlsKeyFile}\"";
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.

Not for this PR but we should update this to use arguments list in the future. I added that to process spec.

Set FileOptions.Asynchronous when writing DCP TLS certificate and key files so async writes use the intended FileStream mode.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/Aspire.Hosting/Dcp/DcpHost.cs Outdated
return dcpProcessSpec;
}

private static async Task WriteFileWithUserOnlyPermissionsAsync(string path, ReadOnlyMemory<byte> contents, CancellationToken cancellationToken)
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 there a single place we write the dev cert so that its accessible by multiple consumers?

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.

There is, and I'm folding this into it (DeveloperCertificateService).

@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

Have DCP use DeveloperCertificateService-owned cached certificate and key files instead of writing its own TLS file copies. Keep async FileStream options in the cache writer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

Extract EnsureCachedKeyMaterial as the single owner of the dev-cert
cache lookup/write cycle, shared between GetKeyMaterialAsync and the
new GetCachedCertificateFilePathsAsync. WriteCacheFiles now also writes
the public .crt PEM, so DCP can consume the cached cert/key file paths
on macOS/Linux without a separate writer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

CLI E2E Tests unknown — 96 passed, 0 failed, 5 unknown (commit e78584b)

View all recordings
Status Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View recording
AddPackageWhileAppHostRunningDetached ▶️ View recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View recording
AgentInitCommand_DefaultSelection_InstallsDefaultSkills ▶️ View recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View recording
AgentMcpListStructuredLogsFromStarterAppCore ▶️ View recording
AllPublishMethodsBuildDockerImages ▶️ View recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View recording
AspireInitSingleFileAppHostRunsViaDotnetRunAppHost ▶️ View recording
AspireInitWithExistingAppHostDirRecreatesMissingNuGetConfigAndPreservesFiles ▶️ View recording
AspireInitWithSolutionFileGeneratesAppHostThatBuildsAgainstChannelHive ▶️ View recording
AspireStartUpdatesStaleTypeScriptAppHostPath ▶️ View recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View recording
AspireUpdateRemovesOrphanAppHostPackageVersionWhenSdkAlreadyCurrent ▶️ View recording
Banner_DisplayedOnFirstRun ▶️ View recording
Banner_DisplayedWithExplicitFlag ▶️ View recording
Banner_NotDisplayedWithNoLogoFlag ▶️ View recording
CertificatesClean_RemovesCertificates ▶️ View recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View recording
CreateAndRunAspireStarterProject ▶️ View recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View recording
CreateAndRunEmptyAppHostProject ▶️ View recording
CreateAndRunJavaEmptyAppHostProject ▶️ View recording
CreateAndRunJsReactProject ▶️ View recording
CreateAndRunPythonReactProject ▶️ View recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View recording
CreateAndRunTypeScriptStarterProject ▶️ View recording
CreateJavaAppHostWithViteApp ▶️ View recording
CreateTypeScriptAppHostWithViteApp_UsesConfiguredToolchain ▶️ View recording
DashboardRunWithAgentMcpCore ▶️ View recording
DashboardRunWithOtelTracesReturnsNoTracesCore ▶️ View recording
DeployK8sBasicApiService ▶️ View recording
DeployK8sWithExternalHelmChart ▶️ View recording
DeployK8sWithGarnet ▶️ View recording
DeployK8sWithMongoDB ▶️ View recording
DeployK8sWithMySql ▶️ View recording
DeployK8sWithPostgres ▶️ View recording
DeployK8sWithRabbitMQ ▶️ View recording
DeployK8sWithRedis ▶️ View recording
DeployK8sWithSqlServer ▶️ View recording
DeployK8sWithValkey ▶️ View recording
DeployTypeScriptAppToKubernetes ▶️ View recording
DescribeCommandResolvesReplicaNames ▶️ View recording
DescribeCommandShowsRunningResources ▶️ View recording
DetachFormatJsonProducesValidJson ▶️ View recording
DetachFormatJsonProducesValidJsonWhenRestartingExistingInstance ▶️ View recording
DoListStepsShowsPipelineSteps ▶️ View recording
DocsCommand_RendersInteractiveMarkdownFromLocalSource ▶️ View recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View recording
DoctorCommand_TypeScriptAppHostReportsMissingConfiguredToolchain ▶️ View recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View recording
GeneratedAspireDevScript_StartsWatchMode_WithConfiguredToolchain ▶️ View recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View recording
GlobalMigration_PreservesAllValueTypes ▶️ View recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View recording
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ View recording
InteractiveCSharpInitCreatesExpectedFiles ▶️ View recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View recording
JavaScriptHostingApisRunFromTypeScriptAppHost ▶️ View recording
LatestCliCanStartStableChannelAppHost ▶️ View recording
LatestCliCanStartStableChannelTypeScriptAppHost ▶️ View recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View recording
LogLevelTrace_ProducesTraceEntriesInCliLogFile ▶️ View recording
LogsCommandShowsResourceLogs ▶️ View recording
OtelLogsReturnsStructuredLogsFromStarterApp ▶️ View recording
OtelLogsReturnsStructuredLogsFromStarterAppIsolated ▶️ View recording
PsCommandListsRunningAppHost ▶️ View recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View recording
PublishJavaScriptPatternsGeneratesExpectedDockerComposeArtifacts ▶️ View recording
PublishWithConfigureEnvFileUpdatesEnvOutput ▶️ View recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View recording
PublishWithoutOutputPathUsesAppHostDirectoryDefault ▶️ View recording
ResourceCommand_FailedExecution_DisplaysAppHostLogPathAndLogContainsEntries ▶️ View recording
ResourceCommand_FailsWhenInteractionServiceIsRequired ▶️ View recording
ResourceCommand_SetAndDeleteParameterUpdatesDescribeOutput ▶️ View recording
RestoreGeneratesSdkFiles ▶️ View recording
RestoreGeneratesSdkFiles_WithConfiguredToolchain ▶️ View recording
RestoreRefreshesGeneratedSdkAfterAddingIntegration ▶️ View recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View recording
RunPublishFailureScenarioAsync ▶️ View recording
RunReportsSyntaxErrorsForDotNetAppHost ▶️ View recording
RunReportsSyntaxErrorsForTypeScriptAppHost ▶️ View recording
SecretCrudOnDotNetAppHost ▶️ View recording
SecretCrudOnTypeScriptAppHost ▶️ View recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View recording
StartReportsSyntaxErrorsForDotNetAppHost ▶️ View recording
StartReportsSyntaxErrorsForTypeScriptAppHost ▶️ View recording
StopAllAppHostsFromAppHostDirectory ▶️ View recording
StopJavaPolyglotAppHostUsingApphostDirectory ▶️ View recording
StopNonInteractiveSingleAppHost ▶️ View recording
StopTypeScriptPolyglotAppHostUsingApphostDirectory ▶️ View recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View recording
UpdateProjectChannelToStable_TypeScript_PicksUpStablePackages ▶️ View recording

📹 Recordings uploaded automatically from CI run #26377094449

@danegsta danegsta marked this pull request as ready for review May 26, 2026 00:53
Copilot AI review requested due to automatic review settings May 26, 2026 00:53
@davidfowl
Copy link
Copy Markdown
Collaborator

davidfowl commented May 26, 2026

PR Testing Report

PR Information

CLI Version Verification

  • Expected Commit: e78584b
  • Installed Version: 13.4.0-pr.17449.ge78584ba
  • Status: ✅ Verified

Changes Analyzed

Files Changed

  • src/Aspire.Hosting/Dcp/DcpHost.cs
  • src/Aspire.Hosting/DeveloperCertificateService.cs
  • src/Aspire.Hosting/Diagnostics/ProfilingTelemetry.cs
  • tests/Aspire.Hosting.Tests/Dcp/DcpHostNotificationTests.cs

Change Categories

  • CLI changes detected
  • Hosting/DCP changes detected
  • Dashboard changes detected
  • Template changes detected
  • Client/Component changes detected
  • Test changes detected

The PR changes DCP TLS preparation so the ASP.NET Core developer certificate is enabled by default. On non-Windows it caches developer certificate .crt and .key files and passes those files to DCP, and it adds profiling tags for TLS preparation.

Test Scenarios Executed

Scenario 1: Non-interactive starter template creation

Objective: Verify aspire new aspire-starter works with the PR hive/version and prompt-suppressing flags used by PR smoke tests.
Coverage Type: Boundary
Status: ✅ Passed

Steps:

  1. Ran aspire new aspire-starter with --source, --version, --test-framework None, --use-redis-cache false, --localhost-tld false, --suppress-agent-init, and --non-interactive.

Evidence:

  • artifacts/scenario-1-template-boundary.log

Observations:

  • Project creation completed without interactive prompts.

Scenario 2: Starter app build/start smoke

Objective: Start a fresh generated aspire-starter app with the PR CLI.
Coverage Type: Happy path
Status: ❌ Failed

Steps:

  1. Created Pr17449Default from aspire-starter using the PR hive/version.
  2. Ran aspire start --isolated for the generated AppHost.

Evidence:

  • artifacts/scenario-2-starter-build-failure.log

Error:

Pr17449Default.Web/Program.cs(2,26): error CS0234: The type or namespace name 'Components' does not exist in the namespace 'Pr17449Default.Web'
Pr17449Default.Web/Components/_Imports.razor(11,27): error CS0234: The type or namespace name 'Components' does not exist in the namespace 'Pr17449Default.Web'
AppHost failed to build.

Analysis:

  • This blocked the originally planned starter-app runtime smoke before DCP resource waits could run.
  • The PR diff does not modify templates, but the dogfood artifact produced a starter app that did not build in this local macOS run.

Scenario 3: Empty AppHost default DCP TLS behavior

Objective: Verify the PR DCP binary starts on macOS with developer-certificate TLS files when developer certificate support is left at its default.
Coverage Type: Happy path / implementation evidence
Status: ✅ Passed

Steps:

  1. Created a fresh aspire-empty app from the PR hive/version.
  2. Started the AppHost with aspire start --isolated.
  3. Ran aspire describe --format Json --include-hidden and verified the dashboard resource was Running and Healthy.
  4. Captured the PR DCP process command line and developer certificate cache file listing.

Evidence:

  • artifacts/scenario-3-empty-default-devcert.log
  • artifacts/scenario-3-pr-dcp-processes.txt
  • artifacts/scenario-3-cert-cache-files.txt

Observations:

  • Observed the PR DCP binary started with --tls-cert-file and --tls-key-file on macOS; the command line also included --tls-cert-thumbprint.
  • The dashboard resource reported Running and Healthy.

Scenario 4: Explicit DCP developer certificate opt-out

Objective: Verify the opt-out path remains safe when ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE=false is set.
Coverage Type: Unhappy path / opt-out path
Status: ✅ Passed

Steps:

  1. Created a fresh aspire-empty app from the PR hive/version.
  2. Started the AppHost with ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE=false.
  3. Ran aspire describe --format Json --include-hidden and verified the dashboard resource was Running and Healthy.
  4. Captured the PR DCP process command line.

Expected Unhappy-Path Outcome: AppHost starts successfully and DCP does not receive developer certificate TLS arguments.

Evidence:

  • artifacts/scenario-4-empty-optout.log
  • artifacts/scenario-4-pr-dcp-processes.txt

Observations:

  • Observed the PR DCP binary running without --tls-cert-file, --tls-key-file, or --tls-cert-thumbprint when ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE=false was set.
  • The dashboard resource reported Running and Healthy.

Summary

Scenario Status Notes
Non-interactive starter template creation ✅ Passed Project generation completed without prompts.
Starter app build/start smoke ❌ Failed Generated Blazor web project failed to build with CS0234.
Empty AppHost default DCP TLS behavior ✅ Passed Observed the PR DCP binary started with --tls-cert-file and --tls-key-file on macOS; the command line also included --tls-cert-thumbprint.
Explicit dev-cert opt-out ✅ Passed Observed the PR DCP binary running without --tls-cert-file, --tls-key-file, or --tls-cert-thumbprint when ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE=false was set.

Overall Result

✅ TARGETED PR BEHAVIOR VERIFIED

The DCP TLS file behavior introduced by this PR was verified on macOS with an empty AppHost, including the explicit opt-out path. The generated aspire-starter build/start smoke failed, but that failure appears unrelated to the files changed by this PR and is recorded as a separate dogfood/template artifact observation rather than a blocking PR issue.

Recommendations

  • Treat the aspire-starter build failure as a separate template/artifact follow-up, not as evidence against this DCP TLS change.
  • No DCP TLS-file issue was found in the focused empty-AppHost scenarios.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 updates Aspire’s DCP hosting integration to use the existing ASP.NET Core developer certificate by default for DCP TLS, including cross-platform support by supplying certificate/key file paths on macOS/Linux (while keeping Windows’ thumbprint flow). It also adds dedicated startup profiling telemetry around TLS certificate preparation and updates tests to validate the new behavior.

Changes:

  • Default DCP TLS to use the ASP.NET Core developer certificate (opt-out via ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE=false).
  • On macOS/Linux, prepare and pass --tls-cert-file / --tls-key-file (plus thumbprint) using cached developer cert material.
  • Add profiling activity/tags for DCP TLS certificate preparation and update unit tests accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/Aspire.Hosting/Dcp/DcpHost.cs Prepares DCP TLS arguments using dev cert by default; adds file-based TLS args for non-Windows and emits new profiling activity/tags.
src/Aspire.Hosting/DeveloperCertificateService.cs Extends dev-cert caching to include a public cert .crt and adds an API to retrieve cached cert/key file paths for DCP.
src/Aspire.Hosting/Diagnostics/ProfilingTelemetry.cs Introduces a dedicated profiling activity plus tags/values for DCP TLS certificate preparation.
tests/Aspire.Hosting.Tests/Dcp/DcpHostNotificationTests.cs Updates process-spec tests to validate default dev-cert TLS behavior across Windows vs macOS/Linux and asserts new profiling tags.

Comment thread src/Aspire.Hosting/DeveloperCertificateService.cs
Comment thread src/Aspire.Hosting/Dcp/DcpHost.cs
@danegsta danegsta merged commit 35d7585 into main May 26, 2026
314 checks passed
@microsoft-github-policy-service microsoft-github-policy-service Bot added this to the 13.4 milestone May 26, 2026
@aspire-repo-bot
Copy link
Copy Markdown
Contributor

Pull request created: #1069

Generated by PR Documentation Check

@aspire-repo-bot
Copy link
Copy Markdown
Contributor

📝 Documentation has been drafted in microsoft/aspire.dev#1069 targeting release/13.4.

Documentation updates for ASPIRE_DCP_USE_DEVELOPER_CERTIFICATE which now defaults to true and works cross-platform (Windows uses thumbprint; macOS/Linux use cert/key files). Updated two docs pages:

  • app-host/configuration.mdx: default changed falsetrue, description updated for cross-platform
  • app-host/certificate-configuration.mdx: section renamed and rewritten from Windows-only opt-in to cross-platform opt-out, with a compatibility note for pre-9.4 behavior

Triggered signals: pr_body_has_user_facing_section (PR body has "## Usage" heading), pr_body_has_cli_flag_mention (PR body references --use-dev-cert).

Note

This draft PR needs human review before merging.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants