Configure SSH authorized keys in EC2 instances using an AWS secret

Posted on

The EC2 user data script can be used to populate the ~/.ssh/authorized_keys file, making it easier to login to the instances via SSH. This is useful if you want to include more than one public key in the file.

The authorized_keys file looks something like this:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMeONV6VCMZJFj8wijLtfkSXHk6hXhw6fQ/1f5l7xD4i
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMeONVsdfgjklhdgfkljgHJKhghhXhw6fQ/1f5l7xD4i

It can be useful to store the contents of this file in an AWS secret so that it can be shared by multiple instances.

Here are two approaches to populating the authorized_keys file via user data: fetch the secret at runtime (i.e. when the instance starts) or at terraform apply time.

Fetch at runtime

Include the following in your user data script:

aws secretsmanager get-secret-value --secret-id dev/ssh_authorized_keys --output text --query SecretString >> /home/ec2-user/.ssh/authorized_keys

Where dev/ssh_authorized_keys is the secret id of the secret containing the data. This assumes you’re using the Amazon Linux distro and the ec2-user.

Fetch at apply time

Terraform data sources allow us to fetch the contents of the secret:

data "aws_secretsmanager_secret" "ssh_authorized_keys" {
  name = "dev/ssh_authorized_keys"

data "aws_secretsmanager_secret_version" "ssh_authorized_keys" {
  secret_id =

The contents of the file can be rendered into the user data template using a variable.

data "template_file" "user_data" {
  template = file("${path.module}/")
  vars = {
    ssh_authorized_keys = base64encode(data.aws_secretsmanager_secret_version.ssh_authorized_keys.secret_string)

The variable is then piped into the authorized_keys file at runtime.

echo ${ssh_authorized_keys} | base64 -d >> /home/ec2-user/.ssh/authorized_keys

Base64 encoding is used to avoid any issues with bad data in the secret breaking the entire user data script.

This assumes you’re using the Amazon Linux distro and the ec2-user.

The user data can then be used in a launch configuration (for autoscaling).

resource "aws_launch_configuration" "ec2-instance"
  # ...
  user_data = data.template_file.user_data.rendered

Or directly on an EC2 instance:

resource "aws_instance" "ec2-instance" {
  user_data                   = data.template_file.user_data.rendered
  user_data_replace_on_change = true

The user_data_replace_on_change argument ensures the instance will be replaced if the user_data is updated, otherwise the changes wont apply automatically.


Amazon EC2 key pairs and Linux instances