I have a private EC2 instance and I need to transfer GPG keys onto it (my particular use case is for Vault. But I am sick of having to first transfer files to my Bastion host, and then onto to my private instance. I wanted to find a way to do this in a single command and read the IP addresses from my Terraform setup. Here is a walkthrough of how I came to a simple solution.

This example will rely on setting up a Bastion host, which I walked through here. In addition to our default Bastion setup, we will add another EC2 instance for our Vault server. We will not expose this EC2 instance to the public.

provider "aws" {
  region = "us-west-1"
}

resource "aws_instance" "vault" {
  ami                         = "ami-969ab1f6"
  key_name                    = "${aws_key_pair.public_key.key_name}"
  instance_type               = "t2.micro"
  security_groups             = ["${aws_security_group.bastion-sg.name}"]
  associate_public_ip_address = false

  tags {
    Name = "Vault"
  }
}

resource "aws_instance" "bastion" {
  ami                         = "ami-969ab1f6"
  key_name                    = "${aws_key_pair.public_key.key_name}"
  instance_type               = "t2.micro"
  security_groups             = ["${aws_security_group.bastion-sg.name}"]
  associate_public_ip_address = true

  tags {
    Name = "Bastion"
  }
}

resource "aws_security_group" "bastion-sg" {
  name   = "bastion-security-group"
  vpc_id = "${aws_default_vpc.default.id}"

  ingress {
    protocol    = "tcp"
    from_port   = 22
    to_port     = 22
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    protocol    = -1
    from_port   = 0
    to_port     = 0
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_key_pair" "public_key" {
  key_name   = "your_key_name"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDWbz6ur89BKQ+am87EovJsv6g9QpbOiw13lTF7Kw1StbQAmkcGGrNTK2LIWsP3cQf+P+gptRAJbuqB1jQKZ283TwwREIv+l5AMKrbEkanOF4zsc8a9zitejlOLvVUxtVoMi5ROVYD2dLKjqAbDtqIC9LmMD+hcpqcXLhS6t+HVSVI862dTNVFY1EGukLGQ3IEJfw5v7FDzLn72NsuUiXEeCZu8DtlXLCTYRnqv+XkJQWVocPdFDUWISSIQ0CTFu+GJvJjdqDyAhYo3it7Eybj6XuSgLDwkQcNU45Ewz4Nn7LwV+f4Av8D25m4FZOfpWaj5+q9Fc9nRdIsB7P0oFgj5YoaTngQKy27MJ5UppMO7OOhriurJ/PBOrGpeqPcftWKLpcHLIGrm3ndoDKQx12R1s0ssYpA4JuNUWHYcxNrFa2rs/6AoFuS7wNUmM+DYB8iTjOl6dT8dS5AgMxGoZ3NepMPYilw1gf+gw9Ft3pHs2IMfDfqwZpXga8KdYwxBmRakpHdA7Nzje8ufvP/TBawsqVcW7z5gG9uPhYtfnYYezSIxv56PMSWEfqchkz+raPsElzIGtPcC1snncQlau95utV25r88BzXhCMJwNy9aDNEfSrm5SORlA97xicroCOuRjw2PnQyIXKvWDZtyqX5799x37K/HDYpJnvcgwpTlDZQ== your_email@example.com"
}

resource "aws_default_vpc" "default" {}

output "bastion_public_ip" {
  value = "${aws_instance.bastion.public_ip}"
}

Deploy the infrastructure:

terraform init
terraform apply
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

Outputs:

bastion_public_ip = 54.67.20.37
vault_private_ip = 172.31.8.133

The Old Way



Transfer the key to the Bastion host:

scp -i ~/.ssh/your_key_name ~/Desktop/my_gpg_key.asc ubuntu@54.67.20.37:~/

SSH onto the Bastion host and verify you see the key:

ssh -i ~/.ssh/your_key_name ubuntu@54.67.20.37
ls
my_gpg_key.asc

Transfer the key to the Vault instance:

scp my_gpg_key.asc ubuntu@172.31.8.133:~/

SSH onto the Vault instance and verify you see the key:

ssh ubuntu@172.31.8.133
ls
my_gpg_key.asc

...but that was waaaay too hard!


The New Way



scp -i ~/.ssh/your_key_name -o ProxyCommand="ssh ubuntu@54.67.20.37 nc 172.31.8.133 22" ~/Desktop/my_gpg_key.asc ubuntu@172.31.8.133:~/

This is similar to our basic scp command, except for this special command:

-o ProxyCommand="ssh ubuntu@54.67.20.37 nc 172.31.8.133 22"
-o ssh_option
  
Can be used to pass options to ssh in the format used in ssh_config(5).
This is useful for specifying options for which there is no separate scp command-line flag.

This allows us to set a Proxy command while running SCP. That proxy command takes advantage of netcat.

The nc (or netcat) utility is used for just about anything under the sun involving TCP or UDP.
It can open TCP connections, send UDP packets, listen on arbitrary TCP 
and UDP ports, do port scanning, and deal with both IPv4 and IPv6.

netcat is listening on port 22 of the Vault instance and is routing that traffic though SSH on the Bastion Host.

Now the only annoying thing is copying and pasting the IP addresses. Luckily terraform output can help us with that. Here is a script that handles fetching the IP addresses and scping for you, just replace your public key and file you want to transfer:

#!/bin/bash

bastion_ip=$(terraform output -json | jq -r '.bastion_public_ip.value')
vault_ip=$(terraform output -json | jq -r '.vault_private_ip.value')

scp -i ~/.ssh/your_key_name -o ProxyCommand="ssh ubuntu@$bastion_ip nc $vault_ip 22" ~/Desktop/my_gpg_key.asc ubuntu@$vault_ip:~/

It uses jq to parse out the IP addresses.


Bonus: Checking the key is on your private instance:

#!/bin/bash

bastion_ip=$(terraform output -json | jq -r '.bastion_public_ip.value')
vault_ip=$(terraform output -json | jq -r '.vault_private_ip.value')

ssh -A ubuntu@$bastion_ip -t "ssh -A ubuntu@"$vault_ip""

ls
my_gpg_key.asc

Hooray!

Three people clinking their wine glasses
Photo by Matthieu Joannon / Unsplash