Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 15, 2026

Description

On Linux, dotnet dev-certs https --trust --check always fails when SSL_CERT_DIR is not set, but the trust operation succeeds regardless. This causes aspire run to attempt certificate trust on every invocation.

This PR uses dotnet dev-certs https --check-trust-machine-readable (.NET 10 SDK) to get detailed trust status and automatically configures SSL_CERT_DIR when certificates are "partially" trusted. Since the CLI requires .NET 10 SDK minimum, the machine-readable check is always available.

Key changes:

  • New machine-readable check: CheckHttpCertificateMachineReadableAsync parses JSON output with TrustLevel (Full/Partial/None)
  • Automatic SSL_CERT_DIR on Linux: When partially trusted, sets SSL_CERT_DIR to include system cert dirs + the dev-certs trust directory
  • Environment variable passthrough: EnsureCertificatesTrustedAsync now returns env vars to apply when launching AppHost
  • Removed legacy check: Removed CheckHttpCertificateAsync method since .NET 10 SDK is the minimum requirement
  • DOTNET_DEV_CERTS_OPENSSL_CERTIFICATE_DIRECTORY support: The dev-certs trust path now respects the DOTNET_DEV_CERTS_OPENSSL_CERTIFICATE_DIRECTORY environment variable override
  • OpenSSL integration: Uses openssl version -d to dynamically query the OpenSSL certificates directory (same approach as ASP.NET Core's UnixCertificateManager)
  • aspire doctor integration: Added SSL_CERT_DIR configuration check to aspire doctor command to warn users about partial trust issues on Linux
// CertificateService now returns environment variables to apply
var certResult = await _certificateService.EnsureCertificatesTrustedAsync(_runner, ct);
foreach (var kvp in certResult.EnvironmentVariables)
{
    env[kvp.Key] = kvp.Value;  // e.g., SSL_CERT_DIR
}

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?
Original prompt

This section details on the original issue you should resolve

<issue_title>Improve Aspire CLI dev cert check/trust process</issue_title>
<issue_description>When aspire run is executed, the Aspire CLI currently checks the status of the dev-cert by shelling out to dotnet dev-certs https --trust --check and inspecting the exit code. If it's non-zero, it assumes the check failed and shells to dotnet dev-certs --trust to perform the certificate trust (and creation if necessary).

This has some issues, particularly on Linux, where the dev-certs trust check will always fail if the SSL_CERT_DIR environment is not set, but the trust operation will succeed whether it's set or not. This leads to a situation where aspire run tries to trust the cert, thinks that's trusted because the trust operation "succeeds", but a subsequent trust check still fails, meaning the check and trust are performed on every single run, until the SSL_CERT_DIR environment variable is manually set to the correct value.

We're making a change in dotnet dev-certs to improve the messages shown by default when the trust operation results in the cert only being "partially" trusted which is an improvement, but we can do more in aspire run to ensure the SSL_CERT_DIR is properly set for the launching AppHost project.

The CLI can be changed to shell to dotnet dev-certs https --check-trust-machine-readable (available since .NET 10 SDK) which gives a JSON blob with details of all dev-certs found:

dotnet dev-certs https --check-trust-machine-readable
[
  {
    "Thumbprint": "027C7563CA3D34B0D9164311DC122991D2BB4128",
    "Subject": "CN=localhost",
    "X509SubjectAlternativeNameExtension": [
      "localhost",
      "*.dev.localhost",
      "*.dev.internal",
      "host.docker.internal",
      "host.containers.internal"
    ],
    "Version": 5,
    "ValidityNotBefore": "2025-12-17T16:07:07-08:00",
    "ValidityNotAfter": "2026-12-17T16:07:07-08:00",
    "IsHttpsDevelopmentCertificate": true,
    "IsExportable": true,
    "TrustLevel": "Partial"
  }
]

The "TrustLevel": "Partial" there indicates that the certificate isn't fully trusted due to SSL_CERT_DIR not being configured. If the highest versioned valid certificate in the list is fully trusted, then run can continue. If it's not trusted at all, then aspire run should run dotnet dev-certs https --trust. If it's partially trusted (or still partially trusted after the trust operation was run) then aspire run should attempt to set/update SSL_CERT_DIR so that it includes the ~/.aspnet/dev-certs/trust folder if not already included. This way, the AppHost should launch with a fully trusted dev-cert even if the user hasn't manually updated their profile to set SSL_CERT_DIR correctly.</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI changed the title [WIP] Improve Aspire CLI dev cert check and trust process Improve Aspire CLI dev cert check/trust process for partial trust on Linux Jan 15, 2026
Copilot AI requested a review from DamianEdwards January 15, 2026 00:40
@github-actions
Copy link
Contributor

github-actions bot commented Jan 15, 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/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 13917

Or

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

@mitchdenny
Copy link
Member

mitchdenny commented Jan 15, 2026

Potential gap with OpenSUSE support

The current implementation only handles certificate directories (/etc/ssl/certs, /etc/pki/tls/certs), but OpenSUSE uses a certificate bundle file instead:

Distribution Certificate Location Type Covered?
Ubuntu/Debian /etc/ssl/certs Directory ✅ Yes
Fedora/RHEL/CentOS /etc/pki/tls/certs Directory ✅ Yes
OpenSUSE /etc/ssl/ca-bundle.pem File ❌ No

The Directory.Exists() checks prevent breakage on OpenSUSE, but the result would only include ~/.aspnet/dev-certs/trust without system CAs, potentially causing trust issues for other certificates.

Suggestions:

  1. Consider also setting SSL_CERT_FILE for distros that use bundle files, or
  2. Query OpenSSL for its compiled-in defaults (openssl version -d), or
  3. Document which distros are fully supported for this feature

This may be acceptable for an initial implementation with a follow-up issue to expand distro coverage.

@mitchdenny
Copy link
Member

E2E Testing Approach

Here's a prompt that could be used to create a CLI E2E test scenario to validate this feature:


Dev Cert Partial Trust on Linux

Overview

Validate that when running on Linux with a partially trusted dev certificate, the Aspire CLI automatically configures SSL_CERT_DIR so the AppHost can use HTTPS without manual environment configuration.

Prerequisites

  • Linux environment (Ubuntu)
  • .NET 10 SDK (for --check-trust-machine-readable)

Test Steps

  1. Clear SSL_CERT_DIR to simulate a fresh environment:

    unset SSL_CERT_DIR
  2. Reset dev certs to ensure a clean state:

    dotnet dev-certs https --clean
    dotnet dev-certs https --trust
  3. Verify partial trust state:

    dotnet dev-certs https --check-trust-machine-readable

    Expected: "TrustLevel": "Partial"

  4. Create and run an Aspire app:

    aspire new --template aspire-starter --name PartialTrustTest
    cd PartialTrustTest
  5. Modify AppHost to log SSL_CERT_DIR - Add before builder.Build():

    Console.WriteLine($"SSL_CERT_DIR: {Environment.GetEnvironmentVariable("SSL_CERT_DIR") ?? "not set"}");
  6. Run and verify:

    aspire run

Success Criteria

  • SSL_CERT_DIR is logged with a value including both system certs (/etc/ssl/certs) and ~/.aspnet/dev-certs/trust
  • ✅ Dashboard loads over HTTPS without certificate errors
  • ✅ Web frontend endpoints work without SSL warnings

Failure Criteria

  • SSL_CERT_DIR is not set or missing dev-certs path
  • ❌ HTTPS endpoints fail with certificate errors

This test is Linux-specific and requires a controlled environment where SSL_CERT_DIR can be unset. It could be added as a test scenario in tests/agent-scenarios/ for validation via /test-scenario on Linux runners.

@davidfowl
Copy link
Member

Fix aspire doctor as well.

@DamianEdwards
Copy link
Member

@copilot make sure the aspire doctor command is updated to properly detect the certificate status based on these changes too.

Copy link
Contributor Author

Copilot AI commented Jan 16, 2026

Fix aspire doctor as well.

Added SSL_CERT_DIR configuration check to aspire doctor in 55756e0. On Linux, it now checks if SSL_CERT_DIR is configured properly to include the dev-certs trust path and shows a warning if not. The warning explains that aspire run will configure this automatically, but users can also set it manually in their shell profile.

Copy link
Contributor Author

Copilot AI commented Jan 16, 2026

@copilot make sure the aspire doctor command is updated to properly detect the certificate status based on these changes too.

Already addressed in 55756e0. The aspire doctor command now includes an SSL_CERT_DIR configuration check on Linux that:

  1. Checks if the environment variable is set
  2. Verifies it includes the dev-certs trust path
  3. Shows a warning with fix instructions if misconfigured

The check respects the DOTNET_DEV_CERTS_OPENSSL_CERTIFICATE_DIRECTORY override and appears in the "environment" category of the doctor output.

Copilot AI and others added 3 commits January 20, 2026 10:44
…dling on Linux

Co-authored-by: DamianEdwards <249088+DamianEdwards@users.noreply.github.com>
…d minimum

Co-authored-by: DamianEdwards <249088+DamianEdwards@users.noreply.github.com>
Copilot AI and others added 3 commits January 20, 2026 10:44
…v-certs trust path

Co-authored-by: DamianEdwards <249088+DamianEdwards@users.noreply.github.com>
…R check to aspire doctor

Co-authored-by: DamianEdwards <249088+DamianEdwards@users.noreply.github.com>
- Add CertificateTrustLevel enum (None, Partial, Full)
- Replace IsCertificateTrusted() with GetCertificateTrustLevel()
- Show 'partially trusted' warning when SSL_CERT_DIR not configured
- Integrate SSL_CERT_DIR check into main trust level logic
- Provide actionable fix instructions for users
@DamianEdwards DamianEdwards force-pushed the copilot/improve-dev-cert-check-process branch from a3773bb to 9ec2257 Compare January 20, 2026 18:45
- Add DoctorCommandTests.cs with two Hex1b-based E2E tests:
  - DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted
  - DoctorCommand_WithSslCertDir_ShowsTrusted
- Add ClearSslCertDir() and ConfigureSslCertDir() helper methods
@DamianEdwards DamianEdwards marked this pull request as ready for review January 20, 2026 19:54
Copilot AI review requested due to automatic review settings January 20, 2026 19:54
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 improves the Aspire CLI's development certificate trust checking on Linux by utilizing the new machine-readable output from dotnet dev-certs (available in .NET 10 SDK) and automatically configuring SSL_CERT_DIR when certificates are only partially trusted.

Changes:

  • Introduced machine-readable certificate trust checking via CheckHttpCertificateMachineReadableAsync that parses JSON output containing trust level information (Full/Partial/None)
  • Automatically configures SSL_CERT_DIR environment variable on Linux when certificates are partially trusted, including OpenSSL integration to dynamically query system certificate directories
  • Updated certificate service to return environment variables that get passed through to AppHost processes, eliminating the need for manual SSL_CERT_DIR configuration
  • Enhanced aspire doctor command to detect and warn about partial trust scenarios on Linux
  • Removed legacy CheckHttpCertificateAsync method since .NET 10 SDK is now the minimum requirement

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/Aspire.Cli/Certificates/CertificateService.cs Implements new machine-readable trust checking logic, automatic SSL_CERT_DIR configuration, and OpenSSL integration
src/Aspire.Cli/Certificates/DevCertInfo.cs Defines data models for certificate trust information from machine-readable output
src/Aspire.Cli/DotNet/DotNetCliRunner.cs Adds CheckHttpCertificateMachineReadableAsync method that parses JSON output from dev-certs
src/Aspire.Cli/Projects/DotNetAppHostProject.cs Applies certificate environment variables when launching AppHost
src/Aspire.Cli/Projects/GuestAppHostProject.cs Applies certificate environment variables when launching guest AppHost
src/Aspire.Cli/Utils/EnvironmentChecker/DevCertsCheck.cs Adds partial trust detection and SSL_CERT_DIR configuration warnings
src/Aspire.Cli/Commands/InitCommand.cs Updates to use new certificate service signature
src/Aspire.Cli/Templating/DotNetTemplateFactory.cs Updates to use new certificate service signature
src/Aspire.Cli/JsonSourceGenerationContext.cs Adds JSON serialization support for certificate info list
tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs Comprehensive unit tests for all trust scenarios (full, partial, none)
tests/Aspire.Cli.Tests/TestServices/TestDotNetCliRunner.cs Updates test service to support new machine-readable check
tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs Removes obsolete certificate check callback setup
tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs Updates mock certificate service
tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs Updates test implementations for new signatures
tests/Aspire.Cli.EndToEnd.Tests/DoctorCommandTests.cs New E2E tests validating partial trust detection in aspire doctor
tests/Aspire.Cli.EndToEnd.Tests/Helpers/CliE2ETestHelpers.cs Helper methods for SSL_CERT_DIR manipulation in E2E tests
.github/workflows/cli-e2e-recording-comment.yml Adds DoctorCommandTests to recording artifact collection

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +41 to +60
/// <summary>
/// Represents the trust level of a development certificate.
/// </summary>
internal static class DevCertTrustLevel
{
/// <summary>
/// The certificate is fully trusted by all trust stores.
/// </summary>
public const string Full = "Full";

/// <summary>
/// The certificate is partially trusted (e.g., SSL_CERT_DIR not configured on Linux).
/// </summary>
public const string Partial = "Partial";

/// <summary>
/// The certificate is not trusted.
/// </summary>
public const string None = "None";
}
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for these internal constants is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only. These constants should have minimal documentation like "Full trust level constant" rather than full sentences.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +44 to +51
/// <summary>
/// Gets the dev-certs trust path, respecting the DOTNET_DEV_CERTS_OPENSSL_CERTIFICATE_DIRECTORY override.
/// </summary>
private static string GetDevCertsTrustPath()
{
var overridePath = Environment.GetEnvironmentVariable(DevCertsOpenSslCertDirEnvVar);
return !string.IsNullOrEmpty(overridePath) ? overridePath : s_defaultDevCertsTrustPath;
}
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for this internal method is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only. The detailed explanation about respecting environment variable overrides should be simplified or removed.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +198 to +204
/// <summary>
/// Attempts to get the OpenSSL certificates directory by running 'openssl version -d'.
/// This is the same approach used by ASP.NET Core's certificate manager.
/// </summary>
/// <param name="certsDir">The path to the OpenSSL certificates directory if found.</param>
/// <returns>True if the OpenSSL certs directory was found, false otherwise.</returns>
private static bool TryGetOpenSslCertsDirectory([NotNullWhen(true)] out string? certsDir)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for this internal method is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only. The detailed explanation about ASP.NET Core's approach should be simplified to just describe what the method does.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +257 to +266
/// <summary>
/// Gets the dev-certs trust path, respecting the DOTNET_DEV_CERTS_OPENSSL_CERTIFICATE_DIRECTORY override.
/// </summary>
private static string GetDevCertsTrustPath()
{
var overridePath = Environment.GetEnvironmentVariable(DevCertsOpenSslCertDirEnvVar);
return !string.IsNullOrEmpty(overridePath)
? overridePath
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".aspnet", "dev-certs", "trust");
}
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for this internal method is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only. The detailed explanation about respecting the environment variable override should be simplified.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +268 to +271
/// <summary>
/// Checks if SSL_CERT_DIR is configured to include the dev-certs trust path.
/// </summary>
private static bool IsSslCertDirConfigured()
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for this internal method is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only. The detailed explanation about what SSL_CERT_DIR does should be simplified.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +8 to +11
/// <summary>
/// Represents information about a development certificate from `dotnet dev-certs https --check-trust-machine-readable`.
/// </summary>
internal sealed class DevCertInfo
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for these internal classes and members is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only, without verbose remarks or examples. These are internal types used only by maintainers, not public consumption.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +62 to +65
/// <summary>
/// The result of checking certificate trust status using machine-readable output.
/// </summary>
internal sealed class CertificateTrustResult
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for this internal class is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only. The detailed explanation of what the result contains should be simplified or removed since this is an internal type.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +17 to +27
/// <summary>
/// The result of ensuring certificates are trusted.
/// </summary>
internal sealed class EnsureCertificatesTrustedResult
{
/// <summary>
/// Gets the environment variables that should be set for the AppHost process
/// to ensure certificates are properly trusted.
/// </summary>
public required IDictionary<string, string> EnvironmentVariables { get; init; }
}
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for this internal class is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only. The detailed explanation of environment variables and their purpose should be simplified since this is an internal type.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +12 to +23
/// <summary>
/// Represents the trust level of a certificate.
/// </summary>
internal enum CertificateTrustLevel
{
/// <summary>Certificate is not in any trusted store.</summary>
None,
/// <summary>Certificate is in a trusted store but SSL_CERT_DIR is not configured (Linux only).</summary>
Partial,
/// <summary>Certificate is fully trusted.</summary>
Full
}
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for this internal enum is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only. The detailed explanations for each enum value should be simplified to concise descriptions.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines 192 to +234
/// <summary>
/// Checks if a certificate is trusted by the system.
/// Gets the trust level of a certificate.
/// </summary>
private bool IsCertificateTrusted(X509Certificate2 certificate)
private CertificateTrustLevel GetCertificateTrustLevel(X509Certificate2 certificate)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

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

