Blog
Using SSH Bastion Hosts With AWS and Dynamically Locating Them With EC2 Tags

By Rob Giseburt

Introduction

This is the second part of a series about using SSH with bastion hosts. You may wish to read the first part for background about using SSH bastion hosts:

Dynamically loading the bastion server address from AWS

Credit to my colleague Jason Mao for devising this technique.

Here we will describe how to load the bastion server’s address from AWS, using AWS tags and the shell environment’s AWS authentication information. This could easily be extended or modified to use any other means of dynamically loading the hostname during the connection.

Setup:

  1. The aws command-line utility must be installed and the shell environment must be configured to allow access to the destination AWS environment.
    • It’s possible to change the AWS access credentials in the shell environment to switch to a different AWS environment.
    • You can test if it’s working by running: aws ec2 describe-instances
  2. There need to be a single EC2 instance in each target AWS environment with a unique tag, so that we can search for that tag and get the address of that single EC2. That EC2 instance must be on a public subnet that you have access to. You must be able to SSH into this machine, since it will be the bastion server.
    • In this example, we have a machine with the tag Name with a value of SSHBastion.
    • You could also have various bastion servers in the same AWS environment, but you will need to be able to map the destination machines (via DNS or IP range in a way that can be put in the Host line of the SSH configuration) to the relevant EC2 tag.
  3. The command line utility jq must be installed.
    • It’s possible to use one of a number of other utilities for that same purpose. Basically, anything that can consume JSON, search for specific subkeys, and return the relevant values will work. This includes most scripting languages.

Now, to test that we can get the relevant bastion host’s address, let’s run a simple command and verify that we get the correct result (lines broken for readability, but this is one long command):

aws ec2 describe-instances --filters
   "Name=instance-state-name,Values=running"
   "Name=tag:Name,Values=SSHBastion"
   "Name=tag:Subnet,Values=public"
 | jq -r .Reservations[].Instances[].PublicDnsName

You will likely wish to replace the bolded “SSHBastion” with whatever tag value you wish to use. You can also use wild cards, such as *. That command should return a public address of the bastion host, such as:

ec2-11-22-333-444.compute-1.amazonaws.com

If that fails, make sure that all the requirements mentioned above have been met.

Now all we need to do is incorporate that command in place of the bastion host address in the ssh configuration:

Host *.aws.destination.com
    ProxyCommand ssh -A ec2-user@`aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" "Name=tag:Name,Values=SSHBastion" "Name=tag:Subnet,Values=public" | jq -r .Reservations[].Instances[].PublicDnsName` -W %h:%p

Note that that is one really long line. You will likely want to adjust *.aws.destination.com and SSHBastion to match your environment.

Now you should be able to connect to any machine matching one of the host patterns and it will automatically and transparently look up and use the bastion host.

What’s next?

In the next post we’ll discuss SSH connection multiplexing, port forwarding, and using SSH as a SOCKS proxy.

Categories: Blog, Uncategorized

Rob Giseburt
19 Jan, 2015