4

I am running a node Express website on my OS X machine locally.

I need to ssh to a remote mysql database so I can start writing queries against it.

Now I'm able to ssh to our remote server (running the mysql database) in the cloud when I do it via my OS X Yosemite terminal.

But I haven't been successful trying to do it in code using the node-mysql and tunnel-ssh node middleware.

My code runs but doesn't really error out except for my GET when debugging my express app in Webstorm.

Some facts:

  • I'm able to SSH into mySQL from OS X using this command:

    ssh -L 3306:127.0.0.1:3306 ourCloudServerName.net MyUserNameHere

  • I'm able to then connect to mySQL using the following command:

    mysql -h localhost -p -u myUserNameHere

  • When I'm at the mysql command prompt and type SHOW GLOBAL VARIABLES LIKE 'PORT', I get that the server (or database I guess that means...not sure) is running on port 3306

  • I'm debugging via Webstorm 10.0.3 This means I'm debugging my Node Express app which starts like this:

    var port = 3000;

    app.set('port', port);

    app.listen(app.get('port'), function(){ console.log('Express web server is listening on port ' + app.get('port')); })

    • I can run my express app via localhost:3000 in Chrome

Now here is my first attempt to try and use both node-mysql and tunnel-ssh

mysqlConfig.js

var databaseConfig = module.exports = function(){};

 module.exports = {

    mySQLConfig: {
       host: '127.0.0.1',
       username: 'root',
       password: 'rootPassword',
       database: 'SomeDatabaseName',
       port: 3306,
       timeout: 100000
    },

    sshTunnelConfig: {
       username: 'myusername',
       password: 'mypassword',
       host: 'ourCloudServerName\.net',
       port: 22,
       //localPort: 3306, //4370
       //port: 3333,
       //localPort: 4370,
       dstPort: 3306,
       srcHost: 'localhost',
       dstHost: 'localhost',
       localHost: 'localhost'
    }
 };

connection.js

 var mysql = require('mysql'),
    config = require('./mysqlConfig'),
    tunnel = require('tunnel-ssh');


var connection = module.exports = function(){};

createDBConnection = function(){
    var mysqlConnection = mysql.createConnection({
        host: config.mySQLConfig.host,
        user: config.mySQLConfig.username,
        password: config.mySQLConfig.password,
        database: config.mySQLConfig.database,
        connectTimeout: config.mySQLConfig.timeout
    });

    return mysqlConnection;
};


connection.invokeQuery = function(sqlQuery){

    var data = undefined;

    var sshTunnel = tunnel(config.sshTunnelConfig, function(error, result) {

        var sqlConnection = createDBConnection();

        sqlConnection.connect(function (err) {
            console.log(err.code);
        });
        sqlConnection.on('error', function (err) {
            console.log(err.code);
        });

        data = sqlConnection.query({
            sql: sqlQuery,
            timeout: config.mySQLConfig.timeout
        }, function (err, rows) {

            if (err && err.code) {
                console.log("error code: " + err.code)
            }
            if (err) {
                console.error("error stack: " + err.stack)
            }
            ;

            if (rows) {
                console.log("We have Rows!!!!!!")
            }
        });

        sqlConnection.destroy();
    });

    return data;
};

router/index.js

var connection = require('../config/database/connection');
var express = require('express');
var router = express.Router();

router.get('/', function(req, res) {
    var sqlStatement = "Select top 10 from someTable";

    var rows = connection.invokeQuery(sqlStatement);
});

module.exports = router;

So I try debugging in Webstorm and I either never really get a good error printed to the console or if I do, it's a generic node error like. I could have a fundamental code problem and/or just not setting up the values right or using the middleware right, I just don't know, It's not telling me much during debug. I just know that:

1) I'm not getting a connection to the server via ssh. Connections show 0 in the ssh server object during debug

2) mysql connection object shows disconnected, never connected

3) I'm also getting this node error in Webstorm while running the express website which doesn't tell me jack:

enter image description here

