Git Product home page Git Product logo

upterm's Introduction

Upterm

Upterm is an open-source tool enabling developers to share terminal sessions securely over the web. It’s perfect for remote pair programming, accessing computers behind NATs/firewalls, remote debugging, and more.

This is a blog post to describe Upterm in depth.

🎥 Quick Demo

demo

🚀 Getting Started

Installation

Mac

brew install owenthereal/upterm/upterm

Standalone

upterm can be easily installed as an executable. Download the latest compiled binaries and put it in your executable path.

From source

git clone [email protected]:owenthereal/upterm.git
cd upterm
go install ./cmd/upterm/...

🔧 Basic Usage

  1. Host starts a terminal session:
upterm host
  1. Host retrieves and shares the SSH connection string:
upterm session current
  1. Client connects using the shared string:

📘 Quick Reference

Dive into more commands and advanced usage in the documentation. Below are some notable highlights:

Command Execution

Host a session with any desired command:

upterm host -- docker run --rm -ti ubuntu bash

Access Control

Host a session with specified client public key(s) authorized to connect:

upterm host --authorized-key PATH_TO_PUBLIC_KEY

Authorize specified GitHub, GitLab, or SourceHut users with their corresponding public keys:

upterm host --github-user username
upterm host --gitlab-user username
upterm host --srht-user username

Force command

Host a session initiating tmux new -t pair-programming, while ensuring clients join with tmux attach -t pair-programming. This mirrors functionarity provided by tmate:

upterm host --force-command 'tmux attach -t pair-programming' -- tmux new -t pair-programming

WebSocket Connection

In scenarios where your host restricts ssh transport, establish a connection to uptermd.upterm.dev (or your self-hosted server) via WebSocket:

upterm host --server wss://uptermd.upterm.dev -- bash

Clients can connect to the host session via WebSocket as well:

ssh -o ProxyCommand='upterm proxy wss://[email protected]' [email protected]:443

💡 Tips

Resolving Tmux Session Display Issue

Issue: The command upterm session current does not display the current session when used within Tmux.

Cause: This occurs because upterm session current requires the UPTERM_ADMIN_SOCKET environment variable, which is set in the specified command. Tmux, however, does not carry over environment variables not on its default list to any Tmux session unless instructed to do so (Reference).

Solution: To rectify this, add the following line to your ~/.tmux.conf:

set-option -ga update-environment " UPTERM_ADMIN_SOCKET"

Identifying Upterm Session

Issue: It might be unclear whether your shell command is running in an upterm session, especially with common shell commands like bash or zsh.

Solution: To provide a clear indication, amend your ~/.bashrc or ~/.zshrc with the following line. This decorates your prompt with an emoji whenever the shell command is running in an upterm session:

export PS1="$([[ ! -z "${UPTERM_ADMIN_SOCKET}"  ]] && echo -e '\xF0\x9F\x86\x99 ')$PS1" # Add an emoji to the prompt if `UPTERM_ADMIN_SOCKET` exists

⚙️ How it works

Upterm starts an SSH server (a.k.a. sshd) in the host machine and sets up a reverse SSH tunnel to a Upterm server (a.k.a. uptermd). Clients connect to a terminal session over the public internet via uptermd using ssh or ssh over WebSocket.

upterm flowchart

🛠️ Deployment

Kubernetes

You can deploy uptermd to a Kubernetes cluster. Install it with helm:

helm repo add upterm https://upterm.dev
helm repo update
helm install uptermd upterm/uptermd

Heroku

The cheapest way to deploy a worry-free Upterm server (a.k.a. uptermd) is to use Heroku. Heroku offers free Dyno hours which should be sufficient for most casual uses.

You can deploy with one click of the following button:

Deploy

You can also automate the deployment with Heroku Terraform. The Heroku Terraform scripts are in the terraform/heroku folder. A util script is provided for your convenience to automate everything:

git clone https://github.com/owenthereal/upterm
cd upterm

Provision uptermd in Heroku Common Runtime. Follow instructions.

bin/heroku-install

Provision uptermd in Heroku Private Spaces. Follow instructions.

TF_VAR_heroku_region=REGION TF_VAR_heroku_space=SPACE_NAME TF_VAR_heroku_team=TEAM_NAME bin/heroku-install

You must use WebScoket as the protocol for a Heroku-deployed Uptermd server because the platform only support HTTP/HTTPS routing. This is how you host a session and join a session:

Use the Heroku-deployed Uptermd server via WebSocket

upterm host --server wss://YOUR_HEROKU_APP_URL -- YOUR_COMMAND

A client connects to the host session via WebSocket

ssh -o ProxyCommand='upterm proxy wss://TOKEN@YOUR_HEROKU_APP_URL' TOKEN@YOUR_HEROKU_APP_URL:443

Digital Ocean

There is an util script that makes provisioning Digital Ocean Kubernetes and an Upterm server easier:

TF_VAR_do_token=$DO_PAT \
TF_VAR_uptermd_host=uptermd.upterm.dev \
TF_VAR_uptermd_acme_email=YOUR_EMAIL \
TF_VAR_uptermd_helm_repo=http://localhost:8080 \
TF_VAR_uptermd_host_keys_dir=PATH_TO_HOST_KEYS \
bin/do-install

Systemd

A hardened systemd service is provided in systemd/uptermd.service. You can use it to easily run a secured uptermd on your machine:

cp systemd/uptermd.service /etc/systemd/system/uptermd.service
systemctl daemon-reload
systemctl start uptermd

⚖️ Comparasion with Prior Arts

Upterm stands as a modern alternative to Tmate.

Tmate originates as a fork from an older iteration of Tmux, extending terminal sharing capabilities atop Tmux 2.x. However, Tmate has no plans to align with the latest Tmux updates, compelling Tmate & Tmux users to manage two separate configurations. For instance, the necessity to bind identical keys twice, conditionally.

On the flip side, Upterm is architected from the ground up to be an independent solution, not a fork. It embodies the idea of connecting the input & output of any shell command between a host and its clients, transcending beyond merely tmux. This paves the way for securely sharing terminal sessions utilizing containers.

Written in Go, Upterm is more hack-friendly compared to Tmate, which is crafted in C, akin to Tmux. The seamless compilation of Upterm CLI and server (uptermd) into a single binary facilitates swift deployment of your pairing server across any cloud environment, devoid of dependencies.

License

Apache 2.0

upterm's People

Contributors

awwaiid avatar bcspragu avatar dependabot[bot] avatar fergusean avatar jhjaggars avatar jonher937 avatar owenthereal avatar reidrac avatar staaldraad avatar svenstaro avatar toastal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

upterm's Issues

Reconnect

(Translated by Google)

Hi

When I disconnect from the remote terminal, doing 'exit', I am actually also closing the process on the server, and I cannot reconnect, since 'upterm' is no longer running.

Is there any way I can close my remote session, but leave the process running on the server?

QUESTION: Use with Visual Studio Code possible?

The Question

I am an avid terminal user, Neovim, tmux, and all the like so Upterm is amazing for me. However, some of my co-workers and friends do not live inside the terminal and use GUI IDE's such as Visual Studio Code. Is there a way use VSCode's Remote Development -SSH extension to connect to Upterm's SSH Server and be able to view with me as I work?

Current Error Output

[12:54:26.383] Log Level: 2
[12:54:26.386] [email protected]
[12:54:26.386] linux x64
[12:54:26.387] SSH Resolver called for "ssh-remote+uptermd.upterm.dev", attempt 1
[12:54:26.388] "remote.SSH.useLocalServer": true
[12:54:26.388] "remote.SSH.path": undefined
[12:54:26.388] "remote.SSH.configFile": undefined
[12:54:26.388] "remote.SSH.useFlock": true
[12:54:26.389] "remote.SSH.lockfilesInTmp": false
[12:54:26.389] "remote.SSH.localServerDownload": auto
[12:54:26.389] "remote.SSH.remoteServerListenOnSocket": false
[12:54:26.389] "remote.SSH.showLoginTerminal": false
[12:54:26.389] "remote.SSH.defaultExtensions": []
[12:54:26.390] "remote.SSH.loglevel": 2
[12:54:26.390] SSH Resolver called for host: uptermd.upterm.dev
[12:54:26.390] Setting up SSH remote "uptermd.upterm.dev"
[12:54:26.395] Acquiring local install lock: /tmp/vscode-remote-ssh-48a5ecb6-install.lock
[12:54:26.430] Looking for existing server data file at /var/home/dudleyp/.var/app/com.visualstudio.code/config/Code/User/globalStorage/ms-vscode-remote.remote-ssh/vscode-ssh-host-48a5ecb6-507ce72a4466fbb27b715c3722558bb15afa9f48-0.65.8/data.json
[12:54:26.431] Using commit id "507ce72a4466fbb27b715c3722558bb15afa9f48" and quality "stable" for server
[12:54:26.439] Install and start server if needed
[12:54:26.476] PATH: /app/bin:/usr/bin:/app/tools/podman/bin:/usr/lib/sdk/golang/bin:/var/home/dudleyp/.var/app/com.visualstudio.code/data/node_modules/bin
[12:54:26.476] Checking ssh with "ssh -V"
[12:54:26.485] > OpenSSH_8.6p1, OpenSSL 1.1.1k  25 Mar 2021

[12:54:26.490] askpass server listening on /run/user/1000/vscode-ssh-askpass-59f8107e5f8a89dfd1a5728c561edb170e91f293.sock
[12:54:26.490] Spawning local server with {"serverId":1,"ipcHandlePath":"/run/user/1000/vscode-ssh-askpass-a68ca513540bb777882508442662771abd6fea3c.sock","sshCommand":"ssh","sshArgs":["-v","-T","-D","42591","-o","ConnectTimeout=15","uptermd.upterm.dev"],"dataFilePath":"/var/home/dudleyp/.var/app/com.visualstudio.code/config/Code/User/globalStorage/ms-vscode-remote.remote-ssh/vscode-ssh-host-48a5ecb6-507ce72a4466fbb27b715c3722558bb15afa9f48-0.65.8/data.json"}
[12:54:26.490] Local server env: {"DISPLAY":":99.0","ELECTRON_RUN_AS_NODE":"1","SSH_ASKPASS":"/var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/local-server/askpass.sh","VSCODE_SSH_ASKPASS_NODE":"/app/extra/vscode/code","VSCODE_SSH_ASKPASS_MAIN":"/var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/askpass-main.js","VSCODE_SSH_ASKPASS_HANDLE":"/run/user/1000/vscode-ssh-askpass-59f8107e5f8a89dfd1a5728c561edb170e91f293.sock"}
[12:54:26.497] Spawned 315
[12:54:26.641] > local-server-1> Spawned ssh, pid=323
[12:54:26.643] stderr> OpenSSH_8.6p1, OpenSSL 1.1.1k  25 Mar 2021
[12:54:26.971] stderr> Connection closed by 157.230.199.75 port 22
[12:54:26.974] > local-server-1> ssh child died, shutting down
[12:54:26.980] Local server exit: 0
[12:54:26.980] Received install output: local-server-1> Spawned ssh, pid=323
OpenSSH_8.6p1, OpenSSL 1.1.1k  25 Mar 2021
Connection closed by 157.230.199.75 port 22
local-server-1> ssh child died, shutting down

