3

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.

DanielM
  • 147
  • 1
  • 8
  • Is there a reason that the template on the [CloudFormation Product Details Page](https://aws.amazon.com/cloudformation/details/) 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

2 Answers2

3

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:

-----BEGIN RSA PRIVATE KEY-----,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX,-----END RSA PRIVATE KEY-----

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

Egor
  • 151
  • 4
1

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.

DanielM
  • 147
  • 1
  • 8
bsvingen
  • 126
  • 3
  • 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