Skip to content
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

Improvements to RunWithHttpsDevCert extensions #667

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
772bf41
Use Directory.CreateTempSubDirectory instead of Path.GetTempDir
DamianEdwards Jan 28, 2025
61e8e77
Update DevCertHostingExtensions.cs
DamianEdwards Jan 28, 2025
88d1363
Put dev-cert in /obj (or user temp if not available)
DamianEdwards Jan 29, 2025
21a3172
Update DevCertHostingExtensions.cs
DamianEdwards Jan 29, 2025
b31c3e0
Fix typo
DamianEdwards Jan 29, 2025
f6f0c1b
WIP
DamianEdwards Jan 30, 2025
898321e
More WIP
DamianEdwards Jan 30, 2025
f7d0f93
WIPpy
DamianEdwards Jan 30, 2025
0e30ab5
Working now
DamianEdwards Jan 30, 2025
bc59278
Update Metrics sample
DamianEdwards Jan 30, 2025
78eacd0
Fix
DamianEdwards Jan 30, 2025
fa88fca
Run Redis with the dev cert in the Node sample
DamianEdwards Jan 31, 2025
6f2bda3
Update RedisHostingExtensions.cs
DamianEdwards Jan 31, 2025
c692ad4
Update Directory.Build.targets
DamianEdwards Jan 31, 2025
c14cacb
Don't use cert with Redis for now
DamianEdwards Feb 1, 2025
25feb3c
Insert resource logger at 0
DamianEdwards Feb 1, 2025
e23cec6
Enable DCP logs
DamianEdwards Feb 1, 2025
cbd2813
Update ci.yml
DamianEdwards Feb 1, 2025
fc132fe
Update ci.yml
DamianEdwards Feb 1, 2025
29a126d
Update ci.yml
DamianEdwards Feb 1, 2025
ef10a2c
pls give me logs!
DamianEdwards Feb 1, 2025
90a1322
Fix double slashes
DamianEdwards Feb 1, 2025
b53e2ec
Update ci.yml
DamianEdwards Feb 1, 2025
b47d9c8
moar logs!
DamianEdwards Feb 1, 2025
995a77e
grrrrr
DamianEdwards Feb 1, 2025
bf36d18
quote container args
DamianEdwards Feb 1, 2025
e328cff
Revert "quote container args"
DamianEdwards Feb 1, 2025
c35823c
Update all to latest 9.1 SDK
DamianEdwards Feb 1, 2025
33fa711
Revert "Update all to latest 9.1 SDK"
DamianEdwards Feb 2, 2025
dfc257b
Don't bind mount the dev-cert folder to see if container starts
DamianEdwards Feb 3, 2025
073f519
Add bind mount back but don't pass Redis TLS args
DamianEdwards Feb 3, 2025
974bc2d
Use SetUnixFileMode
DamianEdwards Feb 4, 2025
15376bd
Update DevCertHostingExtensions.cs
DamianEdwards Feb 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions samples/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project>
Copy link
Member

Choose a reason for hiding this comment

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

It wouldn't hurt to stick a comment in here in case someone stumbles in and asks what this is for.

<Target Name="EmbedAppHostIntermediateOutputPath" BeforeTargets="GetAssemblyAttributes">
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>apphostprojectbaseintermediateoutputpath</_Parameter1>
<_Parameter2>$(BaseIntermediateOutputPath)</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
</Target>
</Project>
70 changes: 60 additions & 10 deletions samples/Shared/DevCertHostingExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -73,13 +74,10 @@ public static IResourceBuilder<TResource> RunWithHttpsDevCertificate<TResource>(

private static async Task<(bool, string CertFilePath, string CertKeyFilPath)> TryExportDevCertificateAsync(IDistributedApplicationBuilder builder, ILogger logger)
{
// Exports the ASP.NET Core HTTPS development certificate & private key to PEM files using 'dotnet dev-certs https' to a temporary
// directory and returns the path.
// TODO: Check if we're running on a platform that already has the cert and key exported to a file (e.g. macOS) and just use those instead.
var appNameHash = builder.Configuration["AppHost:Sha256"]![..10];
var tempDir = Path.Combine(Path.GetTempPath(), $"aspire.{appNameHash}");
var certExportPath = Path.Combine(tempDir, "dev-cert.pem");
var certKeyExportPath = Path.Combine(tempDir, "dev-cert.key");
// Exports the ASP.NET Core HTTPS development certificate & private key to PEM files using 'dotnet dev-certs https' to a directory and returns the path.
var certDir = GetOrCreateAppHostCertDirectory(builder);
var certExportPath = Path.Combine(certDir, "dev-cert.pem");
var certKeyExportPath = Path.Combine(certDir, "dev-cert.key");

if (File.Exists(certExportPath) && File.Exists(certKeyExportPath))
{
Expand All @@ -100,10 +98,10 @@ public static IResourceBuilder<TResource> RunWithHttpsDevCertificate<TResource>(
File.Delete(certKeyExportPath);
}

if (!Directory.Exists(tempDir))
if (!Directory.Exists(certDir))
{
logger.LogTrace("Creating directory to export dev cert to '{ExportDir}'", tempDir);
Directory.CreateDirectory(tempDir);
logger.LogTrace("Creating directory to export dev cert to '{ExportDir}'", certDir);
Directory.CreateDirectory(certDir);
}

string[] args = ["dev-certs", "https", "--export-path", $"\"{certExportPath}\"", "--format", "Pem", "--no-password"];
Expand Down Expand Up @@ -182,4 +180,56 @@ static async Task ConsumeOutput(TextReader reader, Action<string> callback)
}
}
}

