I'd like to establish an ssh tunnel over ssh to my mysql server.
Ideally I'd return a mysqli db pointer just like I was connecting directly.
I'm on a shared host that doesn't have the SSH2 libraries but I might be able to get them installed locally using PECL.
If there's a way that uses native commands that would be great.
I was thinking something like this, but without those libraries it won't work.
$connection = ssh2_connect('SERVER IP', 22);
ssh2_auth_password($connection, 'username', 'password');
$tunnel = ssh2_tunnel($connection, 'DESTINATION IP', 3307);
$db = new mysqli_connect('127.0.0.1', 'DB_USERNAME', 'DB_PASSWORD',
'dbname', 3307, $tunnel)
or die ('Fail: ' . mysql_error());
Anyone have any ideas? I'm running a shared CentOS linux host at liquidweb.
Any thoughts on making the tunnel persistent? Is it possible to establish it with another script and just take advantage of it in PHP?
Thanks.
I would use the autossh tool to create a persistent ssh tunnel to your mysql database. Then all you have to do in your PHP code is point it to localhost.
You can test this (without automatic restart) by doing:
ssh -L 3306:localhost:3306 user#domain.com
Setup a ssh key so that you don't need to use passwords, and tweak the .ssh/authorized_keys file on the mysql system to only allow it to be used for port forwarding.
For more info on ssh tricks see Brian Hatch's excellent series on SSH and port forwarding.
The tunnel must be keep open during the course of the SQL action(s). The following example from RJMetrics explains:
Here's the generic SSH command syntax:
ssh -f -L bind-ip-address:bind-port:remote-ip-address:remote-port \
username#remote-server [command] >> /path/to/logfile
Here's how to securely establish a remote database connection in just 2 lines of PHP code:
shell_exec("ssh -f -L 127.0.0.1:3307:127.0.0.1:3306 user#remote.rjmetrics.com sleep 60 >> logfile");
$db = mysqli_connect("127.0.0.1", "sqluser", "sqlpassword", "rjmadmin", 3307);
We use the shell_exec() function to create the tunnel with a 60 second opening window, and then use the mysqli_connect() function to open a database connection using the forwarded port. Note that we must use the "mysqli" library here because mysql_connect() does not allow us to specify a port and mysql_* functions are deprecated.
sleep 60: When it comes to tunnel connections, we basically have two options: leave the connection open all the time or open it and close it as needed. We prefer the latter, and as such we don't specify the -N option when establishing a tunnel, which would leave it open until the process is manually killed (bad for automation). Since -N is not specified, our tunnel will close itself as soon as its SSH session isn't being used for anything. This is ideal behavior, except for the few seconds between when we create the tunnel and when we get a MySQL connection up and running via the tunnel. To buy us some time during this period, we issue the harmless sleep 60 command when the tunnel is created, which basically buys us 60 seconds to get something else going through the tunnel before it closes itself. As long as a MySQL connection is established in that timeframe, we are all set.
I tried it by doing SSH both by root credentials and and public private key pair. It allows me to conect through command line but not through PHP code.
I also tried by creating a tunnel (by using SSH2 functions), and running shell commands from PHP code (system, exec, etc.); nothing worked.
Finally I tried SSH2 function to execute shell command and it finally worked :)
Here is my code, if it helps you:
$connection = ssh2_connect($remotehost, '22');
if (ssh2_auth_password($connection, $user,$pass)) {
echo "Authentication Successful!\n";
} else {
die('Authentication Failed...');
}
$stream=ssh2_exec($connection,'echo "select * from zingaya.users where id=\"1606\";" | mysql');
stream_set_blocking($stream, true);
while($line = fgets($stream)) {
flush();
echo $line."\n";
}
Try this if want to use PHP functions specifically.
It is possible, but why? It's more complicated than it needs to be, and error prone.
Can you not run the database locally? If not, can you not use something like SQLite, XML files or something else that doesn't require a separate server daemon?
You really do not want to initialise the tunnel inside the PHP scripts. Initialising an SSH tunnel takes a long time (can easily be a second or two), so that will mean every page that connects to the database will have a 2 seconds delay while loading..
If you have to do this (which I strongly recommend against), the best method would be to have a script that maintains the connection in the background..
Setup a SSH keypair. Then using autossh, or a simple script which would execute the required SSH command, wait till the process died and start it again. It could be more intelligent and try and run a command every 10-20 seconds, reconnecting if it fails.
Using the SSH keypair, you wouldn't have to hardcode the remote users password in the script. I would recommend limiting what the remote user can do (by using a dedicated tunnel user, and giving it a restricted shell, see this question)
In short, I would strongly suggest trying SQLite first, then see how feasible it to store data using XML files, then try bugging your web-host about getting a local MySQL server, then look into the SSH tunnelling option.
Eric,
I think you are out of luck on this one. You can either use the ssh extension in your PHP code, or if you have access to the server, you could try to create a ssh tunnel on the command-line.
You probably need special permissions to do that, though. It also looks like you don't have ssh access to this hosting account.
--Joao
I think your best bet is to find a different hosting provider; I suggest a VPS solution (Virtual Private Server) which gives you full control over your web host. THat way, if you don't like the default configuration, you can upgrade it yourself.
Related
If have an amazon ec2 instance running php 5.5 and apache2. I am connecting to my RDS database using a simple test.php script. When I run the script as ec2-user or as root I am able to get a connection.
When I run the same script via my webserver I get
Connection Can't connect to MySQL server on 'ip' (13).
I've tried the public and private IP addresses. This script does work from my other, non-amazon, webservers running the same php and apache. Not sure what to look at next since it works on the command line. Here's the example of the test.php script, pretty straight forward.
define("DB_IP”,”ip”);
define("DB_USERNAME”,”username”);
define("DB_PASSWORD”,”password”);
define("DB_DATABASE”,”database”);
echo "Connecting...\n";
$conn = new mysqli(DB_IP, DB_USERNAME, DB_PASSWORD, DB_DATABASE, 3306);
// Check connection
if ($conn->connect_error) {
die(date('H:i:s')." Connection " . $conn->connect_error);
}
echo date('H:i:s')." Connected successfully\n";
Why would the connection work on the command line but not work when called from the web server?
What are you running on your EC2?
Just asking in case it's an SELinux (Security-Enhanced Linux), in which case it could possibly be the security limitation.
At your terminal, if you run:
getsebool -a | grep httpd
you should be able to see this limitation (whether your webserver can "network" or not. If it cannot, then run this, which should fix your problem:
setsebool -P httpd_can_network_connect 1
Hope that solves it, otherwise I don't see where the issue can arise, especially since you say you can connect via terminal (so AWS security groups should not be the issue).
Keep us posted ;)
I have a central system(web application) which is supposed to connect to a set of nodes one by one (clients), to get a record set from database exposed through mysql views.
I am in a search of best possible implementation for this scenario. Currently I have this flow in my mind.
1. First Approach
Pseudo code
$dataFetched=array();
$clients= getListOfClients();
//
foreach($clients as $client){
// client={details:{id,name,etc....},
// ssh:{pem:'client.pem',localMysqlPort:3307 ,remoteMyqlPort:3306,
// sshUser:'demo#clientHost.com'},
// mysql:{user:'tunnelUser',password:'password'}
// }
//connect ssh connection
$ssh_connection=new Ssh_connect($client->ssh)
if($ssh_connection->status){
$dataFetched[$client->id]=array("status"=>'OK','data'=>fetchMysqlData($client->mysql))
}else{
$dataFetched[$client->id]=array("status"=>'NO_OK','data'=>array())
}
$ssh_connection->destroyTunnel();
unset($ssh_connection);
}
Some Concerns/questions with this approach
Is there a ssh lib/extension to provide me this kind of functionality ?
What is best secure way to store "pem" files for each client ?
Another approach ?
2. Second Approach
Write bash script to implement above pseudo-code
setup cron job to update database
Web application does not know about external clients. It will use table updated
by cron-job to render data.
Some point:
Data size is very light
Both consumer and provider are web-applications
Applications does not know each other except SSH
It should be flexible to add new client node ( I don't known how I will do this in second approach)
Which one will be better from security wise Reverse Tunnel or Forward Tunnel ?
As I know in case of reverse tunnel, echo client knows about server which desires to connect with it. So we need to setup a less privileged user to on each client node to give access to server, which seems more secure as I don't have to keep all pem files in one place at Central server. Correct me if I am getting it wrong.
Your suggestions are much appreciated
There is an SSH library for PHP. It's called ssh2 and it is available on PECL.
It has a dependancy on libssh2, but it should not be too hard to get up and running on a Linux-based machine.
The best way to store the pem files is outside your web directory, with limited permissions - just enough for your web application to be able to read the files. Alternatively, you could set up the ssh-agent daemon to run as your web application.
(I am hoping here that your PHP apps don't all run under the same user. For Apache, there is the little known mod_ruid2 module that allows you to change the user and chroot a PHP web app, without resorting to setting up fastcgi or suExec).
Sadly, ssh2_tunnel only creates a new PHP resource, it's not something that you can use to create a new mysql connection (well, at least not using mysqli_connect & friends).
What you could do is execute the SQL commands on the local server, storing the database password inside ~/.my.cnf
~/.my.cnf:
[client]
password = lolcats1234
And then with PHP & ssh2 you could run the following code once connected:
stream_set_blocking($stream, true);
$stream = ssh2_exec($connection, 'mysql -u db -p db -e "select * from table"');
$first = true;
while($line = fgets($stream)) {
if ($first) {
$first = false;
continue;
}
// process one line of results...
echo $line."<br />";
}
Alternatively, if you just use a Reverse Tunnel, you can just mysql_connect using the local script, without needing to worry about doing any SSH work at all.
I have already written a php file that connects to the mysql database locally. Now, I want to connect to a remote database via SSH. Currently the connect function for my database is the following in php:
$this->db = new mysqli(_SERVR_URL, _SERVR_USER , _SERVR_PASS, _SERVR_DB);
if ($this->db->connect_errno) {
echo "Failed to connect to MySQL: (" . $this->db->connect_errno . ") " . $this->db->connect_error;
}
else{
//echo "Successfully connected!! <BR><BR>";
}
I want to only change the connect function (above) so that the rest of the code still works. I have successfully installed the phpseclib and am not interested in installing php's ssh extensions because those were not working after nearly 5 hours of effort. The phpseclib is working, and I think this because when I use require it does not die.
However, when I try to start working with the ssh stuff, it throws a server error:
$ssh = new Net_SSH1(myURL);
The way that I usually SSH into my server is with a .pem file. Can I get some guidance on:
Why the current code may be throwing an error?
If this is possible.
How would you write the connection code with the .pem file.
I think you are out of luck on this one. You can either use the ssh
extension in your PHP code, or if you have access to the server, you
could try to create a ssh tunnel on the command-line.
You probably need special permissions to do that, though. It also
looks like you don't have ssh access to this hosting account.
duplicate answered by #jpm
Setting up tunneling posted by #Ólafur Waage on Connect to a MySQL server over SSH in PHP
And this one for tunneling by #Sosy
shell_exec(“ssh -f -L 3307:127.0.0.1:3306 user#remote.rjmetrics.com sleep 60 >> logfile”);
$db = mysqli_connect(’127.0.0.1′, ‘sqluser’, ‘sqlpassword’, ‘rjmadmin’, 3307);
The mysql extension doesn't currently support this. Modifying the extension, itself, might not be that difficult, but at that point, it'd have to be a custom PECL extension, at best. The idea was discussed on the PHP Internals mailing list a while back:
http://comments.gmane.org/gmane.comp.php.devel/79520
ssh_exec() is refusing to execute a command in Windows.
Here is my code:
<?php
$connection = ssh2_connect('localhost', 22);
ssh2_auth_none($connection, 'root');
$stream = ssh2_exec($connection, 'C:\Program Files\CCleaner\CCleaner.exe',FALSE);
?>
It shows me the following warning: Unable to request a channel from remote host in.
Firstly, it is unlikely that you are going to be able to connect to your Windows server using ssh2_connect, because SSH is not a protocol typically used to connect to Windows, nor is it an available item in any Windows install.
Secondly, make sure that you have created the user 'root' on the Windows server, because it won't exist by default.
Alternatively
If you are actually connecting to a Unix/Linux server FROM a Windows box, then the fact that you are trying to run a Windows command on it ('C:\Program Files\CCleaner\CCleaner.exe'), will fail. You can only execute commands on the remote server that are present, and any command that begins with "C:\" will obviously fail because Linux has an entirely different filesystem structure from Windows.
So from the code above it looks like you're probably trying to connect to a Linux server using the username 'root' with no password (which according to the PHP.net manual (ssh2_auth_none:
Attempt "none" authentication which usually will (and should) fail.
But as you have said you are trying to execute a command in Windows, and you are connecting to the server with host name 'localhost' - perhaps you actually are running this on Windows, and simply trying to use SSH to connect to the Windows server - which is pointless as the PHP code shown above will already be being executed on the Windows server, making the SSH connection completely redundant except to attempt to execute a command with elevated privileges (as 'root').
If you are trying to execute the command remotely using PHP as an administrator user, which is probably a bad idea, then you could consider using the function.exec.php command in combination with the Windows runas command. Or you could just run your web server as an administrator, which is both very easy to do and very bad for security.
You could basically achieve what you are trying to do above, by installing an SSH server on Windows (What are some good SSH Servers for windows?) - after which you would have to log into the server using a user that exists, and use the password for that user (or create the user 'root' and use that).
Depending on your SSH server installation, running the command C:\Program Files\CCleaner\CCleaner.exe could work in the way you described above.
Is there any 'sudo' command for Windows?
How to run PHP as an Administrator
There are more examples of how to go about this in Linux, different methods have different advantages and disadvantages.
Execute root commands via PHP
How to run PHP exec() as root?
<?php
$connection = ssh2_connect('localhost', 22);
ssh2_auth_none($connection, 'root');
$stream = ssh2_exec($connection, 'C:\Program Files\CCleaner\CCleaner.exe', false);
stream_set_blocking ($stream, true);
I know this question has been asked before in many different ways but I'm still scratching my head over why I can't get this to work.
Firstly I have two SLES servers setup, these are Server A & Server B which are both running on a small private network which is only accessed by a dedicated team.
Server A is configured as a web server which is running Apache, PHP, MYSQL and ssh all of which are running problem free.
Server B is used to run menial tasks with ssh also installed and activated.
I have created my rsa key on Server A and installed it on Server B which when run at the command line logs me in straight away with out asking for a password. I have repeated this process for both root & nobody accounts on Server A.
I have added this a PHP page to Server A which looks like:
<?php
shell_exec('ssh root#192.162.0.5 ./StartTest.sh');
header("Location: archive.php?page=home");
?>
But when I run it it does not create my folder. If I run this from the command line it works for both (I think both, I can't recall if I did try this for the nobody account on the cli now) root & the nobody account. I even went as far as adding the nobody account to the root group but still no joy.
Have I missed some thing here. All I would like to do is connect from Server A to Server B via php & ssh to execute one command and redirect to a another page on the web site.
Any help would be graciously appreciated as my paracetamol stock is running low.
The built-in SSH support George Cummins speaks of is non-existent. It is an extension to PHP that's not included by default. It has to be compiled separately and is notoriously difficult to setup / use. My recommendation would be to use phpseclib, a pure PHP SSH implementation:
<?php
include('Net/SSH2.php');
$ssh = new Net_SSH2('www.domain.tld');
if (!$ssh->login('username', 'password')) {
exit('Login Failed');
}
echo $ssh->exec('pwd');
echo $ssh->exec('ls -la');
?>
You said "I have added this a PHP page", so I will assume that you are executing this script via your web server, rather than as a standalone script.
As such, the script may not be running from the directory you expect. You should use absolute (rather than relative) paths to ensure that the script finds the ssh binary and your script:
shell_exec('/path/to/ssh root#192.162.0.5 /home/yourdirectory/scripts/StartTest.sh');
You will also need to confirm that the webserver user had permissions to execute ssh and the StartTest.sh script.
I know that I'm too late at this answer but maybe can help someone:
To use shell_exec and ssh you need to add as parameter to ssh these
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o LogLevel=quiet
So the command doesn't try to create .ssh folder and you have a clear output without log of ssh