-
Notifications
You must be signed in to change notification settings - Fork 919
Add logging if we detect the app host is running with an untrusted dev cert #14666
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3c42e64
77d3818
be562bd
33f0d1e
7d95f07
b79f3cd
60fe2d2
9bf3372
1fb6076
1783692
0eb5f0c
85e6e21
30f474b
1009421
ca2f7e9
d2c166a
797e82e
4021dbc
b972a48
2f1f17c
c9c57f0
c45514a
6624ff3
a2d7ec2
80cd613
98d854b
e145d44
41e6906
8988e3f
0415f8a
6a951b3
12754bc
db607d3
1f08c4e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,12 +10,15 @@ | |
| using Aspire.Hosting.ApplicationModel; | ||
| using Aspire.Hosting.Dcp.Process; | ||
| using Aspire.Hosting.Resources; | ||
| using Microsoft.Extensions.Configuration; | ||
| using Microsoft.Extensions.Logging; | ||
| using Microsoft.Extensions.Options; | ||
|
|
||
| namespace Aspire.Hosting.Dcp; | ||
|
|
||
| #pragma warning disable ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
| #pragma warning disable ASPIRECERTIFICATES001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
| #pragma warning disable ASPIREFILESYSTEM001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
|
|
||
| internal sealed class DcpHost | ||
| { | ||
|
|
@@ -29,6 +32,9 @@ internal sealed class DcpHost | |
| private readonly IInteractionService _interactionService; | ||
| private readonly Locations _locations; | ||
| private readonly TimeProvider _timeProvider; | ||
| private readonly IDeveloperCertificateService _developerCertificateService; | ||
| private readonly IFileSystemService _fileSystemService; | ||
| private readonly IConfiguration _configuration; | ||
| private readonly CancellationTokenSource _shutdownCts = new(); | ||
| private Task? _logProcessorTask; | ||
|
|
||
|
|
@@ -48,7 +54,10 @@ public DcpHost( | |
| IInteractionService interactionService, | ||
| Locations locations, | ||
| DistributedApplicationModel applicationModel, | ||
| TimeProvider timeProvider) | ||
| TimeProvider timeProvider, | ||
| IDeveloperCertificateService developerCertificateService, | ||
| IFileSystemService fileSystemService, | ||
| IConfiguration configuration) | ||
| { | ||
| _loggerFactory = loggerFactory; | ||
| _logger = loggerFactory.CreateLogger<DcpHost>(); | ||
|
|
@@ -58,11 +67,15 @@ public DcpHost( | |
| _locations = locations; | ||
| _applicationModel = applicationModel; | ||
| _timeProvider = timeProvider; | ||
| _developerCertificateService = developerCertificateService; | ||
| _fileSystemService = fileSystemService; | ||
| _configuration = configuration; | ||
| } | ||
|
|
||
| public async Task StartAsync(CancellationToken cancellationToken) | ||
| { | ||
| await EnsureDcpContainerRuntimeAsync(cancellationToken).ConfigureAwait(false); | ||
| await EnsureDevelopmentCertificateTrustAsync(cancellationToken).ConfigureAwait(false); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why in the DCP host? Doesn't seem DCP related to me. What about doing it in an app host event? For example, Seb added detection for whether secrets are disable and you have persistent container in an app host event.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was purely because it's the place we're doing existing startup runtime dependency checks (the container health check also lives here). We may be able to decide on a better location, but for now this is a location of convenience. |
||
| EnsureDcpHostRunning(); | ||
| } | ||
|
|
||
|
|
@@ -122,6 +135,63 @@ internal async Task EnsureDcpContainerRuntimeAsync(CancellationToken cancellatio | |
| } | ||
| } | ||
|
|
||
| internal async Task EnsureDevelopmentCertificateTrustAsync(CancellationToken cancellationToken) | ||
| { | ||
| AspireEventSource.Instance.DevelopmentCertificateTrustCheckStart(); | ||
|
|
||
| try | ||
| { | ||
| // If no resources use HTTPS/TLS, there's no need to warn about untrusted dev certificates. | ||
| if (!_applicationModel.Resources.Any(ResourceUsesTls)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Check and warn if no trusted dev certs exist, or if a newer untrusted cert was detected | ||
| var hasNewerUntrustedCert = _developerCertificateService.LatestCertificateIsUntrusted; | ||
| var hasNoTrustedCerts = _developerCertificateService.Certificates.Count == 0; | ||
|
|
||
| if (hasNoTrustedCerts || hasNewerUntrustedCert) | ||
| { | ||
|
danegsta marked this conversation as resolved.
|
||
| string title; | ||
| string message; | ||
|
|
||
| if (hasNoTrustedCerts) | ||
| { | ||
| title = InteractionStrings.NoDeveloperCertificateTrustedTitle; | ||
| message = InteractionStrings.NoDeveloperCertificateTrustedMessage; | ||
| _logger.LogWarning("No trusted Aspire development certificate was found. See https://aka.ms/aspire/devcerts for more information."); | ||
| } | ||
| else | ||
| { | ||
| title = InteractionStrings.DeveloperCertificateNotFullyTrustedTitle; | ||
| message = InteractionStrings.DeveloperCertificateNotFullyTrustedMessage; | ||
| _logger.LogWarning("The most recent development certificate isn't fully trusted. See https://aka.ms/aspire/devcerts for more information."); | ||
| } | ||
|
|
||
| // Check if the interaction service is available (dashboard enabled) | ||
| if (!_interactionService.IsAvailable) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // Send notification to the dashboard | ||
| _ = _interactionService.PromptNotificationAsync( | ||
| title: title, | ||
| message: message, | ||
| options: new NotificationInteractionOptions | ||
| { | ||
| Intent = MessageIntent.Error, | ||
| }, | ||
| cancellationToken: cancellationToken); | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| AspireEventSource.Instance.DevelopmentCertificateTrustCheckStop(); | ||
| } | ||
| } | ||
|
|
||
| public async Task StopAsync() | ||
| { | ||
| _shutdownCts.Cancel(); | ||
|
|
@@ -474,6 +544,38 @@ private static bool IsContainerRuntimeHealthy(DcpInfo dcpInfo) | |
| var running = dcpInfo.Containers?.Running ?? false; | ||
| return installed && running; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines whether a resource uses HTTPS/TLS by checking for HTTPS endpoint annotations | ||
| /// or active HTTPS certificate configuration callbacks that haven't been disabled. | ||
| /// </summary> | ||
| private static bool ResourceUsesTls(IResource resource) | ||
| { | ||
| // Check if the resource has any HTTPS endpoints | ||
| if (resource.Annotations.OfType<EndpointAnnotation>().Any(e => e.UriScheme is "https")) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| // Check if the resource has an HTTPS certificate configuration callback that hasn't been | ||
| // disabled via WithoutHttpsCertificate(). HttpsCertificateAnnotation has no effect without | ||
| // HttpsCertificateConfigurationCallbackAnnotation, so it's only checked as a filter here. | ||
| if (resource.Annotations.OfType<HttpsCertificateConfigurationCallbackAnnotation>().Any()) | ||
| { | ||
| // The callback is present. Check if it's been disabled by WithoutHttpsCertificate() | ||
| // which sets UseDeveloperCertificate = false and Certificate = null. | ||
| if (resource.TryGetLastAnnotation<HttpsCertificateAnnotation>(out var certAnnotation) | ||
| && certAnnotation.UseDeveloperCertificate is false or null | ||
| && certAnnotation.Certificate is null) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
|
|
||
| #pragma warning restore ASPIREINTERACTION001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. | ||
Uh oh!
There was an error while loading. Please reload this page.