Trying (and failing) to get the CosmosDB Emulator container working with Dapr

or, I've discovered lots of ways that this doesn't work

Posted by Scott on December 02, 2022

Photo by Bryan Goff on Unsplash.

TL;DR: I've tried everything I can to get Dapr to talk to the CosmosDB Emulator container, which ships with a self-signed certificate and which therefore fails making an https connection, and I'm giving up for now.

If I find a solution to this, I'll write another blog post about it and link to it here. If there's no link, 🤷‍♂️.

Introduction

So... I'm working on my source control system, Grace, and the next thing I'd like to do is to enable contributors (and myself) to work on it using a Docker Compose configuration.

Grace is built using Dapr, which allows users to write code once, and then plug in over 100 different pieces of infrastructure, including cloud PaaS services, so that the platform can be chosen at runtime. I love lots of things about this, but one of the big ones is that you can imagine writing your code locally running on containers for state store, observability, secrets, pub/sub, etc. and then run that same exact code in production on completely different, larger, scalable infrastructure.

Right now, Grace is sort-of hard-coded to expect Azure CosmosDB as the state store under Dapr. There was a reason in earlier versions of Dapr that I needed to do this, but Dapr has since added the features I need to remove that specific code and replace it with higher-level Dapr queries.

As part of the process of getting a Docker Compose setup going, I figured, hey, CosmosDB has an emulator container available. Might as well add that to Compose so it's there while I spend the time to rewrite the CosmosDB-specific parts into Dapr code.

The problem

The CosmosDB emulator container ships expecting to be connected to over https, which is a reasonable thing these days, but it ships with a self-signed certificate, which makes connecting over https a bit challenging. The certificate it has is not sourced from a root CA, and, by default, every browser and HTTP stack will refuse to connect to a site with a broken chain-of-trust, and self-signed certificates do not have a chain-of-trust. This is same problem we run into when, for instance, we spin up a new web project, configure it to run on https, and then get a warning message in the browser when we try to connect to it.

In my F# code, I can bypass this by configuring my CosmosClient with a custom HttpClientFactory that provides HttpClient instances that bypass TLS checking. Because this is something that you never want to do in production, I've configured it to only happen in debug builds.


    let CosmosClient() = 
        if not <| isNull cosmosClient then
            cosmosClient
        else
            let cosmosDbConnectionString = Environment.GetEnvironmentVariable(Constants.EnvironmentVariables.AzureCosmosDBConnectionString)
            let cosmosClientOptions = CosmosClientOptions(
                ApplicationName = Constants.GraceServerAppId, 
                EnableContentResponseOnWrite = false, 
                LimitToEndpoint = true, 
                Serializer = new CosmosJsonSerializer(Constants.JsonSerializerOptions))
#if DEBUG
            let httpClientFactory = fun () ->
                let httpMessageHandler: HttpMessageHandler = new HttpClientHandler(
                    ServerCertificateCustomValidationCallback = (fun _ _ _ _ -> true))
                new HttpClient(httpMessageHandler)
            cosmosClientOptions.HttpClientFactory <- httpClientFactory
            cosmosClientOptions.ConnectionMode <- ConnectionMode.Direct
#endif
            cosmosClient <- new CosmosClient(cosmosDbConnectionString, cosmosClientOptions)
            cosmosClient

And in my Dapr config for a state store using Azure CosmosDB, I can specify the values to connect to the emulator.

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: actorStorage
spec:
  type: state.azure.cosmosdb
  version: v1
  initTimeout: 1m
  metadata:
  - name: url
    value: https://localhost:8081/
	# This key is a well-known default value for the emulator.	
  - name: masterKey
    value: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
  - name: database
    value: gracevcs-development-db
  - name: collection
    value: grace-development
  - name: partitionKey
    value: "/partitionKey"
  - name: actorStateStore
    value: "true"

Cool so far. The problem comes when Dapr starts and tries to connect to the emulator.

It just doesn't work

{"app_id":"grace","instance":"dd6c98246b75","level":"fatal","msg":"process component actorStorage error: [INIT_COMPONENT_FAILURE]: initialization error occurred for actorStorage (state.azure.cosmosdb/v1): Get \"https://localhost:8081/dbs/gracevcs-development-db/colls/grace-development\": dial tcp 127.0.0.1:8081: connect: connection refused","scope":"dapr.runtime","time":"2022-12-03T01:08:37.581552869Z","type":"log","ver":"1.9.4"}

And that connection refused error is happening because of the self-signed cert on the emulator. Sigh.

I'm running on Windows 11, using WSL 2 and Docker Desktop in Linux mode. I've tried to do the things listed in Microsoft's documentation for the emulator container, including exporting the cert from the emulator and installing it into Linux. But, still, the connection is failing.

I don't have any way to tell Dapr to bypass TLS checking on connections, and I don't think they should have a flag in their code to even enable that. It's bad practice, I get that, and I don't want to give anyone a footgun they'll regret using.

Let's call it

Usually you write a blog post announcing how awesome you are for having figured something out.

In this case, I'm just admitting defeat for now. Just wanted this up here in case anyone else is searching for information on this. I might revisit this in a few weeks, but, for now, I'm going to try using the installed CosmosDB Emulator, and if that doesn't work quickly, I'll just keep using actual CosmosDB until I can replace the code that needs it and just use Dapr's functionality.