3

My credentials work perfectly with Robomongo but I can't make the connection with node.js
I have tried to make the connection using ssh2 and tunnel-ssh npm module and failed both times.
-The mongo connection does not require a password
-The ssh connection is made with a pem key

This is the code I've used with ssh2 module, I can establish the tunneling correctly but the mongo connection fails

var Client = require('ssh2').Client;

var conn = new Client();
conn.on('ready', function() {
    console.log('Client :: ready');
    //mongo connection
        mongoose.connect('mongodb://localhost:27000/');
        var db = mongoose.connection;
        db.on('error', console.error.bind(console, 'connection error:'));
        db.once('open', function() {
            console.log("database connection established");
            var users = db.collection('user');
            var getallUsers = function (date, callback){
                users.find({}).toArray(function(err,data){
                    callback(data);
                })
            };
            getallUsers(null, function (data){
                console.log('data :'+  data);
            });
        });
    //end of mongo connection
}).connect({
    host: '**.**.**.**.**',
    port: 22,
    username: 'ec2-user',
    privateKey: key
});

And the code the tunnel-ssh

var config = {
    dstPort: 27000,
    user: 'ec2-user',
    host: '**.**.**.**.**',
    privateKey: key
};

var server = tunnel(config, function (error, server) {
    if(error){
        console.log("SSH connection error: " + error);
    }
    console.log('database connection initalizing');
    mongoose.connect('mongodb://localhost:27000/');

    var db = mongoose.connection;

    db.on('error', console.error.bind(console, 'connection error:'));
    db.once('open', function() {

        console.log("database connection established");

        var users = db.collection('user');
        var getallUsers = function (date, callback){
            users.find({}).toArray(function(err,data){
                callback(data);
            })
        };
        getallUsers(null, function (data){
            console.log(data);
        });

    });
});

I'm not sure whether to use the regular MongoDB connection string after establishing the tunnel or referring to the database as localhost such as
mongodb://localhost:portnumber.
or
mongodb://databasepath.subpath.mongodbdns.com:27000

Localhost gives me a permission denied error, the latter gives me a timeout

