Anatomy of an AWS IAM Policy [Cheat Sheet]

If we had to name the most critical service to understand when it comes to AWS security, it would have to be IAM (Identity and Access Management).

IAM controls the access and permissions to your users, services, data, and applications across all of your AWS accounts. Badly managed AWS IAM can lead to anything from a minor security breach to multi-million dollar breaches.

As an example, I’ve demonstrated how an improper IAM policy with low-level privileges can be exploited to gain admin-level privileges and hijack an AWS account.

Since it’s such an important service to understand and properly configure, how can we go about learning how to use it? While there are many concepts of IAM we need to understand, for this post, let’s focus on IAM policies and how to write them.

What better way to do that than with a cheat sheet?

By the way, this cheat sheet is straight from our Introduction to AWS Security course.

Downloadable image-based cheat sheet

Anatomy of IAM policies

Let’s break this down a bit more.

Evaluating the example IAM policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": "arn:aws:s3:::example-bucket/*"
        },
        {
            "Effect": "Allow",
            "Action": "ec2:StartInstances",
            "Resource": "arn:aws:ec2:region:account-id:instance/instance-id"
        },
        {
            "Effect": "Allow",
            "Action": "ec2:DescribeInstances",
            "Resource": "*"
        },
        {
            "Sid": "RunInstancesRestrictions1",
            "Effect": "Deny",
            "Action": [
                "ec2:CreateVolume",
                "ec2:RunInstances"
            ],
            "Resource": [
                "arn:aws:ec2:*:*:volume/*",
                "arn:aws:ec2:*:*:instance/*"
            ],
            "Condition": {
                "ForAllValues:StringNotLike": {
                    "aws:TagKeys": "Production"
                }
            }
        },
    ]
}
Code language: JSON / JSON with Comments (json)
  • Version – this refers to the version of the policy element, not the policy version itself. It defines the version of the policy language. 2012-10-17 is the latest version and should be used to access the latest features
  • Statement – this is the policy statement itself, and where you add what’s allowed or denied using Effects, Actions, and Resources
  • Effect: “Allow” – this allows the specified action(s) against the specified resource(s) 
  • Effect: “ Deny” – this is an explicit deny which denies the specified action(s) against the specified resource(s)
  • No effect (implicit deny) – By default, AWS IAM assumes actions aren’t allowed for security reasons. This means that if you don’t specify an allow statement, it will be implicitly denied. This is very different from Explicit denies (“Effect”: “Deny”) which overrules any Allow
  • Action – The action to be allowed or denied. This can a single action or an array of actions
  • Condition – Specific conditions for when a policy is in effect. This is an optional element and it can take it a number of different condition operators (in this example: ForAllValues:StringNotLike, condition keys (in this example: aws:TagKeys, and condition values (in this example, Production)
  • Resource – This is the resource(s) for which the effect and action(s) will be applied. For example, it can be ec2 resources, s3 resources, roles, users, etc…
  • While not shown in this basic example, there are also more advanced options, such as to use variables (ie: ${aws:username}), for better filtering and specificity, especially in Condition elements

Example Scenario: Cross-Account Roles

Let’s assume a scenario where we want to grant permission to a service and user in AWS account A to perform some actions in our AWS account B, such as viewing some of our CloudWatch logs for certain logs groups. We will need to create:

  • Role A in account A, which will be attached to the service and will be allowed to assume the role in account B
  • Role B in account B, which will allow Role A from account A to assume it, and the permissions it has (view CloudWatch logs) 

Account A – AssumeRole Policy

This is what allows users or services in Account A to assume the role (RoleB) in Account B, and should be attached to RoleA

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::111111111111:role/RoleB"
        }
    ]
}
Code language: JSON / JSON with Comments (json)

Account B – Trust Relationship Policy

Then in account B, we need to create a policy attached to RoleB

This is what allows the user from Account A to assume this role because we’ve explicitly stated that they are trusted. This is attached to RoleB

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::222222222222:role/RoleA"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}Code language: JSON / JSON with Comments (json)

Account B – Permissions Policies

This is the policy/policies used to dictate what users or services can do once they’ve assumed the role in Account B. In our example, view CloudWatch logs. This will be attached to RoleB.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:GetLogEvents",
                "logs:FilterLogEvents",
                "logs:DescribeLogStreams"
            ],
            "Resource": [
                "arn:aws:logs::111111111111:log-group:log-group-name-1:*",
                "arn:aws:logs::111111111111:log-group:log-group-name-2:*"
            ]
        }
    ]
}Code language: JSON / JSON with Comments (json)

Summary/Overview and Additional Info

  • Account B is our “destination”
  • Account A is our “source”
  • Meaning that we want users or resources in Account A to be able to assume the role in the Account B
  • Once a user or resource assumes the role in Account B, they temporarily renounce their permissions and assume the new permissions based on the role’s policies in Account B.
    • For example, if the user has administrative privileges in Account A but they assume the role in Account B and that role only has very narrow/specific permissions, they will not have administrative privileges in Account B and they will have to re-assume the role in Account A to regain their admin privileges in Account A
  • While assuming Role B, Role A will be able to view the desired logs in Account B

More cheat sheets

If you’d like to grab more AWS security cheat sheets, you can check them out here.

If you’d like to get started with learning AWS Security, check out our Introduction to AWS Security course.

Related Articles

Responses

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.