diff --git a/src/frontend/src/content/docs/app-host/certificate-configuration.mdx b/src/frontend/src/content/docs/app-host/certificate-configuration.mdx index 796d61f47..dc034faa6 100644 --- a/src/frontend/src/content/docs/app-host/certificate-configuration.mdx +++ b/src/frontend/src/content/docs/app-host/certificate-configuration.mdx @@ -41,10 +41,10 @@ Many of the certificate features in Aspire rely on a development certificate. Be The preferred way to manage the development certificate is to use the [Aspire CLI](/get-started/install-cli/). When you run `aspire run` in an interactive session, the CLI automatically ensures the development certificate is created and trusted. No additional manual steps are required. -For non-C# AppHosts (such as [TypeScript](/app-host/typescript-apphost/) or Python AppHosts), the `dotnet` first-run experience that normally creates the HTTPS development certificate never runs, because these AppHosts launch a prebuilt native binary instead of invoking `dotnet`. The Aspire CLI fills this gap when `aspire run` starts and no development certificate exists: +For generated TypeScript, Python, Go, Java, and Rust AppHosts, the `dotnet` first-run experience that normally creates the HTTPS development certificate never runs, because these AppHosts launch a prebuilt native binary instead of invoking `dotnet`. The Aspire CLI fills this gap when `aspire run` starts and no development certificate exists: -- In an interactive session—and on Linux, where establishing trust doesn't require a prompt—the CLI creates *and* trusts the certificate, just as it does for C# AppHosts. -- In a non-interactive session on macOS or Windows (for example, in CI), the CLI can't show the macOS Keychain password prompt or the Windows trust dialog, so it *generates* the certificate without trusting it. This lets servers such as Kestrel load the certificate from the personal store, even though it isn't trusted. If the certificate can't be generated, a warning is displayed and the run continues. +- In an interactive session—and on Linux, where establishing trust doesn't require a prompt—the CLI creates _and_ trusts the certificate, just as it does for C# AppHosts. +- In a non-interactive session on macOS or Windows (for example, in CI), the CLI can't show the macOS Keychain password prompt or the Windows trust dialog, so it _generates_ the certificate without trusting it. This lets servers such as Kestrel load the certificate from the personal store, even though it isn't trusted. If the certificate can't be generated, a warning is displayed and the run continues. To opt out of automatic certificate generation, set the `ASPIRE_CLI_GENERATE_HTTPS_CERTIFICATE` environment variable to `false`. This mirrors the .NET SDK's `DOTNET_GENERATE_ASPNET_CERTIFICATE` opt-out: @@ -85,19 +85,20 @@ aspire certs trust ### Developer certificate for DCP communication @@ -141,8 +142,16 @@ You can control this behavior using the HTTPS endpoint APIs described below. To explicitly configure a resource to use the development certificate for its HTTPS endpoints: - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -157,24 +166,31 @@ var pythonApp = builder.AddUvicornApp("api", "../api", "app:main") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); // Explicitly use the developer certificate -const nodeApp = await builder.addViteApp("frontend", "../frontend") - .withHttpsDeveloperCertificate(); +const nodeApp = await builder + .addViteApp('frontend', '../frontend') + .withHttpsDeveloperCertificate(); // Use developer certificate with an encrypted private key -const certPassword = await builder.addParameter("cert-password", { secret: true }); -const pythonApp = await builder.addUvicornApp("api", "../api", "app:main") - .withHttpsDeveloperCertificate({ password: certPassword }); +const certPassword = await builder.addParameter('cert-password', { + secret: true, +}); +const pythonApp = await builder + .addUvicornApp('api', '../api', 'app:main') + .withHttpsDeveloperCertificate({ password: certPassword }); await builder.build().run(); ``` + @@ -189,8 +205,16 @@ The `WithHttpsDeveloperCertificate` method: To configure a resource to use a specific X.509 certificate for HTTPS endpoints: - + + ```csharp title="AppHost.cs" using System.Security.Cryptography.X509Certificates; @@ -210,19 +234,22 @@ builder.AddNpmApp("frontend", "../frontend") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder, refExpr } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -const api = await builder.addContainer("api", { - image: "my-api", - tag: "latest", +const api = await builder.addContainer('api', { + image: 'my-api', + tag: 'latest', }); -api.createExecutionConfiguration() +api + .createExecutionConfiguration() .withArgumentsConfig() .withEnvironmentVariablesConfig() .withHttpsCertificateConfig(async () => ({ @@ -233,6 +260,7 @@ api.createExecutionConfiguration() await builder.build().run(); ``` + @@ -246,8 +274,16 @@ The certificate must: To prevent Aspire from configuring any HTTPS certificate for a resource: - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -257,19 +293,21 @@ var redis = builder.AddRedis("cache") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); // Disable automatic HTTPS certificate configuration -const redis = await builder.addRedis("cache") - .withoutHttpsCertificate(); +const redis = await builder.addRedis('cache').withoutHttpsCertificate(); await builder.build().run(); ``` + @@ -283,8 +321,14 @@ Use `WithoutHttpsCertificate` when: For resources that need custom certificate configuration logic, use `WithHttpsCertificateConfiguration` to specify how certificate files should be passed to the resource: - +) -> Value, so its typed certificate context cannot be used safely.', + }} +> + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -315,30 +359,76 @@ builder.AddContainer("api", "my-api:latest") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder, refExpr } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -const api = await builder.addContainer("api", { - image: "myimage", - tag: "latest", +const api = await builder.addContainer('api', { + image: 'myimage', + tag: 'latest', }); -api.createExecutionConfiguration() +api + .createExecutionConfiguration() .withArgumentsConfig() .withEnvironmentVariablesConfig() .withCertificateTrustConfig(async () => ({ certificateBundlePath: refExpr`/certs/ca-bundle.crt`, certificateDirectoriesPath: refExpr`/certs`, - rootCertificatesPath: "/etc/ssl/certs", + rootCertificatesPath: '/etc/ssl/certs', isContainer: true, })); await builder.build().run(); ``` + + + + +```go title="apphost.go" +api := builder. + AddContainer("api", "my-api:latest"). + WithHttpsCertificateConfiguration( + func(ctx aspire.HttpsCertificateConfigurationCallbackAnnotationContext) { + arguments := ctx.Arguments() + _ = arguments.Add("--tls-cert") + _ = arguments.Add(ctx.CertificatePath()) + _ = arguments.Add("--tls-key") + _ = arguments.Add(ctx.KeyPath()) + + environment := ctx.Environment() + _ = environment.Set("TLS_CERT_FILE", ctx.CertificatePath()) + _ = environment.Set("TLS_KEY_FILE", ctx.KeyPath()) + _ = environment.Set("TLS_PFX_FILE", ctx.PfxPath()) + }, + ) +if err := api.Err(); err != nil { + log.Fatal(aspire.FormatError(err)) +} +``` + + + + +```java title="AppHost.java" +var api = builder.addContainer("api", "my-api:latest"); +api.withHttpsCertificateConfiguration(ctx -> { + ctx.arguments().add("--tls-cert"); + ctx.arguments().add(ctx.certificatePath()); + ctx.arguments().add("--tls-key"); + ctx.arguments().add(ctx.keyPath()); + + ctx.environment().set("TLS_CERT_FILE", ctx.certificatePath()); + ctx.environment().set("TLS_KEY_FILE", ctx.keyPath()); + ctx.environment().set("TLS_PFX_FILE", ctx.pfxPath()); +}); +``` + @@ -377,8 +467,16 @@ You can control this behavior per resource using the `WithDeveloperCertificateTr To explicitly enable or disable development certificate trust for a specific resource: - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -392,23 +490,28 @@ var pythonApp = builder.AddPythonApp("api", "../api", "main.py") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash import { createBuilder } from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); // Explicitly enable development certificate trust -const nodeApp = await builder.addNodeApp("frontend", "../frontend", "index.js") - .withDeveloperCertificateTrust(true); +const nodeApp = await builder + .addNodeApp('frontend', '../frontend', 'index.js') + .withDeveloperCertificateTrust(true); // Disable development certificate trust -const pythonApp = await builder.addPythonApp("api", "../api", "main.py") - .withDeveloperCertificateTrust(false); +const pythonApp = await builder + .addPythonApp('api', '../api', 'main.py') + .withDeveloperCertificateTrust(false); await builder.build().run(); ``` + @@ -418,6 +521,17 @@ Certificate authority collections allow you to bundle custom certificates and ma #### Create and use a certificate authority collection + + + ```csharp title="AppHost.cs" using System.Security.Cryptography.X509Certificates; @@ -438,9 +552,8 @@ builder.AddNpmApp("my-project", "../myapp") builder.Build().Run(); ``` - + + In the preceding example, the certificate bundle is created with custom certificates and then applied to a Node.js application, enabling it to trust those certificates. @@ -465,8 +578,16 @@ Attempts to append the configured certificates to the default trusted certificat This is the default scope for most resources. For Python resources, only OTEL trust configuration will be applied in this mode. - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -475,18 +596,25 @@ builder.AddNodeApp("api", "../api") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash -import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs'; +import { + createBuilder, + CertificateTrustScope, +} from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -await builder.addNodeApp("api", "../api", "index.js") - .withCertificateTrustScope(CertificateTrustScope.Append); +await builder + .addNodeApp('api', '../api', 'index.js') + .withCertificateTrustScope(CertificateTrustScope.Append); await builder.build().run(); ``` + @@ -496,25 +624,36 @@ await builder.build().run(); #### Override mode Attempts to override a resource to only trust the configured certificates, replacing the default trusted certificates entirely. This mode is useful when you need strict control over which certificates are trusted. + + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -528,9 +667,8 @@ builder.AddPythonModule("api", "./api", "uvicorn") builder.Build().Run(); ``` - + + #### System mode @@ -538,8 +676,16 @@ Attempts to combine the configured certificates with the default system root cer This is the default scope for Python projects because Python only has mechanisms to fully override certificate trust. - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -548,18 +694,25 @@ builder.AddPythonApp("worker", "../worker", "main.py") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash -import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs'; +import { + createBuilder, + CertificateTrustScope, +} from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -await builder.addPythonApp("worker", "../worker", "main.py") - .withCertificateTrustScope(CertificateTrustScope.System); +await builder + .addPythonApp('worker', '../worker', 'main.py') + .withCertificateTrustScope(CertificateTrustScope.System); await builder.build().run(); ``` + @@ -571,6 +724,7 @@ This is the default scope for .NET projects on Windows, as there's no way to aut + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -579,18 +733,61 @@ builder.AddContainer("service", "myimage") builder.Build().Run(); ``` + + ```typescript title="apphost.mts" twoslash -import { createBuilder, CertificateTrustScope } from './.aspire/modules/aspire.mjs'; +import { + createBuilder, + CertificateTrustScope, +} from './.aspire/modules/aspire.mjs'; const builder = await createBuilder(); -await builder.addContainer("service", { image: "myimage", tag: "latest" }) - .withCertificateTrustScope(CertificateTrustScope.None); +await builder + .addContainer('service', { image: 'myimage', tag: 'latest' }) + .withCertificateTrustScope(CertificateTrustScope.None); await builder.build().run(); ``` + + + + +```python title="apphost.py" +service = builder.add_container("service", "myimage") +service.with_certificate_trust_scope("None") +``` + + + + +```go title="apphost.go" +service := builder. + AddContainer("service", "myimage"). + WithCertificateTrustScope(aspire.CertificateTrustScopeNone) +``` + + + + +```java title="AppHost.java" +var service = builder.addContainer("service", "myimage"); +service.withCertificateTrustScope(CertificateTrustScope.NONE); +``` + + + + +```rust title="apphost.rs" +let service = builder.add_container( + "service", + serde_json::json!("myimage"), +)?; +service.with_certificate_trust_scope(CertificateTrustScope::None)?; +``` + @@ -602,8 +799,16 @@ For advanced scenarios, you can specify custom certificate trust behavior using Use `WithCertificateTrustConfiguration` to customize how certificate trust is configured for a resource: - + + ```csharp title="AppHost.cs" var builder = DistributedApplication.CreateBuilder(args); @@ -625,6 +830,7 @@ builder.AddContainer("api", "myimage") builder.Build().Run(); ``` +