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
- Create DocumentDB Cluster
- Bastion SSH instance
- Start SSH Tunnel
telnet
test!- 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!