jimjh's Starter Guide to AWS IAM

Overview #

I wrote this guide to provide newcomers to AWS with an overview of AWS IAM. All of this stuff is probably documented, but is in so many different documents that it is nearly impossible to form a coherent mental model.

This guide is meant to help you acquire intuition. It is not meant to be exactly precise. If that is what you are looking for, read the AWS docs instead.

The Part That Trips Up Everybody #

Inside an AWS account, you can control privileges in (roughly) 2 ways:

  • You configure a role to say, “this role can do $x$ on $r$” aka identity-based policies. $r$ here is a resource.
  • You configure a resource (e.g. S3 bucket, KMS key) to say, “this resource can have $x$ done by $d$” aka resource-based policies. $d$ here is a role.

100% of the confusion and bugs in IAM come from not understanding that you can configure privileges in these two ways.

[Mental Model] Now, close your eyes an imagine this:

Fig. 1: Permissions Graph

  • Inside the space of an AWS account, lump all of the identity-based policies and resource-based policies into one set. We are going to start building a graph.
  • Let every role and every resource be vertices in your graph. These are from $r$ and $d$ above.
  • Let policies describe the edges between your vertices.
    • Some policies create edges using Allow.
    • Some policies break edges using Deny.
    • Sometimes, you need 2 policies to cooperate to fully form an edge.
  • (d)-[x]→(r) forms an edge.

… and you now have, in your mind’s eye, the Permissions Graph™️.

First, a simple ERD for Identities #

As you are roaming through the AWS docs, look for the phrase “identity-based policies”.

erDiagram Account ||..o{ Role: has-many Role ||..o{ Policy: has-many Role { string name } Policy { object[] permissions }

A Role has many Policies.

A Policy describes what the role can and cannot do.

  • Policies can be inline. Imagine that, in some back-end storage somewhere, the role is serialized as one giant JSON. Inline policies are embedded inside that JSON i.e. not shared.
  • Polices can be managed Using the metaphor, managed policies are stored elsewhere, in some other JSON, and have IDs. The role JSON refers to the policies using their IDs. i.e. these policies can be shared.

When you launch a new AWS account, it comes with several managed polices you can choose from. You can also add your own managed policies.

Another simple ERD for Resources #

erDiagram Account ||..o{ Resource: has-many Resource ||..|{ Policy: has-many Resource { string name } Policy { object[] permissions }

Delegation #

This is a poorly documented technique. Suppose I have a resource $r$ in AWS account $A$. How can I allow roles in AWS account $B$ to use it?

My preferred approach is as follows: create a resource-based policy on $A.r$ that gives access to $B.root$, then create identity-based policies in $B$ to further delegate that to more specific roles.

graph LR A.r --Allow--> B.root --Delegate--> B.SomeRole

For example, suppose we have an ECR repository in some centralized AWS account. On that repository, one could configure the resource-based policy like so:

principals {
  type = "AWS"
  identifiers = [
    # By using `root`, we allow IAM policies in these two accounts to
    # further delegate permissions to specific IAM roles.
    "arn:aws:iam::111111111111:root",
    "arn:aws:iam::222222222222:root"
  ]
}

Then, in the ECS roles in both accounts, we use

{
    "Effect": "Allow",
    "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer"
    ],
    "Resource": "*"
}

The key benefit to this approach is the decoupling of $A$’s Permission Graph™️ from $B$’s.

  • $A$ does not know anything about the identities (roles, users) in $B$. This is valuable abstraction and encapsulation.
  • It matters greatly when $A$ is controlled by one of our customers.
  • It allows $B$ to change its Permissions Graph without worrying about $A$.