로그인
KO

튜토리얼: 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(): 브라우저에는 없는 에이전트의 역할).

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 ». 관리자가 게시된 커밋에 대해 승인합니다.

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.