A better way of setting up and managing user ssh keys in AWS EC2 instances Oct 29 2022
I've been using AWS EC2 instances for a while now, and I've always struggled to find a clean way to manage the users and ssh keys for the instances. I've tried a few different approaches and settled on one that I think is the best so far.
In this article, I'll create a regular EC2 instance as an example. But you can use the same approach to set up users and ssh keys or run other commands on any other type of EC2 instance (e.g. Bastion hosts, EKS nodes, ECS nodes, etcetera). The key is using cloud-init.
Prerequisites
- Terraform installed
- AWS account
- AWS CLI configured
What is could-init
?
Quote from the official documentation:
"Cloud-init is the industry standard multi-distribution method for cross-platform cloud instance initialization."
What it means for us is that we can use it to configure our instances in AWS. We can use it to create users, install packages, etc. And that is exactly what we are going to do. We will use a file in the cloud-init
format containing our users and their respective ssh keys.
Creating the EC2 instance
First, we need to create the EC2 instance. We can do that by creating a main.tf
file with the following content:
1
2
3
4
5
6
7
8
9
10
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "example" {
ami = "ami-XXXXX"
instance_type = "t2.micro"
user_data = file("cloud-init.yaml")
}
I created a simple EC2 instance with the t2.micro
instance type and the ami-XXXX
AMI. The user_data
attribute is where we are going to specify the cloud-init
file that we are going to create. The instance type and the AMI are random; ignore their values.
If you want to find Amazon Linux 2 AMI IDs, you can use the following command:
1
aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn*" --query 'sort_by(Images, &CreationDate)[].Name'
reference: Query for the latest Amazon Linux AMI ids
Or, if you want to query Ubuntu AMI IDs, you can use the following command:
1
aws ec2 describe-images --owners 099720109477 --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*" --query 'sort_by(Images, &CreationDate)[].Name'
Ok, after that slight detour, let's get back to setting up the users and their ssh keys.
Creating the cloud-init
file
We now need to create the cloud-init.yaml
file that will contain our users:
1
2
3
4
5
6
7
8
9
10
11
12
13
#cloud-config
users:
- name: rderik
ssh_import_id:
- gh:rderik
lock_passwd: true
- name: pascual
ssh_import_id:
- lp:pascual_fake
- name: adrian
ssh_import_id:
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSL7uWGj8cgWyIOaspgKdVy0cKJ+UTjfv7jBOjG2HXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX fake-public-key
As you can see, we are creating three users: rderik
, pascual
and adrian
. The first two users use the ssh_import_id
attribute to import their ssh keys from GitHub and Launchpad, respectively. The third user uses the ssh_authorized_keys
attribute to specify the ssh keys directly.
And that is it. We are now ready to run terraform apply
and create the EC2 instance.
Connecting to the EC2 instance
Now that we have created the EC2 instance, we can connect to it using the ssh keys specified in the cloud-init.yaml
file. We can do that by running the following command:
1
ssh -i ~/.ssh/id_rsa rderik@<ec2-instance-ip>
If you use a different ssh key, you can use that instead of ~/.ssh/id_rsa
.
And that is it. We have created an EC2 instance with three users and their respective ssh keys. We can now use the ssh_import_id
, and ssh_authorized_keys
attributes to create as many users as we want and specify their ssh keys.
Final thoughts
I hope you found this article useful. I believe it is better to have the users and their ssh_keys
defined in our IaC code instead of manually creating them. This way, we can easily create new users and their ssh keys by adding them to the cloud-init.yaml
file.
could-init
is a very powerful tool that can be used for much more than just creating users and ssh keys. I encourage you to check the official documentation. Check the Module Reference. You can see what other options you can pass to the Users and Groups and many more. I bet that you'll find something useful that you can add to your instances.
One last thing, if you don't like having a separate file for the could-init
configuration, you can specify it directly in your terraform template.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
user_data = "#cloud-config\n${yamlencode({
users = [
{ name = "rderik"
ssh_import_id = "gh:rderik"
ssh_authorized_keys = []
},
{ name = "pascual"
ssh_import_id = "lp:pascual_fake"
ssh_authorized_keys = []
},
{ name = "adrian"
ssh_import_id = ""
ssh_authorized_keys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSL7uWGj8cgWyIOaspgKdVy0cKJ+UTjfv7jBOjG2HXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX fake-public-key",
]
},
]
})}"
}
Notice how we use the yamlencode
function to convert the cloud-init
configuration to a string. This way, we don't need to create a separate file for the cloud-init
configuration. And also, don't miss the first line, #cloud-config
.