Transparent proxy server that works as a poor man's VPN. Forwards over ssh. Doesn't require admin. Works with Linux and MacOS. Supports DNS tunneling.
I often know an EC2 instance ID and need to SSH to the server. In order to avoid manually looking up the IP address or hostname, I wrote a small script to let myself SSH to an EC2 given an instance ID.
I also frequently switch between 4-5 different AWS accounts and regions, with different private keys and AWS API keys, and most existing scripts I found do not support this. Below is my setup that allows me to quickly SSH to any EC2 server across all the accounts and regions.
Filename: ssh-ec2
#!/usr/bin/env ruby require 'aws-sdk' profile = ARGV[0] ssh_command = ARGV[1] config = File.read("#{File.dirname(__FILE__)}/aws/#{profile}.profile") if !config puts "No config file found for #{profile}" exit end access_key_id = config.match(/AWS_ACCESS_KEY=(.+)/)[1] secret_access_key = config.match(/AWS_SECRET_KEY=(.+)/)[1] region = config.match(/EC2_REGION=(.+)/)[1] default_username = (m=config.match(/DEFAULT_USERNAME=(.+)/)) ? m[1] : 'ubuntu' AWS.config access_key_id: access_key_id, secret_access_key: secret_access_key, region: region if match=ssh_command.match(/(.+)@(.+)/) username = match[1] instance_id = match[2] else username = default_username instance_id = ssh_command end begin i = AWS.ec2.instances[instance_id] if i.public_dns_name puts "ssh -i ~/.ssh/#{profile}.aws.pem #{username}@#{i.public_dns_name}" system "ssh -i ~/.ssh/#{profile}.aws.pem #{username}@#{i.public_dns_name}" exit end rescue end puts "Could not find instance with id #{instance_id} on aws account #{profile}"
A good place for this script is a "scripts" folder in your dotfiles repo which you can then add to your path, or if you don't have something like that set up, then just put it in your ~/.ssh/
folder and make a symlink to this script in /usr/local/bin/
.
In order to specify your AWS credentials and region, you'll need to make a folder containing your AWS credentials. Create a folder in the same folder as this script called aws
, and it will contain one file per AWS profile. The profile specifies the AWS account as well as the region.
For example:
. ├── aws │ ├── geoloqi.profile │ ├── geotrigger-production.profile │ ├── geotrigger-dev.profile │ └── esripdx.profile ├── ssh-ec2
Each of the *.profile
files looks like the below:
export AWS_ACCESS_KEY=XXXXXXXXX export AWS_SECRET_KEY=XXXXXXXXX export EC2_REGION=us-east-1
You should also ensure you name your SSH private keys to correspond to the profiles you have here. So for example I have a private key named ~/.ssh/esripdx.aws.pem
that I use to SSH to all the servers in that account and region. (See our project Blacksmith for a way to manage SSH keys for multiple users across multiple AWS accounts).
Once this is set up, you can SSH to a server as follows:
$ ssh-ec2 esripdx i-a3e8542d
This is pretty good. But what if you want to type even fewer characters! This is where some clever bash aliases come in. I've defined short bash functions that help with this.
function ssh-p() { if [[ "$1" =~ i-[a-f0-9]+ ]] then ssh-ec2 geotrigger-production $1 else echo ssh -i geotrigger-production.aws.pem ubuntu@$1 ssh -i ~/.ssh/geotrigger-production.aws.pem ubuntu@$1 fi }
Now I can use this to either SSH to an instance ID, or use it to choose the proper private key when SSH'ing to a server by IP address.
$ ssh-p i-a3e8542d $ ssh-p 54.243.231.166
As anybody who deals with multiple servers in multiple AWS accounts knows, it can be a real pain to maintain an up-to-date ~/.ssh/config
file. Most solutions I've found for managing this assume you only have one AWS account. I've decided to share my setup for how I deal with managing my ssh config file that contains entries from 4 different AWS accounts, two Linode accounts, and some other random entries.
If you want to cut to the chase, you can check out this project on Github.
Folder structure:
. ├── aws │ ├── geoloqi.profile │ ├── geotrigger.profile │ ├── geotrigger-dev.profile │ └── esripdx.profile ├── ssh │ ├── _base.sshconfig │ ├── esripdx.sshconfig │ ├── geoloqi.sshconfig │ ├── geotrigger.sshconfig │ ├── geotrigger-dev.sshconfig │ └── personal.sshconfig ├── bash-complete.sh ├── rebuild-ssh-config.sh └── ssh-servers-from-aws.rb
The "aws" folder contains my AWS API key/secrets for each AWS account. For example:
export AWS_ACCESS_KEY=XXXXXXXXX export AWS_SECRET_KEY=XXXXXXXXX
(The reason I have "export" in there is so that I can source this file from bash, allowing me to also use the same set of files with the Amazon command line utils, switching accounts by sourcing each different file)
The "ssh" folder contains the generated and hand-edited ssh config files. I created a _base.sshconfig
file where I put global config data, such as:
Host * ServerAliveInterval 60 ForwardAgent yes IdentitiesOnly yes
My personal.sshconfig
file is a hand-edited file where I put a list of some of my Linodes and home servers. The other files are auto-generated by querying each AWS account for their list of servers.
bundle install
to install the necessary gemsbash-complete.sh
file from your .bash_profile
to enable autocomplete~/.ssh
folder that matches with the key listed on each EC2To generate the ssh config file for an AWS account, run:
bundle exec ./ssh-servers-from-aws.rb example
Replace "example" with the name of your AWS profile. This will query your AWS account for all running servers, adding each to a file in the "ssh" folder. The following information will be pulled from each EC2:
After you generate the ssh configs for each AWS account, you'll need to update your master ~/.ssh/config
file.
Run rebuild-ssh-config.sh
, which combines all your ssh/*.sshconfig
files into the master ~/.ssh/config
file. Note that your previous ~/.ssh/config
file will be overwritten, so make sure you move any important things into the "ssh" folder before you do so.
Now you are ready to go! You can do things like:
$ ssh e[TAB]
auto-expands to $ ssh example
Or if you have multiple servers with the same prefix,
$ ssh e[TAB][TAB] example-1 example-2
If you name your servers with common prefixes per account, or by prefixing types of machines with the same prefix (worker-* vs db-* for example) then the tab-complete becomes even more useful!
sshtun is a daemon that executes an ssh command to form a secure tunnel and then blocks on it. If the tunnel goes down, sshtun can attempt to reestablish it. It can also be set up to monitor a file on an http server to determine if the tunnel should be up or not, so you can switch it on or off remotely.