Download & Extend

Include SFTP support to MM File FTP Module

Project:Media Mover
Version:6.x-1.0-beta7
Component:Code
Category:feature request
Priority:normal
Assigned:Unassigned
Status:postponed

Issue Summary

I'm currently on a server that only allows SFTP connections for security reasons.
Sorry If your module already supports SFTP and I'm just not using it correctly.

If anyone has a patch I would appreciate it, otherwise I don't see anything I can do aside from ripping open the module and changing it myself.

Thanks for your time and you have a killer module.

Comments

#1

Status:active» postponed

The bummer is that SFTP requires storing the password in the db which is unsecure, or creating ssh keys which requires some knowledge. It also requires php to be complied with sftp support which not all hosts offer unfortunately. This module could pretty easily be written, it just requires a fair bit of knowledge for the end user.

#2

That makes sense. Perhaps I can convince the powers that be to allow FTP access that is read-only since I don't need to alter anything on my side via FTP. I'm just uploading to an external server, probably a better solution than hacking up the module.

Thanks again for your time.

#3

phpseclib would eliminate the requirement that the hosts copy of php have built-in sftp support.

I also don't see what the problem is storing passwords in the DB - you have to do that for regular FTP so why is it all of a sudden a bad thing when you have to do it for SFTP too?

Alternatively, you could just create public / private key pairs on the server. Using phpseclib again you'd just do extract($rsa->createKey()); and then give the user both $privatekey and $publickey. Then you'd tell the user to add that to $HOME/.ssh/authorized_keys on the target server. And that's only if they don't have a private key provided for them already. Of course, that still requires the private key, which is pretty much akin to a password.

Here's phpseclib's URL:

http://phpseclib.sourceforge.net/

Including phpseclib in a Drupal module isn't a problem, either, per this:

http://drupal.org/node/609592

There's also this:

http://drupal.org/node/671702

#4

The reason why storing SFTP passwords in the db is different than FTP is that it compromises the server that you are accessing. FTP is inherently insecure as it passes passwords over plain text- this means storing the password is less of a risk (though it still it is).

Yes, ssh keys are the correct solution, however it requires some knowledge of how to implement them.

As per http://phpseclib.sourceforge.net/, from my cursory look, it seems as though it relies quite heavily on pear libraries- this is not a deal breaker, but complicates matters.

Here's a totally rough idea for implementing a SFTP harvest. I haven't tested this code at all, but it might serve as the base for something. I cribbed it from: http://php.net/manual/en/function.ssh2-sftp.php

<?php
/**
* Run the SFTP harvest.
* @param $configuration
*   array, current media mover configuration data
* @return array of files
*
* */
function mm_sftp_harvest($configuration) {
 
$connection = new mm_SFTP($configuration['host'], $configuration['port']);
 
 
// Login with password
 
$connection->login_password($configuration['user'], $configuration['password']); 
 
// Login with ssh keys
 
$connection->login_keys($configuration['user'], $configuration['public_key'], $configuration['private_key']); 
 
 
// Get the list of files from the remote directory
 
$remote_files = $connection->scanFilesystem($configuration['directory']);
 
$files = array();
  foreach (
$remote_files as $remote_filepath) {
   
$local_filepath = tmpnam(file_directory_temp(), 'mm_sftp_file');
   
$configuration->receiveFile($remote_filepath, $local_filepath);
   
// Move the file to the directory
   
if (! file_move($local_filepath, $configuration['harvest_files_directory'])) {
     
watchdog('mm_sftp', 'Failed to move !local_filepath to !harvest_dir', array(
       
'!local_filepath' => $local_filepath,
       
'harvest_dir' => $configuration['harvest_files_directory']
        ),
       
WATCHDOG_ERROR
     
);
    }
    else {
     
$files[] = $local_filepath;
    }
  }
  return
$files;
}


/**
* Cribbed from: <a href="http://php.net/manual/en/function.ssh2-sftp.php
" title="http://php.net/manual/en/function.ssh2-sftp.php
" rel="nofollow">http://php.net/manual/en/function.ssh2-sftp.php
</a> */
class mm_SFTP {
  private
$connection;
  private
$sftp;