What connection is refused, I'm running this website via localhost port 3000. Is this saying it couldn't connect to ssh? I have no idea here. This doesn't always show up or happen, it's an intermittent error and might just be a webstorm debug thing to where I just need to run debug again and usually goes away...but might be interrelated, no clue.

And here is what my config object has when I check it out during debug

enter image description here enter image description here enter image description here

And in tunnel-ssh/index.js, line 70 where it returns the server that it attempts to create, I see there is no ssh connection:

enter image description here

UPDATE - per suggestions from answers

enter image description here enter image description here enter image description here enter image description here

PositiveGuy
  • 17,621
  • 26
  • 79
  • 138
  • Can you try console.log(error, result) inside the SSH tunnel callback to see what the SSH error is? – Jack Guy Jun 05 '15 at 05:53
  • where would I put that right after I close the DB connection (destroy line)? – PositiveGuy Jun 05 '15 at 06:13
  • Just right at the beginning of your callback (after var sshTunnel...). If what your post says is true, an SSH connection is never being established so we need to see what's causing that. – Jack Guy Jun 05 '15 at 07:13
  • I also need to confirm if my config values look right. I think I got them right...but who knows... I am basing it off how I connected via ssh in terminal but I'm not sure if I am wiring up the right values to dstPort, dstHost, etc. or if I even need all the variables I've setup TBH. I have no way of confirming if the ssh connection call even got there or failed due to invalid config values that are rejected by the Ubuntu / mySQL or what so I'm flying blind. – PositiveGuy Jun 05 '15 at 07:24
  • I wonder if you could use `127.0.0.1` instead of `localhost`; I know they should be the same, but it eliminates one more potential issue. – Ja͢ck Jun 05 '15 at 07:31
  • good idea jack, will try – PositiveGuy Jun 05 '15 at 07:50
  • didn't make a diff, hard coded the 127 – PositiveGuy Jun 05 '15 at 08:15
  • @J4G yea when I put the logging of the error, result, both are undefined. – PositiveGuy Jun 05 '15 at 08:17

2 Answers2

5

This can actually be simplified if all you need to do is tunnel MySQL connections from inside your application. The mysql2 module has (better) support for passing a custom stream to use as the database connection, this means you do not have to start a local TCP server and listen for connections to tunnel through.

Here's an example using mysql2 and ssh2:

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

var sshConf = {
  host: 'ourCloudServerName.net',
  port: 22,
  username: 'myusername',
  password: 'mypassword',
};
var sqlConf = {
  user: 'root',
  password: 'rootPassword',
  database: 'SomeDatabaseName',
  timeout: 100000
};

var ssh = new SSH2Client();
ssh.on('ready', function() {
  ssh.forwardOut(
    // source IP the connection would have came from. this can be anything since we
    // are connecting in-process
    '127.0.0.1',
    // source port. again this can be randomized and technically should be unique
    24000,
    // destination IP on the remote server
    '127.0.0.1',
    // destination port at the destination IP
    3306,
    function(err, stream) {
      // you will probably want to handle this better,
      // in case the tunnel couldn't be created due to server restrictions
      if (err) throw err;

      // if you use `sqlConf` elsewhere, be aware that the following will
      // mutate that object by adding the stream object for simplification purposes
      sqlConf.stream = stream;
      var db = mysql2.createConnection(sqlConf);

      // now use `db` to make your queries
    }
  );
});
ssh.connect(sshConf);

You will want to expand upon this example of course, to add error handling at the ssh and mysql level in case either go away for some reason (TCP connection gets severed or the ssh/mysql services get stopped for example). Typically you can just add error event handlers for ssh and db to handle most cases, although you may want to listen for end events too to know when you need to re-establish either/both the ssh and db connections.

Additionally it may be wise to configure keepalive at both the ssh and mysql level. ssh2 has a couple of keepalive options that behave just like the OpenSSH client's keepalive options. For mysql2, typically what I've done is just call db.ping() on some interval. You can pass in a callback that will get called when the server responds to the ping, so you could use an additional timer that gets cleared when the callback executes. That way if the callback doesn't execute, you could try to reconnect.

