Using ProxyCommand to SSH to Cloud Managed Servers
Problem Statement
Given:
- You have a series of server instances: something like an AWS Autoscaling Group (or "ASG").
- These groups are named in a regular pattern:
environment-service
. - You don't necessarily have a stable set of IP addresses to SSH into.
- Your instances are attached to a private network.
- You have a well-known bastion server with SSH access.
- You don't want to use AWS Secure Session Manager, or their equivalent (for whatever reason!)
- You have
jq
installed.
You wish to be able to SSH to an ASG-managed instance without knowing the IP address ahead of time.
Solution
These steps enable you to run ssh [email protected]
.
- Use
.ssh/config
host aliases to capture an autoscaling group name. - Point that host alias at a
ProxyCommand
. - The
ProxyCommand
script receives two arguments: the user-specified host and the port (See Fig. 1) - The script removes the host alias prefix (
myhosts.
), extracts the environment, service name, and (optional) instance index into variables (See Fig. 2, section 1) - The script uses a command line interface with your cloud provider to list instances for that autoscaling group and select an instance by the requested index (See Fig. 2, section 2.)
- Then the script retrieves private IP information for that instance. (See Fig. 2, section 3.)
- Finally, we connect to the target instance through a jumpbox.
Figures
Figure 1: An SSH config with ProxyCommand
inserted:
Host myhosts.*
ProxyCommand /path/to/proxy-command-script %h %p
ForwardAgent yes
Figure 2: An example proxy command:
#!/bin/bash
# if necessary!
source path/to/some/credentials
# Section 1
target=${1/myhosts\./}
environment=$(echo $target | cut -d'.' -f1)
service=$(echo $target | cut -d'.' -f2)
num=$(echo $target | cut -d'.' -f3)
num=${num:-0}
# Section 2
autoScalingGroup=$(
aws autoscaling describe-auto-scaling-groups --output json | \
jq -r '.AutoScalingGroups[] |
select( .AutoScalingGroupName == "'"${environment}-${service}"'" ) |
.Instances[].InstanceId' | \
sort
)
if [ ${#autoScalingGroup} -lt 1 ]; then
>&2 echo "Autoscaling group \"$1\" not found or empty"
exit 1
fi
instance=$(echo ${autoScalingGroup} | head -n$((num + 1)) | tail -n1)
if [ -z "$instance" ]; then
>&2 echo "Could not find server by index \"$num\""
exit 1
fi
# Section 3
ip=$(
aws ec2 describe-instances --instance-id "$instance" --output json | \
jq -r '.Reservations[].Instances[] | .PrivateIpAddress'
)
if [ -z "$ip" ]; then
>&2 echo "Could not find ip of instance \"$instance\""
exit 1
fi
# Section 4
ssh -q ubuntu@jumpbox.$environment.example.com -W $ip:$2