GNS3 / gns3-web-ui

WebUI implementation for GNS3
GNU General Public License v3.0
147 stars 52 forks source link

Websockets-based console access #1220

Open candlerb opened 4 years ago

candlerb commented 4 years ago

Is your feature request related to a problem? Please describe. The TCP port numbers for consoles are dynamic, and change even when the same project is stopped and restarted. This makes it awkward to connect to a specific device's console when not going through the GNS3 GUI, e.g. for a classroom of students interacting with a shared lab server.

Being able to set fixed console ports was proposed in GNS3/gns3-gui#2811.

I would like to suggest an an alternative solution: expose the console ports using websockets, with a Javascript console frontend.

Describe the solution you'd like GNS3 already has the ability to multiplex TCP sessions to a console, forwarded to the same underlying kvm port. This has the benefit that several users can connect to the same console and see what each one is typing.

Websockets would just be an additional input into that multiplexer.

Therefore, the solution would expose a websockets API at a known URL. It would simply receive bytes in and send bytes out, on the same console multiplexer as already exists.

Selection of project and node can be done through the URL path. Ideally I'd like to be able to specific project and node name here, as an alternative to UUID, so that the paths are fixed.

When accessed via a regular HTTP request (rather than ws/wss), serve a static page containing some javascript which connects to the websocket and emulates a standard terminal.

The end result is that students can connect to e.g. http://x.x.x.x:3080/v2/projects/foo/node/bar/console and interact directly with the console.

As a side benefit, this would avoid the need for users to install a telnet client such as putty.

Describe alternatives you've considered I made a prototype using shellinabox here: https://gist.github.com/candlerb/12607ee118248de9ac70a3e77b9f2b78

This does not modify GNS3 to implement websockets. Rather, shellinabox launches a standalone python script at the backend which talks to an in-browser terminal frontend. This python script, if invoked with query string ?port=NNNN simply execs telnet -E 127.0.0.1 NNNN. If invoked without query string, it calls the GNS3 API and lists the available nodes, generating suitable URLs including the port numbers. shellinabox recognises URL strings and renders them as clickable links.

image

These URLs are still dynamic, so can't be bookmarked, but the initial page that generates the listing can be bookmarked.

Integrating the websockets code into GNS3 has the advantage of lower resource utilisation, plus you can't end up with stale telnet processes hanging around on the server side.

Additional context Refer to GNS3/gns3-gui#2811 and community discussion.

Whilst I want students to be able to interact with consoles, I don't want them to have full administrative access to the GNS3 server as well. I protect the GNS3 server using HTTP username/password. Therefore I'd either like the console API to be unprotected, or protected by a different username/password.

Unprotected console access would be fine for me: this mirrors the raw TCP console access. However I can imagine that once websocket consoles are enabled, some users might want to disable the raw TCP consoles entirely.

(Aside: if the console API were protected with a different username/password, then you are going down a path which ends up with a full-blown rights model with roles and users/groups. It would be nice for students to be able to access the GNS3 web UI to visualise the topology, without being allowed to change anything, or being able to stop/start nodes without changing configuration. But I think that would be the subject of a different feature request, and is probably not of interest to the majority of GNS3 users)

candlerb commented 4 years ago

Googling javascript terminal emulator turns up a bunch of options, but I've found that xterm.js already has an addon for attaching to a websocket.

grossmj commented 4 years ago

Items left to complete this issue:

Selection of project and node can be done through the URL path. Ideally I'd like to be able to specific project and node name here, as an alternative to UUID, so that the paths are fixed.

Need to consider this, we would have to create dynamic handlers because the node name can change.

When accessed via a regular HTTP request (rather than ws/wss), serve a static page containing some javascript which connects to the websocket and emulates a standard terminal.

I like this idea.

Therefore I'd either like the console API to be unprotected, or protected by a different username/password.

We will have an unprotected console access for now until we have a full-blown rights model with roles and users/groups. Ref https://github.com/GNS3/gns3-server/issues/1337

