PHPからSSH2接続を行う方法(phpseclib 2.0.31)
PHPを動かしているサーバーから別にサーバーにSSHで接続してなんやかんやの操作を行いたいことがあったのでいろいろと調べた。
PHPには公式でSSH2というPECL拡張モジュール(https://www.php.net/manual/ja/book.ssh2.php)があるが、インストールが少々面倒。
なので他にいい方法が無いか探してみるとphpseclibというライブラリーが公開されていたので、これを使うことにした。
■phpseclib
https://github.com/phpseclib/phpseclib
■サンプルコード
http://phpseclib.sourceforge.net/new/ssh/examples.html
今回はお試しとして
・SSH2でログインする。
・「cd /var/data」を実行する。
・「ls -la」を実行する。
・「zip -r src.zip src」を実行する。
・作成したsrc.zipファイルをダウンロードする。
という操作をPHPで行う。
また、動作環境は以下の通り。
・AmazonLinux2
・PHP7.4(AmazonLinuxExtra)
・phpseclib 2.0.31
※最新版は3.xだが、実際に導入したかった環境のPHPのバージョンがだいぶ古くて動かなかったため、2.xを採用した。
下準備
PHPを動かすサーバーにて以下のコマンドを実行する。
amazon-linux-extras enable php7.4
yum clean metadata
yum -y install php
php -v
PHP 7.4.15 (cli) (built: Feb 11 2021 17:53:39) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
mkdir /var/php_test
「/var/php_test」配下にSSHでログインするための秘密鍵ファイルを置く。
ファイル名は「key.pem」としておく。
今度はSSHで操作される側のサーバーにて以下のコマンドを実行する。
mkdir /var/data
mkdir /var/data/src
「/var/data/src」配下に適当にいくつかファイルを置く。
chmod -R 777 /var/data
お試しPHPコード
PHPを動かすサーバーの「/var/php_test」配下にssh_sample.phpという名前で以下の内容のファイルを保存する。
<?php
// 簡易autoloader
$path = dirname(__FILE__) . "/phpseclib-2.0.31";
set_include_path(get_include_path() . PATH_SEPARATOR . $path);
spl_autoload_register(function($class){
if (strpos($class, "phpseclib") === 0) {
$class = str_replace("\\", "/", $class);
include $class . ".php";
}
});
use phpseclib\Net\SSH2;
use phpseclib\Net\SCP;
use phpseclib\Crypt\RSA;
// ログイン情報
$host = "127.0.0.1";
$port = "22";
$username = "username";
$key_file = "/var/php_test/key.pem";
try {
$ssh = new SSH2($host, $port);
$key = new RSA();
$key->loadKey(file_get_contents($key_file));
if (!$ssh->login($username, $key)) {
throw new Exception("ログインできませんでした。");
}
// タイムアウトを無効にする。
$ssh->setTimeout(0);
echo "~~~~~ START ~~~~~\r\n";
// ログイン成功時のコンソール内容を読み込む。
echo $ssh->read('/\[(root|' . $username . ')@.*\ .*][#$]/', SSH2::READ_REGEX);
$ssh->write("cd /var/data \n");
echo $ssh->read('/\[(root|' . $username . ')@.*\ .*][#$]/', SSH2::READ_REGEX);
$ssh->write("ls -la \n");
echo $ssh->read('/\[(root|' . $username . ')@.*\ .*][#$]/', SSH2::READ_REGEX);
$ssh->write("zip -r src.zip src \n");
echo $ssh->read('/\[(root|' . $username . ')@.*\ .*][#$]/', SSH2::READ_REGEX);
// ファイルをダウンロードする。
$scp = new SCP($ssh);
$scp->get("/var/data/src.zip", dirname(__FILE__) . "/donwload.zip");
echo "\r\n";
echo "~~~~~ END ~~~~~\r\n";
} catch (Exception $e) {
echo "エラー発生\r\n";
echo $e->getMessage() . "\r\n";
}
結果
php -f ssh_sample.php
~~~~~ START ~~~~~
Last login: Fri Apr 16 09:14:53 2021 from xxx.xxx.xxx.xxx
__| __|_ )
_| ( / Amazon Linux 2 AMI
___|\___|___|
https://aws.amazon.com/amazon-linux-2/
No packages needed for security; 2 packages available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-xxx-xxx-xxx-xxx ~]$ cd /var/data
[ec2-user@ip-xxx-xxx-xxx-xxx data]$ ls -la
total 0
drwxrwxrwx 3 root root 32 Apr 16 09:14 .
drwxr-xr-x 20 root root 281 Apr 16 08:11 ..
drwxrwxrwx 8 root root 160 Apr 16 08:13 src
[ec2-user@ip-xxx-xxx-xxx-xxx data]$ zip -r src.zip src
updating: src/ (stored 0%)
updating: src/html/ (stored 0%)
updating: src/html/.htaccess (deflated 47%)
・・・略・・・
updating: src/composer.json (deflated 43%)
[ec2-user@ip-xxx-xxx-xxx-xxx data]$
~~~~~ END ~~~~~
実行後にPHPを動かすサーバーの「/var/data」配下を見てみると「src.zip」がダウンロードできていたのでバッチリ成功。
メモ
writeメソッドでコマンドを実行した際に、そのコマンドが成功したのかどうかのエラーハンドリングの方法を考える必要がある。
何かしらファイルを作成するようなコマンドを実行した場合は、そのあとにlsコマンドでファイルの存在を確認し、ファイルが無ければエラーにする等。
writeメソッドでコマンド実行 → readメソッドで第1引数に指定した内容が現れるまで待機(タイムアウト時間あり)という流れなので、
コマンドの実行状況によっては設定したタイムアウト時間内に終わりきらなかったり、不意に確認プロンプトが挟まってしまってreadメソッドがタイムアウト時間を迎えるまで止まってしまうという事が起こってしまう。
事前に実行するコマンドがどういう動きをする可能性があるのかを確認しつつ、コマンドごとにreadメソッドの第1引数、エラーハンドリングを考える必要があるのがなかなか厄介。
それでも簡単にSSH経由で操作を自動化できるのは魅力的だ。