教程:通过 SSH 建立连接,一步一步来
示例项目 apps/hello-ssh 连接到一台 只有 SSH 服务器(堡垒机)才能访问的 Redis 服务器:ssh -W host:port,连接的数据流就是
通往 Redis 的数据流。在电脑上用的是系统的 ssh;在浏览器中则是编译成 WebAssembly 的真正 OpenSSH 客户端,通过管道与你的 PHP 相连。
1. 创建应用
bin/jumphp gui:new mon-ssh "Mon SSH" && bin/jumphp install mon-ssh
2. 窗口
forms/MainForm.form.yaml(复制 apps/hello-ssh/forms/MainForm.form.yaml):SSH 服务器(sshHost、sshPort、sshUser),一个按钮 « Choose a key file… »(chooseKey),密钥的
口令(passphrase),从堡垒机看到的目标(host、port),一个 ping 按钮,status,result。
3. 密钥:文件选择器
浏览器没有 ~/.ssh 文件夹:只能请用户选择文件。FilePicker::open() 在电脑上和浏览器中都能用(从点击事件中调用);所选
文件会出现在某个路径上,读取后再删除。把它保存到应用的密钥环中($this->engine->sshKeyring():相当于浏览器里没有的代理(agent))。
public function onChooseKey(Button $sender): void
{
FilePicker::open(['title' => 'SSH private key'])->then(function (?string $file): void {
if ($file === null) { return; } // annulé
$text = (string)file_get_contents($file);
unlink($file);
$this->engine->sshKeyring()->add(basename($file), $text, $this->passphrase->text); // nom, clé, phrase secrète
$this->keyState->text = 'Keys: ' . implode(', ', $this->engine->sshKeyring()->names());
});
}
密钥保存在应用的存储中(var/ssh_keyring.json:浏览器中是 IndexedDB,电脑上是数据文件夹),绝不会发送给服务器:它只进入 SSH 客户端的
内存。每次连接都会像代理一样提供密钥环中的所有密钥。
4. 连接
$options = ['sshHost' => 'bastion.exemple.com', 'sshPort' => 22, 'sshUser' => 'moi', 'sshKeys' => $this->engine->sshKeyring()->all()];
$connection = $this->engine->ssh()->connect('127.0.0.1', 6379, $options); // 127.0.0.1:6379 vu depuis le bastion
$connection->on('open', fn () => $connection->write("PING\r\n"));
$connection->on('data', fn (string $bytes) => $this->result->text = trim($bytes));
$connection->on('error', fn (string $m) => $this->result->text = $m); // « Permission denied (publickey) », « Could not resolve hostname »...
$connection->on('knownHosts', fn (string $text) => $this->engine->storage->writeJson('known_hosts.json', ['text' => $text])); // l'empreinte du serveur, gardée
事件与 tcp() 相同。对于接受 Tcp 的代码(如 apps/jumphpsql 的 MySQL 客户端):$this->engine->ssh()->via($options) 具有同样的 connect($host, $port, $timeout)。
选项:docs/ssh-wasm.md。
5. 测试
bin/jumphp test mon-ssh # le trousseau, les champs manquants, un ssh qui n'atteint pas son serveur (le message dit pourquoi)
bin/jumphp shot mon-ssh
bin/jumphp run mon-ssh # sur l'ordinateur : le ssh du système (votre agent et ~/.ssh marchent aussi)
6. 浏览器:两项权限
bin/jumphp build mon-ssh && bin/jumphp serve mon-ssh
在浏览器中,有两个环节要经过 jumphp.net 的 TCP 网桥,它只接受已获批准的应用:SSH 客户端(WebAssembly)连接堡垒机(22 端口或其他端口)→ 需要 SSH 权限。(直接与数据库通信的 SQL 客户端则需要 TCP 权限。)Studio → 项目 → Accès SSH → « Demander l'accès SSH »;管理员针对已发布的 commit 予以批准。
SSH 权限范围更广(公共地址的任意端口,但绝不能是私有地址):管理员只会授予他审查过的代码。统计、日志、切断和封锁: Administration → Pont TCP。
7. 在浏览器中验证
jumphp.net 上的项目页面:选择你的密钥(会打开你电脑的文件选择器),填写堡垒机和目标,点击 « Send PING through SSH »:The server answered: +PONG。
最初几秒会显示 « The SSH session is starting… »:SSH 客户端在 Web Worker 中启动(一个按需加载的 3.8 MB 文件)。
工作原理(由管道相连的两个 wasm)、安全性以及 OpenSSH 的编译:docs/ssh-wasm.md。第一个教程:docs/tuto-tcp.md。