Skip to content

Commit fa71340

Browse files
danegstaradical
authored andcommitted
Rework certificate environment and argument config to be more idiomatic (#12358)
* Rework certificate environment and argument config to be more idiomatic * Don't double apply SSL_CERT_DIR * Add extra examples * Respond to PR feedback * Switch to factories for certificate path value providers * Ensure service is available * Fix logging tests * Fix another logging test case
1 parent 296b1a7 commit fa71340

13 files changed

+478
-577
lines changed

src/Aspire.Hosting.NodeJs/NodeExtensions.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,15 @@ public static IResourceBuilder<NodeAppResource> AddNpmApp(this IDistributedAppli
8383
private static IResourceBuilder<TResource> WithNodeDefaults<TResource>(this IResourceBuilder<TResource> builder) where TResource : NodeAppResource =>
8484
builder.WithOtlpExporter()
8585
.WithEnvironment("NODE_ENV", builder.ApplicationBuilder.Environment.IsDevelopment() ? "development" : "production")
86-
.WithExecutableCertificateTrustCallback((ctx) =>
86+
.WithCertificateTrustConfiguration((ctx) =>
8787
{
8888
if (ctx.Scope == CertificateTrustScope.Append)
8989
{
90-
ctx.CertificateBundleEnvironment.Add("NODE_EXTRA_CA_CERTS");
90+
ctx.EnvironmentVariables["NODE_EXTRA_CA_CERTS"] = ctx.CertificateBundlePath;
9191
}
9292
else
9393
{
94-
ctx.CertificateTrustArguments.Add("--use-openssl-ca");
95-
ctx.CertificateBundleEnvironment.Add("SSL_CERT_FILE");
94+
ctx.Arguments.Add("--use-openssl-ca");
9695
}
9796

9897
return Task.CompletedTask;

src/Aspire.Hosting.Python/PythonAppResourceBuilderExtensions.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ private static IResourceBuilder<PythonAppResource> AddPythonAppCore(
330330
// way to simply append additional certificates to default Python trust stores such as certifi.
331331
resourceBuilder
332332
.WithCertificateTrustScope(CertificateTrustScope.System)
333-
.WithExecutableCertificateTrustCallback(ctx =>
333+
.WithCertificateTrustConfiguration(ctx =>
334334
{
335335
if (ctx.Scope == CertificateTrustScope.Append)
336336
{
@@ -342,21 +342,16 @@ private static IResourceBuilder<PythonAppResource> AddPythonAppCore(
342342
{
343343
// Override default certificates path for the requests module.
344344
// See: https://docs.python-requests.org/en/latest/user/advanced/#ssl-cert-verification
345-
ctx.CertificateBundleEnvironment.Add("REQUESTS_CA_BUNDLE");
345+
ctx.EnvironmentVariables["REQUESTS_CA_BUNDLE"] = ctx.CertificateBundlePath;
346346

347347
// Requests also supports CURL_CA_BUNDLE as an alternative config (lower priority than REQUESTS_CA_BUNDLE).
348348
// Setting it to be as complete as possible and avoid potential issues with conflicting configurations.
349-
ctx.CertificateBundleEnvironment.Add("CURL_CA_BUNDLE");
350-
351-
// Override default certificates path for Python modules that honor OpenSSL style paths.
352-
// This has been tested with urllib, urllib3, httpx, and aiohttp.
353-
// See: https://docs.openssl.org/3.0/man3/SSL_CTX_load_verify_locations/#description
354-
ctx.CertificateBundleEnvironment.Add("SSL_CERT_FILE");
349+
ctx.EnvironmentVariables["CURL_CA_BUNDLE"] = ctx.CertificateBundlePath;
355350
}
356351

357352
// Override default opentelemetry-python certificate bundle path
358353
// See: https://opentelemetry-python.readthedocs.io/en/latest/exporter/otlp/otlp.html#module-opentelemetry.exporter.otlp
359-
ctx.CertificateBundleEnvironment.Add("OTEL_EXPORTER_OTLP_CERTIFICATE");
354+
ctx.EnvironmentVariables["OTEL_EXPORTER_OTLP_CERTIFICATE"] = ctx.CertificateBundlePath;
360355

361356
return Task.CompletedTask;
362357
});
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Hosting.ApplicationModel;
5+
6+
/// <summary>
7+
/// An annotation that indicates a resource wants to manage how custom certificate trust is configured.
8+
/// </summary>
9+
/// <param name="callback">The callback used to customize certificate trust for the resource.</param>
10+
public sealed class CertificateTrustConfigurationCallbackAnnotation(Func<CertificateTrustConfigurationCallbackAnnotationContext, Task> callback) : IResourceAnnotation
11+
{
12+
/// <summary>
13+
/// Gets the callback to invoke to populate or modify the certificate authority collection.
14+
/// </summary>
15+
public Func<CertificateTrustConfigurationCallbackAnnotationContext, Task> Callback { get; } = callback ?? throw new ArgumentNullException(nameof(callback));
16+
}
17+
18+
/// <summary>
19+
/// Context provided to a <see cref="CertificateTrustConfigurationCallbackAnnotation"/> callback.
20+
/// </summary>
21+
public sealed class CertificateTrustConfigurationCallbackAnnotationContext
22+
{
23+
/// <summary>
24+
/// Gets the <see cref="DistributedApplicationExecutionContext"/> for this session.
25+
/// </summary>
26+
public required DistributedApplicationExecutionContext ExecutionContext { get; init; }
27+
28+
/// <summary>
29+
/// Gets the resource to which the annotation is applied.
30+
/// </summary>
31+
public required IResource Resource { get; init; }
32+
33+
/// <summary>
34+
/// Gets the command line arguments associated with the callback context. Values can be either a string or a path
35+
/// value provider such as <see cref="CertificateBundlePath"/> or <see cref="CertificateDirectoriesPath"/>.
36+
/// </summary>
37+
/// <remarks>
38+
/// <example>
39+
/// <code language="csharp">
40+
/// builder.AddContainer("my-resource", "my-image:latest")
41+
/// .WithCertificateTrustConfigurationCallback(ctx =>
42+
/// {
43+
/// ctx.Arguments.Add("--use-system-ca");
44+
/// return Task.CompletedTask;
45+
/// });
46+
/// </code>
47+
/// </example>
48+
/// </remarks>
49+
public required List<object> Arguments { get; init; }
50+
51+
/// <summary>
52+
/// Gets the environment variables required to configure certificate trust for the resource.
53+
/// The dictionary key is the environment variable name; the value can be either a string or a path
54+
/// value provider such as <see cref="CertificateBundlePath"/> or <see cref="CertificateDirectoriesPath"/>.
55+
/// By default the environment will always include an entry for `SSL_CERT_DIR` and may include `SSL_CERT_FILE` if
56+
/// <see cref="CertificateTrustScope.Override"/> or <see cref="CertificateTrustScope.System"/> is configured.
57+
/// </summary>
58+
/// <remarks>
59+
/// <example>
60+
/// <code language="csharp">
61+
/// builder.AddContainer("my-resource", "my-image:latest")
62+
/// .WithCertificateTrustConfigurationCallback(ctx =>
63+
/// {
64+
/// ctx.EnvironmentVariables["MY_CUSTOM_CERT_VAR"] = ctx.CertificateBundlePath;
65+
/// ctx.EnvironmentVariables["CERTS_DIR"] = ctx.CertificateDirectoriesPath;
66+
/// return Task.CompletedTask;
67+
/// });
68+
/// </code>
69+
/// </example>
70+
/// </remarks>
71+
public required Dictionary<string, object> EnvironmentVariables { get; init; }
72+
73+
/// <summary>
74+
/// A value provider that will resolve to a path to a custom certificate bundle.
75+
/// </summary>
76+
public required ReferenceExpression CertificateBundlePath { get; init; }
77+
78+
/// <summary>
79+
/// A value provider that will resolve to paths containing individual certificates.
80+
/// </summary>
81+
public required ReferenceExpression CertificateDirectoriesPath { get; init; }
82+
83+
/// <summary>
84+
/// Gets the <see cref="CertificateTrustScope"/> for the resource.
85+
/// </summary>
86+
public required CertificateTrustScope Scope { get; init; }
87+
88+
/// <summary>
89+
/// Gets the <see cref="CancellationToken"/> that can be used to cancel the operation.
90+
/// </summary>
91+
public required CancellationToken CancellationToken { get; init; }
92+
}
93+
94+
internal sealed class CertificateTrustConfigurationPathsProvider
95+
{
96+
/// <summary>
97+
/// The actual path to the certificate bundle file to be resolved at runtime
98+
/// </summary>
99+
public string? CertificateBundlePath { get; set; }
100+
101+
/// <summary>
102+
/// The actual path to the certificate directories to be resolved at runtime
103+
/// </summary>
104+
public string? CertificateDirectoriesPath { get; set; }
105+
106+
/// <summary>
107+
/// Gets a reference expression that resolves to the certificate bundle path.
108+
/// </summary>
109+
public ReferenceExpression CertificateBundlePathReference => ReferenceExpression.Create($"{CertificateBundlePath}");
110+
111+
/// <summary>
112+
/// Gets a reference expression that resolves to the certificate directories path.
113+
/// </summary>
114+
public ReferenceExpression CertificateDirectoriesPathReference => ReferenceExpression.Create($"{CertificateDirectoriesPath}");
115+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Immutable;
5+
6+
namespace Aspire.Hosting.ApplicationModel;
7+
8+
/// <summary>
9+
/// An annotation that allows overriding default certificate paths for container resources.
10+
/// </summary>
11+
public sealed class ContainerCertificatePathsAnnotation : IResourceAnnotation
12+
{
13+
/// <summary>
14+
/// The default destination path in the container under which custom certificates will be placed.
15+
/// </summary>
16+
public const string DefaultCustomCertificatesDestination = "/usr/lib/ssl/aspire";
17+
18+
/// <summary>
19+
/// Default paths to default certificate bundle files in a container.
20+
/// </summary>
21+
public static ImmutableList<string> DefaultCertificateBundlePaths = ImmutableList.Create(
22+
// Debian/Ubuntu/Gentoo etc.
23+
"/etc/ssl/certs/ca-certificates.crt",
24+
// Fedora/RHEL 6
25+
"/etc/pki/tls/certs/ca-bundle.crt",
26+
// OpenSUSE
27+
"/etc/ssl/ca-bundle.pem",
28+
// OpenELEC
29+
"/etc/pki/tls/cacert.pem",
30+
// CentOS/RHEL 7
31+
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
32+
// Alpine Linux
33+
"/etc/ssl/cert.pem");
34+
35+
/// <summary>
36+
/// Default paths to default directories containing individual CA certificates in a container.
37+
/// </summary>
38+
public static ImmutableList<string> DefaultCertificateDirectoriesPaths = ImmutableList.Create(
39+
"/etc/ssl/certs",
40+
"/usr/local/share/ca-certificates",
41+
"/etc/pki/tls/certs");
42+
43+
/// <summary>
44+
/// Paths to default certificate bundle files in the container that should be replaced when the resource's
45+
/// <see cref="CertificateTrustScope"/> is set to <see cref="CertificateTrustScope.Override"/> or
46+
/// <see cref="CertificateTrustScope.System"/>.
47+
/// If not set, a set of common default paths for popular Linux distributions will be used.
48+
/// </summary>
49+
public List<string>? DefaultCertificateBundles { get; init; }
50+
51+
/// <summary>
52+
/// Paths to default directories containing individual CA certificates in the container that should be appended
53+
/// when the resource's <see cref="CertificateTrustScope"/> is set to <see cref="CertificateTrustScope.Append"/>.
54+
/// If not set, a set of common default paths for popular Linux distributions will be used.
55+
/// </summary>
56+
public List<string>? DefaultCertificateDirectories { get; init; }
57+
58+
/// <summary>
59+
/// The destination path in the container under which custom certificates will be placed.
60+
/// If not set, defaults to /usr/lib/ssl/aspire.
61+
/// </summary>
62+
public string? CustomCertificatesDestination { get; init; }
63+
}

src/Aspire.Hosting/ApplicationModel/ContainerCertificateTrustCallbackAnnotation.cs

Lines changed: 0 additions & 111 deletions
This file was deleted.

0 commit comments

Comments
 (0)