Better AWS Access Control with IAM (and Fog)
--
Amazon are the undisputed kings of the developer plumbing revolution. EC2 and S3 are so ubiquitious to deploying web applications that sometimes it seems difficult to remember when we had to self-host so much of this stuff. But sometimes managing my access keys can be a pain. Or even worse: a security hazard.
On one hand, I don’t want to create a new account with different billing information for every single client project demo or collaboration that I roll out. Yet on the other, I’m hesitant to use my own set of keys in places where another person might have access to them. One set of keys to the entire kingdom seems like a bad idea.
The solution to this problem is Amazon’s Identity and Access Management product beta. IAM allows you to create different users and groups, and attach those to your account. So, for example, if you’re a company or a group of people who needs to share a single AWS account, you could create separate identities with separate security credentials (access key ID + secret access key) for each individual. Or if you run several different projects and want to segregate those by account, you can do that too.
Even better, you can control which AWS services that those users (or groups) have access to via policy documents. A common example might be giving a user identified as ‘projectx’ access to only a ‘projectx’ S3 bucket and no others. This way, you can keep your accounts separate, and limit them to only what they need. And if you ever need to revoke or regenerate keys because a single application is compromised, it’s far less of a problem.
Anyway, outside of Amazon’s own documentation, I couldn’t find a hell of a lot of information about using IAM in the real world, so I thought I’d demonstrate how I’m using it with Fog, which is an absolutely awesome cloud services library for Ruby.
Annotated example code seems like the right way to go here. Let’s generate a new IAM user and a set of keys for him, and give him permission to only a single bucket, which we’ll create (note that your bucket name must be unique so don’t use the sample one shown here).
First, we set up the new user name and the bucket name that we want to create, and establish our AWS credentials:
require 'fog'
username = 'testuser'
bucket = 'uniquebucketname1234'
aws_credentials = {
:aws_access_key_id => 'YOUR-ACCESS-KEY-ID',
:aws_secret_access_key => 'YOUR-SECRET-ACCESS-KEY'
}
Creating a new S3 bucket is easy with Fog:
storage = Fog::Storage.new(aws_credentials.merge(:provider => 'AWS'))
storage.put_bucket(bucket)
Fog also provides a lightweight wrapper around IAM API methods. Using this, we can create the new user and create an access key. Assuming this is successful, we’ll end up with the user ARN (which we’ll need later) and a new set of credentials.
iam = Fog::AWS::IAM.new(aws_credentials)
user_response = iam.create_user(username)
key_response = iam.create_access_key('UserName' => username)
access_key_id = key_response.body['AccessKey']['AccessKeyId']
secret_access_key = key_response.body['AccessKey']['SecretAccessKey']
arn = user_response.body['User']['Arn']
We want to give this user the ability to manage their own keys (as shown in Amazon’s Getting Started guide so we create our first policy document as they suggest.
iam.put_user_policy(username, 'UserKeyPolicy', {
'Statement' => [
'Effect' => 'Allow',
'Action' => 'iam:*AccessKey*',
'Resource' => arn
]
})
We also want to grant them access to the bucket we created. But only our bucket and no others. If you think constructing policy documents by hand is shitty, you’re right. Fortunately the AWS Policy Generator can help us generate the nasty bits. Check it out.
iam.put_user_policy(username, 'UserS3Policy', {
'Statement' => [
{
'Effect' => 'Allow',
'Action' => ['s3:*'],
'Resource' => [
"arn:aws:s3:::#{bucket_name}",
"arn:aws:s3:::#{bucket_name}/*"
]
}, {
'Effect' => 'Deny',
'Action' => ['s3:*'],
'NotResource' => [
"arn:aws:s3:::#{bucket_name}",
"arn:aws:s3:::#{bucket_name}/*"
]
}
]
})
Finally, let’s reset our IAM access credentials and verify we can access the bucket and upload a file to it:
aws_credentials = {
:aws_access_key_id => access_key_id,
:aws_secret_access_key => secret_access_key
}
storage = Fog::Storage.new(aws_credentials.merge(:provider => 'AWS'))
storage.get_bucket(bucket)
storage.put_object(bucket, 'image.png', File.open('/path/to/image.png'))
Note that you can use the keys that are generated in place of your normal account keys pretty much anywhere, as demonstrated. However, if you’re limiting bucket access as we’ve done here, be aware that you won’t be able to do a list command to retrieve all buckets accessible to a user. I had an incredibly frustrating couple of hours the other day trying to figure out why s3cmd and the Firefox S3 Organizer wouldn’t work with my newly generated IAM keys, and the reason was simply that they were trying to get a list of buckets first, which was returning an access denied message. D’oh. (and thanks to crazed for helping me see the light on that.)
If you’re not a Ruby developer, or you just need command line access, you can also use the IAM command line tools provided by Amazon, and you should definitely check out their Getting Started guide. Hopefully IAM will find its way into the AWS Console so routine management tasks are a bit easier in the future. In the meantime, creating policy documents by hand is a big pain in the ass, but at least the Policy Generator takes a little bit of the suck out of it.