I'm creating an EC2 instance using CloudFormation. The first thing I want to do is checkout a git repository containing puppet manifests. To do this I need an SSH key.

What is the best way to get the key on to the server? This is what I've considered:

  • Using KMS, but that doesn't seem to allow you to "store a key for later use"
  • Using EC2 key pairs, but this also doesn't seem to allow you to get the private key later
  • Writing the key into the UserData property, but (despite it's name) this seems like the wrong place to store any kind of data, let alone sensitive data
  • Storing it in an S3 bucket, but I'm not 100% sure how to set the permissions on the bucket to allow the EC2 instance to pull the data using the aws cli tool

This seems like it'd be a common thing to do, however I must be searching for the wrong things because I can't find a sensible answer.

  • Is there a reason that the template on the CloudFormation Product Details Page would not work? The second template mentions setting up a key for ssh access to the instance. I would imagine that this is the Amazon recommended best practice for setting up ssh keys. – Matt Jul 15 '15 at 16:52

A straightforward way of handling this is to store your secrets (like the SSH key) in a dedicated S3 bucket, and then give the EC2 instances access to that bucket.

You can start by creating an IAM role:

"DeploymentRole" : {
  "Type" : "AWS::IAM::Role",
  "Properties" : {
    "Policies" : [{
      "PolicyName" : "SecretsBucketPolicy",
      "PolicyDocument" : {
        "Version" : "2012-10-17",
        "Statement" : [{
          "Resource" : "arn:aws:s3:::wherever-the-secrets-are-stored/*",
          "Action" : ["s3:GetObject"],
          "Effect" : "Allow"
    "Path" : "/",
    "AssumeRolePolicyDocument" : {
      "Version" : "2012-10-17",
      "Statement" : [{
        "Action" : ["sts:AssumeRole"],
        "Principal" : {"Service": ["ec2.amazonaws.com"]},
        "Effect" : "Allow"

This role defines a policy that lets it read the secret bucket, and allows EC2 to assume this role.

You then create an instance profile for this role:

"DeploymentProfile" : {
  "Type" : "AWS::IAM::InstanceProfile",
  "Properties" : {
    "Roles" : [{"Ref" : "DeploymentRole"}],
    "Path" : "/"

For your EC2 instance or launch configuration you can now use the IamInstanceProfile property to assign this profile to the instance(s).

The secret bucket should then be readable.

| improve this answer | |
  • 1
    This worked. I've edited the code slightly to fix commas in the JSON and we needed to add /* to the S3 resources as the permission applies to the objects rather than the bucket itself. (I've made an edit, pending peer review) – DanielM Jul 16 '15 at 10:29
  • 1
    Great. I would just use the actual key of the object containing the secrets, but /* works as well. – bsvingen Jul 16 '15 at 11:12
  • Quite right. I know exactly which file I need to download, so giving permissions to access only that file is absolutely the right thing to do. "Resource" : "arn:aws:s3:::wherever-the-secrets-are-stored/path/to/file" – DanielM Jul 16 '15 at 11:24

The S3 bucket approach doesn't work if you need the SSH key for your OpsWorks cookbooks repository or application deployment.

Another solution is you can add a parameter of type CommaDelimitedList for the SSH key with newlines replaced by commas, then use Fn::Join to put the lines of the key back together again where you need it.

Example CloudFormation template:

  "Parameters": {
    "CookbooksDeployKey": {
      "Type": "CommaDelimitedList",
      "Description": "Enter the deploy key as CSV (replace newlines with commas)",
      "NoEcho": true
  "Resources": {
    "myStack": {
      "Type": "AWS::OpsWorks::Stack",
      "Properties": {
        "CustomCookbooksSource": {
          "Type": "git",
          "Url": "git@github.com:user/repository.git",
          "Revision": "master",
          "SshKey": {"Fn::Join": ["\n", {"Ref": "CookbooksDeployKey"}]}

To generate the single-line "CSV" formatted version of a private key file, the following sed command can be used (this simply replaces all newlines in the file with commas, and returns the result on stdout):

sed ':a;N;$!ba;s/\n/,/g' /home/user/.ssh/id_rsa

The result looks something like:


You can then paste this value into the parameter when creating or updating your stack in CloudFormation.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.