Using MongoDB connection via SSH tunnel fails with 'AuthenticationFailed'

Hey,

thank your for your replies. A simple:

def TestRemoteConnection(self) -> None:
    """
    """
    print(f"{self.mongoClient.server_info() = }")

will also raise the error. And I of course misspoke in my prior posting, where I talked about collections, I meant databases.

Have you tried passing directConnection=True to your MongoClient, as described in the docs here?

Thank you for the tip. No, I did not, but it does not seem to make a difference. I updated the code snippet, the error remains the same.

How did you create the user? Making a SSH connection to localhost, i.e. to the current machine looks a bit useless to me.

How do you mean that and which user do you mean (because there is a MongoDB user and an Azure user)? I am trying to tunnel to a MongoDB instance on an Azure remote. I am not showing the credentials in my snippet for obvious reasons. The Azure server and MongoDB database on it and their users are in use for quite some time and work. All these credentials also work when using the Compass app, and using the SSH option there in the connections. New is only that I want to do work from my local machine with Python on that remote (instead of having to run scripts on that Linux VM directly).

The self.binding is the (local, port) bundle from which the tunnel is forwarding. I.e., the MongoClient client would send there its commands, and the tunnel then forwards them to the remote. At least that is how I meant to set it up. There is pymongo-ssh and it is set up quite similarly. I would like to avoid having dependencies/external code, but I will probably give it a spin tomorrow.

My hunch is that some of our stupid enterprise firewall/VPN security measures is interfering here somehow. But I already turned off what I can turn off with no luck. The other thing which could be an issue, is that the forwarding somehow fails because I have also a MongoDB installation on my local machine, and its service then collides with the tunnel, as it listens on that same standard MongoDB port. But the Compass app works, but maybe it is one of the cases where things are much more complex than they seem. Will probably either have to kill the local MongoDB service tomorrow or try to use a different port.

Cheers,
Ferdinand

My current code:

from sshtunnel import SSHTunnelForwarder
from pymongo import MongoClient

class MongoDatabaseSshConnection:
    """
    """
    def __init__(self, server_url: str, server_port: int, server_user: str, server_password: str, 
                 database_uri: str, binding: tuple[str, int] = ("127.0.0.1", 27017), 
                 autoConnect: bool = True) -> None:
        """
        """
        self.mongoClient: MongoClient | None = None
        self.sshTunnel: SSHTunnelForwarder | None = None

        self.server_user: str = server_user
        self.server_password: str = server_password 
        self.server_port: int = server_port
        self.server_url: str = server_url
        self.database_uri: str = database_uri
        self.binding: tuple[str, int] = binding

        if autoConnect:
            self.Connect()

    
    def Connect(self) -> None:
        """
        """
        self.sshTunnel = SSHTunnelForwarder(
            (self.server_url, self.server_port),
            ssh_username=self.server_user,
            ssh_password=self.server_password,
            remote_bind_address=self.binding
        )
        self.sshTunnel.start()
        self.mongoClient = MongoClient(self.database_uri, directConnection=True)

    def __del__(self) -> None:
        """
        """
        if self.mongoClient is not None:
            self.mongoClient.close()
        if self.sshTunnel is not None:
            self.sshTunnel.stop()
    
    def TestRemoteConnection(self) -> None:
        """
        """
        print(f"{self.mongoClient.server_info() = }")
        # try:
        #     self.mongoClient.nodebb.command('ping')
        #     print("Ping successful")
        # except Exception as e:
        #     print(f"Ping failed: {e}")

def main() -> None:
    """
    """
    ...