candlerb commented 4 years ago

I'd like to test out the websocket console feature in 2.3.0dev, but I can't see how to.

What I've tried is:

The combination starts OK, both in macOS GUI and in web UI. I just can't see a way in either of these to get a web console.

In the web UI, if I right-click on a running node and select "console", the log window shows that it makes an access to the node status API endpoint only:

GET http://192.168.122.1:3080/v2/projects/ea9f662b-a2d7-41f6-abd8-3c6ec6bda608/nodes/3026b921-8d95-4567-9a73-d4ced1933b85

There isn't a new console type (options are still "telnet", "vnc", "spice", "spice+agent", "none")

I looked at the gns3-server source changes relating to websockets and I can see the URL which should be accessed. If I access it as regular HTTP, I get an error about not upgrading to websocket (which I expect):

$ curl http://192.168.122.1:3080/v2/compute/projects/ea9f662b-a2d7-41f6-abd8-3c6ec6bda608/qemu/nodes/3026b921-8d95-4567-9a73-d4ced1933b85/console/ws
{
    "message": "No WebSocket UPGRADE hdr: None\n Can \"Upgrade\" only to \"WebSocket\".",
    "method": "GET",
    "path": "/v2/compute/projects/{project_id}/qemu/nodes/{node_id}/console/ws",
    "request": {},
    "status": 400
}

So I think the server is ready to go.

I can see commits in gns3-web-ui, but I'm not sure what version of this is included in the gns3-server repo:

$ git log gns3server/static/web-ui/main.c31c0c40fa0fcdc2cdcf.js
commit 4df10d14278d225ce2593d16ccb422e874adee03
Author: piotrpekala7 <31202938+piotrpekala7@users.noreply.github.com>
Date:   Wed Mar 25 00:26:02 2020 +0100

    Release 2019.2.0-alpha.11

There is a similar-looking commit in the gns3-web-ui repo:

commit 3b452d746c1621880b1dd22e2f19ebe9726c496a (tag: v2019.2.0-alpha.11)
Author: Piotr Pekala <piotrpawelpekala@gmail.com>
Date:   Mon Dec 30 02:29:52 2019 -0800

    Release 2019.2.0-alpha.11

This suggests that the web UI included in gns3-server is too old :-(

I tried a CLI utility called websocat:

$ ./websocat_amd64-linux-static ws://192.168.122.1:3080/v2/compute/projects/ea9f662b-a2d7-41f6-abd8-3c6ec6bda608/qemu/nodes/3026b921-8d95-4567-9a73-d4ced1933b85/console/ws
��������
^C

$ echo -e 'hello\n' | ./websocat_amd64-linux-static ws://192.168.122.1:3080/v2/compute/projects/ea9f662b-a2d7-41f6-abd8-3c6ec6bda608/qemu/nodes/3026b921-8d95-4567-9a73-d4ced1933b85/console/ws | hexdump -C
00000000  ff fb 01 ff fb 03 ff fb  00 ff fd 00 0a           |.............|
0000000d

No response apart from those unicode replacement chars.

I tried hacking some Javascript:

<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="https://unpkg.com/xterm@4.0.0/css/xterm.css">
    <script src="https://unpkg.com/xterm@4.0.0/lib/xterm.js"></script>
    <script src="https://unpkg.com/xterm-addon-attach@0.5.0/lib/xterm-addon-attach.js"></script>
  </head>
  <body>
    <div id="terminal"></div>
    <script>

const term = new window.Terminal.Terminal();
term.open(document.getElementById('terminal'));

const socket = new WebSocket('ws://192.168.122.1:3080/v2/compute/projects/ea9f662b-a2d7-41f6-abd8-3c6ec6bda608/qemu/nodes/3026b921-8d95-4567-9a73-d4ced1933b85/console/ws');
const attachAddon = new window.AttachAddon.AttachAddon(socket);
alert("nearly there...");
term.loadAddon(attachAddon);

    </script>
  </body>
</html>

... but this fails with an error inside attachAddon:

      this._disposables.push(terminal. onBinary(data => this._sendBinary(data)));
// TypeError: t.onBinary is not a function

Any clues gratefully received :-)