The documentation for this internal method is too verbose. According to the coding guidelines, internal APIs should have brief summary tags only. The detailed explanation about the trust level logic should be simplified to just describe what the method does.

Copilot generated this review using guidance from repository custom instructions.
@DamianEdwards
Copy link
Member

@copilot rebase the branch to resolve the conflicts

Copy link
Contributor Author

Copilot AI commented Jan 21, 2026

@copilot rebase the branch to resolve the conflicts

I'm unable to rebase the branch due to environment limitations - I cannot use git rebase to change commit history as force push is not available, and I cannot pull branches from GitHub. You'll need to rebase this branch manually to resolve the merge conflicts.

@DamianEdwards DamianEdwards enabled auto-merge (squash) January 21, 2026 23:02
@DamianEdwards DamianEdwards merged commit 1580ce1 into main Jan 21, 2026
306 checks passed
@DamianEdwards DamianEdwards deleted the copilot/improve-dev-cert-check-process branch January 21, 2026 23:25
@github-actions
Copy link
Contributor

🎬 CLI E2E Test Recordings

The following terminal recordings are available for commit 1982ca5:

Test Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateEmptyAppHostProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording

📹 Recordings uploaded automatically from CI run #21228843148

@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Jan 21, 2026
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.

Improve Aspire CLI dev cert check/trust process

5 participants