Connecting to remote machines via SSH becomes increasingly complicated, when these machines are hidden behind residential routers or corporate firewalls. In these situations, a high-bandwidth SSH proxy is useful that can be reached from anywhere.
This project provides such a proxy using Amazon EC2 virtual machines. It targets macOS machines and can be used to replace Apple’s discontinued Back To My Mac service for remote management using an SSH command line. Linux support should also work, but is less tested.
The system consists of four pieces:
- some initial setup at Amazon,
- a web service you need to host on a PHP-enabled web server,
- a launch daemon for the Macs you want to connect to, and
- an SSH proxy command for the machine that establishes the connection.
Each of these pieces is described below in its own section.
In order to set everything up at Amazon Web Services, you need to perform the following steps:
- Create an SSH key pair and upload the public key to EC2 with the name
ssh-proxy
. Store the keys in a filesproxy
andproxy.pub
in your~/.ssh
directory. - Create an AWS stack from
aws.json
using CloudFormation, either from the AWS Console or the command line. Use the namessh-proxy
for the stack. - Retain the credentials of the created IAM user
ssh-proxy
.
To install the web service, you need PHP-enabled web space. Follow these steps:
- Put
index.php
and.htaccess
on your web server. - Obtain the latest
aws.phar
from the AWS SDK releases and put it next toindex.php
. - Make sure your web server also has the credentials of the
ssh-proxy
IAM account stored in its~/.aws/credentials
file or wherever you keep your AWS credentials. Use a profile name ofssh-proxy
. - The web service uses a pre-shared secret to authenticate its API requests. Store this
API key and optionally any other configuration in
config.php
.
An authentication token is formed by first generating a 10-byte random nonce. Then, a
SHA256-HMAC is calculated over the string <nonce><command>?<identifier>
. The result is
Base64-encoded and appended to the request URL.
The web service understands three commands, all of which use an identifier for the proxied endpoint as their query string:
/launch?<identifier>&<token>
Starts a new SSH proxy for the given endpoint, waits until the proxy is running and returns
its IP address. When a proxy is already running, only the IP address is returned.
/status?<identifier>&<token>
Returns the public IP address of the SSH proxy when such a proxy has been started for the
given endpoint. An authentication token similar to the one used for requests is generated to
verify the IP address. The same nonce is used to prevent replay attacks.
/terminate?<identifier>&<token>
Terminates the running SSH proxy.
All the machines that you want to SSH into must run a launch daemon. This daemon regularly queries the status of the EC2 VMs using the PHP service. A running VM signifies a connection request and the daemon will forward its local SSH port to the VM.
- You install the launch daemon by invoking
make
in theproxy
directory. You can override variables (DESTDIR
,SIGNING_NAME
, …) to configure the installation. - Register the daemon with launchd by copying the included plist file from
SSHProxy.bundle/Contents/Resources
to/Library/LaunchDaemons/
. You may want to customize the file if the defaults don’t suit your needs.
Connecting to an endpoint requires launching and later tearing down the respective EC2 VM.
This can be automated and integrated into SSH by way of a proxy command. The binary
ssh-connect
is installed alongside the daemon in the SSHProxy.bundle/Contents/MacOS
directory. You can use it in your SSH configuration by way of the ProxyCommand
directive.
It understands the same command line options as the daemon:
--id
Specifies the name of the endpoint to connect to. Usage of %h
in you SSH config is
practical.
--api-url
The API URL where the PHP web service can be reached.
--api-key
The pre-shared API key to authenticate web service requests.
A useful SSH config file, which establishes a local connection when possible and connects via proxy when necessary looks like this:
Match host <hostnames> exec "route get %h.local &> /dev/null"
HostName %h.local
Match host <hostnames>
ProxyCommand /path/to/SSHProxy.bundle/Contents/MacOS/ssh-connect --id %h --api-url <server> --api-key <secret>
You can also read the secret from a file using shell command substitution (`cat <keyfile>`
). Be aware that the secret is still exposed to all users on the machine
through the list of all running processes and their arguments.
This work is licensed under the WTFPL, so you can do anything you want with it.