AWS IAM and other permission gotchas
Tags: aws
Published: 2022-07-17 06:09:26

Part of my day to day is helping dev teams with their journey to AWS. There are several aspects to this including architecting, implementing, and other advisory tasks. However one common theme that every team will ask is "Why can I not perform X".

The reason? As anyone who works with AWS IAM knows, it could be one of several. The role may not have permissions, the resource may not allow it, it could even be service roles a user is unaware of. Issues can arise from missing permissions or misconfigured permissions. The latter is especially true when trying to properly configure IAM permissions to be least-permissive.

We're going to take a look at some of the simpler issues that are frequently encountered, and how to fix them.

## Scoping the unscopeable
### Policy has permissions but I still get an error... Part 1
A common pattern I've seen teams use when creating their roles is starting open, and then scoping these down. This can lead to scoping actions that **cannot** be scoped. That's correct, some actions in AWS cannot be scoped. The below example is pretty common when trying to create a least permissive policy.

Take the following policy:
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeImages"
            ],
            "Resource": "arn:aws:ec2:*:000000000000:*"
        }
    ]
}
```
Pretty simple right? We're giving a policy the ability to perform the ec2:DescribeImages action in our account, right? Although that's how this looks, most ec2 Describe actions cannot be restricted.
Given the above role, this would a result:
```
$ aws ec2 describe-images --owners self

An error occurred (UnauthorizedOperation) when calling the DescribeImages operation: You are not authorized to perform this operation.
```

How should this policy actually look?
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeImages"
            ],
            "Resource": "*"
        }
    ]
}
```

Now how do we find out what permissions can and cannot be scoped, how are they scoped, and even what conditions can we apply to them?

[This link](https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html) is something I keep in my bookmarks and headers of relevant AWS chat channels. You can navigate to that page, look up your AWS service, and find every action available to that service as well as how to scope them.
Example (NOTE: an empty `Resource types` column means it must be a full wildcard resource):
![example entry from the ec2 service actions table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5zd4pkx9ic97106zkm9m.png)

## Conditions causing issues
### Policy has permissions but I still get an error... Part 2
Another case we can encounter, particularly with KMS, is when permissions are conditioned.

Say you have a key, and you want to apply an alias to it, but only want to allow specific aliases. You can add a permission with `CreateAlias` on `arn:aws:kms:*:000000000000:key/*`, but you scope it down a bit more makes sense. So let's add that scoping for `my-key-alias`.

To do this, I've seen teams add the following:
```
{
    "Effect": "Allow",
    "Action": "kms:CreateAlias",
    "Resource": "arn:aws:kms:*:000000000000:key/*",
    "Condition": {
        "StringLike": {
            "kms:ResourceAliases": "alias/my-key-alias"
        }
    }
}
```

Cool! Now lets create the alias:
```
$ aws kms create-alias --alias-name alias/my-key-alias --target-key-id 738d8f2e-0fc5-4d2c-b00f-a8888777755e

An error occurred (AccessDeniedException) when calling the CreateAlias operation: User: arn:aws:iam::000000000000:role/your-role-name is not authorized to perform: kms:CreateAlias on resource: arn:aws:kms:us-west-2:000000000000:alias/my-key-alias because no identity-based policy allows the kms:CreateAlias action
```

It failed, but why? We have access to the `CreateAlias` on any key in the account, with the condition that the alias is `alias/my-key-alias`. And that's why, the key needs to **have the alias** `alias/my-key-alias`. It doesn't have this alias yet, we're still creating it!
What we need to do is allow `CreateAlias` for both the key, and the alias we want. We do that with the following:
```
{
    "Effect": "Allow",
    "Action": "kms:CreateAlias",
    "Resource": "arn:aws:kms:*:000000000000:key/*",
    "Condition": {
        "StringEquals": {
            "aws:ResourceTag/KeyPurpose": "Demo"
        }
    }
},
{
    "Effect": "Allow",
    "Action": "kms:CreateAlias",
    "Resource": "arn:aws:kms:*:000000000000:alias/my-key-alias"
}
```
The above allows the `CreateAlias` action to be performed on any KMS key. Additionally it has a condition on a resource tag so we're only allowing the action to be performed on keys with the specific tags.

I have seen the above issue frequently when teams work with wide-open roles in a dev account, scope them in a dev account, and then promote to a test account without performing a full teardown/redeploy of their resources to really test the roles permissions.

It is important to remember when using conditions, especially conditions based on resource attributes, those attributes you're checking in the condition need to exist **before** the action is taking place.

## Cross Account
### Policy has permissions but I still get an error... Part 3
Working with cross account access adds a new dynamic to permissions in AWS.

When working in the same account, in most cases, you only need 1 direction of permissions. This is because by default AWS services in account X assume that roles in account X are allowed to access them, so you only need to give the role permissions. In a cross account setup, you need permissions on your role to access a resource, but for most services they also need a resource policy to allow the role to access them.

Let's use lambda as an example. If I wanted to invoke MyFunction in the same account as my AWS role, I would need the following:
```
{
    "Effect": "Allow",
    "Action": "lambda:InvokeFunction",
    "Resource": "arn:aws:lambda:*:000000000000:function:MyFunction"
}
```
This allows you to invoke a function in your own account, without any permissions added to the lambda. However, lets say you have a role in account `000000000000` and a lambda in `000000000001` that you want to allow the role to invoke.

The policy for the role, again fairly simple.
```
{
    "Effect": "Allow",
    "Action": "lambda:InvokeFunction",
    "Resource": "arn:aws:lambda:*:000000000001:function:MyFunction"
}
```
However if you try to invoke the function, it fails! This is because you need to allow the role to invoke the resource **from the resource** as well when performing cross-account access.
There are lots of ways to add this permission. For terraform it's the `aws_lambda_permission` resource. For cloudformation, it's `AWS::Lambda::Permission`. We'll look at how to do it in the console.
Go to your lambda, under the configuration tab, look at permissions. From here you'll add a permission in the `Resource-based policy` section for your role.

![Adding cross account lambda permission](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gg7r0tzqt0du379l61vz.png)

Adding that permission will then let you invoke the lambda!

## Summary
AWS IAM is a very large service, it is how you control who or what can access resources in the amazon cloud. Some of the potential issues we went over are:

- Scoping actions that are not scopeable
- Adding conditions before the conditions are met
- Cross-account permissions and needing both identity and resource policies

If you look at the solutions, nothing in IAM is **complex** if it's thought through. It's just a matter of determining what the action is, what role/policy is being used, and what (if any) permissions are on the resource being accessed.

Obviously this raises the question of "How do I determine all that?". There are lots of places to look. AWS Cloudtrail, networking tab in your browsers developer tools, **most** requests will come back with a useful error, and even some third party tools.

Any other tips people have are welcome, I'm sure there are other interesting scenarios people have encountered!