Skip to content

Provisioning EC2 key pairs with terraform.

In the previous example, we created an EC2 instance, which we wouldn’t be able to access, that is because we neither provisioned a new key pair nor used existing one, which we could see from the state report:

➜  terraform_demo grep key_name terraform.tfstate
                            "key_name": "",
➜  terraform_demo

As you can see key_name is empty.

Now, if you already have a key pair which you are using to connect to your instance, which you will find
in EC2 Dashboard, NETWORK & SECURITY – Key Pairs:

then we can specify it in aws_instance section so EC2 can be accessed with that key:

resource "aws_instance" "ubuntu_zesty" {
  ami           = "ami-6b7f610f"
  instance_type = "t2.micro"
  key_name = "myec2key"
}

Let’s create an instance:

 
➜  terraform_demo vault read -field=value secret/aws  | terraform apply  --auto-approve
var.secret_key
  Enter a value:
aws_instance.ubuntu_zesty: Creating...
  ami:                          "" => "ami-6b7f610f"
  associate_public_ip_address:  "" => "<computed>"
  availability_zone:            "" => "<computed>"
  ebs_block_device.#:           "" => "<computed>"
  ephemeral_block_device.#:     "" => "<computed>"
  instance_state:               "" => "<computed>"
  instance_type:                "" => "t2.micro"
  ipv6_address_count:           "" => "<computed>"
  ipv6_addresses.#:             "" => "<computed>"
  key_name:                     "" => "myec2key"
  network_interface.#:          "" => "<computed>"
  network_interface_id:         "" => "<computed>"
  placement_group:              "" => "<computed>"
  primary_network_interface_id: "" => "<computed>"
  private_dns:                  "" => "<computed>"
  private_ip:                   "" => "<computed>"
  public_dns:                   "" => "<computed>"
  public_ip:                    "" => "<computed>"
  root_block_device.#:          "" => "<computed>"
  security_groups.#:            "" => "<computed>"
  source_dest_check:            "" => "true"
  subnet_id:                    "" => "<computed>"
  tenancy:                      "" => "<computed>"
  volume_tags.%:                "" => "<computed>"
  vpc_security_group_ids.#:     "" => "<computed>"
aws_instance.ubuntu_zesty: Still creating... (10s elapsed)
aws_instance.ubuntu_zesty: Creation complete after 17s (ID: i-00ebe9b4c1c18b286)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

As you can see key_name is populated now, so we associated our instance with the existing key, meaning we can now use it to connect.
Let’ check the public_ip first:

➜  terraform_demo grep public_ip terraform.tfstate
                            "associate_public_ip_address": "true",
                            "public_ip": "35.177.75.181",

We are ready to connect now, I will run ssh with the command to get release info:

  terraform_demo ssh ubuntu@35.177.75.181 -i myec2key.pem lsb_release -a

Distributor ID:	Ubuntu
Description:	Ubuntu 17.04
Release:	17.04
Codename:	zesty
No LSB modules are available.
➜  terraform_demo

If you can’t connect and getting ‘Operation timed out’:

➜  terraform_demo ssh ubuntu@35.177.75.181 -i myec2key.pem lsb_release -c

ssh: connect to host 35.177.75.181 port 22: Operation timed out
➜  terraform_demo

make sure you can access port 22 on the other side, quick tcpdump will show something like below:

➜  ~    tcpdump   -i en0  'port 22'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on en0, link-type EN10MB (Ethernet), capture size 262144 bytes
23:06:50.187731 IP 192.168.1.3.58547 > ec2-35-177-75-181.eu-west-2.compute.amazonaws.com.ssh: Flags [S], seq 3506345055, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 421858571 ecr 0,sackOK,eol], length 0
23:06:50.502969 IP 192.168.1.3.58547 > ec2-35-177-75-181.eu-west-2.compute.amazonaws.com.ssh: Flags [S], seq 3506345055, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 421858885 ecr 0,sackOK,eol], length 0
23:06:50.621113 IP 192.168.1.3.58547 > ec2-35-177-75-181.eu-west-2.compute.amazonaws.com.ssh: Flags [S], seq 3506345055, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 421859003 ecr 0,sackOK,eol], length 0
23:06:50.739172 IP 192.168.1.3.58547 > ec2-35-177-75-181.eu-west-2.compute.amazonaws.com.ssh: Flags [S], seq 3506345055, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 4218591

As you can see your ssh client sends series of synchronisation requests([S]) and doesn’t get anything back. Normal sequence would be something like:

➜  ~    tcpdump   -i en0  'port 22'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on en0, link-type EN10MB (Ethernet), capture size 262144 bytes
23:05:02.319613 IP 192.168.1.3.58530 > ec2-35-177-75-181.eu-west-2.compute.amazonaws.com.ssh: Flags [S], seq 111698112, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 421750979 ecr 0,sackOK,eol], length 0
23:05:02.345666 IP ec2-35-177-75-181.eu-west-2.compute.amazonaws.com.ssh > 192.168.1.3.58530: Flags [S.], seq 2183189031, ack 111698113, win 26847, options [mss 1392,sackOK,TS val 812966100 ecr 421750979,nop,wscale 7], length 0
23:05:02.345736 IP 192.168.1.3.58530 > ec2-35-177-75-181.eu-west-2.compute.amazonaws.com.ssh: Flags [.], ack 1, win 4096, options [nop,nop,TS val 421751005 ecr 812966100], length 0
23

