Skip to content

Ensure the typescript app host trusts the dev cert#15634

Open
danegsta wants to merge 3 commits intomainfrom
danegsta/tsCerts
Open

Ensure the typescript app host trusts the dev cert#15634
danegsta wants to merge 3 commits intomainfrom
danegsta/tsCerts

Conversation

@danegsta
Copy link
Member

Description

Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.

Fixes #15489

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 March 27, 2026 00:44
@github-actions
Copy link
Contributor

github-actions bot commented Mar 27, 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 -- 15634

Or

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

Copy link
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 addresses #15489 by ensuring the TypeScript (Node.js) guest AppHost process trusts the ASP.NET Core development certificate, enabling TLS connections to Aspire-managed HTTPS endpoints without disabling certificate validation in user code.

Changes:

  • Export the current ASP.NET Core HTTPS dev certificate public PEM to a stable path during CLI certificate trust setup.
  • Inject NODE_EXTRA_CA_CERTS for Node.js-based guest AppHosts so Node trusts the exported dev cert.
  • Add CLI unit tests covering PEM export success/failure behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs Updates test DI setup for CertificateService’s new constructor dependencies.
tests/Aspire.Cli.Tests/TestServices/TestCertificateToolRunner.cs Extends test runner to support exporting the dev cert PEM.
tests/Aspire.Cli.Tests/TestServices/TestCertificateService.cs Updates test stub result to include DevCertPemPath.
tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs Adds tests verifying PEM export and that export failures are non-fatal.
src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs Formatting-only change (trailing commas).
src/Aspire.Cli/Projects/GuestAppHostProject.cs Sets NODE_EXTRA_CA_CERTS for Node.js guest AppHosts based on exported PEM path.
src/Aspire.Cli/Certificates/NativeCertificateToolRunner.cs Implements exporting the highest-versioned valid dev cert as PEM.
src/Aspire.Cli/Certificates/ICertificateToolRunner.cs Adds contract for exporting the dev cert PEM.
src/Aspire.Cli/Certificates/CertificateService.cs Exports dev cert PEM as part of ensuring certificate trust and returns the PEM path.

Comment on lines +495 to +496
// trusts the ASP.NET Core development certificate when connecting over HTTPS
if (devCertPemPath is not null && LanguageId.Contains("nodejs", StringComparison.OrdinalIgnoreCase))
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

This unconditionally sets NODE_EXTRA_CA_CERTS, which can override a user-provided value and potentially break environments that already rely on their own extra CA bundle. Consider only setting it when the variable is not already present, or merging the existing bundle with the exported dev cert so both are trusted.

Suggested change
// trusts the ASP.NET Core development certificate when connecting over HTTPS
if (devCertPemPath is not null && LanguageId.Contains("nodejs", StringComparison.OrdinalIgnoreCase))
// trusts the ASP.NET Core development certificate when connecting over HTTPS.
// Do not override an existing NODE_EXTRA_CA_CERTS value provided by the user/environment.
if (devCertPemPath is not null
&& LanguageId.Contains("nodejs", StringComparison.OrdinalIgnoreCase)
&& !environmentVariables.ContainsKey("NODE_EXTRA_CA_CERTS"))

Copilot uses AI. Check for mistakes.
Comment on lines +494 to +499
// Set NODE_EXTRA_CA_CERTS for Node.js-based runtimes so the TypeScript app host
// trusts the ASP.NET Core development certificate when connecting over HTTPS
if (devCertPemPath is not null && LanguageId.Contains("nodejs", StringComparison.OrdinalIgnoreCase))
{
environmentVariables["NODE_EXTRA_CA_CERTS"] = devCertPemPath;
}
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

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

New behavior: NODE_EXTRA_CA_CERTS is injected for Node.js-based guest AppHosts, but there’s no test coverage ensuring it’s set when DevCertPemPath is returned (and that existing env var values are handled appropriately). Please add/extend a GuestAppHostProject test to validate the environment passed to the guest process includes the expected trust configuration.

Copilot uses AI. Check for mistakes.
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.