[12:54:26.982] Failed to parse remote port from server output
[12:54:26.983] Resolver error: Error: 
	at Function.Create (/var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:397041)
	at Object.t.handleInstallOutput (/var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:395684)
	at Object.e [as tryInstallWithLocalServer] (/var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:435292)
	at processTicksAndRejections (internal/process/task_queues.js:93:5)
	at async /var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:452665
	at async Object.t.withShowDetailsEvent (/var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:456460)
	at async /var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:433816
	at async C (/var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:430392)
	at async Object.t.resolveWithLocalServer (/var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:433431)
	at async Object.t.resolve (/var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:454065)
	at async /var/home/dudleyp/.var/app/com.visualstudio.code/data/vscode/extensions/ms-vscode-remote.remote-ssh-0.65.8/out/extension.js:1:526942
[12:54:26.990] ------

I do know that there is already a "co-editing" experience called Live Share for Visual Studio and VSCode, but I was having issues with it reverting changes we had made, and syncing improperly.

ARMv7/armhf support?

This project looks really promising! I see there's only binaries for amd64 and arm64/ARMv8, do you plan to support ARMv7/armhf as well with precompiled binaries? If not, would it be possible to compile from source and use that same binary myself for all ARMv7 devices?

"Server refuse our key" if upterm client lost time sync

If I try to start upterm client on a host with wrong time on it, I will have "Server refuse our key" message when I try to connect to it by ssh client.

How to repeat:

  1. Let's break time setting on target server:
$ timedatectl set-ntp false
$ timedatectl set-time 10:00
$ timedatectl
               Local time: Mon 2023-01-16 10:00:03 UTC
               Universal time: Mon 2023-01-16 10:00:03 UTC
               RTC time: Mon 2023-01-16 10:00:03
               Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: no
              NTP service: inactive
              RTC in local TZ: no
  1. Run "upterm host" command:
./upterm host
=== CYCTSSRPMHSQZIRTANAP
Command:                /bin/bash
Force Command:          n/a
Host:                   ssh://uptermd.upterm.dev:22
SSH Session:            ssh CyCTSsrPMHsqzirTanap:[email protected]

Run 'upterm session current' to display this screen again

Press <q> or <ctrl-c> to accept connections...
  1. Try to connect on some host to "target server" by upterm:
$ ssh CyCTSsrPMHsqzirTanap:[email protected]
CyCTSsrPMHsqzirTanap:[email protected]: Permission denied (publickey).

As soon as we sync time on target server everything works fine

Private key required when ssh-agent already populated

Hello, we are trying upterm as an alternative to tmate, and it's seems to be working great, so, first of all, thanks for this project!

On my machine though, I don't have any private key files, they are stored in a safe (KeePassXC) and, when I unlock my safe, my key is added to my ssh-agent.

My workaround for upterm is to create an ssh-key, but I wonder if we could have an option to specify an ssh-agent directly where it could retrieve the keys and the signers.

If you agree with that, I could try and create a PR

Support running host in daemon mode?

First of all, thanks for making this amazing tool! It's elegant in design and neat in implementation.

One of the intended use is

Access remote computers behind NATs and firewalls

but currently it is not the ideal tool for cases like accessing home servers from outside or troubleshooting deployed devices behind NAT because:

  1. The host is assigned a different session ID each time it connects to the server, so an out-of-band channel is required to publish the session ID so people can log in. If ID clash is not a concern, it would be more convenient if one can simply specify the ID/name of the host to log in.
  2. The session is deleted when the first logged in SSH session exits. What if we delete the session only when the host exits? I can imagine that to be useful in pairing or running a demo too - guests can join or leave without affecting the session. Or am I missing some important context here?
  3. Person on the host side needs to press a key to start accepting connection. This is easy to change by introducing a flag.

So to support daemon mode, it would require flags on both host and server side for 1 and 3, but would also need to change the termination behaviour of 2. Before I get my hands dirty I'd like to ask 1) does the above make sense, and 2) is this daemon mode idea fits in the scope of upterm or I'd better assume it to be a "hard fork"?

source labels on metrics

As a uptermd operator i want to get the possibility to view the different origins of the upterm host command.
For that i've thought about adding an optional flag where it's possible to define a custom string/tag. This information will also get propagated to the uptermd component and used as an additional tag: value on the generated metrics.

With that it should be possible to "aggregate" the metrics by different tags, e.g.:

upterm_uptermd_routing_active_connections_count{tag="dynamic/runner"} 1
upterm_uptermd_routing_active_connections_count{tag="hostname7744"} 1

and the corresponding upterm host command could look like upterm host --tag "dynamic/runner".
And if no --tag is defined, the current HOSTNAME will be used.

Distribution-based package formats install binary at the wrong place

Greetings, first of all thank you for this project, been using a lot at my corpa job.

I just came here to report you that, I think most, if not all of distribution-based package formats (deb, rpm) are installing the binary in a place that does not belong in the default PATH environment variable.

$ dpkg-deb --contents upterm_linux_amd64.deb
drwxr-xr-x root/root         0 2024-04-17 18:39 ./usr/
-rwxr-xr-x root/root  14704640 2024-04-17 18:38 ./usr/upterm
$ rpm -qlp upterm_linux_amd64.rpm
/usr/upterm

I think you meant to place this in /usr/bin/ and not /usr

It's been like this for quite a while ever since you started making deb/rpm releases basically, and I thought you would eventually figure out with time or someone would report it earlier.

Foregrounded mode

I'd like to suggest adding a foreground mode, similar to TMate's -F option, to allow clients to connect without requiring further interaction on the host side. This would make upterm useful in automated systems/CI environments where it's not always possible to provide input.

config file for `upterm host` command

If all the three PRs i've done today got accepted we will get 11 flags to configure upterm host command.
I would like to have the option for using a configuration file beside all the different arguments.

Would you accept a PR for supporting a config file?

Recent PRs which add more flags to upterm host subcommand:

missing flag private-key

Hello, I'm trying to use self-hosted server, after deployment I can host sessions from same machine that I've run terraform deployment. But other machine returns this error:

# upterm host --server wss://<deleted>.herokuapp.com -- bash
Error: 1 error occurred:
	* missing flag --private-key

Where I can get this private key?

Packet loss causing upterm to crash?

Hi,

We use Upterm + tmux almost every day to pair but lately I've been experiencing quite a bit of unexpected disconnects. We have a theory that it may be coinciding with temporary moments of high packet loss, but are not entirely sure. Usually the session completely dies when this happens and I have to issue a new SSH upterm connection to my pair.

Do you have any recommendations for how to debug this?

Is project abandoned?

