Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 14 additions & 9 deletions src/Aspire.Hosting/Dcp/DcpExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2384,9 +2384,9 @@ await modelResource.ProcessContainerRuntimeArgValues(

if (modelResource.TryGetLastAnnotation<ContainerCertificatePathsAnnotation>(out var pathsAnnotation))
{
certificatesDestination ??= pathsAnnotation.CustomCertificatesDestination;
bundlePaths ??= pathsAnnotation.DefaultCertificateBundles;
certificateDirsPaths ??= pathsAnnotation.DefaultCertificateDirectories;
certificatesDestination = pathsAnnotation.CustomCertificatesDestination ?? certificatesDestination;
bundlePaths = pathsAnnotation.DefaultCertificateBundles ?? bundlePaths;
certificateDirsPaths = pathsAnnotation.DefaultCertificateDirectories ?? certificateDirsPaths;
}

bool failedToApplyConfig = false;
Expand Down Expand Up @@ -2482,18 +2482,23 @@ await modelResource.ProcessContainerRuntimeArgValues(
{
// If overriding the default resource CA bundle, then we want to copy our bundle to the well-known locations
// used by common Linux distributions to make it easier to ensure applications pick it up.
foreach (var bundlePath in bundlePaths!)
// Group by common directory to avoid creating multiple file system entries for the same root directory.
foreach (var bundlePath in bundlePaths!.Select(bp =>
{
var filename = Path.GetFileName(bp);
var dir = bp.Substring(0, bp.Length - filename.Length);
return (dir, filename);
Comment on lines +2488 to +2490
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to be more defensive here?

Copy link
Member

Choose a reason for hiding this comment

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

Only reason this would result in invalid directory or filename is if the user customized the paths and then it would be DCP that would actually return an error due to invalid create file arguments (missing filename or invalid path).

}).GroupBy(parts => parts.dir))
{
createFiles.Add(new ContainerCreateFileSystem
{
Destination = bundlePath,
Entries = [
Destination = bundlePath.Key,
Entries = bundlePath.Select(bp =>
new ContainerFileSystemEntry
{
Name = Path.GetFileName(bundlePath),
Name = bp.filename,
Contents = caBundleBuilder.ToString(),
},
],
}).ToList(),
});
}
}
Expand Down
148 changes: 134 additions & 14 deletions tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -627,16 +627,61 @@ public async Task VerifyContainerCreateFile()
[Theory]
[RequiresDocker]
[RequiresDevCert]
[InlineData(null, null, true)]
[InlineData(null, false, false)]
[InlineData(null, true, true)]
[InlineData(false, null, false)]
[InlineData(false, false, false)]
[InlineData(false, true, true)]
[InlineData(true, null, true)]
[InlineData(true, false, false)]
[InlineData(true, true, true)]
public async Task VerifyContainerIncludesExpectedDevCertificateConfiguration(bool? implicitTrust, bool? explicitTrust, bool expectDevCert)
[InlineData(null, null, true, false, CertificateTrustScope.Append)]
Copy link
Member

Choose a reason for hiding this comment

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

I hope you generated this. I would have used property data and a helper routine to generate all combinations.

Copy link
Member

Choose a reason for hiding this comment

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

This was very much some copilot generated test combos.

[InlineData(null, false, false, false, CertificateTrustScope.Append)]
[InlineData(null, true, true, false, CertificateTrustScope.Append)]
[InlineData(false, null, false, false, CertificateTrustScope.Append)]
[InlineData(false, false, false, false, CertificateTrustScope.Append)]
[InlineData(false, true, true, false, CertificateTrustScope.Append)]
[InlineData(true, null, true, false, CertificateTrustScope.Append)]
[InlineData(true, false, false, false, CertificateTrustScope.Append)]
[InlineData(true, true, true, false, CertificateTrustScope.Append)]
[InlineData(null, null, true, true, CertificateTrustScope.Append)]
[InlineData(null, false, false, true, CertificateTrustScope.Append)]
[InlineData(null, true, true, true, CertificateTrustScope.Append)]
[InlineData(false, null, false, true, CertificateTrustScope.Append)]
[InlineData(false, false, false, true, CertificateTrustScope.Append)]
[InlineData(false, true, true, true, CertificateTrustScope.Append)]
[InlineData(true, null, true, true, CertificateTrustScope.Append)]
[InlineData(true, false, false, true, CertificateTrustScope.Append)]
[InlineData(true, true, true, true, CertificateTrustScope.Append)]
[InlineData(null, null, true, false, CertificateTrustScope.Override)]
[InlineData(null, false, false, false, CertificateTrustScope.Override)]
[InlineData(null, true, true, false, CertificateTrustScope.Override)]
[InlineData(false, null, false, false, CertificateTrustScope.Override)]
[InlineData(false, false, false, false, CertificateTrustScope.Override)]
[InlineData(false, true, true, false, CertificateTrustScope.Override)]
[InlineData(true, null, true, false, CertificateTrustScope.Override)]
[InlineData(true, false, false, false, CertificateTrustScope.Override)]
[InlineData(true, true, true, false, CertificateTrustScope.Override)]
[InlineData(null, null, true, true, CertificateTrustScope.Override)]
[InlineData(null, false, false, true, CertificateTrustScope.Override)]
[InlineData(null, true, true, true, CertificateTrustScope.Override)]
[InlineData(false, null, false, true, CertificateTrustScope.Override)]
[InlineData(false, false, false, true, CertificateTrustScope.Override)]
[InlineData(false, true, true, true, CertificateTrustScope.Override)]
[InlineData(true, null, true, true, CertificateTrustScope.Override)]
[InlineData(true, false, false, true, CertificateTrustScope.Override)]
[InlineData(true, true, true, true, CertificateTrustScope.Override)]
[InlineData(null, null, false, false, CertificateTrustScope.None)]
[InlineData(null, false, false, false, CertificateTrustScope.None)]
[InlineData(null, true, false, false, CertificateTrustScope.None)]
[InlineData(false, null, false, false, CertificateTrustScope.None)]
[InlineData(false, false, false, false, CertificateTrustScope.None)]
[InlineData(false, true, false, false, CertificateTrustScope.None)]
[InlineData(true, null, false, false, CertificateTrustScope.None)]
[InlineData(true, false, false, false, CertificateTrustScope.None)]
[InlineData(true, true, false, false, CertificateTrustScope.None)]
[InlineData(null, null, false, true, CertificateTrustScope.None)]
[InlineData(null, false, false, true, CertificateTrustScope.None)]
[InlineData(null, true, false, true, CertificateTrustScope.None)]
[InlineData(false, null, false, true, CertificateTrustScope.None)]
[InlineData(false, false, false, true, CertificateTrustScope.None)]
[InlineData(false, true, false, true, CertificateTrustScope.None)]
[InlineData(true, null, false, true, CertificateTrustScope.None)]
[InlineData(true, false, false, true, CertificateTrustScope.None)]
[InlineData(true, true, false, true, CertificateTrustScope.None)]
public async Task VerifyContainerIncludesExpectedDevCertificateConfiguration(bool? implicitTrust, bool? explicitTrust, bool expectDevCert, bool overridePaths, CertificateTrustScope trustScope)
{
using var testProgram = CreateTestProgram("verify-container-dev-cert", trustDeveloperCertificate: implicitTrust);
SetupXUnitLogging(testProgram.AppBuilder.Services);
Expand All @@ -647,6 +692,27 @@ public async Task VerifyContainerIncludesExpectedDevCertificateConfiguration(boo
container.WithDeveloperCertificateTrust(explicitTrust.Value);
}

var expectedDestination = "/usr/lib/ssl/aspire";
var expectedDefaultCertificateDirs = new List<string>();
var expectedDefaultBundleFiles = new List<string>();
if (overridePaths)
{
expectedDestination = "/usr/lib/ssl/someotherpath";
expectedDefaultCertificateDirs.Add("/usr/lib/someothercertpath");
expectedDefaultCertificateDirs.Add("/usr/share/lib/anotherpath");
expectedDefaultBundleFiles.Add("/usr/lib/somessl/cert.pem");
expectedDefaultBundleFiles.Add("/usr/share/certfile.pem");

container.WithContainerCertificatePaths(customCertificatesDestination: expectedDestination, defaultCertificateBundlePaths: expectedDefaultBundleFiles, defaultCertificateDirectoryPaths: expectedDefaultCertificateDirs);
}
else
{
expectedDefaultCertificateDirs.AddRange(ContainerCertificatePathsAnnotation.DefaultCertificateDirectoriesPaths);
expectedDefaultBundleFiles.AddRange(ContainerCertificatePathsAnnotation.DefaultCertificateBundlePaths);
}

container.WithCertificateTrustScope(trustScope);

await using var app = testProgram.Build();

await app.StartAsync().DefaultTimeout(TestConstants.DefaultOrchestratorTestLongTimeout);
Expand All @@ -665,17 +731,40 @@ public async Task VerifyContainerIncludesExpectedDevCertificateConfiguration(boo
if (expectDevCert)
{
Assert.NotNull(item.Spec.Env);
Assert.Collection(item.Spec.Env.OrderBy(e => e.Name),
if (trustScope == CertificateTrustScope.Append)
{
Assert.DoesNotContain(item.Spec.Env, e => e.Name == "SSL_CERT_FILE");
}
else if (trustScope == CertificateTrustScope.Override)
{
Assert.Collection(item.Spec.Env.Where(e => e.Name == "SSL_CERT_FILE"),
certFile =>
{
Assert.Equal("SSL_CERT_FILE", certFile.Name);
Assert.Equal($"{expectedDestination}/cert.pem", certFile.Value);
});
}

Assert.Collection(item.Spec.Env.Where(e => e.Name == "SSL_CERT_DIR"),
certDir =>
{
Assert.Equal("SSL_CERT_DIR", certDir.Name);
Assert.StartsWith("/usr/lib/ssl/aspire/certs:", certDir.Value);
Assert.NotNull(certDir.Value);
var certDirPaths = certDir.Value.Split(':');
Assert.Contains($"{expectedDestination}/certs", certDirPaths);
if (trustScope == CertificateTrustScope.Append)
{
foreach (var expectedPath in expectedDefaultCertificateDirs)
{
Assert.Contains(expectedPath, certDirPaths);
}
}
});

Assert.NotNull(item.Spec.CreateFiles);
Assert.Collection(item.Spec.CreateFiles,
Assert.Collection(item.Spec.CreateFiles.Where(cf => cf.Destination == expectedDestination),
createCerts =>
{
Assert.Equal("/usr/lib/ssl/aspire", createCerts.Destination);
Assert.NotNull(createCerts.Entries);
Assert.Collection(createCerts.Entries,
bundle =>
Expand All @@ -702,6 +791,37 @@ public async Task VerifyContainerIncludesExpectedDevCertificateConfiguration(boo
}
});
});

if (trustScope == CertificateTrustScope.Override)
{
foreach (var bundlePath in expectedDefaultBundleFiles!.Select(bp =>
{
var filename = Path.GetFileName(bp);
var dir = bp.Substring(0, bp.Length - filename.Length);
return (dir, filename);
}).GroupBy(parts => parts.dir))
{
Assert.Collection(item.Spec.CreateFiles.Where(cf => cf.Destination == bundlePath.Key),
createCerts =>
{
Assert.NotNull(createCerts.Entries);
Assert.Equal(bundlePath.Count(), createCerts.Entries.Count);
foreach (var expectedFile in bundlePath)
{
Assert.Collection(createCerts.Entries.Where(file => file.Name == expectedFile.filename),
bundle =>
{
Assert.Equal(expectedFile.filename, bundle.Name);
Assert.Equal(ContainerFileSystemEntryType.File, bundle.Type);
var certs = new X509Certificate2Collection();
certs.ImportFromPem(bundle.Contents);
Assert.Equal(dc.Certificates.Count, certs.Count);
Assert.All(certs, (cert) => cert.IsAspNetCoreDevelopmentCertificate());
});
}
});
}
}
}
else
{
Expand Down