mscdex
  • 104,356
  • 15
  • 192
  • 153
  • thanks let me try. I wonder then what's the point of connect-ssh – PositiveGuy Jun 05 '15 at 15:00
  • sweet! appears I have an ssh connection. Just getting mysql auth errors now, working on it, should hopefully be working soon. I didn't even see mysql2, wtf! should have been using this in the first place! – PositiveGuy Jun 05 '15 at 15:57
  • I guess it was just a matter of trying out different mysql middleware. Thanks – PositiveGuy Jun 05 '15 at 16:59
  • Thanks for all the help. – PositiveGuy Jun 05 '15 at 18:26
  • @mscdex This doesn't work for SSL connections to MySQL though. Are you aware of what the problem could be? The host I'm using is not localhost. This throws me a timeout error for SSL connections but it works when i connect using putty. – Akhoy May 17 '19 at 06:56
  • @Akhoy If the MySQL server is not the same as the SSH server, then you'd have to replace the destination `'127.0.0.1'` address in the code in the answer with the ip/host of the MySQL server that's reachable from the SSH server. – mscdex May 17 '19 at 09:30
  • @mscdex Sorry, should've been more clearer. The change you mentioned already exists in my code but it works for non-SSL connections to the MySQL server. It just times out when I add the SSL property to my MySQL config object. I'm trying to connect to an AWS RDS instance. Not sure what the issue could be. – Akhoy May 17 '19 at 10:23
0

This is SELinux problem and your httpd / webserver is not allowed to connect over network, answer to your problem is following command:

setsebool -P httpd_can_network_connect 1

It should work fine then. I believe you are using apache?

maqsimum
  • 56
  • 1
  • 1
  • 5
  • I tried to paste that into terminal (on my local OS X machine) but it doesn't recognize setsebool – PositiveGuy Jun 05 '15 at 06:20
  • Isn't this command supposed to be copied in SSH terminal - in other words the server you are trying to connecT? – Peter Jun 05 '15 at 06:21
  • if this is to be run from Ubuntu, I do not have access to the server and don't know why I'd need to run this if I'm able to ssh from my OS X, but can't in node – PositiveGuy Jun 05 '15 at 06:22
  • @pbwned what do you mean copied in SSH terminal. – PositiveGuy Jun 05 '15 at 06:22
  • As I understand, you've copied the command that maqsimum gave you to your OS X terminal, right? But you are running the SSH on an other server (called ourCloudServerName). Copy the code there.. – Peter Jun 05 '15 at 06:25
  • @WeDoTDD you need to run this command on your webserver where your httpd or your application is running. Where is your node app is running, on OS X ? – maqsimum Jun 05 '15 at 06:27
  • I don't maintain that server. I can't just run that command on the Ubuntu server running mysql. – PositiveGuy Jun 05 '15 at 06:27
  • My application is running locally on my Macbook Pro / Yosemite, not on an apache server yet as this is all new code in my local dev environment only, just my PC. Which is why I'm running it and debugging in webstorm, all local but want to hit that remote DB via ssh. The DB is running in Ubuntu/apache – PositiveGuy Jun 05 '15 at 06:27
  • @WeDoTDD Follow again your first step as given in your question here to connect to mysql server, then run query `show processlist;` and see what is your hostname in the result, and then make sure your user of your node app is granted privileges `grant all to db.* on 'user'@'host';` Moreover, it appears that, your route your request via your Ubuntu server, we need to understand that, if your request goes from Ubuntu to MysQL using httpd, then you must execute the command I suggested in answer anyhow, else, you'll not be able to achieve what you want. – maqsimum Jun 05 '15 at 06:35
  • one thing I get when I ssh is "bind: address already in use"..although I'm sill able to connect to ubuntu successfully..just an fyi – PositiveGuy Jun 05 '15 at 06:57
  • who would have ever thought this was going to be this involved just to get node to talk to mysql. I sure didn't expect it. – PositiveGuy Jun 05 '15 at 07:12
  • yea no luck after granting privilages – PositiveGuy Jun 05 '15 at 07:21
  • I ran setsebool on our remote server and it says it's not even installed – PositiveGuy Jun 05 '15 at 14:59