private static string GetOrCreateAppHostCertDirectory(IDistributedApplicationBuilder builder)
{
// TODO: Check if we're running on a platform that already has the cert and key exported to a file (e.g. macOS) and just use those instead.
// macOS: Path.Combine(
// Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".aspnet", "dev-certs", "https"),
// "aspnetcore-localhost-{certificate.Thumbprint}.pfx");
// linux: CurrentUser/Root store

// Create a directory in the project's /obj dir or TMP to store the exported certificate and key
var assemblyMetadata = builder.AppHostAssembly?.GetCustomAttributes<AssemblyMetadataAttribute>();
var projectDir = GetMetadataValue(assemblyMetadata, "AppHostProjectPath");
var objDir = GetMetadataValue(assemblyMetadata, "AppHostProjectBaseIntermediateOutputPath");
Comment on lines +283 to +284
Copy link
Member

Choose a reason for hiding this comment

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

I half wonder if we should just stamp the full path into the file during the build and then we just need to read the 1 value out and use it.

var dirPath = projectDir is not null && objDir is not null
DamianEdwards marked this conversation as resolved.
Show resolved Hide resolved
? Path.Combine(projectDir, objDir, "aspire")
: Directory.CreateTempSubdirectory(GetAppHostSpecificTempDirPrefix(builder)).FullName;
Copy link
Member

Choose a reason for hiding this comment

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

Should we even mess with the temp directory stuff if the OutputPath isn't found in the assembly? Can we just throw and make the code simpler?


if (!Directory.Exists(dirPath))
DamianEdwards marked this conversation as resolved.
Show resolved Hide resolved
{
// Create the directory
Directory.CreateDirectory(dirPath);
}

return dirPath;
}

private static string? GetMetadataValue(IEnumerable<AssemblyMetadataAttribute>? assemblyMetadata, string key) =>
assemblyMetadata?.FirstOrDefault(a => string.Equals(a.Key, key, StringComparison.OrdinalIgnoreCase))?.Value;

private static string GetAppHostSpecificTempDirPrefix(IDistributedApplicationBuilder builder)
{
var appName = Sanitize(builder.Environment.ApplicationName).ToLowerInvariant();
var appNameHash = builder.Configuration["AppHost:Sha256"]![..10].ToLowerInvariant();
return $"aspire.{appName}.{appNameHash}";
}

private static readonly char[] _invalidPathChars = Path.GetInvalidPathChars();
DamianEdwards marked this conversation as resolved.
Show resolved Hide resolved

private static string Sanitize(string name)
Copy link
Member

Choose a reason for hiding this comment

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

assuming this is not hot and you're already using Linq here how about making this a one liner

return new string(name.Select(c => Path.GetInvalidPathChars().Contains(c) ? '_' : c).ToArray());

Copy link
Member Author

Choose a reason for hiding this comment

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

It coulde be LINQ, but this is derived from the existing method we have in Aspire.Hosting for volume name sanitization, so I'm inclined to just keep it this way for now.

{
return string.Create(name.Length, name, static (s, name) =>
{
var nameSpan = name.AsSpan();
DamianEdwards marked this conversation as resolved.
Show resolved Hide resolved

for (var i = 0; i < nameSpan.Length; i++)
{
var c = nameSpan[i];

s[i] = Array.IndexOf(_invalidPathChars, c) == -1 ? c : '_';
DamianEdwards marked this conversation as resolved.
Show resolved Hide resolved
}
});
}
}