diff options
Diffstat (limited to '_drafts/ssh-proxy.md')
-rw-r--r-- | _drafts/ssh-proxy.md | 296 |
1 files changed, 0 insertions, 296 deletions
diff --git a/_drafts/ssh-proxy.md b/_drafts/ssh-proxy.md deleted file mode 100644 index 26a68c0..0000000 --- a/_drafts/ssh-proxy.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -title: "SSH Proxy" -description: "How to establish a secure tunnel between two firewalled machines." -published: 2020-06-22 ---- - -Consumer grade internet connections typically prevent inbound connections by means of network address translation (NAT) or firewalls. -While this protects consumers to some degree from the evil outside, it also hinders them from providing network services. -Luckily, people came up with several [NAT traversal techniques](https://en.wikipedia.org/w/index.php?title=NAT_traversal&oldid=950406393#Techniques) including the widespread [SOCKS](https://en.wikipedia.org/w/index.php?title=SOCKS&oldid=963014782) protocol and the martial [UDP hole punching](https://en.wikipedia.org/w/index.php?title=UDP_hole_punching&oldid=957144154). -However, I found that [OpenSSH](https://www.openssh.com/)'s little-known remote port forwarding feature enables a simple and yet secure alternative that I would like to share in this post. - -We'll use the [OpenSSH](https://www.openssh.com/) client and server here because its a free, battle-tested, and portable implementation of the SSH protocol. -Chances are, that your operating system ships with OpenSSH built-in or packaged. -In fact, even [macOS](https://support.apple.com/guide/remote-desktop/about-systemsetup-apd95406b8d/mac) and [Windows 10](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_overview) include OpenSSH, activatable from the command-line. - -Contents: - -* TCP port forwarding. Anyone on the proxy may access the exposed service. -* End-to-end encrypted tunnel. Servers needs to run local SSH server. -* Unix domain socket forwarding. Protected socket instead of TCP port. - -## TL;DR - -Basically, you can ... - -You've got three different options to share a local network service by means of a publically reachable OpenSSH proxy: - -1. Create a remote port forwarding from the proxy to the server and - -Authentication and authorization aside, you can share a local network service in three steps: - -1. Create a remote port forwarding from a publically reachable proxy to the firewalled server: - - $ ssh -nNTR 8080:localhost:80 proxy.example.com - -1. Create a local port forwarding from the client to the proxy: - - $ ssh -nNTL 8080:localhost:8080 proxy.example.com - -1. Access the exposed network service from the client: - - $ curl http://localhost:8080/ - -So far so good, but the real work is of course to setup the public key authentication and to restrict the keys' permissions. -Thus, the remainder of this blog post describes a typical use case in detail. - -## Screen sharing example - -Let's say, Alex wants to share his screen with Tyler. -Sure enough, Alex can run a local [RFB](https://www.iana.org/assignments/rfb/rfb.xml) server such as [TigerVNC](https://tigervnc.org/) or [Apple Remote Desktop](https://support.apple.com/remote-desktop) on port 5900. -But thanks to their internet provider's NAT, neither of the two may connect to the other directly. -Fortunately, Tyler has access to a publicly reachable OpenSSH server that they can use as a forward proxy as follows. - -Note: -OpenSSH is developed as a part of [OpenBSD](https://www.openbsd.org/). -Thus, it comes with excellent [man pages](https://www.openssh.com/manual.html). -So please _read the fine manual_ to understand the various options that we'll use. -I cannot describe them better. - -First, Alex starts an OpenSSH server on his machine. -By default, the SSH server should listen on port 22 of all local addresses and permit both, password and public key authentication, see [`sshd_config(5)`](https://man.openbsd.org/sshd_config). -This is sufficient for our setup. -However, the truly paranoid may safely bind the SSH server to the loopback interface only and forbid password authentication: - - ListenAddress 127.0.0.1 - ListenAddress ::1 - Port 22 - PasswordAuthentication no - PubkeyAuthentication yes - -Second, Alex prepares a [`known_hosts`](https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) file for Tyler: - - $ cat /etc/ssh/ssh_host_*_key.pub \ - | sed -e 's/^/alex_workstation /' \ - | tee -a alex_public_host_keys - alex_workstation ssh-dss AAAAB3N... root@alex.localdomain - alex_workstation ssh-ed25519 AAAAC3N... root@alex.localdomain - alex_workstation ssh-rsa AAAAB3N... root@alex.localdomain - -Third, Alex generates an SSH key to authenticate at the proxy. -We'll generate a dedicated key here, so you can follow this guide without using your normal SSH key: - - $ ssh-keygen -f ~/.ssh/id_proxy - -Fourth, Tyler imports Alex' public host keys on his machine: - - $ cat alex_public_host_keys >> ~/.ssh/known_hosts - -Fifth, Tyler authorizes Alex to connect to the proxy. -That is, Tyler adds Alex' public user key to his personal [`authorized_keys`](https://man.openbsd.org/sshd#AUTHORIZED_KEYS_FILE_FORMAT) file on the proxy, applying the following restrictions to prevent Alex from misusing Tyler's account: - - restrict,command="echo 'ssh command restricted by authorized_keys'",port-forwarding,permitlisten="2222" ssh-rsa AAAAB3N... - -The truly paranoid may wish to create a dedicated [`nologin(8)`](https://man.openbsd.org/nologin) user account on the proxy instead and apply the above restrictions by means of the [`sshd_config(5)`](https://man.openbsd.org/sshd_config): - - Match User port-forward-only - DisableForwarding yes - ForceCommand echo 'ssh command forced by sshd_config' - PermitTTY no - AllowTcpForwarding remote - PermitListen 2222 - -Sixth, Tyler prepares a [`known_hosts`](https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) file for Alex: - - $ cat /etc/ssh/ssh_host_*_key.pub \ - | sed -e 's/^/[proxy.example.com]:22 /' \ - | tee -a proxy_public_host_keys - [proxy.example.com]:22 ssh-dss AAAAB3N... root@proxy.example.com - [proxy.example.com]:22 ssh-ed25519 AAAAC3N... root@proxy.example.com - [proxy.example.com]:22 ssh-rsa AAAAB3N... root@proxy.example.com - -Seventh, Alex imports the public host keys of the proxy on his machine: - - $ cat proxy_public_host_keys >> ~/.ssh/known_hosts - -Eighth, Alex authorizes Tyler to connect to his machine. -That is, Alex adds Tyler's public user key to his personal [`authorized_keys`](https://man.openbsd.org/sshd#AUTHORIZED_KEYS_FILE_FORMAT) file on his machine, applying the following restrictions to prevent Tyler from misusing Alex' account: - - restrict,command="echo 'ssh command restricted by authorized_keys'",port-forwarding,permitopen="5900" ssh-rsa AAAAB3N... - -Again, the truly paranoid may wish to create a dedicated user account as described above. - -Ninth, Alex connects to the proxy and forwards port 2222 to port 22 on his machine: - - $ ssh \ - -nNT \ - -R 2222:localhost:22 \ - -i ~/.ssh/id_proxy \ - -o "IdentitiesOnly yes" \ - -o "StrictHostKeyChecking yes" \ - -o "ExitOnForwardFailure yes" \ - -l tyler \ - proxy.example.com - -Alternatively, Alex may add the following settings to his personal [`ssh_config(5)`](https://man.openbsd.org/ssh_config) and run `ssh -nN proxy`: - - Host proxy - Hostname proxy.example.com - Port 22 - User alex - StrictHostKeyChecking yes - IdentitiesFile ~/.ssh/id_proxy - IdentitiesOnly yes - RemoteForward 2222 localhost:22 - ExitOnForwardFailure yes - RequestTTY no - - Host * - Protocol 2 - AddKeysToAgent yes - IgnoreUnknown UseKeychain - UseKeychain yes - - -Tenth, Tyler connects to Alex' machine and forwards port 5900 from his machine to Alex' machine: - - $ ssh \ - -J tyler@proxy.example.com \ - -L 5900:localhost:5900 \ - -o "ExitOnForwardFailure yes" \ - -o "StrictHostKeyChecking yes" \ - -l alex \ - -p 2222 \ - -o "HostKeyAlias alex_workstation" \ - localhost - -Alternatively, Tyler may add the following settings to his personal [`ssh_config(5)`](https://man.openbsd.org/ssh_config) and run `ssh alex_workstation`: - - Host alex_workstation - ProxyJump tyler@proxy.example.com - User alex - Hostname localhost - Port 2222 - HostKeyAlias alex_workstation - StrictHostKeyChecking yes - ExitOnForwardFailure yes - LocalForward 5900 localhost:5900 - - Host * - AddKeysToAgent yes - IgnoreUnknown UseKeychain - UseKeychain yes - -Finally, Tyler accesses the tunneled RFD server from his machine. -For example, using TigerVNC's [`vncviewer(1)`](https://tigervnc.org/doc/vncviewer.html)[^vncviewer] - - $ vncviewer localhost - -[^vncviewer]: -Apparently, the [TigerVNC](https://tigervnc.org/)'s client, [`vncviewer(1)`](https://tigervnc.org/doc/vncviewer.html), may not connect to an [Apple Remote Desktop](https://support.apple.com/remote-desktop) agent, even if you enable the legacy VNC option of the latter. -However, you can use [FreeRDP](https://www.freerdp.com/) instead, or one of its graphical front-ends like [Gnome Boxes](https://wiki.gnome.org/Apps/Boxes). - -TODO: permitlisten="none",permitopen="none" - -## Conclusion - -We've established a secure, end-to-end encrypted tunnel between two otherwise disconnected machines through an OpenSSH forward proxy. -Moreover, the two parties need minimal trust in each other because we've restricted the keys' permissions. - -OpenSSH enables even more fine grained control if need be. -For example: - -* Apply an `expiry-time` to your `authorized_keys` to grant temporary access. - -* Add a trusted `cert-authority` to your `authorized_keys` instead of individual keys. - -* Enable `VerifyHostKeyDNS` to automatically trust host keys with a corresponding SSHFP resource record in the DNS. - -* Use [Unix domain sockets](https://en.wikipedia.org/wiki/Unix_domain_socket) instead of network ports to further restrict access to the tunnel from the proxy to the actual server.[^socket] - -[^socket]: -Simply replace the port number with an absolute path like `/home/tyler/proxy.sock`. -Do not use the `~` to represent the user's home directory. - -# Rewrite - -## Goal - -Forward a (protected) Unix domain socket on the proxy to a local port. -Such that nobody else on the proxy may use the forwarding, as with a forwarded TCP port. - -Serve the current directory at http://127.0.0.1:8080/: - - python3 -m http.server --bind 127.0.0.1 8080 - -Using explicit loopback address 127.0.0.1 (or ::1) instead of localhost lest socat or ssh shoud bind to a non-loopback address. - -Want either end-to-end encryption (using SSH server on the server) or protected address (Unix domain socket instead of TCP port) on the proxy. - -## OpenSSH RemoteForward - -Works as advertised. -Missing `PermitListen` (and `permitlisten`) equivalent to restrict the name of the socket. - -## OpenBSD netcat - -Idea: manually bind to remote socket using netcat. -Problem: Doesn't even work locally. Neither on Arch Linux nor on OpenBSD. - -Forward Unix domain socket test.sock to TCP port 8080 using OpenBSD's [`nc`](https://man.openbsd.org/nc): - - #! /bin/sh - rm -f backpipe - mkfifo backpipe - nc -lkU test.sock 0<backpipe \ - | nc 127.0.0.1 8080 1>backpipe - -Retrieve the home page http://127.0.0.1:8080/ via the socket: - - printf "GET / HTTP/1.0\r\n\r\n" \ - | nc -UN test.sock - -Unfortunately, this doesn't work reliably. -The retrieval command prints the response at most once, ofter on the second invocation and NOT on the first invocation. - -I found neither the cause of this behavior nor a workaround. -I suspect, the problem is a combination of the following: - -* Netcat closes connection to early because of an early EOF - -* Shell pipe errors - -* Shell buffers standard input/output - -Moreover, each Linux appears to implement a slightly different version of OpenBSD's netcat. - -## socat - -Forward remote socket test.sock to local port 8080: - - socat EXEC:'ssh -T engine.skreutz.com socat "UNIX-LISTEN:test.sock,fork,unlink-early" STDIO' TCP4:127.0.0.1:8080,fork - -TODO: Test local nc and remote socat, because it might be easier to find a nc implementation for Windows than socat. - -Forward local port 8081 to remote socket test.sock: - - ssh -vnNT -L 127.0.0.1:8081:/home/stefan/test.sock -o "ExitOnForwardFailure yes" engine.skreutz.com - -Note: Specify the local bind address 127.0.0.1 and option `ExitOnForwardFailure` to make `ssh` fail if `GatewayPorts` is set to `yes`. - -Noto: Specify the absolute path of the socket. Do not rely on the ~. - -Retrieve home page: - - printf "GET / HTTP/1.0\r\n\r\n" \ - | nc -N 127.0.0.1 8081 - -Alternatively: - - curl http://127.0.0.1:8081/ - -TODO: Restrict authorized_keys -TODO: Inspect ports with netstat. -TODO: permitlisten="none",permitopen="none". -TODO: Request OpenSSH feature PermitStreamLocalListen/Open analoguous to PermitListen/Open. -TODO: Test tcpserver instead of socat: https://cr.yp.to/ucspi-tcp.html |