|
|
|
@ -0,0 +1,100 @@
|
|
|
|
|
# Dockerized reverse SSH tunneling
|
|
|
|
|
|
|
|
|
|
**Case: You have a linux server (let’s say a webserver) behind a corporate firewall / NAT. This means everyone outside the network can’t reach that webserver.**
|
|
|
|
|
|
|
|
|
|
What you *can* do however, is initiate a connection from inside the corporate network. You can use this to set up a connection to a public server; the **middleman**, a VPS you rent at a cloud provider like Hetzner for example. A client would connect to this middleman, and the middleman would in turn pass the request trough the connection you just initiated (the one from server ➡ middleman).
|
|
|
|
|
|
|
|
|
|
We’ll use OpenSSH to achieve this. OpenSSH provides the tools we need by default, it’s secure, and available nearly everywhere.
|
|
|
|
|
|
|
|
|
|
## The middleman
|
|
|
|
|
|
|
|
|
|
Get yourself a VPS or other ‘cheap’ server, and install docker. This guide was tested on a Hetzner cloud instance running alpine-linux.
|
|
|
|
|
|
|
|
|
|
1. Create a new folder and download the Dockerfile from this repo.
|
|
|
|
|
|
|
|
|
|
2. Generate an SSH keypair using `ssh-keygen -b 2048 -t rsa -f ./id_rsa -q -N ""`. This keypair will *not* be password protected because we’re going to automate the connection!
|
|
|
|
|
|
|
|
|
|
> Use caution with this command. If ran/copied incorrectly, you might be adding these keys to your middleman host OS instead of the docker container!
|
|
|
|
|
|
|
|
|
|
3. Build the Dockerfile `docker build . -t ssh-reverse-proxy`. This will also copy-paste the keyfile you’ve just created into the docker container
|
|
|
|
|
|
|
|
|
|
> I haven't given any thought to the security aspect of including sensitive information in a docker build (the ssh private key). If you plan on pushing this to a docker registry, **please research this topic first!** I figured including it in a local docker build can’t hurt since the keys are already accessible on the host anyway.
|
|
|
|
|
|
|
|
|
|
4. Run the docker image, and publish the ports. You’ll need to publish the container’s port 22 (ssh) + any port you want to forward from your server. In the example below, we bind ssh to our middleman’s port 2222, and allow the server to publish port 80 trough the middleman.
|
|
|
|
|
You will set up the ssh tunnel by connecting to middleman:2222!
|
|
|
|
|
|
|
|
|
|
``` bash
|
|
|
|
|
docker run -d \
|
|
|
|
|
-p 2222:22 \
|
|
|
|
|
-p 80:80 \
|
|
|
|
|
--name ssh-reverse-proxy \
|
|
|
|
|
ssh-reverse-proxy
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Server
|
|
|
|
|
|
|
|
|
|
This machine will be connecting to the middleman you just configured. We’ll be using a tool called ‘autossh’ to monitor the tunnel and make sure it’s restarted in the event something crashes. In order to load our tunnel at boot, we will create a systemd boot script.
|
|
|
|
|
|
|
|
|
|
**Setting up the tunnel**
|
|
|
|
|
|
|
|
|
|
1. Make sure you’re logged in as root. We’ll need the privileges to tunnel our privileged port (80).
|
|
|
|
|
|
|
|
|
|
2. Install autossh (depending on your OS, something like `apt-get install autossh`)
|
|
|
|
|
|
|
|
|
|
3. Before we open the tunnel, we need to retrieve the public key we just created from the middleman. You could use scp; `scp user@middleman:/path/to/git/clone/id_rsa.pub ~/reverse_ssh_key`
|
|
|
|
|
|
|
|
|
|
4. Make sure you’ve got the correct permissions or ssh will reject the key; `chmod 400 ~/reverse_ssh_key`
|
|
|
|
|
|
|
|
|
|
5. Create a script that will open the tunnel according to our configuration; `vi ~/autossh.sh`
|
|
|
|
|
|
|
|
|
|
``` bash
|
|
|
|
|
autossh -M 10984 -o PubkeyAuthentication=yes -o PasswordAuthentication=no -i \
|
|
|
|
|
/root/reverse_ssh_key \
|
|
|
|
|
-R \*:80:localhost:80 \
|
|
|
|
|
root@middleman -p 2222 -N
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
And add execute permissions `cmod +x ~/autossh.sh`
|
|
|
|
|
|
|
|
|
|
You can add as many ports you want, just copy line 2 and paste it between line 2 and 3. Just remember to forward it trough docker as well (middleman step 4).
|
|
|
|
|
|
|
|
|
|
Run this script in a shell to check if everything is working correctly, before adding it as a service.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**Auto-starting the tunnel**
|
|
|
|
|
|
|
|
|
|
This will only work for systemd, To check if you’re running systemd, run `systemd --version`.
|
|
|
|
|
|
|
|
|
|
1. Create a file wherever your OS decided systemd files should go, and put the following inside it. For debian it’s; `vi /etc/systemd/system/reverse-ssh-proxy.service`
|
|
|
|
|
|
|
|
|
|
``` ini
|
|
|
|
|
[Unit]
|
|
|
|
|
Description=Reverse ssh tunnel. Publish ports from inside a restricted network.
|
|
|
|
|
After=network.target
|
|
|
|
|
|
|
|
|
|
[Service]
|
|
|
|
|
ExecStart=/root/autossh.sh
|
|
|
|
|
StandardOutput=inherit
|
|
|
|
|
StandardError=inherit
|
|
|
|
|
Restart=always
|
|
|
|
|
User=root
|
|
|
|
|
|
|
|
|
|
[Install]
|
|
|
|
|
WantedBy=multi-user.target
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
2. Activate the service by running `systemctl daemon-reload && systemctl enable reverse-ssh-proxy`
|
|
|
|
|
|
|
|
|
|
3. Now start the service (or reboot) `systemctl start reverse-ssh-proxy`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Notes
|
|
|
|
|
|
|
|
|
|
I’m not responsible for your beef with the sysadmin. If your corporate network is restricted, it’s probably for a reason.
|
|
|
|
|
|
|
|
|
|
- Although some thought has been put into security, a double-check can never hurt, right?
|
|
|
|
|
- If you really want to get into that docker container, you can use `docker exec -it reverse-ssh-proxy sh` to open a shell. Because of security concerns, you cannot get shell access trough SSH.
|
|
|
|
|
- Dockerizing the middleman is actually important for security reasons. It prevents a server from publishing ports that aren't meant to be published. For example, you can’t overwrite the middleman’s port 22 (and effectively locking yourself out of your middleman-server).
|
|
|
|
|
- Starting and stopping the middleman docker container is fine. However, when removing and re-adding the container you’ll regenerate the OpenSSH server keys. That means the restricted server will show a ‘known_hosts’ error when trying to reconnect.
|