Using Dynamically Generated Non-AWS Owned SSH Keys with Test Kitchen on EC2

Featured image for sharing metadata for article

As I've mentioned recently, I'm working on rebuilding our Chef pipelines at work.

Although I'm a huge fan of unit testing cookbooks using ChefSpec, there's also a really important place for integration tests. As AWS is the target for our cookbooks, we need to validate against an EC2 to confirm that our cookbook actually works for real-world solutions.

When interacting with AWS from Test Kitchen using kitchen-ec2, we need to be able to SSH into the instance so we can set up the instance, and then execute the cookbook.

kitchen-ec2 has the ability to create on-demand SSH keys for this purpose, but it's not always possible as it requires the user/role you're executing under to be able to create these keys, which may not be possible in your environment.

The solution I've found that works best for this is to generate them dynamically, inserting them into the user_data of the instance, as alluded to by the README of kitchen-ec2:

This will not directly associate the EC2 instance with an AWS-managed key pair (pre-existing or auto-generated). This may be useful in environments that have disabled AWS-managed keys. Getting SSH keys onto the instance then becomes an exercise for the reader, though it can be done, for example, with scripting in user_data or if the credentials are already baked into the AMI.

Doing it this way makes sure that we don't need to bake a key into the AMI, as well as not juggling a shared key, and even though it's likely a development account, this reduces the attack surface by having one time usage keys.

This solution was also aimed at making it easiest for local development and code review, so I wanted to make sure that the user data wasn't embedded in the kitchen.yml because I didn't want it duplicated across each of the platforms used, and I also wanted syntax highlighting.

I'd tried using kitchen.yml's ERB templating to try and read a file during parsing, but found it a bit awkward, and hard to read, so instead settled on a script to prepare a which would then be used as such:

- name: amzl2
    name: ec2
    user_data: ./

The would be generated from the following ERB template:

#!/usr/bin/env sh
echo 'Defaults:ec2-user !requiretty' > /etc/sudoers.d/ec2-user
mkdir -m 0700 -p ~ec2-user/.ssh
echo '<%= public_key %>' >> ~ec2-user/.ssh/authorized_keys
chmod 0600 ~ec2-user/.ssh/authorized_keys

To actually render this, we have a simple wrapper script, prepare.rb:

#!/usr/bin/env ruby
require 'erb'

user_data =[0])
public_key =[1])
erb =
result = erb.result(binding)[2], 'w') { |f| f.write result }

This then leads to the following set of commands to actually execute Test Kitchen:

ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -N ''
prepare.rb ~/.ssh/
kitchen test # ...

And there we have it - one-use SSH keys!

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #chef #test-kitchen #aws #ssh.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.