Understanding AWS IAM
If you are new to AWS, or if you’ve never had to manage AWS IAM roles and policies before, you’re probably frustrated and aren’t sure where to start.
This article is brief overview of these topics, with links to the important parts of the AWS documentation. (I wrote an even simpler intro to IAM here!)
What is IAM used for? #
If you’ve used any AWS resources at all, you’ve almost certainly used IAM policies and roles, even if you didn’t realize it.
IAM stands for “Identity and Access Management”, and it is the primary way to control who can talk to and use your AWS resources.
Four Components #
There are four primary IAM components:
- Policies: These are specific, named rules for interacting with resources. You can use pre-made policies, or make your own.
- Roles: These are usually assigned to an AWS-managed resource, with one or more policies attached. You reference these in things like CloudFormation templates.
- Users: Like roles, except you can generate secret key/values and authenticate with those. Usually used by non-AWS resources, like continuous integration pipelines.
- Groups: Assign policies to a group, and a user in that group will inherit those policies.
Policies #
By themselves, policies do not do anything. You would attach a policy to a role, user, or group, and that policy would either allow or restrict access to one or more resources.
AWS has many pre-defined policies, which are logical divisions of access for resource management levels. For example, there’s a policy named AmazonDynamoDBReadOnlyAccess
which gives read-only access to DynamoDB tables. That specific policy will grant read access to all DynamoDB tables in an account, which may not be quite what you want.
You can also create your own policies, which are restricted to a set of operations and named resources, and attach those policies to roles, users, or groups.
For example, if you had a DynamoDB table named GameOutcome
you might want to give one user read-only access and another user read-write access.
The read-access policy might look like this:
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:*:*:table/GameOutcome"
}
While the write-access policy might look like this:
{
"Effect": "Allow",
"Action": [
"dynamodb:PutItem",
"dynamodb:DeleteItem"
],
"Resource": "arn:aws:dynamodb:*:*:table/GameOutcome"
}
You would then attach the first policy to the first user, and attach the first and second policy to the second user, thus giving the second user both privileges.
As you might guess from looking at the policy examples, you can use wildcard syntax in the policy definition. For example, if you wanted a policy that granted access to tables with a Game
prefix, your Resource
property might look like:
"arn:aws:dynamodb:*:*:table/Game*"
The other wildcard asterisks denote AWS region and account id.
This means, if you have DynamoDB tables in different regions, you can create a policy that grants access to only one region:
"arn:aws:dynamodb:us-east-1:*:table/Game*"
Or to a specific account id:
"arn:aws:dynamodb:*:123456789012:table/Game*"
In general this will probably be sufficient for your needs, but if the wildcard syntax is not enough you can also make use of properties like NotAction
and NotResource
to make your rules more complete. See here for the list of all available properties.
Note: Each resource type (e.g. DynamoDB, S3, Lambda, etc.) has its own
Resource
string andAction
strings. They are all of the same general design (e.g.s3:PutItem
) but you’ll want to look up the documentation for each resource type to be sure.
Roles #
When you have an AWS resource, and you want to control it’s access to another AWS resource, you’re probably going to use “Roles”.
A role is essentially a collection of Policies, and you assign that role to an AWS resource.
For example, suppose you are making an AWS Lambda and you want to give it read-only access to a DynamoDB table. If you create the Lambda manually under the “Permissions” section you’ll see that you can assign an “Execution Role”. You would create a role, attach a DynamoDB read-only policy (see the “Policy” section earlier in this article), and then assign that role to your Lambda.
Roles are pretty straightforward, but not every AWS resource can make use of them. The most common example is inside an EC2 instance.
In these cases, you’ll need a password (a secret key and value) that you’ll pass to your application, and for that you’ll likely create a User.
Users #
When you have an application that can’t have Roles assigned to it, for example integration tests that run in a CI environment external to AWS, you’ll typically create a User and assign that user a collection of Policies.
A user can have several different kinds of credentials, but the most common one is an “Access Key”, which is a key/value pair that typically gets set as an environment variable that looks like:
export AWS_ACCESS_KEY_ID=AKABCDEFGHIJKLMNOPQR
export AWS_SECRET_ACCESS_KEY=vmva1b2c3d4e5f6g7h8i9j0k
The key/secret are used to authenticate requests to AWS, and then the policies determine if the request is authorized for that user.
For example, in an automated deploy setup, you might want to write a build number to DynamoDB on completion. The deploy process would need a key/secret in order to authenticate, and would need a write policy attached to it.
Groups #
If you have multiple teams in your organization, it might make sense to use Groups. A group is given a collection of policies, and then a user is added to one or more groups. The user then has the policies of those groups.
For example, if your organization has two teams working on two applications, you might create policies that enable administrative access to each of those applications separately, and then attach those policies to two different groups. When you add a new user, you would simply add them to a group, and they would have administrative access to those resources.
Bonus Material #
Example 1: Small Startup #
You are a tech startup with only a handful of developers, but you have a handful of resources in AWS. You have a QA and a production environment.
Policies #
Create one for each resource, each policy representing a level of interaction with that resource. For example, a DynamoDB read-only policy for Lambdas that only read.
Don’t try to combine the policies, keep them distinct per resource+“level”. You might be surprised by how many rules a single resource policy needs.
Roles #
Create roles that match the “intent”. Try to answer the question: what actions does this role take, and therefore which policies will this role need.
For example, if you manage sporting events, perhaps a Lambda might need to execute periodically to check for updated game statistics. This role would require write access to a DynamoDB table, and possibly authorization to reset an API Gateway cache after an update.
Users #
Create a new user for each engineer, and a user for each external service (such as a continuous integration or automated deploy server).
Limit the policies attached to a user as much as possible. Periodic auditing of user policies is a good idea.
Groups #
Engineers at small tech startups typically have access to everything, and that’s usually fine, but it means that groups probably won’t give you much value.
Example 2: Enterprise Company #
You are a larger company with several teams, each team working on a distinct application.
Policies #
The same as Example 1, the main difference is that the number of policies will be higher.
Roles #
The same as Example 1, but again, the number of roles will be higher.
Users #
Create a new user for each engineer, but do not assign policies to the engineer directly. Instead add them to groups.
If you have very few external services, you could create users for each external service and attach policies directly to them, but if you have a larger number consider creating groups that encapsulate those interactions, and adding those service-users to the groups instead.
Groups #
Create a group for each application+“level”, and attach policies to those groups. Then add each user to the appropriate group.
For example, if you had a mobile division and desktop division, for the mobile division you might have groups called “mobile-devs-qa”, “mobile-readers-qa”, and “mobile-writers-qa”, and then the same but a “prod” suffix instead of “qa”. These would each have different policies attached, that allowed varying levels of access to some DynamoDB tables.
Finding Documentation #
Unlike the AWS SDK documentation, which is well organized, the IAM documentation is split across each resource type, using different page names.
There are two ways to find the policy documentation:
-
Find your resource type in the AWS Documentation, open the “Develop Guide (HTML)” for that resource, and look for something related to “Access Control”. For example, S3 policy examples are found here, while DynamoDB policy examples are found here.
-
I usually find duckduckgo.com search results for
identity based iam policies for aws $SERVICE
take me directly there. For example, the search for DynamoDB policies has the correct page as the first result.
Here are some links to ones I work with commonly:
Other links:
- AWS services that work with IAM: A list of the different AWS resources, whether they work with IAM, and links to more documentation for each type.
- IAM JSON policy elements reference: Definition of the JSON elements in an IAM policy object.
Special Notes #
S3: Bucket vs Item #
Bucket versus item are different, so you need to pay attention to the Resource
string for different Action
s.
For example, if you want to grant the s3:ListBucket
permission, you need bucket-level resource strings:
{
"Action": [ "s3:ListBucket" ],
"Resource": [ "arn:aws:s3:::my-bucket" ]
}
But if you want to grant permission to read items, you need object-level resource strings:
{
"Action": [ "s3:GetItem" ],
"Resource": [ "arn:aws:s3:::my-bucket/*" ]
}