How to create an SSH Key in Terraform

October 17, 2023

terraformlogo

Prerequisites:

  1. AWS Account: You must have an active AWS account. If you don't have one, you can sign up for an AWS account on the AWS website. You can create it here
  2. IAM User or Role: Create an IAM (Identity and Access Management) user or role in your AWS account with the necessary permissions to create and manage EC2 Instances. At a minimum, the user or role should have permissions to create EC2 instances, VPCs, and related resources.
  3. Terraform Installed: Install Terraform on your local machine. You can download Terraform from the official Terraform website and follow the installation instructions for your operating system here

Terraform

Providers

Link to Github code repo here

First lets create our providers, in you project folder create provider.tf file:

provider "aws" {
  region  = var.aws_region
  profile = var.aws_profile
}

Main.tf

Create VPC

You can find more detailed guide on VPC creation in our article here

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "5.1.2"

  name = "${var.env}-vpc"

  cidr = var.cidr
  azs  = var.aws_availability_zones

  private_subnets = var.private_subnets
  public_subnets  = var.public_subnets

  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true

  public_subnet_tags = merge(
    var.additional_tags,
    {
      "Subnet" = "Public"
    }
  )

  private_subnet_tags = merge(
    var.additional_tags,
    {
      "Subnet" = "Private"
    }
  )
}

resource "aws_security_group" "ssh" {
  name        = "allow_ssh"
  description = "Allow ssh inbound traffic"
  vpc_id      = module.vpc.vpc_id

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

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

Security group added to allow ssh connection to instance

Create new SSH Key

In this example we would create a new SSH Key to connect to our instance:

resource "tls_private_key" "ssh_key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "local_file" "private_key" {
    content  = tls_private_key.ssh_key.private_key_pem
    filename = "${var.path}/${var.private_key_name}"
    file_permission = "0600"
}

resource "aws_key_pair" "public_key" {
  key_name   = var.ssh_key_name
  public_key = tls_private_key.ssh_key.public_key_openssh
}

resource "tls_private_key" "ssh_key": Generates a secure private key and encodes it
resource "local_file" "private_key": Saves value of private_key_pem attribute to a file in a specified location. In order to use the key we would need to specify permisions
resource "aws_key_pair" "public_key": Terraform add ssh key to instance

Create EC2 Instance:

Here we would create EC2 instance using created key for access to it:

module "ec2-instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "5.5.0"

  name = var.ec2_name
  instance_type = var.ec2_type
  subnet_id = module.vpc.public_subnets[0]
  associate_public_ip_address = true
  key_name = aws_key_pair.public_key.key_name
  vpc_security_group_ids = [aws_security_group.ssh.id]
}

name: The name for the EC2 instance
instance_type: Specifies the instance type for the EC2 instance.
subnet_id: The ID of the subnet where the EC2 instance will be launched. It selects the first public subnet from the VPC module provided by module.vpc.
associate_public_ip_address: This attribute is set to true, indicating that the EC2 instance should be assigned a public IP address.
key_name: The SSH key pair name to associate with the EC2 instance. It uses the key name created by an aws_key_pair resource.
vpc_security_group_ids: This specifies a list of security group IDs that should be associated with the EC2 instance.

Variables and Outputs

Don't forget to declare variables:

variable "aws_profile" {
  description = "Set this variable if you use another profile besides the default awscli profile called 'default'."
  type        = string
  default     = "default"
}

variable "aws_region" {
  description = "Set this variable if you use another aws region."
  type        = string
  default     = "us-east-1"
}

variable "env" {
  description = "Set this variable to specify environment"
  type        = string
  default     = "dev"
}

variable "private_subnets" {
  description = "private subnets to create, need to have 1 for each AZ"
  default     = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
}

variable "public_subnets" {
  description = "public subnets to create, need to have 1 for each AZ"
  default     = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]
}

variable "aws_availability_zones" {
  description = "AWS availability zones"
  default     = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

variable "cidr" {
  description = "Cird block for your VPC"
  type        = string
  default     = "10.0.0.0/16"
}

variable "additional_tags" {
  default     = {
    "project"     = "cloudtipss-vpc"
    "owner"       = "cloudtipss.com"
  }
  description = "Additional resource tags"
  type        = map(string)
}

variable "ssh_key_name" {
  description = "File name to save your private key"
  type        = string
  default     = "ec2-terraform"
}

variable "private_key_name" {
  description = "File name to save your private key"
  type        = string
  default     = "ec2-terraform.pem"
}

variable "path" {
  description = "Full path to you ssh folder or other folder to save private key"
  type        = string
  default     = "/Users/user1/.ssh"
}

variable "ec2_name" {
  description = "Name for your EC2 Instance"
  type        = string
  default     = "ec2-terraform"
}

variable "ec2_type" {
  description = "Type of your instance"
  type        = string
  default     = "t3.small"
}

Also we would declare output to get command to run to connect to instance after terraform applied:

output "EC2_public_ip" {
  description = "The public IP address assigned to the instance"
  value       = "ssh -i ${var.path}/${var.private_key_name} ec2-user@${module.ec2-instance.public_ip}"
}
Provisoning resources and verification:

After we prepared our code, we would need to initialize it. From your project folder run:

terraform init

Just to to see which resources terraform is going to create you can run:

terraform plan

In order to create resources run:

terraform apply

After apply complete you will see output similar to this:

Outputs:

Connect_to_instance = "ssh -i /Users/user1/.ssh/ec2-terraform.pem ec2-user@18.232.182.201"

You can run output value in your terminal to connect to your new instance

Use existing ssh key

In this example we would use in terraform already excisting SSH Key. You can find source code here. There would be slight adjustments in a code, variables and output

  1. In a project folder I would create a key pair to use:
ssh-keygen -t rsa -b 4096 -f ./ec2-manual-key
  1. Now we need terraform to read ssh key from file we created:
### SSH Key 
resource "aws_key_pair" "public_key" {
  key_name   = var.ssh_key_name
  public_key = "${file("ec2-manual-key.pub")}"
}

### EC2 Instance 
module "ec2-instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "5.5.0"

  name = var.ec2_name
  instance_type = var.ec2_type
  subnet_id = module.vpc.public_subnets[0]
  associate_public_ip_address = true
  key_name = aws_key_pair.public_key.key_name
  vpc_security_group_ids = [aws_security_group.ssh.id]
}

You will find both usescases in our Github repo here