Skip to content

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

@Hubert-Rybak

Description

@Hubert-Rybak

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:

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:

builder.SubscribeHttpsEndpointsUpdate(ctx =>
{
    builder.WithEndpoint("emulator", ep => { ep.UriScheme = "https"; });
});

Inside SubscribeHttpsEndpointsUpdate, the check is:

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:

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:
var cosmosDb = builder.AddAzureCosmosDB("cosmos");
cosmosDb.RunAsPreviewEmulator(emulator =>
{
    emulator.WithDataExplorer();
});
  1. Create an integration test using DistributedApplicationTestingBuilder:
var builder = await DistributedApplicationTestingBuilder
    .CreateAsync<Projects.MyAppHost>([], (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);
  1. 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:

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:

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<BeforeStartEvent>((@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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-integrationsIssues pertaining to Aspire Integrations packages

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions