In this post, let's see how we can set up Azure Private Endpoints to connect to a MongoDB Atlas cluster.
When we connect to a MongoDB Atlas cluster, we typically use a connection string like this:
mongodb+srv://<CLUSTER_NAME>.bzmphh.mongodb.net
This goes over the public internet. If we are on Azure and want traffic to stay private and routed through Azure's backbone network, we need a Private Endpoint.
- Atlas
- A Private Link Service that our Azure VNet can connect to
- Azure:
- A Private Endpoint in our VNet's subnet that gets a private IP
- An Azure Private DNS Zone so our apps resolve the Atlas hostname to the private IP instead of the public one
Let's walk through how to achieve this step by step using PowerShell. Please note that we need az cli and atlas cli for this.
$atlasProjectId = "<ATLAS_PROJECT_ID>" $atlasClusterName = "<ATLAS_CLUSTER_NAME>" $atlasRegion = "<ATLAS_REGION>" $subscription = "<AZURE_SUBSCRIPTION_ID>" $resourceGroup = "<RESOURCE_GROUP>" $location = "<REGION>" $vnetName = "<VNET>" $subnetName = "<SNET_FOR_PRIVATE_ENDPOINT>" $peName = "<PE_NAME>" $peNicName = "<PE_NAME>_nic"
$peService = atlas privateEndpoints azure create ` --projectId $atlasProjectId ` --region $atlasRegion -o json | ConvertFrom-Json $endpointServiceId = $peService.id $privateLinkServiceResourceId = $peService.privateLinkServiceResourceId
az account set --subscription $subscription $pe = az network private-endpoint create ` --resource-group $resourceGroup ` --location $location ` --name $peName ` --nic-name $peNicName ` --vnet-name $vnetName ` --subnet $subnetName ` --private-connection-resource-id $privateLinkServiceResourceId ` --connection-name "$peName-connection" ` --manual-request true | ConvertFrom-Json $peResourceId = $pe.id
$pePrivateIp = az network nic show ` --subscription $subscription ` --resource-group $resourceGroup ` --name $peNicName ` --query "ipConfigurations[0].privateIPAddress" -o tsv
atlas privateEndpoints azure interfaces create $endpointServiceId ` --privateEndpointId $peResourceId ` --privateEndpointIpAddress $pePrivateIp ` --projectId $atlasProjectId
$cluster = atlas clusters describe $atlasClusterName ` --projectId $atlasProjectId -o json | ConvertFrom-Json $srvHost = $cluster.connectionStrings.standardSrv -replace "mongodb\+srv://", "" $dnsZoneName = $srvHost.Substring($srvHost.IndexOf('.') + 1) # Result: bzmphh.mongodb.net az network private-dns zone create ` --resource-group $resourceGroup ` --name $dnsZoneName
$vnetResourceId = az network vnet show ` --subscription $subscription ` --resource-group $resourceGroup ` --name $vnetName ` --query "id" -o tsv az network private-dns link vnet create ` --resource-group $resourceGroup ` --zone-name $dnsZoneName ` --name $vnetName ` --virtual-network $vnetResourceId ` --registration-enabled false
$connectionStrings = atlas clusters connectionStrings describe $atlasClusterName ` --projectId $atlasProjectId ` -o json | ConvertFrom-Json
$peConnStr = $connectionStrings.privateEndpoint[0] $srvHostFull = $peConnStr.srvConnectionString -replace "mongodb\+srv://", "" $srvPrefix = $srvHostFull.Split('.')[0] $connPart = ($peConnStr.connectionString -replace "mongodb://", "").Split('?')[0].TrimEnd('/') $hostPortEntries = $connPart.Split(',') $aRecordHostFull = $hostPortEntries[0].Split(':')[0] $aRecordName = $aRecordHostFull -replace "\.$dnsZoneName$", "" $ports = $hostPortEntries | ForEach-Object { $_.Split(':')[1] } $queryParams = ($peConnStr.connectionString -split '\?')[1] $txtValue = ($queryParams -split '&' | Where-Object { $_ -match "authSource|replicaSet" }) -join '&'
# A Record az network private-dns record-set a add-record ` --resource-group $resourceGroup ` --zone-name $dnsZoneName ` --record-set-name $aRecordName ` --ipv4-address $pePrivateIp # SRV Records foreach ($port in $ports) { az network private-dns record-set srv add-record ` --resource-group $resourceGroup ` --zone-name $dnsZoneName ` --record-set-name "_mongodb._tcp.$srvPrefix" ` --target $aRecordHostFull ` --priority 0 --weight 0 --port $port } # TXT Record az network private-dns record-set txt add-record ` --resource-group $resourceGroup ` --zone-name $dnsZoneName ` --record-set-name $srvPrefix ` --value "`"$txtValue`""
mongodb+srv://<CLUSTER_NAME>-pl-0.bzmphh.mongodb.net
- SRV Record
- This tells the driver which hosts and ports to connect to. For private endpoints, the port is 1024 (not the standard 27017). The SRV record returns:
<CLUSTER_NAME>-pl-0-0.bzmphh.mongodb.net:1024 <CLUSTER_NAME>-pl-0-0.bzmphh.mongodb.net:1025 <CLUSTER_NAME>-pl-0-0.bzmphh.mongodb.net:1026
- TXT Record
- This provides the replica set name and auth database:
authSource=admin&replicaSet=atlas-5x58u7-shard-0
- A Record
- This resolves the hostname to the Private IP address of our Private Endpoint. This is what actually routes traffic through Azure Private Link instead of the public internet.
mongodb://<CLUSTER_NAME>-pl-0-0.bzmphh.mongodb.net:1024,<CLUSTER_NAME>-pl-0-0.bzmphh.mongodb.net:1025,<CLUSTER_NAME>-pl-0-0.bzmphh.mongodb.net:1026/?authSource=admin&replicaSet=atlas-5x58u7-shard-0
# Check DNS resolution nslookup <CLUSTER_NAME>-pl-0-0.bzmphh.mongodb.net # Test connection mongosh 'mongodb+srv://<CLUSTER_NAME>-pl-0.bzmphh.mongodb.net' \ --username dbadmin --password 'yourpassword'