with a series of SYNC/SYNC ACK/ACK – if you want to know more about TCP handshake read this article which explains this in detail

So as you can see:

➜  terraform_demo grep security_groups terraform.tfstate
                            "security_groups.#": "1",
                            "security_groups.3814588639": "default",
➜  terraform_demo

it is using ‘default’ security group, now go to VPC dashboard, security group:

and make sure ssh/22 port is added to your ip address or all(0.0.0.0/0).

Provisioning a new key pair.

Now, let’s say you don’t have any keys, or you just want to provision a new key just for this EC2 instance.
Let’s destroy our instance first:

➜  terraform_demo vault read -field=value secret/aws  | terraform destroy -force
var.secret_key
  Enter a value:
aws_instance.ubuntu_zesty: Refreshing state... (ID: i-00ebe9b4c1c18b286)
aws_instance.ubuntu_zesty: Destroying... (ID: i-00ebe9b4c1c18b286)
aws_instance.ubuntu_zesty: Still destroying... (ID: i-00ebe9b4c1c18b286, 10s elapsed)
aws_instance.ubuntu_zesty: Still destroying... (ID: i-00ebe9b4c1c18b286, 20s elapsed)
aws_instance.ubuntu_zesty: Still destroying... (ID: i-00ebe9b4c1c18b286, 30s elapsed)
aws_instance.ubuntu_zesty: Still destroying... (ID: i-00ebe9b4c1c18b286, 40s elapsed)
aws_instance.ubuntu_zesty: Still destroying... (ID: i-00ebe9b4c1c18b286, 50s elapsed)
aws_instance.ubuntu_zesty: Destruction complete after 51s

Destroy complete! Resources: 1 destroyed.

and then reprovision again with a new key, for this, you will need to generate a key first:

➜  terraform_demo ssh-keygen -f terraform_ec2_key
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in terraform_ec2_key.
Your public key has been saved in terraform_ec2_key.pub.
The key fingerprint is:
SHA256:4O2exkz/gH8IgB3dXxrsJir8iyIT/R1WsPz5r1u69Iw kayanazimov@kayanazimov.local
The key's randomart image is:
+---[RSA 2048]----+
|       . . .     |
|      . o . o .  |
|     o.o o o +   |
|    ..ooo o =    |
|   . ...S+ +     |
|  . . o.*.o      |
|   . . Oo+.o. .  |
|  o . .o*oooo*   |
|   o ...+..oE=+  |
+----[SHA256]-----+
➜  terraform_demo

We now have two files:

➜  terraform_demo ls terraform_ec2*
terraform_ec2_key     terraform_ec2_key.pub

We will need to provision public key, and keep private key safe and hidden:

provider "aws" {
  access_key = "AKIAIVBOWPGYHYWPZ2NQ"
  secret_key = "${var.secret_key}"
  region     = "eu-west-2"
}

resource "aws_instance" "ubuntu_zesty" {
  ami           = "ami-6b7f610f"
  instance_type = "t2.micro"
  key_name = "terraform_ec2_key"
}

resource "aws_key_pair" "terraform_ec2_key" {
  key_name = "terraform_ec2_key"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfMCqXraSPxvhL2LIGluGC7Y8UsV1PuMcH1L3u7zdHnMQl0CzAt+1yjqdcbu/OVDBMtoPfimTp5BxawuodDdEEewNSOonL517oSQqwdaunkoy6bioITMvj6iiG4ab3thy0BaT0MWb7Thbf8KDHPIxLm0fdgJHSOhXRb6TEToNCi+zm9BVYcKiYK6HBfnh4wp9CI2pyhZ1OEhly/8K+SjQzg4j8TR/5EH7JEiCl64Y5gXwNxLDyjHHiGMqk2sv6EfxRncroAYVhonG/N63Fkd1BTOIWLNovgId/ehw/+ejh2LHi5Y7+whgPzVqaFfzmhXW/RSRMaAmxeAoLZWDUpeGx kayanazimov@kayanazimov.local"
//  public_key = "${file("terraform_ec2_key.pub")}"
}

As you can see we added key_name to aws_instance resource and defined public_key inside aws_key_pair resource,
alternatively you could refer to file as well instead putting contents, it is actually more preferable as less chances to make copy-paste mistake.

Let’s connect and show the key is added:

➜  terraform_demo ssh ubuntu@35.177.147.19  -i terraform_ec2_key cat .ssh/authorized_keys
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDyx+ksR4sayAn5Bg0esnqqqp5qvhUqEseV43J/ck8tdtUZe7qaof3Upm0mE6GCjMygGqxRNOVGWn5FGws7ILd5desdooNC0tIxa9OF/TmupGEEJ5NBxTZIeUl31+tBjM5wwO7+Cc1FfZBGy/9VQU6sv7zNsqz1a66Zeq4Jif9+31hpVzTmsdXC8cmdXzSrKiEGDRo6/eKqhzELHnNfPQ0xsvD0yjCx4nOcZnIPV4rl2k7goMblRL+p40kIbjOeDj9xb7GWtGvHncgRHrCCqpoDove8OtTikHO7wHCxISsCpkdNVDhkbDfCY1e0dMnFr4L24EVu5Zx4RPqNVhiarGx1 terraform_ec2_key
➜  terraform_demo