  public function
__construct($host, $port = 22, $hostkey = array('hostkey'=>'ssh-rsa')) {
    if (!
$this->connection = @ssh2_connect($host, $port, $hostkey)) {
     
watchdog('mm_sftp','Could not connect to !host on port !port.', array('!host' => $host, '!port' => $port), WATCHDOG_ERROR);
      return
FALSE;
    }
    return
TRUE;
  }

 
/**
   * Login via password
   */
 
public function login_password($username, $password) {   
    if (!
ssh2_auth_password($this->connection, $username, $password)) {
     
watchdog('mm_sftp', 'Could not authenticate with username !username', array('!username' => $username), WATCHDOG_ERROR);
      return
FALSE;
    }
    if (!
$this->sftp = ssh2_sftp($this->connection)) {
     
watchdog('mm_sftp', 'Could not initialize SFTP subsystem.', WATCHDOG_ERROR);
      return
FALSE;
    }
    return
TRUE;
  }

 
/**
   * Login via ssh keys
   * @TODO
   */
 
public function login_keys ($user, $ssh_key_pub_path, $ssh_key_private_path, $ssh_key_pass_phrase = NULL) {
 
$connection = ssh2_connect($this->host, $this->port, array('hostkey'=>'ssh-rsa'));
    if (!
$this->sftp = ssh2_auth_pubkey_file($this->connection, $user, $ssh_key_pub_path, $ssh_key_private_path, $ssh_key_pass_phrase)) {
     
watchdog('mm_sftp', 'Failed to connect with SSH keys using !pub !priv keys', array('!pub' => $ssh_key_pub_path, '!priv' => $ssh_key_private_path), WATCHDOG_ERROR);
      return
FALSE;
    }
    return
TRUE;
  }


  public function
uploadFile($filepath, $remote_filepath) {
   
$sftp = $this->sftp;
   
$stream = @fopen("ssh2.sftp://$sftp$remote_filepath", 'w');
    if (!
$stream)
        throw new
Exception("Could not open file: $remote_filepath");
   
$data_to_send = @file_get_contents($filepath);
    if (
$data_to_send === false)
        throw new
Exception("Could not open local file: $filepath.");
    if (@
fwrite($stream, $data_to_send) === false)
        throw new
Exception("Could not send data from file: $filepath.");
    @
fclose($stream);
  }
  
  function
scanFilesystem($remote_dir) {
   
$sftp = $this->sftp;
   
$dir = "ssh2.sftp://$sftp$remote_dir";
   
$files = array();
   
$handle = opendir($dir);
   
// List all the files
   
while (false !== ($file = readdir($handle))) {
      if (
substr("$file", 0, 1) != ".") {
        if (
is_dir($file)){
 
//      $tempArray[$file] = $this->scanFilesystem("$dir/$file");
       
}
        else {
         
$tempArray[]=$file;
        }
      }
    }
   
closedir($handle);
    return
$tempArray;
  }  

  public function
receiveFile($remote_file, $local_file) {
   
$sftp = $this->sftp;
   
$stream = @fopen("ssh2.sftp://$sftp$remote_file", 'r');
    if (!
$stream)
        throw new
Exception("Could not open file: $remote_file");
   
$contents = fread($stream, filesize("ssh2.sftp://$sftp$remote_file"));          
   
file_put_contents ($local_file, $contents);
    @
fclose($stream);
  }
      
  public function
deleteFile($remote_file){
   
$sftp = $this->sftp;
   
unlink("ssh2.sftp://$sftp$remote_file");
  }
}
?>

#5

The reason why storing SFTP passwords in the db is different than FTP is that it compromises the server that you are accessing. FTP is inherently insecure as it passes passwords over plain text- this means storing the password is less of a risk (though it still it is).

Yes, ssh keys are the correct solution, however it requires some knowledge of how to implement them.

