microsoft / microsoft/aspire

CosmosDB preview emulator: HTTPS endpoint switch ignores certificate availability, breaks CI

Open
#15,620 3 comments 2 reactions 0 assignees View on GitHub
area-integrations internal-customer-impact
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.