OpenSSH as a SOCKS server
Sometimes we are given access via ssh to nodes that do not have, for policy or technical reasons, access to the internet (i.e. they cannot make outbound connections). Depending on the policies, we may be able to open reverse SSH tunnels, so things are not so bad.
Recently I discovered that OpenSSH comes with a SOCKS proxy server integrated. This is probably a well known feature of OpenSSH but I thought it was interesting to share how it can be used.
SOCKS
Nowadays, access to the Internet is ubiquitous and most of the time assumed as a fact. However, in some circumstances, direct access to the internet is not available or not desirable. In those cases we can resort on proxy servers that act as intermediaries between the Internet and the node without direct access.
Many tools used commonly assume one is connected to the Internet: package
managers such as pip
and cargo
can automatically download the files
required to install a package. If no outbound connection is possible,
software deployment and installation becomes complicated.
However, most of the time, those tools only require HTTP/HTTPS support. So a proxy that only forwards HTTP and HTTPS requests is enough. Examples of these kind of proxies are tinyproxy and squid.
SOCKS, is a general proxy protocol that
can be used for any TCP connection, not only those for HTTP/HTTPS. An
interesting thing is that ssh
comes with an integrated SOCKS proxy which is
relatively easy to use. Often most tools that can use a HTTP/HTTPS proxy can
also use a SOCKS proxy so this is a handy option to consider.
Example: Installing Rust through a proxy
If we try to install Rust on a machine that does not allow outbound connections, this is what happens. (Let’s ignore the question whether piping a download directly to the shell is a reasonable thing to do).
user@no-internet$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
This command will likely time out after a long time because outbound connections are silently dropped and the installation will fail.
Set up proxy server
To address this, let’s first open a SOCKS proxy using ssh
on our local machine
(with-internet
). This machine must have internet access (change user
to
your username). ssh
will request you to authenticate (via password or ssh
key).
user@with-internet$ ssh -N -D 127.0.0.1:12345 user@localhost
The flag -N
means not to execute a command and -D interface:port
means to
open the port
bound to the interface
. This is the SOCKS proxy. In this
example we are opening port 12345 and binding it to the 127.0.0.1 (localhost)
interface. We are using the same machine as the proxy, hence user@localhost
(it is possible to use another node, but we don’t have to given that
with-internet
already can connect to the internet). This must stay running so
you will have to open another terminal and set up the reverse tunnel.
To set up the reverse tunnel do the following.
user@with-internet$ ssh -R 127.0.0.1:9999:127.0.0.1:12345 -N user@no-internet
This opens the port 9999 in the host without internet (no-internet
) and binds
it to its localhost (i.e. the localhost
of no-internet
) then it tunnels it
to the port 12345 bound to the interface 127.0.0.1
of our local node
(with-internet
). Again this will not run any command (due to -N
) and the
syntax of -R is -R remote-interface:remote-port:local-interface:local-port
.
Keep this command running.
Note: Because we are using an unprivileged port on no-internet
and the
-D
option does not allow setting authentication, anyone in no-internet
could proxy connections through with-internet
. Do this only on a
no-internet
host you trust.
Proxy configuration
Now we can setup curl
to use a socks proxy. We do this with the
--proxy-option
. For convenience we will first download the installation
script into a file.
user@no-internet$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
--proxy socks5://localhost:9999 -o install-rust.sh
We can do a quick check that it contains what we expect
user@no-internet$ head install-rust.sh
#!/bin/sh
# shellcheck shell=dash
# This is just a little script that can be downloaded from the internet to
# install rustup. It just does platform detection, downloads the installer
# and runs it.
# It runs on Unix shells like {a,ba,da,k,z}sh. It uses the common `local`
# extension. Note: Most shells limit `local` to 1 var per line, contra bash.
Install Rust
We can set up https_proxy
environment variable to point to the SOCKS
server so it is used by the installation script.
user@no-internet$ export https_proxy=socks5://localhost:9999
Now we are read to install Rust using the script we downloaded.
user@no-internet $ bash install-rust.sh
info: downloading installer
Welcome to Rust!
This will download and install the official compiler for the Rust
programming language, and its package manager, Cargo.
Rustup metadata and toolchains will be installed into the Rustup
home directory, located at:
/home/user/.rustup
This can be modified with the RUSTUP_HOME environment variable.
The Cargo home directory located at:
/home/user/.cargo
This can be modified with the CARGO_HOME environment variable.
The cargo, rustc, rustup and other commands will be added to
Cargo's bin directory, located at:
/home/user/.cargo/bin
This path will then be added to your PATH environment variable by
modifying the profile files located at:
/home/user/.profile
/home/user/.zshenv
You can uninstall at any time with rustup self uninstall and
these changes will be reverted.
Current installation options:
default host triple: x86_64-unknown-linux-gnu
default toolchain: stable (default)
profile: default
modify PATH variable: yes
1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
>1
info: profile set to 'default'
info: default host triple is x86_64-unknown-linux-gnu
info: syncing channel updates for 'stable-x86_64-unknown-linux-gnu'
info: latest update on 2021-12-02, rust version 1.57.0 (f1edd0429 2021-11-29)
info: downloading component 'cargo'
info: downloading component 'clippy'
info: downloading component 'rust-docs'
info: downloading component 'rust-std'
24.9 MiB / 24.9 MiB (100 %) 19.9 MiB/s in 1s ETA: 0s
info: downloading component 'rustc'
53.9 MiB / 53.9 MiB (100 %) 20.1 MiB/s in 2s ETA: 0s
info: downloading component 'rustfmt'
info: installing component 'cargo'
info: installing component 'clippy'
info: installing component 'rust-docs'
5.3 MiB / 17.9 MiB ( 29 %) 1.7 MiB/s in 6s ETA: 7s
...
Once Rust is installed, you can setup cargo
so it always uses this
proxy.
Example: Using pip using SOCKS
pip
is used to install Python packages. Unfortunately pip
does not support
SOCKS by default. If you try to install yapf
using the configuration above this happens:
user@no-internet$ pip install --proxy=socks5://localhost:9999 yapf
Collecting yapf
ERROR: Could not install packages due to an EnvironmentError: Missing dependencies for SOCKS support.
Based on this answer from Stack Overflow
we need to first install pysocks
. Now we have a chicken-and-egg situation
that we need to solve: we cannot download pysocks
on the no-internet
machine!
To solve it, download pysocks
locally:
user@with-internet$ python3 -m pip download pysocks
Collecting pysocks
Downloading PySocks-1.7.1-py3-none-any.whl (16 kB)
Saved ./PySocks-1.7.1-py3-none-any.whl
Successfully downloaded pysocks
Copy this python wheels file to no-internet
, for
instance using scp
.
user@with-internet$ scp PySocks-1.7.1-py3-none-any.whl user@no-internet
And install it manually there. I’m installing it in the user environment
(--user
flag) because in this machine I don’t have enough permissions, but
your mileage may vary here.
user@no-internet$ pip install --user PySocks-1.7.1-py3-none-any.whl
Processing ./PySocks-1.7.1-py3-none-any.whl
Installing collected packages: PySocks
Successfully installed PySocks-1.7.1
If we use pip and SOCKS, now we succeed.
user@no-internet$ pip install --user --proxy=socks5://localhost:9999 yapf
Collecting yapf
Downloading https://files.pythonhosted.org/packages/47/88/843c2e68f18a5879b4fbf37cb99fbabe1ffc4343b2e63191c8462235c008/yapf-0.32.0-py2.py3-none-any.whl (190kB)
|████████████████████████████████| 194kB 933kB/s
Installing collected packages: yapf
Successfully installed yapf-0.32.0
Yay!
Cleanup
Recall that we have two connections opened: one is the SOCKS proxy (-D
) and
the other the reverse tunnel (-R
). Just end them both with Ctrl-C and you are
done. I’m sure this can be scripted somehow but given that the ssh
commands
may require password input, this is not a trivial thing to do.