Storing the private key in the DB compromises matters just as much as storing the password in the DB. The reason you use publickey authentication in SSH is if you don't want the SSH server to know the password. ie. SSH protects against MITM attacks but it doesn't protect against server compromise. That's why you use publickey authentication. But if the client is compromised and have either the plaintext password or the private key on the server it's game over either way.

Why do you suppose private keys can be encrypted? Why do you suppose this bug report was filed on PEAR's Crypt_RSA?:

http://pear.php.net/bugs/bug.php?id=7257

The reason, of course, is that a compromised private key is just as much a game ender as compromised passwords are. An encrypted private key gets you the best of both private keys and passwords. You can't access the actual private key without the password, which is only, in theory, stored in your head, and the server doesn't get the password at all. Of course, Media Mover won't work if it requires user intervention every time it tries to upload something, so as nice as encrypted private keys are, they're just not all that practical.

I mean, just look at ssh-agent or pageant for Windows. With those tools you just get auto logged in. And unless your SSH server is firewalled it doesn't matter where you're at - they'll log you in anywhere. Just like saved passwords will.

As per http://phpseclib.sourceforge.net/, from my cursory look, it seems as though it relies quite heavily on pear libraries- this is not a deal breaker, but complicates matters.

Which PEAR libraries do you suppose phpseclib uses? Both PEAR and phpseclib have a Crypt_RSA but they're totally different, per the above pear.php.net link. If they were the same then people wouldn't be recommending phpseclib *over* PEAR since they're the exact same thing.

PEAR and phpseclib also both have a Net_SSH2:

http://pear.php.net/package/Net_SSH2

Except that PEAR's implementation is just a wrapper for PECL's ssh2 extension whereas phpseclib's Net_SSH2 isn't a wrapper for anything save for maybe fsockopen().

Both PEAR and phpseclib have a Math_BigInteger but the author is the same. Near as I can figure, the phpseclib author tried to release all the phpseclib packages in PEAR, gave up, and then released them on their own.

Both PEAR and phpseclib also have a PHP_Compat but PHP_Compat uses the same license as phpseclib. Plus, phpseclib only includes a small subset of PEAR's PHP_Compat and at least one of the PHP_Compat functions it contains was written by the phpseclib author themselves.

Really, PHP_Compat is the only thing phpseclib and PEAR have in common (well, aside from similar naming conventions, coding standards, etc). And since they all have the same license that's not really a problem either.

#6

@bespokenfan

I don't think ssh creds- whether keys, key passwords, or passwords - should ever be stored in the db- this is because of the risk of compromising an additional machine outside of the Drupal environment. Yes, the difference between storing ssh keys with a key password (or using a key without a password) and passwords is negligible here, however the ability to easily revoke access on the remote machine with ssh keys is more desirable from my perspective. The real concern from me is locking the ssh user down on the remote system- preferably to read or write access only depending on what is needed.

The problem that I have with implementing ssh via php is that the potential to reduce the security that SSH offers is really easy- it's almost a false sense of security unless someone has a degree of system administration knowledge. This being said, it is obviously possible to do and my proto code above shows how it can be done with media mover.

I personally don't care what library is used to implement ssh in php. I'm somewhat biased against pear because it's often not easy to implement on shared hosting, hence my reticence. I also don't like the ssh2 module for the same reasons. From your post it sounds like phpseclib may provide a better solution- if that's true, has anybody done a drupal wrapper for it? If not, it'd be great to build one and I'd be happy to build a media mover module that relies on it.

Ultimately I don't think we're in disagreement. We both are clearly concerned about security and making sure that what ever solution is used does incur any additional risks than are already inherent in connecting drupal to remote systems.

#7

I can't really comment on your concern that SSH via PHP might offer a false sense of security. I think that people sometimes assume SSH absolves them of the responsibility of having to chose a strong password, but as to this particular concern... it's not one I've had but I do see what you're saying.

#9

I am very interested in using sftp with media mover because I made my site on a rackspace cloud server and I built the site without ftp installed.
Has there been any developments with this feature, is there anywhere I can get some help with this thanks !!
I can provide any info that would help make this easier to answer or help with.

thanks!