Connecting to AWS DocumentDb with C# from your local machine

I’ve had a lot of trouble connecting to a test cluster of AWS DocumentDB from my local machine. Sure, I should probably just use a local MongoDB instance (which I will) but I needed to just test a raw connection for my first attempt.

I was using Windows with Ubuntu WSL. Your mileage may vary.

Steps

  1. Create DocumentDB Cluster
  2. Bastion SSH instance
  3. Start SSH Tunnel
  4. telnet test!
  5. Programmatic Access

Steps 1 and 2

A decent how-to for steps 1 & 2 is here: https://medium.com/softinstigate-team/how-to-create-a-web-api-for-aws-documentdb-using-restheart-987921df3ced

It doesn’t explain everything and wants to set up something else but the AWS basics are there. Ensure same VPC, subnet access and security group access

Step 3

ssh -A -p 2202 -L 27017:<cluster-dns>:27017 <bastion-user>@<bastion-dns>

The above is the SSH tunnel I used.

Step 4

telnet localhost 27017 should now work. If it doesn’t then it will error out instantly.

Using localhost:27017 for my Mongo connection should have just worked right? Nope!

Step 5

The official documentation for how to connect to Document for dotnet isn’t that great. However the basic steps are still required: https://docs.aws.amazon.com/documentdb/latest/developerguide/connect.html

I didn’t like inserting the RDS cert into my machine so I changed my code to look similar to this:


string caContentString = System.IO.File.ReadAllText("rds-combined-ca-bundle.pem");
X509Certificate2 caCert = new X509Certificate2(Encoding.ASCII.GetBytes(caContentString));
var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString));
settings.AllowInsecureTls = true;
settings.SslSettings = new SslSettings()
{
    ClientCertificates = new[] {caCert}
};

Will likely embed the pem file into the assembly for production.

The problem

I’m not 100% sure what happened but somehow the Clustering logic of the Mongo Driver knew about the AWS DNS for the instance. I guess the clustering feature shares connection information which is usually a good thing. However, in this case, it is not.

Constant timeouts when clustering was configuring itself was my symptom.

After digging around source, I found a solution: I needed to override the Server Selectors for the cluster with IServerSelector.

The solution

public class LocalhostOnlySelector : IServerSelector
{
    public IEnumerable<ServerDescription> SelectServers(ClusterDescription cluster, IEnumerable<ServerDescription> servers)
    {
        foreach (var server in cluster.Servers)
        {
            switch (server.EndPoint)
            {
                case DnsEndPoint dep:
                    {
                        if (dep.Host.Equals("localhost"))
                        {
                            yield return server;
                        }
                        continue;
                    }
                    default:
                        continue;
            }
        }
    }
    }

So now my complete test connection looks like:

string template = "mongodb://{0}:{1}@{2}/test?ssl=true&replicaSet=rs0&readpreference={3}";
string username = "Adam";
string readPreference = "secondaryPreferred";
string connectionString = String.Format(template, username, password, "localhost:27017", readPreference);


string caContentString = System.IO.File.ReadAllText("rds-combined-ca-bundle.pem");
X509Certificate2 caCert = new X509Certificate2(Encoding.ASCII.GetBytes(caContentString));
var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString));
settings.AllowInsecureTls = true;
settings.SslSettings = new SslSettings()
{
    ClientCertificates = new[] {caCert}
};
settings.ClusterConfigurator = x =>
{
    x.ConfigureCluster(y => y.With(preServerSelector : new LocalhostOnlySelector(), postServerSelector : new LocalhostOnlySelector()));
};
var client = new MongoClient(settings);
var database = client.GetDatabase("test");
var collection = database.GetCollection<BsonDocument>("test1");
var count = await collection.CountDocumentsAsync(x => true);
Console.WriteLine(count);

My console prints 1!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s