There doesnt seem to be much of an activity in repo and serious issues (like #93) are not resolved.

Even though servers for sharing are still active, they might not be if they are not funded for long enough.

So, naturally, there comes a question... is project dead?

Error: open /home/dyno/.ssh/known_hosts: no such file or directory

Upterm errors out if known_hosts doesn't exist:

./build/upterm host -- bash
Error: open /home/dyno/.ssh/known_hosts: no such file or directory
Usage:
  upterm host [flags]

Examples:
  # Host a terminal session that runs $SHELL with
  # client's input/output attaching to the host's
  upterm host

  # Host a terminal session that only allows specified public key(s) to connect
  $ upterm host --authorized-key PATH_TO_PUBLIC_KEY

  # Host a session with a custom command.
  upterm host -- docker run --rm -ti ubuntu bash

  # Host a session that runs 'tmux new -t pair-programming' and
  # force clients to join with 'tmux attach -t pair-programming'.
  # This is similar to tmate.
  upterm host --force-command 'tmux attach -t pair-programming' -- tmux new -t pair-programming

  # Use a different Uptermd server and host a session via WebSocket
  upterm host --server wss://YOUR_UPTERMD_SERVER -- YOUR_COMMAND

Flags:
  -a, --authorized-key string   an authorized_keys file that lists public keys that are permitted to connect.
  -f, --force-command string    force execution of a command and attach its input/output to client's.
  -h, --help                    help for host
      --known-hosts string      a file contains the known keys for remote hosts (required). (default "/home/dyno/.ssh/known_hosts")
  -i, --private-key strings     private key for public key authentication against the upterm server (required). (default [/home/dyno/.ssh/id_rsa])
  -r, --read-only               host a read-only session. Clients won't be able to interact.
      --server string           upterm server address (required), supported protocols are shh, ws, or wss. (default "ssh://uptermd.upterm.dev:22")

FATA[0000] open /home/dyno/.ssh/known_hosts: no such file or directory 

Segfault in upterm 0.7.3

If I start a session with upterm 0.7.3...

$ upterm version
Upterm version v0.7.3
$ upterm host
=== ...
Command:                /bin/bash
Force Command:          n/a
Host:                   ssh://uptermd.upterm.dev:22
SSH Session:            ssh [email protected]
Press <q> or <ctrl-c> to continue...
$

And then connect to that session with ssh, the session crashes with:

[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x86fc92]

goroutine 47 [running]:
golang.org/x/crypto/ssh.(*curve25519sha256).Server(0xc000474390, {0x7f3cdbfc4f10, 0xc000164000}, {0xb94880, 0xc0000b8570}, 0x80, {0x0, 0x0})
/home/runner/work/upterm/upterm/vendor/golang.org/x/crypto/ssh/kex.go:515 +0x272
golang.org/x/crypto/ssh.(*handshakeTransport).server(0xc00017e2c0, {0xba06f8, 0x1038970}, 0xc000128500, 0xc0001645a0)
/home/runner/work/upterm/upterm/vendor/golang.org/x/crypto/ssh/handshake.go:643 +0xe3
golang.org/x/crypto/ssh.(*handshakeTransport).enterKeyExchange(0xc00017e2c0, {0xc000302700, 0x0, 0x0})
/home/runner/work/upterm/upterm/vendor/golang.org/x/crypto/ssh/handshake.go:592 +0x3eb
golang.org/x/crypto/ssh.(*handshakeTransport).kexLoop(0xc00017e2c0)
/home/runner/work/upterm/upterm/vendor/golang.org/x/crypto/ssh/handshake.go:301 +0xa6
created by golang.org/x/crypto/ssh.newServerTransport
/home/runner/work/upterm/upterm/vendor/golang.org/x/crypto/ssh/handshake.go:143 +0x13d

It also leaves the terminal in a bad state such that running stty sane is necessary (the traceback is also formatted very poorly due to the terminal state. I've just remove all leading whitespace to try to make it a little more legible).

Adding the ability to specify a session name

Hi!

This project is amazing!

I have a very specific use-case for which it's necessary for me to be able to generate session names from the client-side (I work for a university where we're investigating this for peer-programming between first-year students, but I'm going to build some tooling around it). We could just give them upterm, but I'm building in some features specifically for them. For one of these features, I want to have control of the session's name when logging in (so I can login to <session_name>:<b64 ip>@<my_server>).

I've forked the project (https://github.com/tfpk/upterm/tree/tfpk/named-sessions) to build a draft to make sure it's feasible; but I'd love to discuss how best to upstream the feature. At the moment, I've already got it to support being given a session name. I don't have to worry about name-spacing, since on the server I'll be hosting, I can assume collisions won't happen between people. I can also assume there's only one upstream server behind the proxy.

I'd like to ask if/how this would be best upstreamed? There are a few main questions:

  • Does there need to be some API key mechanism for namespacing (similar to how tmate does it's named sessions)?
  • How should we deal with the base64 encoded upstream IP? There are two options I have thought of:
    • Alias those IPs to some constant (so you'd have <session_name>:codename@<my_server>); and we could distribute that codename, even if the upstream changed. I can't tell from my cursory examination if there is a circumstance where you'd have multiple servers, but if we went this route we'd need to think about how that worked. If there's only one server, maybe we can avoid this altogether with a global?
    • Store the upstream server in some session store? I'm not sure of how much traffic/data this would require, and I don't want to add a bottleneck, but this would likely be the nicest solution.

Please let me know what you think!

Thanks again for this amazing project!

~Tom

Integration with zellij for "multiplayer" support?

Hello, and thanks for building upterm! I've set up a self-hosted instance and it works like a charm. My intention is to use it for pair programming-like use cases, and one idea I think would be fun is to pair this with a terminal multiplexer (like Zellij) for multiple cursor, multiple pane pair programming.

Zellij does support this, you basically create a session in one terminal and then (likely over SSH) connect to the same session in another terminal, which gets its own cursor.

This is cool, but requires allowing others to SSH into your machine, which one generally doesn't want to allow all the time. It seems like upterm is an obvious fit here, and I think there should be some magic incantation when sharing the ssh ... connection string that makes this work (assuming the host has Zellij), but I don't have a strong enough mental model of upterm (or Zellij, really) to figure out how to make them play nicely.

I'm also not sure if upterm's native 'mirroring' of the session would cause problems here. Mostly just looking to see if this is feasible and get some pointers. Thanks!

Clarification: INFO[0191] dialing sshd

Hello!

I'm running uptermd on a server and when I connect to it with upterm I see the dialing message above. When people try to connect to the session the get permission denied. I'm guessing this is because upterm is trying to do some pubkey authentication with my locally running sshd? I'm guessing that since if I run upterm on a computer that has no sshd running everything seems to work normally and clients connect with no permission problems. I was just wondering how the dialing sshd part works and how to set it up so that other clients can connect to my machine.

On uptermd node:

uptermd --debug --ssh-addr <ip>:<port> --node-addr <ip>:<port>

On upterm host

upterm host --server ssh://<ip>:<port> -- bash

On upterm client

<copied ssh command>
Connection closed by <ip> port <port>

uptdermd logs:

DEBU[0850] connection establishing failed                addr="ip:port" app=uptermd com=ssh-proxy error="error checking user cert: ssh: principal \"blahblahblabh" not in the set of valid principals for given certificate: [\"host\"]" network=mem network-opt="[]" node-addr="<ip>:<port>" ssh-addr="<ip>:<port>"

Question: upterm token and ports

Hello!

How does upterm achieve forwarding of encrypted traffic of multiple users on the same port?

It seems the token would be needed to determine where to forward traffic to but the proxy could not get to that if ssh connection were end-to-end encrypted.
The diagram shows two tunnels however does not explain port and purpose.

Is an upterm session comprised of 2 consecutive SSH sessions with data decrypted and reencrypted at the proxy?

`upterm host` should reject any unauthenticated users when `--authorized-key`, `--github-user` or `--gitlab-user` option is present

Problem:
I ran into an issue where I had a passed an empty file to --authorized-key option for the upterm host command.

Steps to reproduce

  1. Start a new upterm host session in a docker container:
# start a new ubuntu docker container
docker run --rm -it ubuntu:20.04 bash
# in the container run these commands to start upterm
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get -y install --no-install-recommends curl openssh-client ca-certificates
curl -sL https://github.com/owenthereal/upterm/releases/download/v0.7.6/upterm_linux_amd64.tar.gz | \
  tar zxvf - -C /tmp upterm && \
  install /tmp/upterm /usr/local/bin/ && rm -rf /tmp/upterm
mkdir -p ~/.ssh && chmod 0700 ~/.ssh
ssh-keygen -q -t ed25519 -N "" -f ~/.ssh/id_ed25519
# Auto-generate ~/.ssh/known_hosts by attempting connection to uptermd.upterm.dev
ssh -i ~/.ssh/id_ed25519 -o 'StrictHostKeyChecking no' uptermd.upterm.dev
cat <(cat ~/.ssh/known_hosts | awk '{ print "@cert-authority * " $2 " " $3 }' | sort | uniq) >> ~/.ssh/known_hosts
touch ~/.ssh/authorized_keys
upterm host -a $HOME/.ssh/authorized_keys -- bash
  1. Use the SSH session string in another terminal. The client connects to the upterm session. This is unexpected since -a $HOME/.ssh/authorized_keys is passed to upterm host.

Expected behavior

upterm host should reject any unauthenticated users when --authorized-key, --github-user or --gitlab-user option is present.
An empty authorized keys file shouldn't be an exception.

Support tunneling of commands

I'd like to run commands on the remote host directly, via something like

ssh IqKsfoiclsNnxqztDKoj:[email protected] echo ok

Currently, I'm seeing error output when trying this.

Is this something as easy as starting sshd with less restrictive options? Is this something you would support, perhaps via switch?

Use case: when debugging GitHub Actions run, I'd like to connect to the workers with VS Code. I guess this feature is a prerequisite here, not sure it will be sufficient, though.

Is this related to #181?

feature request: upterm host should shutdown if no connection is established within a given time frame

It would be nice to configure upterm host command with some kind of a timeout flag where it's possible to shutdown the server if no connection got established within the given time period.

more context/details:

The current implementation (and your fork as well) currently are doing all the connection timeouts only on a connection layer in the HandleConnection func (https://github.com/gliderlabs/ssh/blob/master/server.go#L262).

I've added a comment to the generic timeout issue #11

slightly related is this PR for a new timeout config param: PR #204 - handshake timeout

ftests fail occationally on Linux

make test
go test ./... -timeout=60s -coverprofile=c.out -covermode=atomic -mod=vendor -count=1 -race -v
?       github.com/jingweno/upterm/cmd/gendoc   [no test files]
?       github.com/jingweno/upterm/cmd/upterm   [no test files]
=== RUN   Test_parseURL
=== RUN   Test_parseURL/port_443
=== PAUSE Test_parseURL/port_443
=== RUN   Test_parseURL/port_80
=== PAUSE Test_parseURL/port_80
=== RUN   Test_parseURL/port_22
=== PAUSE Test_parseURL/port_22
=== RUN   Test_parseURL/no_port
=== PAUSE Test_parseURL/no_port
=== CONT  Test_parseURL/port_443
=== CONT  Test_parseURL/port_80
=== CONT  Test_parseURL/port_22
=== CONT  Test_parseURL/no_port
--- PASS: Test_parseURL (0.01s)
    --- PASS: Test_parseURL/port_80 (0.00s)
    --- PASS: Test_parseURL/port_22 (0.00s)
    --- PASS: Test_parseURL/no_port (0.00s)
    --- PASS: Test_parseURL/port_443 (0.01s)
PASS
coverage: 5.0% of statements
ok      github.com/jingweno/upterm/cmd/upterm/command   0.650s  coverage: 5.0% of statements
?       github.com/jingweno/upterm/cmd/uptermd  [no test files]
?       github.com/jingweno/upterm/cmd/uptermd/internal/command [no test files]
time="2020-04-30T22:05:15Z" level=info msg="waiting for server" addr="127.0.0.1:34496"
time="2020-04-30T22:05:15Z" level=debug msg="connection establishing failed" addr="127.0.0.1:58508" componet=ssh-proxy error=EOF
time="2020-04-30T22:05:16Z" level=info msg="waiting for server" addr="127.0.0.1:45139"
=== RUN   Test_ftest
=== RUN   Test_ftest/ssh/testClientNonExistingSession
=== PAUSE Test_ftest/ssh/testClientNonExistingSession
=== RUN   Test_ftest/ws/testClientNonExistingSession
=== PAUSE Test_ftest/ws/testClientNonExistingSession
=== RUN   Test_ftest/ssh/testClientAttachHostWithSameCommand
=== PAUSE Test_ftest/ssh/testClientAttachHostWithSameCommand
=== RUN   Test_ftest/ws/testClientAttachHostWithSameCommand
=== PAUSE Test_ftest/ws/testClientAttachHostWithSameCommand
=== RUN   Test_ftest/ssh/testClientAttachHostWithDifferentCommand
=== PAUSE Test_ftest/ssh/testClientAttachHostWithDifferentCommand
=== RUN   Test_ftest/ws/testClientAttachHostWithDifferentCommand
=== PAUSE Test_ftest/ws/testClientAttachHostWithDifferentCommand
=== RUN   Test_ftest/ssh/testClientAttachReadOnly
=== PAUSE Test_ftest/ssh/testClientAttachReadOnly
=== RUN   Test_ftest/ws/testClientAttachReadOnly
=== PAUSE Test_ftest/ws/testClientAttachReadOnly
=== RUN   Test_ftest/ssh/testHostFailToShareWithoutPrivateKey
=== PAUSE Test_ftest/ssh/testHostFailToShareWithoutPrivateKey
=== RUN   Test_ftest/ws/testHostFailToShareWithoutPrivateKey
=== PAUSE Test_ftest/ws/testHostFailToShareWithoutPrivateKey
=== RUN   Test_ftest/ssh/testHostSessionCreatedCallback
=== PAUSE Test_ftest/ssh/testHostSessionCreatedCallback
=== RUN   Test_ftest/ws/testHostSessionCreatedCallback
=== PAUSE Test_ftest/ws/testHostSessionCreatedCallback
=== CONT  Test_ftest/ws/testClientNonExistingSession
=== CONT  Test_ftest/ssh/testClientAttachReadOnly
=== CONT  Test_ftest/ssh/testClientAttachHostWithSameCommand
=== CONT  Test_ftest/ssh/testHostSessionCreatedCallback
=== CONT  Test_ftest/ws/testClientAttachHostWithSameCommand
=== CONT  Test_ftest/ssh/testHostFailToShareWithoutPrivateKey
=== CONT  Test_ftest/ssh/testClientNonExistingSession
=== CONT  Test_ftest/ws/testHostFailToShareWithoutPrivateKey
time="2020-04-30T22:05:16Z" level=info msg="dialing sshproxy sshd" compoent=ws-sshproxy-dialer host=u51696 sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:16Z" level=info msg="dialing sshproxy sshd" compoent=ws-sshproxy-dialer host=u51696 sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:16Z" level=info msg="dialing sshproxy sshd" compoent=ws-sshproxy-dialer host=u51696 sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=debug msg="connection establishing failed" addr="127.0.0.1:58572" componet=ssh-proxy error=EOF
time="2020-04-30T22:05:17Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn74h7k8ut6sl6oi0 tunnel-port=0
time="2020-04-30T22:05:17Z" level=error msg="error running host" error="ssh://127.0.0.1:34496: Permission denied (publickey)."
=== CONT  Test_ftest/ws/testClientAttachReadOnly
time="2020-04-30T22:05:17Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn74h7k8ut6sl6oj0 tunnel-port=0
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn74h7k8ut6sl6ohg tunnel-port=0
time="2020-04-30T22:05:17Z" level=info msg="dialing sshproxy sshd" compoent=ws-sshproxy-dialer host=u51696 sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=error msg="error running host" error="ws://127.0.0.1:45139: Permission denied (publickey)."
=== CONT  Test_ftest/ws/testHostSessionCreatedCallback
time="2020-04-30T22:05:17Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn74h7k8ut6sl6oig tunnel-port=0
time="2020-04-30T22:05:17Z" level=info msg="dialing sshproxy sshd" compoent=ws-sshproxy-dialer host=u51696 sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=debug msg="connection establishing failed" addr="127.0.0.1:58582" componet=ssh-proxy error=EOF
time="2020-04-30T22:05:17Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn74h7k8ut6sl6ok0 tunnel-port=0
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn74h7k8ut6sl6ol0 tunnel-port=0
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn7ch7k8ut6sl6olg tunnel-port=0
time="2020-04-30T22:05:17Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn7ch7k8ut6sl6om0 tunnel-port=0
time="2020-04-30T22:05:17Z" level=info msg="waiting for unix socket" socket=/tmp/upterm502739695/upterm.sock
time="2020-04-30T22:05:17Z" level=info msg="waiting for unix socket" socket=/tmp/upterm677746421/upterm.sock
time="2020-04-30T22:05:17Z" level=info msg="waiting for unix socket" socket=/tmp/upterm549451216/upterm.sock
time="2020-04-30T22:05:17Z" level=info msg="waiting for unix socket" socket=/tmp/upterm007125753/upterm.sock
time="2020-04-30T22:05:17Z" level=info msg="waiting for unix socket" socket=/tmp/upterm175240578/upterm.sock
time="2020-04-30T22:05:17Z" level=info msg="waiting for unix socket" socket=/tmp/upterm888190355/upterm.sock
=== CONT  Test_ftest/ssh/testClientAttachHostWithDifferentCommand
time="2020-04-30T22:05:17Z" level=info msg="dialing sshproxy session" addr="127.0.0.1:34496" compoent=ws-sshproxy-dialer session=not-existance sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:18Z" level=info msg="dialing sshproxy session" addr="127.0.0.1:34496" compoent=ws-sshproxy-dialer session=bqlkn74h7k8ut6sl6ok0 sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:18Z" level=debug msg="error handling ssh session" component=stream-local-handler componet=sshd error=closed session-id=bqlkn74h7k8ut6sl6ohg
time="2020-04-30T22:05:18Z" level=info msg="dialing session" addr="127.0.0.1:34496" compoent=ssh-conn-dialer node="127.0.0.1:34496" session=bqlkn74h7k8ut6sl6oig
time="2020-04-30T22:05:18Z" level=info msg="dialing session" addr="127.0.0.1:34496" compoent=ssh-conn-dialer node="127.0.0.1:34496" session=not-existance
time="2020-04-30T22:05:18Z" level=debug msg="connection establishing failed" addr="127.0.0.1:58642" componet=ssh-proxy error="dial mem mem->mem: listener with address not-existance not found"
time="2020-04-30T22:05:18Z" level=info msg="dialing session" addr="127.0.0.1:34496" compoent=ssh-conn-dialer node="127.0.0.1:34496" session=bqlkn74h7k8ut6sl6oj0
=== CONT  Test_ftest/ws/testClientAttachHostWithDifferentCommand
time="2020-04-30T22:05:18Z" level=info msg="dialing session" addr="127.0.0.1:34496" compoent=ssh-conn-dialer node="127.0.0.1:34496" session=not-existance
time="2020-04-30T22:05:18Z" level=info msg="dialing sshproxy sshd" compoent=ws-sshproxy-dialer host=u51696 sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:18Z" level=debug msg="connection establishing failed" addr="127.0.0.1:58648" componet=ssh-proxy error="dial mem mem->mem: listener with address not-existance not found"
time="2020-04-30T22:05:18Z" level=info msg="dialing session" addr="127.0.0.1:34496" compoent=ssh-conn-dialer node="127.0.0.1:34496" session=bqlkn74h7k8ut6sl6ok0
time="2020-04-30T22:05:18Z" level=debug msg="error handling ssh session" component=stream-local-handler componet=sshd error=closed session-id=bqlkn74h7k8ut6sl6oi0
time="2020-04-30T22:05:18Z" level=info msg="dialing sshd" compoent=ssh-conn-dialer host=u51696 ndoe="127.0.0.1:34496"
time="2020-04-30T22:05:18Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn7ch7k8ut6sl6omg tunnel-port=0
time="2020-04-30T22:05:18Z" level=debug msg="error handling ssh session" component=stream-local-handler componet=sshd error=closed session-id=bqlkn74h7k8ut6sl6ol0
time="2020-04-30T22:05:18Z" level=info msg="waiting for unix socket" socket=/tmp/upterm636938557/upterm.sock
    Test_ftest/ssh/testClientAttachHostWithSameCommand: client_test.go:140: want="echo hello" got="hello":
          string(
        -       "echo hello",
        +       "hello",
          )
time="2020-04-30T22:05:18Z" level=info msg="attempt to bind" componet=sshd tunnel-host=bqlkn7kh7k8ut6sl6on0 tunnel-port=0
time="2020-04-30T22:05:18Z" level=info msg="waiting for unix socket" socket=/tmp/upterm373378936/upterm.sock
time="2020-04-30T22:05:18Z" level=debug msg="error handling ssh session" component=stream-local-handler componet=sshd error=closed session-id=bqlkn74h7k8ut6sl6oig
time="2020-04-30T22:05:18Z" level=info msg="dialing sshproxy session" addr="127.0.0.1:34496" compoent=ws-sshproxy-dialer session=bqlkn7ch7k8ut6sl6olg sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:18Z" level=debug msg="error handling ssh session" component=stream-local-handler componet=sshd error=closed session-id=bqlkn7ch7k8ut6sl6om0
time="2020-04-30T22:05:18Z" level=info msg="dialing session" addr="127.0.0.1:34496" compoent=ssh-conn-dialer node="127.0.0.1:34496" session=bqlkn7ch7k8ut6sl6olg
time="2020-04-30T22:05:18Z" level=debug msg="error handling ssh session" component=stream-local-handler componet=sshd error=closed session-id=bqlkn74h7k8ut6sl6ok0
time="2020-04-30T22:05:18Z" level=info msg="waiting for unix socket" socket=/tmp/upterm474249847/upterm.sock
time="2020-04-30T22:05:19Z" level=info msg="dialing session" addr="127.0.0.1:34496" compoent=ssh-conn-dialer node="127.0.0.1:34496" session=bqlkn7ch7k8ut6sl6omg
time="2020-04-30T22:05:19Z" level=info msg="waiting for unix socket" socket=/tmp/upterm305380202/upterm.sock
time="2020-04-30T22:05:19Z" level=info msg="dialing sshproxy session" addr="127.0.0.1:34496" compoent=ws-sshproxy-dialer session=bqlkn7kh7k8ut6sl6on0 sshproxy-addr="127.0.0.1:34496"
time="2020-04-30T22:05:19Z" level=info msg="dialing session" addr="127.0.0.1:34496" compoent=ssh-conn-dialer node="127.0.0.1:34496" session=bqlkn7kh7k8ut6sl6on0
2020/04/30 22:05:19 Timeout hit..
time="2020-04-30T22:05:19Z" level=debug msg="error handling ssh session" component=stream-local-handler componet=sshd error=closed session-id=bqlkn74h7k8ut6sl6oj0
2020/04/30 22:05:19 Timeout hit..
time="2020-04-30T22:05:19Z" level=debug msg="error handling ssh session" component=stream-local-handler componet=sshd error=closed session-id=bqlkn7ch7k8ut6sl6olg
time="2020-04-30T22:05:20Z" level=debug msg="error handling ssh session" component=stream-local-handler componet=sshd error=closed session-id=bqlkn7ch7k8ut6sl6omg
--- FAIL: Test_ftest (0.01s)
    --- PASS: Test_ftest/ssh/testHostFailToShareWithoutPrivateKey (0.16s)
    --- PASS: Test_ftest/ws/testHostFailToShareWithoutPrivateKey (0.25s)
    --- PASS: Test_ftest/ssh/testHostSessionCreatedCallback (1.03s)
    --- PASS: Test_ftest/ws/testClientNonExistingSession (1.10s)
    --- PASS: Test_ftest/ssh/testClientNonExistingSession (1.12s)
    --- FAIL: Test_ftest/ssh/testClientAttachHostWithSameCommand (1.25s)
    --- PASS: Test_ftest/ws/testHostSessionCreatedCallback (1.00s)
    --- PASS: Test_ftest/ws/testClientAttachHostWithSameCommand (1.32s)
    --- PASS: Test_ftest/ssh/testClientAttachReadOnly (2.26s)
    --- PASS: Test_ftest/ws/testClientAttachReadOnly (2.19s)
    --- PASS: Test_ftest/ssh/testClientAttachHostWithDifferentCommand (2.07s)
    --- PASS: Test_ftest/ws/testClientAttachHostWithDifferentCommand (2.11s)
FAIL
coverage: [no statements]
FAIL    github.com/jingweno/upterm/ftests       5.762s
=== RUN   Test_signerFromFile
=== RUN   Test_signerFromFile/rsa_private_key_wrong_passphrase
=== RUN   Test_signerFromFile/rsa_private_key_correct_passphrase
=== RUN   Test_signerFromFile/ed25519_private_key_wrong_passphrase
=== RUN   Test_signerFromFile/ed25519_private_key_correct_passphrase
--- PASS: Test_signerFromFile (31.77s)
    --- PASS: Test_signerFromFile/rsa_private_key_wrong_passphrase (16.35s)
    --- PASS: Test_signerFromFile/rsa_private_key_correct_passphrase (3.13s)
    --- PASS: Test_signerFromFile/ed25519_private_key_wrong_passphrase (9.34s)
    --- PASS: Test_signerFromFile/ed25519_private_key_correct_passphrase (2.94s)
PASS
coverage: 11.5% of statements
ok      github.com/jingweno/upterm/host 37.164s coverage: 11.5% of statements
=== RUN   Test_EncodeDecodeIdentifier
=== RUN   Test_EncodeDecodeIdentifier/client_type
=== PAUSE Test_EncodeDecodeIdentifier/client_type
=== RUN   Test_EncodeDecodeIdentifier/host_type
=== PAUSE Test_EncodeDecodeIdentifier/host_type
=== CONT  Test_EncodeDecodeIdentifier/host_type
=== CONT  Test_EncodeDecodeIdentifier/client_type
--- PASS: Test_EncodeDecodeIdentifier (0.00s)
    --- PASS: Test_EncodeDecodeIdentifier/client_type (0.00s)
    --- PASS: Test_EncodeDecodeIdentifier/host_type (0.00s)
PASS
coverage: 12.9% of statements
ok      github.com/jingweno/upterm/host/api     0.099s  coverage: 12.9% of statements
?       github.com/jingweno/upterm/host/api/swagger/client      [no test files]
?       github.com/jingweno/upterm/host/api/swagger/client/admin_service        [no test files]
?       github.com/jingweno/upterm/host/api/swagger/models      [no test files]
?       github.com/jingweno/upterm/host/internal        [no test files]
=== RUN   Test_ContextReader
=== PAUSE Test_ContextReader
=== RUN   Test_MultiWriter
=== PAUSE Test_MultiWriter
=== CONT  Test_ContextReader
=== RUN   Test_ContextReader/happy_path
=== CONT  Test_MultiWriter
--- PASS: Test_MultiWriter (0.00s)
=== PAUSE Test_ContextReader/happy_path
=== RUN   Test_ContextReader/pass_in_canceled_context
=== PAUSE Test_ContextReader/pass_in_canceled_context
=== RUN   Test_ContextReader/cancel_context_during_copy
=== PAUSE Test_ContextReader/cancel_context_during_copy
=== RUN   Test_ContextReader/cancel_context_after_copy
=== PAUSE Test_ContextReader/cancel_context_after_copy
=== CONT  Test_ContextReader/happy_path
=== CONT  Test_ContextReader/cancel_context_after_copy
=== CONT  Test_ContextReader/pass_in_canceled_context
=== CONT  Test_ContextReader/cancel_context_during_copy
--- PASS: Test_ContextReader (0.01s)
    --- PASS: Test_ContextReader/pass_in_canceled_context (0.01s)
    --- PASS: Test_ContextReader/happy_path (0.02s)
    --- PASS: Test_ContextReader/cancel_context_during_copy (1.02s)
    --- PASS: Test_ContextReader/cancel_context_after_copy (3.48s)
    Test_ContextReader: reader_test.go:54: should never get here
FAIL
==================
WARNING: DATA RACE
Read at 0x00c0001a2163 by goroutine 20:
  testing.(*common).Fail()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:612 +0xc5
  testing.(*common).Fail()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:607 +0x14c
  testing.(*common).FailNow()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:635 +0x38
  testing.(*common).Fatal()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:718 +0x89
  github.com/jingweno/upterm/io.Test_ContextReader.func3.1()
      /home/dyno/project/upterm/io/reader_test.go:54 +0xa0
  github.com/jingweno/upterm/io.readFunc.Read()
      /home/dyno/project/upterm/io/reader_test.go:99 +0x55
  github.com/jingweno/upterm/io.contextReader.Read.func1()
      /home/dyno/project/upterm/io/reader.go:38 +0x14f

Previous write at 0x00c0001a2163 by goroutine 7:
  testing.tRunner.func1()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:977 +0x467
  testing.tRunner()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:995 +0x20d

Goroutine 20 (running) created at:
  github.com/jingweno/upterm/io.contextReader.Read()
      /home/dyno/project/upterm/io/reader.go:28 +0xed
  github.com/jingweno/upterm/io.(*contextReader).Read()
      <autogenerated>:1 +0xb6
  bytes.(*Buffer).ReadFrom()
      /home/dyno/.heroku/lib/go/src/bytes/buffer.go:204 +0x158
  io.copyBuffer()
      /home/dyno/.heroku/lib/go/src/io/io.go:391 +0x3fa
  io.Copy()
      /home/dyno/.heroku/lib/go/src/io/io.go:364 +0x271
  github.com/jingweno/upterm/io.Test_ContextReader.func3()
      /home/dyno/project/upterm/io/reader_test.go:64 +0x239
  testing.tRunner()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:991 +0x1eb

Goroutine 7 (finished) created at:
  testing.(*T).Run()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:1042 +0x660
  testing.runTests.func1()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:1284 +0xa6
  testing.tRunner()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:991 +0x1eb
  testing.runTests()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:1282 +0x527
  testing.(*M).Run()
      /home/dyno/.heroku/lib/go/src/testing/testing.go:1199 +0x2ff
  main.main()
      _testmain.go:96 +0x337
==================
panic: Fail in goroutine after Test_ContextReader has completed

goroutine 35 [running]:
testing.(*common).Fail(0xc0001a2120)
        /home/dyno/.heroku/lib/go/src/testing/testing.go:613 +0x1f3
testing.(*common).Fail(0xc0001a25a0)
        /home/dyno/.heroku/lib/go/src/testing/testing.go:607 +0x14d
testing.(*common).FailNow(0xc0001a25a0)
        /home/dyno/.heroku/lib/go/src/testing/testing.go:635 +0x39
testing.(*common).Fatal(0xc0001a25a0, 0xc0000c0e18, 0x1, 0x1)
        /home/dyno/.heroku/lib/go/src/testing/testing.go:718 +0x8a
github.com/jingweno/upterm/io.Test_ContextReader.func3.1(0xc0000b6000, 0x200, 0x200, 0xc0000b0e80, 0x43339a, 0xc0000b8000)
        /home/dyno/project/upterm/io/reader_test.go:54 +0xa1
github.com/jingweno/upterm/io.readFunc.Read(0xc0000a0010, 0xc0000b6000, 0x200, 0x200, 0x0, 0x0, 0x0)
        /home/dyno/project/upterm/io/reader_test.go:99 +0x56
github.com/jingweno/upterm/io.contextReader.Read.func1(0xc000096060, 0x6d3d20, 0xc0000a0010, 0x6d60a0, 0xc0000aa000, 0xc0000b6000, 0x200, 0x200)
        /home/dyno/project/upterm/io/reader.go:38 +0x150
created by github.com/jingweno/upterm/io.contextReader.Read
        /home/dyno/project/upterm/io/reader.go:28 +0xee
FAIL    github.com/jingweno/upterm/io   9.188s
=== RUN   Test_MemListener_Listen
=== PAUSE Test_MemListener_Listen
=== RUN   Test_MemListener_Dial
=== PAUSE Test_MemListener_Dial
=== RUN   Test_MemListener_RemoveListener
=== PAUSE Test_MemListener_RemoveListener
=== CONT  Test_MemListener_Listen
=== CONT  Test_MemListener_Dial
=== CONT  Test_MemListener_RemoveListener
--- PASS: Test_MemListener_Listen (0.00s)
--- PASS: Test_MemListener_Dial (0.00s)
--- PASS: Test_MemListener_RemoveListener (0.00s)
PASS
coverage: 73.1% of statements
ok      github.com/jingweno/upterm/memlistener  0.140s  coverage: 73.1% of statements
?       github.com/jingweno/upterm/metrics      [no test files]
=== RUN   Test_SSHD_DisallowSession
time="2020-04-30T22:05:15Z" level=info msg="waiting for server" addr="127.0.0.1:41115"
--- PASS: Test_SSHD_DisallowSession (1.06s)
=== RUN   Test_SSHProxy_findUpstream
time="2020-04-30T22:05:16Z" level=info msg="waiting for server" addr="127.0.0.1:38452"
time="2020-04-30T22:05:16Z" level=debug msg="connection establishing failed" addr="127.0.0.1:37532" error="read tcp 127.0.0.1:38452->127.0.0.1:37532: read: connection reset by peer"
time="2020-04-30T22:05:17Z" level=info msg="waiting for server" addr="127.0.0.1:43881"
time="2020-04-30T22:05:17Z" level=info msg="dialing neighbour" addr="127.0.0.1:43881" node="127.0.0.1:38452" session=bqlkn7ch7k8ut7coik60
time="2020-04-30T22:05:17Z" level=error msg="failed to accept connection" error="accept tcp 127.0.0.1:38452: use of closed network connection"
--- PASS: Test_SSHProxy_findUpstream (2.04s)
=== RUN   Test_WebSocketProxy_Host
time="2020-04-30T22:05:17Z" level=info msg="dialing sshd" host=owen ndoe=
--- PASS: Test_WebSocketProxy_Host (0.01s)
PASS
coverage: 25.6% of statements
ok      github.com/jingweno/upterm/server       3.263s  coverage: 25.6% of statements
?       github.com/jingweno/upterm/upterm       [no test files]
?       github.com/jingweno/upterm/utils        [no test files]
?       github.com/jingweno/upterm/ws   [no test files]
FAIL
make: *** [Makefile:55: test] Error 1

Compliance / Logging Mode

In certain conditions it would be useful to have enforced logging available. Especially in cases where logs are required for compliance, having upterm record the session and export logs would be extremely useful.

This would also position upterm to fulfil a role similar to what sudo_pair does. It lacks the "approve" feature, but engineers who are performing sensitive actions have both another engineer reviewing actions during the session, and exported logs for future audit requirements.

Expected behaviour

There are multiple options here, but the first requirement would be that the session is started in "compliance" mode. This would be through a flag --compliance. The user could also specify a log file location.

One option with the log file generation is to only generate the log file on the host side. Alternatively a client that connects to a host that has --compliance enabled would also start recording logs. The benefit here is that there are two records of the logs and you remove the single party responsibility.

Actual logging can be added to the stdin, stdout and stderr of the session. This would need to be added to the session.stdin etc, maybe an io.Pipe that does session.stdin --> logger-pipe --> ptx.stdin

Cannot build in Windows inside MINGW32 bash session

Hello!

I'm unable to build from sources for Windows running inside a bash session of Mingw32:

$ go build ./cmd/upterm/...
# github.com/jingweno/upterm/host/internal
host\internal\command.go:86:21: undefined: syscall.SIGWINCH
host\internal\command.go:87:9: undefined: syscall.SIGWINCH
host\internal\pty.go:13:12: undefined: pty.Start
host\internal\pty.go:33:9: undefined: pty.Getsize
host\internal\pty.go:54:11: undefined: pty.Winsize
host\internal\pty.go:58:9: undefined: pty.Setsize
```

"upterm upgrade" fails with "no binary for your system"

I'm running upterm (v0.6.7) locally, and trying to run upterm upgrade results in:

Error: no binary for your system
Usage:
  upterm upgrade [flags]

Examples:
  # Upgrade to the latest version
  upterm upgrade

  # Upgrade to a specific version
  $ upterm upgrade 0.2.0

Flags:
  -h, --help   help for upgrade

FATA[0000] no binary for your system

That's an odd error (it's just a regular amd64 system), and the error message doesn't really provide enough information to be actionable.

Don't terminate when last client leaves

When the last client leaves the upterm session my upterm host session terminates. This is not ideal, as I could be running an editor and have unsaved changes.

As soon as the last user terminates their session by closing their terminal this is shown:

[...]
 ~ ▓▒░ Error: EOF
Usage:
  upterm host [flags]

Examples:
  # Host a terminal session that runs $SHELL with
  # client's input/output attaching to the host's
  upterm host

  # Host a terminal session that only allows specified public key(s) to connect
  $ upterm host --authorized-key PATH_TO_PUBLIC_KEY

  # Host a session with a custom command.
  upterm host -- docker run --rm -ti ubuntu bash

  # Host a session that runs 'tmux new -t pair-programming' and
  # force clients to join with 'tmux attach -t pair-programming'.
  # This is similar to tmate.
  upterm host --force-command 'tmux attach -t pair-programming' -- tmux new -t pair-programming

  # Use a different Uptermd server and host a session via WebSocket
  upterm host --server wss://YOUR_UPTERMD_SERVER -- YOUR_COMMAND

Flags:
  -a, --authorized-key string   an authorized_keys file that lists public keys that are permitted to connect.
  -f, --force-command string    force execution of a command and attach its input/output to client's.
  -h, --help                    help for host
      --known-hosts string      a file contains the known keys for remote hosts (required). (default "/home/hexa/.ssh/known_hosts")
  -i, --private-key strings     private key for public key authentication against the upterm server (required). (default [/home/hexa/.ssh/id_ed25519,/home/hexa/.ssh/id_rsa])
  -r, --read-only               host a read-only session. Clients won't be able to interact.
      --server string           upterm server address (required), supported protocols are shh, ws, or wss. (default "ssh://uptermd.upterm.dev:22")

FATA[0031] EOF   

Can't connect. Connection closed.

Version: Upterm version v0.7.3

Terminal 1:

➜  ~ upterm host
Enter passphrase for key '/home/langit/.ssh/id_ed25519': 
=== RU3JOU8EBSCQRSH53NCW                                                                 
Command:                /bin/fish                                                       
Force Command:          n/a                                                             
Host:                   ssh://uptermd.upterm.dev:22                                     
SSH Session:            ssh rU3jou8ebSCQRsh53ncw:[email protected]
Press <q> or <ctrl-c> to continue...

Terminal 2:
(one minutes later)

➜  ~ ssh rU3jou8ebSCQRsh53ncw:[email protected]
Connection closed by 157.230.199.75 port 22

panic: runtime error: slice bounds out of range [1:0] on Linux

On Linux:

./build/upterm host
=== BQL7C16EDK2L120DTC80                                                                 
Command:                                                                                
Force Command:          n/a                                                             
Host:                   ssh://uptermd.upterm.dev:22                                     
SSH Session:            ssh bql7c16edk2l120dtc80:[email protected]
Press <q> or <ctrl-c> to continue...
panic: runtime error: slice bounds out of range [1:0]

goroutine 39 [running]:
github.com/jingweno/upterm/host/internal.(*Server).ServeWithContext(0xc0000ea000, 0xd31160, 0xc000442240, 0xd2eaa0, 0xc000590e20, 0x0, 0x0)
        /home/heroku/project/upterm/host/internal/server.go:46 +0x122c
github.com/jingweno/upterm/host.(*Host).Run.func3(0x0, 0x0)
        /home/heroku/project/upterm/host/host.go:131 +0x52
github.com/oklog/run.(*Group).Run.func1(0xc00060e240, 0xc0004ca390, 0xc0004c4310)
        /home/heroku/project/upterm/vendor/github.com/oklog/run/group.go:38 +0x27
created by github.com/oklog/run.(*Group).Run
        /home/heroku/project/upterm/vendor/github.com/oklog/run/group.go:37 +0xbb

errwrap 1.1.0

Hi could/should errwrap use 1.1.0 instead of 1.0.0?

github.com/hashicorp/errwrap

Heroku hosted server and permission denied

I've used your instructions to deploy to Heroku. I have the host running and the session current command is giving the appropriate ssh command pointing at the Heroku app. But when executed on the Mac to connect, I get a Permission denied (publickey). Here are the logs from the Heroku app:

2021-11-03T22:58:24.656438+00:00 heroku[router]: at=info method=GET path="/" host=cdr-upterm.herokuapp.com request_id=7c41e010-56d7-4381-938e-9febbf558f88 fwd="198.217.125.15" dyno=web.1 connect=0ms service=772ms status=101 bytes=1240 protocol=https
2021-11-03T22:58:30.721907+00:00 app[web.1]: time="2021-11-03T22:58:30Z" level=info msg="dialing sshproxy session" addr="0.0.0.0:2222" app=uptermd com=ws-sshproxy-dialer network=mem network-opt="[]" node-addr="0.0.0.0:2222" session=sRiv4wTY6mtuYtBedj0w ssh-addr="[::]:2222" sshproxy-addr="[::]:2222" ws-addr="[::]:36680"
2021-11-03T22:58:31.052368+00:00 app[web.1]: time="2021-11-03T22:58:31Z" level=info msg="dialing session" addr="0.0.0.0:2222" app=uptermd com=ssh-conn-dialer network=mem network-opt="[]" node="0.0.0.0:2222" node-addr="0.0.0.0:2222" session=sRiv4wTY6mtuYtBedj0w ssh-addr="[::]:2222" ws-addr="[::]:36680"
2021-11-03T22:58:31.567653+00:00 heroku[router]: at=info method=GET path="/" host=cdr-upterm.herokuapp.com request_id=62845bfc-7769-41bd-9f9a-55410f3de5cf fwd="198.217.125.15" dyno=web.1 connect=0ms service=845ms status=101 bytes=1240 protocol=https
2021-11-03T22:58:31.567441+00:00 app[web.1]: time="2021-11-03T22:58:31Z" level=error msg="error piping" app=uptermd com=ws-proxy error="readfrom tcp 127.0.0.1:53424->127.0.0.1:2222: websocket: close 1006 (abnormal closure): unexpected EOF" network=mem network-opt="[]" node-addr="0.0.0.0:2222" ssh-addr="[::]:2222" ws-addr="[::]:36680"
2021-11-03T22:58:49.138609+00:00 app[web.1]: time="2021-11-03T22:58:49Z" level=info msg="dialing sshproxy session" addr="0.0.0.0:2222" app=uptermd com=ws-sshproxy-dialer network=mem network-opt="[]" node-addr="0.0.0.0:2222" session=sRiv4wTY6mtuYtBedj0w ssh-addr="[::]:2222" sshproxy-addr="[::]:2222" ws-addr="[::]:36680"
2021-11-03T22:58:49.460306+00:00 app[web.1]: time="2021-11-03T22:58:49Z" level=info msg="dialing session" addr="0.0.0.0:2222" app=uptermd com=ssh-conn-dialer network=mem network-opt="[]" node="0.0.0.0:2222" node-addr="0.0.0.0:2222" session=sRiv4wTY6mtuYtBedj0w ssh-addr="[::]:2222" ws-addr="[::]:36680"
2021-11-03T22:58:49.971328+00:00 heroku[router]: at=info method=GET path="/" host=cdr-upterm.herokuapp.com request_id=f3603b62-d4be-4a48-9195-04b74629b484 fwd="198.217.125.15" dyno=web.1 connect=0ms service=832ms status=101 bytes=1240 protocol=https
2021-11-03T22:58:49.971068+00:00 app[web.1]: time="2021-11-03T22:58:49Z" level=error msg="error piping" app=uptermd com=ws-proxy error="readfrom tcp 127.0.0.1:54220->127.0.0.1:2222: websocket: close 1006 (abnormal closure): unexpected EOF" network=mem network-opt="[]" node-addr="0.0.0.0:2222" ssh-addr="[::]:2222" ws-addr="[::]:36680"

Any suggestions?

Incompatibility with a running gpg-agent as ssh-agent?

Expected behavior:
upterm host, Ctrl-c gives me a ssh command to connect.
I enter the ssh command on another host and I'm sharing a terminal.

What actually happens:
I'm running an gpg-agent in ssh-agent mode.
upterm host leads to my gpg-agent authenticating against uptermd.upterm.dev. If I don't supply the password, I get an FATA[0016] ssh dial error: ssh: handshake failed: agent: failed to sign challenge. If I supply the password, the session is protected with a pubkey and entering the ssh <token>@uptermd.upterm.dev command on another host leads to permission denied (publickey). I can't share a session even if I delete my .ssh/config.

Is this expected behavior? How can I achieve the desired behavior similar to tmate?

Custom Token Heroku

Is it possible to generate a custom token so that I can always access my devices that are behind NAT?

uptermd with websockets: "FATA[0006] EOF" and "error waiting for pipe" error since 0.13.1

Buildung uptermd and running version 0.13.0 sessions are working. Using version 0.13.1 or higher results in FATA[0006] EOF errors:

Host:

:~$ upterm host --server ws://0.0.0.0:2223                                                                            2024-02-06 12:14:52
=== S7EAMGIBCCJ5L5OZRKSA                                                                                                                                                      
Command:                /usr/bin/zsh                                                                                                                                         
Force Command:          n/a                                                                                                                                                  
Host:                   ws://0.0.0.0:2223                                                                                                                                    
Authorized Keys:        n/a                                                                                                                                                  
SSH Session:            ssh -o ProxyCommand='upterm proxy ws://S7EAmGiBCCJ5L5ozRksA:[email protected]:2223' S7EAmGiBCCJ5L5ozRksA:[email protected]:2223

Run 'upterm session current' to display this screen again

Press <q> or <ctrl-c> to accept connections...
No matches.
:~$ Error: EOF
Usage:
  upterm host [flags]

Examples:
  # Host a terminal session running $SHELL, attaching client's IO to the host's:
  upterm host

  # Accept client connections automatically without prompts:
  upterm host --accept

  # Host a terminal session allowing only specified public key(s) to connect:
  upterm host --authorized-keys PATH_TO_AUTHORIZED_KEY_FILE

  # Host a session executing a custom command:
  upterm host -- docker run --rm -ti ubuntu bash

  # Host a 'tmux new -t pair-programming' session, forcing clients to join with 'tmux attach -t pair-programming':
  upterm host --force-command 'tmux attach -t pair-programming' -- tmux new -t pair-programming

  # Use a different Uptermd server, hosting a session via WebSocket:
  upterm host --server wss://YOUR_UPTERMD_SERVER -- YOUR_COMMAND

Flags:
      --accept                   Automatically accept client connections without prompts.
      --authorized-keys string   Specify a authorize_keys file listing authorized public keys for connection.
  -f, --force-command string     Enforce a specified command for clients to join, and link the command's input/output to the client's terminal.
      --github-user strings      Authorize specified GitHub users by allowing their public keys to connect. Configure GitHub CLI environment variables as needed; see https://cli.github.com/manual/gh_help_environment for details.
      --gitlab-user strings      Authorize specified GitLab users by allowing their public keys to connect.
  -h, --help                     help for host
      --known-hosts string       Specify a file containing known keys for remote hosts (required). (default "/home/user/.ssh/known_hosts")
  -i, --private-key strings      Specify private key files for public key authentication with the upterm server (required). (default [/home/user/.ssh/id_ed25519,/home/user/.ssh/id_rsa])
  -r, --read-only                Host a read-only session, preventing client interaction.
      --server string            Specify the upterm server address (required). Supported protocols: ssh, ws, wss. (default "ssh://uptermd.upterm.dev:22")
      --srht-user strings        Authorize specified SourceHut users by allowing their public keys to connect.

FATA[0006] EOF  

Upterm join:

:~$ ssh -o ProxyCommand='upterm proxy ws://S7EAmGiBCCJ5L5ozRksA:[email protected]:2223' S7EAmGiBCCJ5L5ozRksA:[email protected]:2223
No matches.
:~$ dError: websocket: close 1006 (abnormal closure): unexpected EOF                                                  2024-02-06 12:15:56
                                                                                        Usage:
                                                                                                upterm proxy [flags]
                                                                                                                    
                                                                                                                    Examples:
                                                                                                                               # Host shares a session running $SHELL over WebSocket:
                         upterm host --server wss://uptermd.upterm.dev -- YOUR_COMMAND
                                                                                      
                                                                                        # Client connects to the host session via WebSocket:
                                                                                                                                              ssh -o ProxyCommand='upterm proxy wss://[email protected]' TOKEN:uptermd.uptermd.dev:443
                                                                               
                                                                               Flags:
                                                                                       -h, --help   help for proxy
                                                                                                                  
                                                                                                                  FATA[0001] websocket: close 1006 (abnormal closure): unexpected EOF 
                        client_loop: send disconnect: Broken pipe
:~$ efefefefe  

uptermd log:

INFO[0000] starting server                               app=uptermd network=mem network-opt="[]" node-addr="127.0.0.1:2222" ssh-addr="127.0.0.1:2222" ws-addr="[::]:2223"
INFO[0002] dialing sshproxy sshd                         app=uptermd com=ws-sshproxy-dialer host=user network=mem network-opt="[]" node-addr="127.0.0.1:2222" ssh-addr="127.0.0.1:2222" sshproxy-addr="127.0.0.1:2222" ws-addr="[::]:2223"
INFO[0002] dialing sshd                                  app=uptermd com=ssh-conn-dialer host=user network=mem network-opt="[]" node="127.0.0.1:2222" node-addr="127.0.0.1:2222" ssh-addr="127.0.0.1:2222" ws-addr="[::]:2223"
INFO[0002] attempt to bind                               app=uptermd com=sshd network=mem network-opt="[]" node-addr="127.0.0.1:2222" ssh-addr="127.0.0.1:2222" tunnel-host=S7EAmGiBCCJ5L5ozRksA tunnel-port=0 ws-addr="[::]:2223"
INFO[0007] dialing sshproxy session                      addr="127.0.0.1:2222" app=uptermd com=ws-sshproxy-dialer network=mem network-opt="[]" node-addr="127.0.0.1:2222" session=S7EAmGiBCCJ5L5ozRksA ssh-addr="127.0.0.1:2222" sshproxy-addr="127.0.0.1:2222" ws-addr="[::]:2223"
INFO[0007] dialing session                               addr="127.0.0.1:2222" app=uptermd com=ssh-conn-dialer network=mem network-opt="[]" node="127.0.0.1:2222" node-addr="127.0.0.1:2222" session=S7EAmGiBCCJ5L5ozRksA ssh-addr="127.0.0.1:2222" ws-addr="[::]:2223"
DEBU[0009] error waiting for pipe                        addr="127.0.0.1:42878" app=uptermd com=ssh-proxy error=EOF network=mem network-opt="[]" node-addr="127.0.0.1:2222" ssh-addr="127.0.0.1:2222" ws-addr="[::]:2223"
ERRO[0009] error piping                                  app=uptermd com=ws-proxy error="readfrom tcp 127.0.0.1:48936->127.0.0.1:2222: websocket: close 1006 (abnormal closure): unexpected EOF" network=mem network-opt="[]" node-addr="127.0.0.1:2222" ssh-addr="127.0.0.1:2222" ws-addr="[::]:2223"
DEBU[0009] error waiting for pipe                        addr="127.0.0.1:48936" app=uptermd com=ssh-proxy error=EOF network=mem network-opt="[]" node-addr="127.0.0.1:2222" ssh-addr="127.0.0.1:2222" ws-addr="[::]:2223"
DEBU[0009] error handling ssh session                    app=uptermd com=stream-local-handler error=closed network=mem network-opt="[]" node-addr="127.0.0.1:2222" session-id=S7EAmGiBCCJ5L5ozRksA ssh-addr="127.0.0.1:2222" ws-addr="[::]:2223"

Upterm rejecting all ssh connections

I was trying to spin up my first upterm session; after running upterm host -- bash I am presented with the expected ssh session information, but when I attempt to connect to that session from another system, I get a "permission denied" error:

[lars@madhatter ~]$ ssh hGjlbiMJqi0M9Jjo4SSo:[email protected]
hGjlbiMJqi0M9Jjo4SSo:[email protected]: Permission denied (publickey).

I haven't included any --authorized-key argument in my upterm host command so I expected any ssh client to be able to connect.

Add view only mode

Are there any plans to add a view only mode anyone could join? Right now that seems to be not possible and this was one of the bigger use cases for tmate.

0.7.5 crashing on ssh connection

I compiled this from source following official instructions.
Then:

go/bin/upterm host

On a second shell, I run the printed ssh command. Then the upterm host crashes with:

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x876252]
goroutine 13 [running]:
golang.org/x/crypto/ssh.(*curve25519sha256).Server(0xc000446580, {0x7f77a1d19008, 0xc0002b6000}, {0xb9d380, 0xc000134570}, 0x80, {0x0, 0x0})
/home/svenstaro/src/upterm/vendor/golang.org/x/crypto/ssh/kex.go:515 +0x272
golang.org/x/crypto/ssh.(*handshakeTransport).server(0xc000216480, {0xba92d8, 0x1042d10}, 0xc0001a2880, 0xc0001e2c60)
/home/svenstaro/src/upterm/vendor/golang.org/x/crypto/ssh/handshake.go:691 +0xe3
golang.org/x/crypto/ssh.(*handshakeTransport).enterKeyExchange(0xc000216480, {0xc000472a80, 0xc0004e2420, 0x0})
/home/svenstaro/src/upterm/vendor/golang.org/x/crypto/ssh/handshake.go:616 +0x3ec
golang.org/x/crypto/ssh.(*handshakeTransport).kexLoop(0xc000216480)
/home/svenstaro/src/upterm/vendor/golang.org/x/crypto/ssh/handshake.go:325 +0xa6
created by golang.org/x/crypto/ssh.newServerTransport
/home/svenstaro/src/upterm/vendor/golang.org/x/crypto/ssh/handshake.go:167 +0x13d

Invalid entry added to known hosts

After running upterm an invalid entry is added to the known_hosts file. Attempting to run upterm later results in the following error:

FATA[0000] knownhosts: /Users/daniel/.ssh/known_hosts:4: address [2a09:8280:1::3:4b89]: missing port in address

OS: Darwin 21.6.0 Darwin Kernel Version 21.6.0: Sat Jun 18 17:07:22 PDT 2022; root:xnu-8020.140.41~1/RELEASE_ARM64_T6000 arm64
Upterm: Upterm version v0.9.0
Removing the entry enables you to run upterm again.
Manually adding port 22 to the address resolves the issue.

Automatic authorized keys from github/gitlab

I would like to simplify something like upterm host --authorized-key <(curl https://github.com/rothgar.keys)

It would be great if this can be a single option like --github-user rothgar. If we want to make it more flexible maybe it could be something like --git-user rothgar@github which would automatically fetch https://github.com/rothgar.keys. This could limit the amount of flags and still make it pluggable for things like jgarr@gitlab

proposal: please make SSH public keys fingerprints via trusted way visible

Hereby I propose to make uptermd.upterm.dev SSH key fingerprints publicly available. Then we can verify it BEFORE we connect first time. Currnetly we have to just trust blindly and cannot avoid MITM. Looks like https://uptermd.upterm.dev/ works, one option is to show fingerprints there as index page.

Also noticed, that following command gave empty output:
ssh-keyscan uptermd.upterm.dev | ssh-keygen -lf -
... although this is not reliable as it wouldn't avoid MITM. The only trusted way is get fingerprints directly from upterm server admin via reliable public way. To not depend on 3rd party service, then it would not be good to share these keys via GitHub. Certainly this is also one option, if sharing fingerprints directly via web is not suitable for you. The most important is, that you will share these fingerprints so, that we can use them.
I use it e.g. so - create a script

#!/bin/sh
user=<token>
server=uptermd.upterm.dev
serversshport=22
key=ED25519
trustedhash=<fingerprint>
serverhash=<fingerprint>
# Connecting
if [ $serverhash = $trustedhash ]; then clear; printf "\nOffered $(echo $key) key fingerprint\n\n$(echo $serverhash)\n\n...CAN be trusted.\n\nLogging in...\n\n"; ssh $user@$server -p $serversshport; else clear; printf "\nOffered $(echo $key) key fingerprint\n\n$(echo $serverhash)\n\n...CAN NOT be trusted.\n\nThe $(echo $key) key trusted fingerprint is:\n\n$(echo $trustedhash)\n\nDO NOT log in\n\n"; fi

... and then run it. For easier run, I will create bash alias (~/.bash_aliases) to run it:
alias upterm-connect='sh upterm.connect.sh'
Certainly I will edit that script and fulfill with appropriate data. trustedhash would be the fingerprint got from you via reliable way. The serverhash is the fingerprint offered on first connection. I just will answer no on first time, then copy that hash to script and then connect already via script.

So my proposal - please adopt to your needs (fix the real web server root location) and run that command on your server
for i in /etc/ssh/*.pub; do ssh-keygen -lf $i; done | uniq > uptermd.upterm.dev/index.html

This means, that server key fingerprints (key pair has same fingerprint) will be redirected as front page of uptermd.upterm.dev server. This is just hack, we all know, that there will be plain text file without any HTML syntax and this syntax is not needed - we just need fingerprints and that purpose will be done.

The output of that proposed command could look like (depending on what keys are available):

1024 SHA256:pm5g8a021dZsqZzyajgvrdI8YgeBOFTtzoYVS/+m8+s root@server (DSA)
256 SHA256:OWMdutEQ7xpgUnOIxYFcQsZJhW+soeGqok9nzY root@server (ECDSA)
256 SHA256:9ajV8JqMe6jJE/s3TYjb/9xw7T0pfJ2+gADiBIJWDPE root@server (ED25519)
3072 SHA256:uFiKe97+ooizk3/pF1IaUCnbqmnd4ablQpHd40 root@server (RSA)

Then we can ensure about the fingerprint BEFORE first connection and verify it and THEN connect.

On first connection there is such a message:

The authenticity of host 'uptermd.upterm.dev (157.230.199.75)' can't be established.
ED25519 key fingerprint is SHA256:9ajV8JqMe6jJE/s3TYjb/9xw7T0pfJ2+gADiBIJWDPE.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

After accepting it, we can check the accepted fingerprint from ~/.ssh/authorized_keys file:

ssh-keygen -F uptermd.upterm.dev | ssh-keygen -lf -
256 SHA256:9ajV8JqMe6jJE/s3TYjb/9xw7T0pfJ2+gADiBIJWDPE |1|DAZ5Gxn3yPCHGMM6QtJRyyfRjrk=|cV9vcPY7UrC+koTzNJsgAscPEzw= (ED25519)

... and we can see, that 9ajV8JqMe6jJE/s3TYjb/9xw7T0pfJ2+gADiBIJWDPE is the fingerprint of Ed25519 key, which was offered also on first SSH-connection.

The point is: this will avoid MITM on first connection, next connections will be checked anyway via ~/.ssh/authorized_keys file and in case of mismatch connection will not be established. Current idea of fingerprints availability will avoid MITM on first connection. This is actually regular practice, what sysadmins are using in order to be sure about the server they connecting and avoid MITM.

Can't connect: Permission denied (publickey)

Hi ! I've tried replacing tmate with upterm today, and I've faced an issue, when I try to connect to the remote machine, I get a public key denied (but I didn't make a private key ?)
The objective is to quickly connect from one machine to the other to help a friend with some stuff, on the remote machine, I just do upterm host -- <some shell here> then press q
on the other machine, here are the logs with ssh -vvv

$ ~> ssh -vvv yK11b5BHuYSCIaRD5hRk:[email protected]
OpenSSH_8.8p1, OpenSSL 1.1.1m  14 Dec 2021
debug3: expanded UserKnownHostsFile '~/.ssh/known_hosts' -> '/home/byjumperx4/.ssh/known_hosts'
debug3: expanded UserKnownHostsFile '~/.ssh/known_hosts2' -> '/home/byjumperx4/.ssh/known_hosts2'
debug1: Authenticator provider $SSH_SK_PROVIDER did not resolve; disabling
debug2: resolving "uptermd.upterm.dev" port 22
debug3: resolve_host: lookup uptermd.upterm.dev:22
debug3: ssh_connect_direct: entering
debug1: Connecting to uptermd.upterm.dev [157.230.199.75] port 22.
debug3: set_sock_tos: set socket 3 IP_TOS 0x48
debug1: Connection established.
debug1: identity file /home/byjumperx4/.ssh/id_rsa type 0
debug1: identity file /home/byjumperx4/.ssh/id_rsa-cert type -1
debug1: identity file /home/byjumperx4/.ssh/id_dsa type -1
debug1: identity file /home/byjumperx4/.ssh/id_dsa-cert type -1
debug1: identity file /home/byjumperx4/.ssh/id_ecdsa type -1
debug1: identity file /home/byjumperx4/.ssh/id_ecdsa-cert type -1
debug1: identity file /home/byjumperx4/.ssh/id_ecdsa_sk type -1
debug1: identity file /home/byjumperx4/.ssh/id_ecdsa_sk-cert type -1
debug1: identity file /home/byjumperx4/.ssh/id_ed25519 type -1
debug1: identity file /home/byjumperx4/.ssh/id_ed25519-cert type -1
debug1: identity file /home/byjumperx4/.ssh/id_ed25519_sk type -1
debug1: identity file /home/byjumperx4/.ssh/id_ed25519_sk-cert type -1
debug1: identity file /home/byjumperx4/.ssh/id_xmss type -1
debug1: identity file /home/byjumperx4/.ssh/id_xmss-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_8.8
debug1: Remote protocol version 2.0, remote software version uptermd
debug1: compat_banner: no match: uptermd
debug2: fd 3 setting O_NONBLOCK
debug1: Authenticating to uptermd.upterm.dev:22 as 'yK11b5BHuYSCIaRD5hRk:MTAuMjQ0LjAuNjU6MjI='
debug3: record_hostkey: found ca key type ED25519 in file /home/byjumperx4/.ssh/known_hosts:1
debug3: load_hostkeys_file: loaded 1 keys from uptermd.upterm.dev
debug1: load_hostkeys: fopen /home/byjumperx4/.ssh/known_hosts2: No such file or directory
debug1: load_hostkeys: fopen /etc/ssh/ssh_known_hosts: No such file or directory
debug1: load_hostkeys: fopen /etc/ssh/ssh_known_hosts2: No such file or directory
debug3: order_hostkeyalgs: prefer hostkeyalgs: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected]
debug3: send packet: type 20
debug1: SSH2_MSG_KEXINIT sent
debug3: receive packet: type 20
debug1: SSH2_MSG_KEXINIT received
debug2: local client KEXINIT proposal
debug2: KEX algorithms: curve25519-sha256,[email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,ext-info-c
debug2: host key algorithms: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,[email protected],[email protected],rsa-sha2-512,rsa-sha2-256
debug2: ciphers ctos: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected]
debug2: ciphers stoc: [email protected],aes128-ctr,aes192-ctr,aes256-ctr,[email protected],[email protected]
debug2: MACs ctos: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: MACs stoc: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-sha1
debug2: compression ctos: none,[email protected],zlib
debug2: compression stoc: none,[email protected],zlib
debug2: languages ctos: 
debug2: languages stoc: 
debug2: first_kex_follows 0 
debug2: reserved 0 
debug2: peer server KEXINIT proposal
debug2: KEX algorithms: [email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group14-sha1
debug2: host key algorithms: [email protected]
debug2: ciphers ctos: [email protected],[email protected],aes128-ctr,aes192-ctr,aes256-ctr
debug2: ciphers stoc: [email protected],[email protected],aes128-ctr,aes192-ctr,aes256-ctr
debug2: MACs ctos: [email protected],hmac-sha2-256,hmac-sha1,hmac-sha1-96
debug2: MACs stoc: [email protected],hmac-sha2-256,hmac-sha1,hmac-sha1-96
debug2: compression ctos: none
debug2: compression stoc: none
debug2: languages ctos: 
debug2: languages stoc: 
debug2: first_kex_follows 0 
debug2: reserved 0 
debug1: kex: algorithm: [email protected]
debug1: kex: host key algorithm: [email protected]
debug1: kex: server->client cipher: [email protected] MAC: <implicit> compression: none
debug1: kex: client->server cipher: [email protected] MAC: <implicit> compression: none
debug3: send packet: type 30
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug3: receive packet: type 31
debug1: SSH2_MSG_KEX_ECDH_REPLY received
debug1: Server host certificate: [email protected] SHA256:9ajV8JqMe6jJE/s3TYjb/9xw7T0pfJ2+gADiBIJWDPE, serial 0 ID "uptermd" CA ssh-ed25519 SHA256:9ajV8JqMe6jJE/s3TYjb/9xw7T0pfJ2+gADiBIJWDPE valid forever
debug2: Server host certificate hostname: uptermd.upterm.dev
debug3: record_hostkey: found ca key type ED25519 in file /home/byjumperx4/.ssh/known_hosts:1
debug3: load_hostkeys_file: loaded 1 keys from uptermd.upterm.dev
debug1: load_hostkeys: fopen /home/byjumperx4/.ssh/known_hosts2: No such file or directory
debug1: load_hostkeys: fopen /etc/ssh/ssh_known_hosts: No such file or directory
debug1: load_hostkeys: fopen /etc/ssh/ssh_known_hosts2: No such file or directory
debug1: Host 'uptermd.upterm.dev' is known and matches the ED25519-CERT host certificate.
debug1: Found CA key in /home/byjumperx4/.ssh/known_hosts:1
debug3: check_host_key: certificate host key in use; disabling UpdateHostkeys
debug3: send packet: type 21
debug2: set_newkeys: mode 1
debug1: rekey out after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug3: receive packet: type 21
debug1: SSH2_MSG_NEWKEYS received
debug2: set_newkeys: mode 0
debug1: rekey in after 134217728 blocks
debug1: Will attempt key: /home/byjumperx4/.ssh/id_rsa RSA SHA256:5zhaqED4v7LBmJ7KfXk+AZiuLUEPvGZNYgLlROgzcL8
debug1: Will attempt key: /home/byjumperx4/.ssh/id_dsa 
debug1: Will attempt key: /home/byjumperx4/.ssh/id_ecdsa 
debug1: Will attempt key: /home/byjumperx4/.ssh/id_ecdsa_sk 
debug1: Will attempt key: /home/byjumperx4/.ssh/id_ed25519 
debug1: Will attempt key: /home/byjumperx4/.ssh/id_ed25519_sk 
debug1: Will attempt key: /home/byjumperx4/.ssh/id_xmss 
debug2: pubkey_prepare: done
debug3: send packet: type 5
debug3: receive packet: type 6
debug2: service_accept: ssh-userauth
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug3: send packet: type 50
debug3: receive packet: type 51
debug1: Authentications that can continue: publickey
debug3: start over, passed a different list publickey
debug3: preferred publickey,keyboard-interactive,password
debug3: authmethod_lookup publickey
debug3: remaining preferred: keyboard-interactive,password
debug3: authmethod_is_enabled publickey
debug1: Next authentication method: publickey
debug1: Offering public key: /home/byjumperx4/.ssh/id_rsa RSA SHA256:5zhaqED4v7LBmJ7KfXk+AZiuLUEPvGZNYgLlROgzcL8
debug1: send_pubkey_test: no mutual signature algorithm
debug1: Trying private key: /home/byjumperx4/.ssh/id_dsa
debug3: no such identity: /home/byjumperx4/.ssh/id_dsa: No such file or directory
debug1: Trying private key: /home/byjumperx4/.ssh/id_ecdsa
debug3: no such identity: /home/byjumperx4/.ssh/id_ecdsa: No such file or directory
debug1: Trying private key: /home/byjumperx4/.ssh/id_ecdsa_sk
debug3: no such identity: /home/byjumperx4/.ssh/id_ecdsa_sk: No such file or directory
debug1: Trying private key: /home/byjumperx4/.ssh/id_ed25519
debug3: no such identity: /home/byjumperx4/.ssh/id_ed25519: No such file or directory
debug1: Trying private key: /home/byjumperx4/.ssh/id_ed25519_sk
debug3: no such identity: /home/byjumperx4/.ssh/id_ed25519_sk: No such file or directory
debug1: Trying private key: /home/byjumperx4/.ssh/id_xmss
debug3: no such identity: /home/byjumperx4/.ssh/id_xmss: No such file or directory
debug2: we did not send a packet, disable method
debug1: No more authentication methods to try.
yK11b5BHuYSCIaRD5hRk:[email protected]: Permission denied (publickey).
$ ~>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.