user3382714
  • 185
  • 2
  • 4
  • 9
  • Generic options on how to build an SSH tunnel for a Node.js application (which of course works for MongoDB as well) are discussed as well [here](https://stackoverflow.com/questions/24687932/node-js-connecting-through-ssh/75547238) – swimmer Feb 23 '23 at 15:56

5 Answers5

6

As mscdex mentioned ssh2 isn't a good module to use to make an ssh tunnel connection to a database. tunnel-ssh is more appropriate.

Here are the configuration options I've used :

dstPort: remote database connection port

localPort: same as dstPort, It'll be the port you'll use for your local machine

username: SSH username,

host: SSH address

dstHost: database connection url (...mongodbns.com) ,

privateKey: SSH key

Then once your tunnel is connected connect via mongoose to your localhost such as mondodb://localhost:27000 (use the localport you defined in localPort)

var server = tunnel(config, function (error, server) {
    if(error){
        console.log("SSH connection error: " + error);
    }
    mongoose.connect('mongodb://localhost:27000/');
    //...rest of mongoose connection
}
user3382714
  • 185
  • 2
  • 4
  • 9
  • 4
    I am getting this>> **Error: All configured authentication methods failed** note:i am using **require('mongodb').MongoClient ,require('tunnel-ssh'),var config = { username: 'root', Password: 'xxxxxx', host: "x.x.11.130", dstHost: "x.x.11.130", port: 22, dstHost:url, dstPort: 10945, localPort: 10945 };** – bhaRATh May 17 '19 at 14:33
  • 1
    FYI, tunnel-ssh internally use ssh2 to establish the tunnel – MiDaa May 16 '20 at 05:28
  • @user3382714 does dstHost look something like "mongodb+src//user:pass@mongohostname" ? – mtro May 23 '22 at 19:13
1

Since mongoose does not support passing in a stream to use as the underlying connection, you will have to listen on a local port (e.g. 27000) and forward incoming connections to that port over the ssh connection.

Fortunately there exists third party modules that build on ssh2 that provide this kind of functionality for you, such as tunnel-ssh. Try using one of those.

mscdex
  • 104,356
  • 15
  • 192
  • 153
  • thanks for the response I've spent a good amount of time on tunnel-ssh as well, I couldn't get it to connect, my code for tunnel-ssh is above. – user3382714 Jul 21 '16 at 18:25
  • did you try `username` instead of `user` in your config object? – mscdex Jul 21 '16 at 19:17
0

You can do it with official mongodb client for node

const sshTunnelConfig = {
  agent: process.env.SSH_AUTH_SOCK,
  username: 'ec2-user',
  privateKey: require('fs').readFileSync('./path-to-ec2-key.pem'),
  host: '3.98.174.12', //IP adress of VPS which is the SSH server
  port: 22,
  dstHost: 'docdb-cluster-vmabwxueb51y.eu-central-1.docdb.amazonaws.com',
  dstPort: 27017,
  localHost: '127.0.0.1',
  localPort: 27018 //or anything else unused you want
};


const connectionProperties = {
  sslValidate: true,
  ssl: true,
  sslCA: [fs.readFileSync('rds-combined-ca-bundle.pem')],
  useNewUrlParser: true,
  useUnifiedTopology: true,
  authMechanism: 'SCRAM-SHA-1',
  auth: {
    user: 'docdbuser',
    password: '<PASSWORD>'
  },
  tlsAllowInvalidHostnames: true,
  tlsAllowInvalidCertificates: true,
};

tunnel(sshTunnelConfig, async (error, server) => {
  if (error) {
    console.log('SSH connection error: ', error);
  }
  
   const MongoClient = require('mongodb').MongoClient;
   const client = MongoClient.connect('mongodb://localhost:27018/', propertiesConnection,
    function(err, client) {
      if(err)
        throw err;

      //Specify the database to be used
      db = client.db('database-name');

      //Specify the collection to be used
      col = db.collection('collection-name');

      //Insert a single document
      col.insertOne({'hello':'Amazon DocumentDB'}, function(err, result){
        //Find the document that was previously written
        col.findOne({'hello':'Amazon DocumentDB'}, function(err, result){
          //Print the result to the screen
          console.log(result);

          //Close the connection
          client.close()
        });
      });
    });
  
});
dimaqw
  • 637
  • 6
  • 9
  • Thank you for sharing that codes but are you sure that block is working well? Im trying to do same thing. Ssh connection is ok, but after that, i cant connect mongo client. I tried with your sample codes and same again. – Atakan Savaş Dec 05 '20 at 12:30
  • propertiesConnection vs. connectionProperties doesn't make sense....has this been tested? – Mark Richman Jan 13 '21 at 22:36
  • The `sshTunnelConfig` object's properties seem to match the interface of [tunnel-ssh](https://www.npmjs.com/package/tunnel-ssh). The following q/a should be more useful in getting this to work: https://stackoverflow.com/questions/40903566/node-js-ssh-tunneling-to-mongodb-using-mongoose – Yegor Jun 15 '21 at 08:32
0

Because all the answers above didn't work for me for some reason, I am posting the code that worked for me. I'm tunneling from my Nodejs webserver to an PostgreSQL Database on an online ubuntu vm:

const SSH2Promise = require('ssh2-promise');
const {Client } = require('pg');

let config = {
    host:process.env.SSH_HOST, //your machine IP-address like [193.xxx.xx.xxx]
    port:process.env.SSH_PORT, //port you ssh to, probably 22
    username: process.env.SSH_USERNAME, //username of your machine
    privateKey: fs.readFileSync(path.join(__dirname, "../" + process.env.PRIVATE_KEY)) //your ssh private key to log in
};

function getDBConfig(port) {

    return new Client({
        user: process.env.DB_USER,
        host: process.env.DB_HOST,
        database: process.env.DB_NAME,
        password: process.env.DB_PASS,
        port: port,
    });
}

async function makeDb(port) {
    let dbClient = getDBConfig(port);
    await dbClient.connect();
    return {
        async query(sql) {
            return (await dbClient.query(sql)).rows;
        }
    };
}

const sshConn = new SSH2Promise(config);
let con;
(async function(){
    await sshConn.connect();
    console.log("Connection established");
    let tunnel = await sshConn.addTunnel({remoteAddr: process.env.REMOTE_HOST, remotePort: process.env.REMOTE_PORT});
//Remote host: just use 127.0.0.1
//Remote port: port where your db is connected to ex: 5432
    con = await makeDb(tunnel.localPort);
})();

//use connection like this:
await con.query("SELECT ... sql statement here);
0

Connect to a private Amazon RDS instance from local node js application This will allow you to securely connect to the RDS instance from your local machine.

https://repost.aws/questions/QUuRQ-oorMQLahG6egHb4-bg/how-can-i-connect-to-a-private-amazon-rds-instance-from-local-node-js-application-which-was-earlier-using-non-ssh-sequelize-connection

gokul
  • 383
  • 1
  • 8