grossmj commented 4 years ago

I'd like to test out the websocket console feature in 2.3.0dev, but I can't see how to.

Actually we have merged the websocket console support into our 2.2 branch. This should work since version 2.2.7. Version 2.2.8 will have everything work using the Web-ui.

There isn't a new console type (options are still "telnet", "vnc", "spice", "spice+agent", "none")

You are right, it would make sense we add the new websocket console type there. For now we only use the websocket console in the Web-Ui by accessing the /v2/compute/projects/<project_id>/qemu/nodes/<node_id>/console/ws endpoint.

I tried hacking some Javascript:

That reminds me that I was trying to test the websocket console using websocat and other tools a few months ago without great success... I ended up hacking some Javascript too. I sent you an invitation to the repository with my client code if you want to try. I think you just have to edit this line with the correct endpoint: https://github.com/GNS3/gns3-xterm.js-test/blob/master/main.js#L9

candlerb commented 4 years ago

I've now upgraded to 2.2.8, and have a very awesome working web console in the UI. Thank you very much!!

I've also tried your gns3-xterm.js-test. When I ran webpack it created dist/bundle.js so I changed the HTML to point to this instead of bundle.js, and after upgrading to 2.2.8 that also worked, yay! :-)

Regarding the console type: "telnet" already works for both telnet and websocket - in fact you can connect with both simultaneously. Therefore I don't think it's necessary to introduce a new console type "websocket". I think it would be better as a global preference to choose to use web consoles instead of telnet - e.g. under "Preferences > Console Applications" where you currently choose the telnet command line.

One other thing. I see that .../console/ws bypasses authentication, which is good. Ideally I'd also like GNS3 to serve all the necessary Javascript and static HTML so that an unauthenticated user doesn't need any extra pieces to get a working console.

I think the ideal solution would be that .../console returns a simple static HTML web page, and the Javascript it needs is either embedded or links to a non-authenticated URL. This would also be useful becaues a user the GNS3 GUI could open a console in a web browser, without having to enter authentication details into the web browser.

I could then generate a front page (which I can do in a simple CGI) which queries the API to list all the running devices, and presents a list of hyperlinks for students to click on to select the console they need - and I wouldn't need to bundle my own copy of xterm.js.

However, this is just finishing touches. Thanks again!

candlerb commented 4 years ago

Using option Web console in new tab I can see it takes me to:

http://192.168.122.1:3080/static/web-ui/server/1/project/ea9f662b-a2d7-41f6-abd8-3c6ec6bda608/nodes/79e31f41-813e-4b3c-8700-7e17b4be9ea9

That's what I want, except open to unauthorized users (gives 401 currently).

grossmj commented 4 years ago

That's what I want, except open to unauthorized users (gives 401 currently).

Noted, we should change that (or you have to deactivate authentication in the server).

candlerb commented 4 years ago

Good point - I can turn off authentication for now.

candlerb commented 4 years ago

Hmm... seems it's not quite that simple. If I go to an incognito window, or another browser entirely, and paste in http://192.168.122.1:3080/static/web-ui/server/1/project/ea9f662b-a2d7-41f6-abd8-3c6ec6bda608/nodes/79e31f41-813e-4b3c-8700-7e17b4be9ea9 then I'm required to authenticate; but after entering the username and password, instead of seeing the console I'm redirected to http://192.168.122.1:3080/static/web-ui/servers

image

Is there cookie state which is needed as well?

candlerb commented 4 years ago

Retested with authentication disabled. Same result: unauthenticated user gets redirected to an empty servers list.

However, if the user who has fully logged into the web UI changes their URL to /static/web-ui/servers, the list is non-empty:

image

But if the unauthenticated user first goes to the home page http://192.168.122.1:3080/, then pastes the console URL, it works.