cirruslabs / orchard

Orchestrator for running Tart Virtual Machines on a cluster of Apple Silicon devices
Other
194 stars 16 forks source link

Port forwarding through websocket documentation #151

Closed neronmoon closed 5 months ago

neronmoon commented 9 months ago

Hello!

I'm struggling to make ssh connection via websocket using php/python. Could you please provide some hints/documentation/example how to do it?

edigaryev commented 9 months ago

Hello Vitaliy 👋

I don't have much exprience with PHP, but I've just tried using the port forwarding endpoint from Python, and it is indeed somewhat painful: both of the Python libraries for SSH I've tried (Paramiko and AsyncSSH) expect a real socket.socket to be handed to them, on which they call things like settimeout(...) and setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1).

In Golang however, this is way easier because of a well-defined net.Conn inteface. We actually just use a result of NetConn() method provided by the [nhooyr.io/websocket package]() to instantiate the SSH client, which powers our orchard ssh vm command:

https://github.com/cirruslabs/orchard/blob/5da967b88cd0a625691a3eba006f056b07e7e8c1/internal/command/ssh/vm.go#L75-L78

This got me thinking that we should expose a more convenient method for running commands inside of a VM, something like a /v1/vms/{name}/exec method that would accept commands as an input and return stdout/stderr. Would that work for what you're trying to achieve?

neronmoon commented 9 months ago

Thank you for answering, Nikolay!

v1/vms/{name}/exec endpoint looks promising at first sight. But i think that will fail in case that i would execute some very-long running script. For example: connection can be dropped by any middleware server by timeout.

Of course, that problem could be easely solved by foregrounding script process and reading stdout/stderr in bunch of repeating read-logs-if-exist scripts., but it smells badly. I really don't like make unnecessary http requests

neronmoon commented 9 months ago

Ok, i came to some point. I'm using folloginw libraries:

I have wrapper around php socket

<?php

namespace App\Services;

use WSSC\Components\ClientConfig;
use WSSC\Contracts\CommonsContract;
use WSSC\WebSocketClient;

class WebsocketStreamWrapper
{
    private WebSocketClient $client;

    public function stream_open($url, $options, $test, $test1)
    {
        $config = new ClientConfig();
        $config->setHeaders([
            'User-Agent' => 'Orchard/v2.0.0',
        ]);
        $this->client = new WebSocketClient($url, $config);
        return $this->client->isConnected();
    }

    public function stream_write($data)
    {
        $this->client->send($data, CommonsContract::EVENT_TYPE_BINARY);
        return strlen($data);
    }

    public function stream_read()
    {
        return $this->client->receive();
    }

    public function stream_eof()
    {
        return feof($this->client->socket);
    }

    public function stream_cast($cast_as)
    {
        return $this->client->socket ?: false;
    }

    public function stream_stat()
    {
        return stream_get_meta_data($this->client->socket);
    }
}

And using following code to connect:

stream_wrapper_register("ws", WebsocketStreamWrapper::class);
$socket = fopen("ws://<orchard ip>:6120/v1/vms/test/port-forward?port=22&wait=60", "w+b");
$ssh = new SSH2($socket);
define('NET_SSH2_LOGGING', 3);
$ssh = new SSH2($socket);

$ssh->login('admin', 'admin');

Looks like eather websocket server or ssh server hands on handshake process. Here is some logs:

<-
00000000  53:53:48:2d:32:2e:30:2d:4f:70:65:6e:53:53:48:5f  SSH-2.0-OpenSSH_
00000010  39:2e:33:0d:0a                                   9.3..

->
00000000  53:53:48:2d:32:2e:30:2d:70:68:70:73:65:63:6c:69  SSH-2.0-phpsecli
00000010  62:5f:33:2e:30:20:28:6c:69:62:73:6f:64:69:75:6d  b_3.0 (libsodium
00000020  2c:20:6f:70:65:6e:73:73:6c:29:0d:0a              , openssl)..

-> NET_SSH2_MSG_KEXINIT (since last: 0.0812, network: 0.0005s)
00000000  6a:40:04:29:0f:eb:bc:7a:f5:46:38:6e:47:78:06:b4  j@.)...z.F8nGx..
00000010  00:00:01:87:63:75:72:76:65:32:35:35:31:39:2d:73  ....curve25519-s
[DELETED]
00000630  00:1a:6e:6f:6e:65:2c:7a:6c:69:62:40:6f:70:65:6e  ..none,zlib@open
00000640  73:73:68:2e:63:6f:6d:2c:7a:6c:69:62:00:00:00:00  ssh.com,zlib....
00000650  00:00:00:00:00:00:00:00:00                       .........

<- NET_SSH2_MSG_KEXINIT (since last: 0.003, network: 0.0001s)
00000000  93:bb:43:74:8d:8b:4c:66:fb:29:d5:eb:b8:53:a4:03  ..Ct..Lf.)...S..
00000010  00:00:01:09:73:6e:74:72:75:70:37:36:31:78:32:35  ....sntrup761x25
00000020  35:31:39:2d:73:68:61:35:31:32:40:6f:70:65:6e:73  519-sha512@opens
00000030  73:68:2e:63:6f:6d:2c:63:75:72:76:65:32:35:35:31  sh.com,curve2551
[DELETED]
00000420  00:00:00:00:00:00:00:00:00:00:00                 ...........

-> NET_SSH2_MSG_KEXDH_INIT (since last: 0.0815, network: 0.0001s)
00000000  00:00:00:20:7e:ba:a3:8a:b0:ca:8a:60:64:ee:26:d0  ... ~......`d.&.
00000010  c9:fb:83:8f:c3:23:5f:ef:81:e7:67:a2:8f:7c:8d:a6  .....#_...g..|..
00000020  a1:98:19:52                                      ...R

<- NET_SSH2_MSG_KEXDH_REPLY (since last: 0.0078, network: 0.0077s)
00000000  00:00:00:33:00:00:00:0b:73:73:68:2d:65:64:32:35  ...3....ssh-ed25
[DELETED]
000000b0  d9:0d                                            ..

-> NET_SSH2_MSG_NEWKEYS (since last: 0.0013, network: 0.0001s)

Hangs here, and then throws exception because of timeout
neronmoon commented 9 months ago

I've also tested phpseclib's connection on my ubuntu box and it works just fine. Without websocket wrapping, of course

fkorotkov commented 9 months ago

Hey @neronmoon, will it be a one off long running script that you know before starting a VM? If so, you can consider adding a startup script for the VM and then checking for log events.

Alternatively we can also consider adding a new API to submit a script for execution on a VM, logs of which will be streamed to events.

edigaryev commented 5 months ago

Closing because of no reply.