Looks good overall — the approach of exporting the public PEM and setting NODE_EXTRA_CA_CERTS is solid. A few suggestions inline, mostly around avoiding unnecessary work on every run.

}

var pem = certificate.ExportCertificatePem();
File.WriteAllText(outputPath, pem);
Copy link
Member

Choose a reason for hiding this comment

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

The PEM file is written unconditionally on every run, even if the certificate hasn't changed. This updates the file timestamp unnecessarily and does redundant I/O. Consider comparing the existing file content (or certificate thumbprint) before overwriting, e.g.:

var pem = certificate.ExportCertificatePem();
if (File.Exists(outputPath) && File.ReadAllText(outputPath) == pem)
{
    return outputPath;
}
File.WriteAllText(outputPath, pem);

Copy link
Member

Choose a reason for hiding this comment

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

Up to you whether it's worth doing this, or unconditionally overwriting is better/simplier. Add a comment if so.

{
// Step 1: Ensure certificates are trusted
Dictionary<string, string> certEnvVars;
string? devCertPemPath;
Copy link
Member

Choose a reason for hiding this comment

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

devCertPemPath is declared but only assigned inside the try. If anyone later changes the catch to not re-throw, it would be used uninitialized. Safer to initialize at declaration:

string? devCertPemPath = null;

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, I'm surprised this compiles. I would have thought it would complain about using an uninitialized variable if it's only set in a try block.

return new EnsureCertificatesTrustedResult
{
EnvironmentVariables = environmentVariables
EnvironmentVariables = environmentVariables,
Copy link
Member

Choose a reason for hiding this comment

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

ExportDevCertificatePem() runs on every call to EnsureCertificatesTrustedAsync, including for .NET app hosts where the PEM is never used. Consider making the export opt-in (e.g., a parameter or flag) or moving it to the call site that actually needs it (GuestAppHostProject), so .NET app hosts don't pay for the cert store enumeration + file write on every run.

}
}

public string? ExportDevCertificatePublicPem(string outputPath)
Copy link
Member

@JamesNK JamesNK Mar 27, 2026

Choose a reason for hiding this comment

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

There isn't any logging of what's going on here. Could you get agent to add more logging throughout this method.

There are other methods in this class that could do with more logging. Use your judgement on what's important.

Double check that nothing sensitive is logged.

@JamesNK
Copy link
Member

JamesNK commented Mar 27, 2026

GuestAppHostProject.cs line 496 — The LanguageId.Contains("nodejs", ...) substring check is fragile and inconsistent with the rest of the codebase. Other places use the KnownLanguageId constants with direct equality, e.g. project.LanguageId == KnownLanguageId.CSharp in AddCommand.cs. This should be:

if (devCertPemPath is not null && LanguageId == KnownLanguageId.TypeScript)

where KnownLanguageId.TypeScript is "typescript/nodejs". TypeScript is the only Node.js-based runtime today, and if more are added later, a dedicated property on RuntimeSpec or LanguageInfo would be the right abstraction.

IDictionary<string, string> contextEnvironmentVariables,
string? devCertPemPath)
{
if (devCertPemPath is null || !LanguageId.Contains("nodejs", StringComparison.OrdinalIgnoreCase))
Copy link
Member

Choose a reason for hiding this comment

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

See other comment about improving LanguageId check.

@github-actions
Copy link
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 2 jobs were 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.

@github-actions
Copy link
Contributor

🎬 CLI E2E Test Recordings — 52 recordings uploaded (commit 378e063)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ 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
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RunWithMissingAwaitShowsHelpfulError ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
TypeScriptAppHostWithProjectReferenceIntegration ▶️ View Recording

📹 Recordings uploaded automatically from CI run #23626553641

/// Configures NODE_EXTRA_CA_CERTS for Node.js-based runtimes to trust the ASP.NET Core
/// development certificate. Skips configuration and warns if the variable is already set.
/// </summary>
internal void ConfigureNodeCertificateEnvironment(
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

TypeScript AppHost Node process does not trust dev certs

4 participants