IAM Roles Save Lives

Well, maybe not lives, but hey clickbait headlines are all the rage. Kids these days, right?

Anyway, once upon a time I was talking to someone else in the industry who had a little "Oops"TM go down the day before. The conversation went something like this.

Other Guy: So, we had someone get ahold of our AWS keys and rack up some charges.

Me: Oh, geez, how did that happen?

Other Guy: Well, the customer we were working with made their GitHub repo that was supposed to be private into a public repo so the AWS keys were made public.

And hit the brakes. Full stop. I realized here that there's a lesson here that can make a lot of peoples' lives better. No, it's not a lesson about better training people who have access to repos. This is a lesson that deserves a line break and some emphasis.

AWS KEYS DO NOT BELONG IN YOUR CODE UNDER ANY CIRCUMSTANCE

If that sounds terrifying to you, read on and I'll explain how to avoid this problem. There are a number of methods by which you can give your application access to AWS services.

  1. Provide the IAM Access Keys in your code via the AWS SDK flavor of yoru choosing. (This is what happened in Other Guy's situation.)
  2. Use the AWS CLI to configure AWS credentials used for a user on a given machine.
  3. Assign EC2 instances to IAM Roles.

All of the current AWS SDKs work such that if you go with Option 1, they will always obey the keys directly provided by the code. If you do not provide a key, the SDK will check for the presence of an AWS credentials file (~/.aws/credentials/) for the user under which code is executing. Finally, if no credentials file is available the SDK will check if it's running on an EC2 instance and assume the IAM Role from the EC2 instance metadata if the EC2 instance has been assigned a role.

Now, Option 1 above is basically the "goto" of interacting with AWS. You can certainly do it, but your design probably needs some reconsideration. In case you need a refresher about goto...

Goto

That method of providing credentials is really only meant to be used for legacy systems and shouldn't be used unless you have no choice. The other two options are much more palatable, and you'll likely find that you need each one in different circumstances. I'd say these circumstances play out as follows:

  1. If you're on an EC2 instance: always, always, always use an IAM Role. It's very easy to assign an IAM Role from the EC2 Console and tools like CloudFormation, Terraform, and Packer all provide mechanisms to ensure a launched EC2 instance is assigned to an IAM Role if you provide one.
  2. If you're not on EC2 and instead are using a local development environment or something along those lines, use Option 2. Configure the AWS CLI on your machine and use aws configure to setup your profile. Whatever keys you use for this should be carefully scoped for whatever services you need.

I tend to use Vagrant as a development environment, and so it's handy to share my host OS AWS credentials with the guest. Here's a snippet from the Vagrantfile to show how you can do that:

# Copy AWS credentials and config from host to guest
$awscredentialspath   = ENV['HOME'] + "/.aws/credentials"
$awsconfigpath        = ENV['HOME'] + "/.aws/config"

if File.file?($awscredentialspath) && File.file?($awsconfigpath) then config.vm.provision "shell", inline: "mkdir -p /root/.aws", privileged: true config.vm.provision "shell", inline: "mkdir -p /home/nginx/.aws", privileged: true config.vm.provision "file", source: $awscredentialspath, destination: "/home/vagrant/.aws/credentials" config.vm.provision "shell", inline: "/bin/cp -f /home/vagrant/.aws/credentials /root/.aws/credentials", privileged: true config.vm.provision "shell", inline: "/bin/cp -f /home/vagrant/.aws/credentials /home/nginx/.aws/credentials", privileged: true config.vm.provision "shell", inline: "chown nginx:nginx /home/nginx/.aws/credentials && chmod 0644 /home/nginx/.aws/credentials", privileged: true config.vm.provision "file", source: $awsconfigpath, destination: "/home/vagrant/.aws/config" config.vm.provision "shell", inline: "/bin/cp -f /home/vagrant/.aws/config /root/.aws/config", privileged: true config.vm.provision "shell", inline: "/bin/cp -f /home/vagrant/.aws/config /home/nginx/.aws/config", privileged: true config.vm.provision "shell", inline: "chown nginx:nginx /home/nginx/.aws/config && chmod 0644 /home/nginx/.aws/config", privileged: true else puts "AWS Credentials do not exist on host!" end

It's down and dirty and certainly doesn't work on Windows (you're not using Windows are you?), but it gets the job done and might give you some ideas on how you can approach it in your own configuration. The Vagrant guest in this case uses nginx as a web server and the nginx user runs the application, so keys are copied to that user for that reason. Keys are also copied to the vagrant user and root for CLI convenience.