CosmosDB preview emulator: HTTPS endpoint switch ignores certificate availability, breaks CI
- Dominant language
- C#
- Stars
- 6.3k
- Forks
- 991
- Avg merge
- 2d 15h
- Merged PRs (30d)
- 196
Description
## Description
After upgrading from `Aspire.Hosting.Azure.CosmosDB` 13.1.x to 13.2.0, integration tests that use `RunAsPreviewEmulator` started failing on CI (Azure DevOps) with SSL errors, while working fine on developer machines.
The root cause is a logic mismatch introduced in #14663 (`267e787`) — the endpoint scheme is switched to HTTPS unconditionally, but the certificate environment variables are only configured when a dev cert is actually available.
## Root Cause
In `AzureCosmosDBExtensions.cs`, `RunAsPreviewEmulator` does two things:
**1. Adds an `HttpsCertificateAnnotation` with `UseDeveloperCertificate = true`:**
```csharp
emulatorSurrogateBuilder.WithHttpsDeveloperCertificate(password: ...);
```
Since `AzureCosmosDBEmulatorResource.Annotations` delegates to the inner `AzureCosmosDBResource`, this annotation ends up on the main resource.
**2. Subscribes to `BeforeStartEvent` to switch the endpoint to HTTPS:**
```csharp
builder.SubscribeHttpsEndpointsUpdate(ctx =>
{
builder.WithEndpoint("emulator", ep => { ep.UriScheme = "https"; });
});
```
Inside `SubscribeHttpsEndpointsUpdate`, the check is:
```csharp
else if (annotation.UseDeveloperCertificate.GetValueOrDefault(developerCertificateService.UseForHttps) || ...)
{
addHttps = true;
}
```
Because `UseDeveloperCertificate` is explicitly `true` (not `null`), `GetValueOrDefault` returns `true` **regardless** of `developerCertificateService.UseForHttps`. The endpoint always switches to HTTPS.
**Meanwhile**, the certificate env var setter (`HttpsCertificateExecutionConfigurationGatherer.GatherAsync`) does the right thing:
```csharp
if (certificate is null)
{
return; // No dev cert → exit early, no PROTOCOL/CERT_PATH/CERT_SECRET set
}
```
So on CI (no trusted dev cert):
- `SubscribeHttpsEndpointsUpdate` → endpoint becomes `https://`
- `GatherAsync` → no cert found → exits early → no `PROTOCOL=https` env var
- Emulator starts with HTTP
- Connection string resolves to `AccountEndpoint=https://host:port` (via `EndpointProperty.Url`)
- Aspire's internal `CosmosClient.ReadAccountAsync()` in `OnResourceReady` tries HTTPS against an HTTP server → `HttpRequestException: The SSL connection could not be established` / `Cannot determine the frame size or a corrupted frame was received`
On dev machines it works because the dev cert exists, so both the endpoint switch **and** the env var configuration happen together.
## Steps to Reproduce
1. Create an Aspire AppHost with a CosmosDB preview emulator:
```csharp
var cosmosDb = builder.AddAzureCosmosDB("cosmos");
cosmosDb.RunAsPreviewEmulator(emulator =>
{
emulator.WithDataExplorer();
});
```
2. Create an integration test using `DistributedApplicationTestingBuilder`:
```csharp
var builder = await DistributedApplicationTestingBuilder
.CreateAsync([], (options, _) =>
{
options.AllowUnsecuredTransport = true;
});
var app = await builder.BuildAsync();
await app.StartAsync();
// This will timeout on CI — OnResourceReady fails with SSL error
await app.ResourceNotifications.WaitForResourceHealthyAsync("cosmos", cts.Token);
```
3. Run on a CI agent that has no trusted ASP.NET Core developer certificate (standard Azure DevOps / GitHub Actions Linux agent).
**Expected:** Emulator starts with HTTP, endpoint uses HTTP, tests pass.
**Actual:** Endpoint switches to HTTPS, emulator stays HTTP, SSL handshake fails.
## Suggested Fix
In `WithHttpsDeveloperCertificate`, set `UseDeveloperCertificate = null` instead of `true`:
```csharp
var annotation = new HttpsCertificateAnnotation
{
UseDeveloperCertificate = null, // was: true
Password = password?.Resource,
};
```
This way, `SubscribeHttpsEndpointsUpdate` falls back to `developerCertificateService.UseForHttps` — which properly checks whether a cert actually exists — and the endpoint only switches to HTTPS when the full HTTPS chain can actually work.
Alternatively, `SubscribeHttpsEndpointsUpdate` could be changed to verify cert availability before switching the scheme, not just check the annotation's intent.
## Workaround
For anyone hitting this in the meantime — in the AppHost, after `RunAsPreviewEmulator`, remove the cert config callbacks and force the endpoint back to HTTP for the testing environment:
```csharp
if (builder.Environment.EnvironmentName == "Testing")
{
foreach (var annotation in cosmosDb.Resource.Annotations.ToList())
{
var typeName = annotation.GetType().Name;
if (typeName.Contains("HttpsCertificateConfiguration") ||
typeName.Contains("CertificateTrust"))
{
cosmosDb.Resource.Annotations.Remove(annotation);
}
}
builder.Eventing.Subscribe((@event, _) =>
{
cosmosDb.WithEndpoint("emulator", e => e.UriScheme = "http");
return Task.CompletedTask;
});
}
```
## Environment
- Aspire.Hosting.Azure.CosmosDB: 13.2.0
- .NET SDK: 10.0
- CI: Azure DevOps (Linux agent, no trusted ASP.NET Core dev cert)
- Emulator image tag: `vnext-EN20260227` (also reproducible with default `vnext-preview`)
Contributor guide
Assessment
This issue has not been assessed yet.