Achieving Least Privilege with AWS IAM
Published: 2021-12-06 01:19:02

AWS IAM (Identity and Access Management) is a powerful tool to help ensure your teams and applications are following the principle of least privilege. However, it can still be very complex to learn and understand. Certain resources also provide their own access control mechanisms as well, which can complicate things even further.

I've collected a few tips and tricks over the past few years that should help anyone looking to advance their understanding of the service. While there's no shortcut for hands on experience, hopefully these items will help you avoid some frustration.

These will focus on the authorization side of AWS IAM. This is managed via IAM policy resources. Authentication is handled via IAM users or IAM roles.

## Client Side Monitoring and Cloudtrail
A common scenario where a developer may run into a denied error message is when they are starting to automate the deployment of resources via an infrastructure-as-code tool like Terraform. Such tools leverage the AWS SDKs to interact with the AWS APIs to create and manage the various resources. 

The SDKs support a sub feature called client side monitoring, that allows the IAM acitons being taken to be captured. This [blog post](https://summitroute.com/blog/2020/05/25/client_side_monitoring/) provides a great overview of the functionality. The blog post also inspired me to write Action Hero last year which cleanly displays the actions being taken. More details on Action Hero can be found in one of my previous [posts](https://dev.to/prince_of_pasta/action-hero-to-the-rescue-creating-least-privilege-aws-iam-policies-53o2). There are other similar tools that exist like [iamlive](https://github.com/iann0036/iamlive) that play in the same problem space.

Overall using the CSM feature can be useful when working as a more privileged actor designing the lower privilege policy. However, one major limitation of the feature is that it does not provide resource information. For example, it may know you called `s3:GetObject` but will not provide which S3 bucket/object.

To overcome those limitations, you can leverage the information Cloudtrail provides. If you have direct access to those logs, you will be able to see the relevant resources. AWS also provides a great feature of IAM Access Analyzer that is able to do some of the heavy lifting in that process for you. That integration is discussed [here](https://aws.amazon.com/blogs/security/iam-access-analyzer-makes-it-easier-to-implement-least-privilege-permissions-by-generating-iam-policies-based-on-access-activity/).

## Understanding which actions support resources restrictions

With the information from the above tooling, the next useful bit of information is to understand if the action can be limited to specific actions or not. To extend the example from above, you might not want to grant ``s3:GetObject`` to any object across any AWS account. Similarly, you may want to limit it to objects in a single bucket or in a specific path of a single bucket.

Lucky for us, ``s3:GetObject`` supports this filtering. However, there are other actions like ``kms:CreateKey`` that do not support restrictions. For example, the below policy would **not** be valid and return an error.

```
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "InvalidResourceFilter",
      "Effect": "Allow",
      "Action": [
        "kms:CreateKey"
      ],
      "Resource": "arn:aws:kms:us-east-1:111111111111:key/*"
    }
  ]
}

```

This [documentation](https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html) provides a reference table for each AWS service. Below you can see that the row for ``kms:CreateKey`` does not note a resource in the `resource types` column.


![AWS Documentation showing Create Key row not containing a resource restriction](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ngbufaf93ed84emr3zo.png)

The absence of that resource lets you know that the action will not support anything other than ``"*"`` in the resource field of an IAM policy stanza. If the column does contain a resource type, the action can be filtered down.

While this information is definitely helpful there is currently no api to access this information. To make exploring this information easier, I would suggest the `policy_sentry` tool from salesforce. The documentation for the project can be found [here](https://policy-sentry.readthedocs.io/en/latest/). 

The documentation outlines the ability to find which commands for a service support resource filtering in the [querying the iam database](https://policy-sentry.readthedocs.io/en/latest/querying/action-table/) section.
After installation we could run the following command to see our ``kms:CreateKey`` action again.

```
policy_sentry query action-table --service kms --resource-type '*'

```
We can also see the additional actions in KMS that would require similar treatment.

![Screenshot of output of query command for KMS actions that do not support restrictions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lwmjl3b8ft5v1ksstqpb.png)

## Policy Management

As you use AWS more and more, you will quickly find that the number of policies you need to manage will grow rapidly. To help with this you can borrow the DRY (Don't repeat yourself) concept from programming to help. 

If multiple AWS identities need the same exact access to certain resources (ex a url in the parameter store service), you can leverage a managed policy instead of an inline policy. AWS provides some great [documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_managed-vs-inline.html) on the difference between the two. One of the major items is managed policies get their own ARN and therefore can be reused by multiple identities. AWS also provides a set of managed policies with every account (ex ReadOnlyAccess, LambdaBasicExecution). 

Both AWS and customer managed policies can remove repetition but could also provide excessive access if used recklessly. Balancing how much should be shared amongst versus purpose-built policies for performing a particular task. A hybrid of managed and inline policies can help achieve that goal

## Leveraging Conditions

While the base effect, action, and resource fields of an IAM policy stanza can handle a decent number of scenarios, the condition field helps provide even more flexibility.

Two of my favorite examples for leveraging conditions are for KMS and S3. AWS outlines the KMS usage [here](https://docs.aws.amazon.com/kms/latest/developerguide/abac.html). In short, you can use conditions to grant the ability to decrypt using keys with a certain tag key/value pair, or a particular alias. 

The policy below is from the above link and demonstrates granting access to use keys with one of the three aliases.
```
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AliasBasedIAMPolicy",
      "Effect": "Allow",
      "Action": [
        "kms:List*",
        "kms:Describe*",
        "kms:Decrypt"
      ],
      "Resource": "arn:aws:kms:*:111122223333:key/*",
      "Condition": {
        "ForAnyValue:StringEquals": {
          "kms:ResourceAliases": [
            "alias/ProjectAlpha",
            "alias/ProjectAlpha_Test",
            "alias/ProjectAlpha_Dev"
          ]
        }
      }
    }
  ]
}
``` 

Approaches such as this can help when there are multiple teams / multitenancy in the same account or when the same alias is reused across nonproduction and production accounts. Using human friendly aliases can be much easier than dealing with key ids (which are also not known before creation time).

With S3, I am a big fan of the `s3:ResourceAccount` condition, which helps limit access to a particular account. S3 bucket/object arns do not have account numbers in them which can lead to situations where more access is granted than intended. For example, if I use a resource filter of `companyname-*`, an attacker could create a bucket in their own account that matches that prefix. If the policy grants `s3:PutObject` to that prefix, there could be the potential for data exfiltration. By adding the condition, that situation can be avoid. 

This [blog post](https://aws.amazon.com/blogs/storage/limit-access-to-amazon-s3-buckets-owned-by-specific-aws-accounts/) discusses the feature and provides an example of limiting an instance to be able to write to a shared services account but not the development account. Even if the instance was somehow compromised, the attacker would have no way to have it write information to the development bucket without modifying the IAM policy.

Reference information is also available about what conditions can be used for each service in the [same documentation](https://docs.aws.amazon.com/service-authorization/latest/reference/reference_policies_actions-resources-contextkeys.html) mentioned above around resource restriction. 

![Table of supported KMS conditions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0l5b6bzmp19ifisdvhch.png)

Additionally, `policy_sentry` also supports querying that same information programmatically. 

```
policy_sentry query condition-table --service kms
```

The actions tables on those pages will note which conditions are supported by which actions in the `Condition keys` column. You can also see which actions support a particular condition with `policy_sentry` like so

```
policy_sentry query action-table --service kms --condition kms:CallerAccount
```

## Conclusion

The AWS IAM service is a powerful tool to secure your cloud resources, but a solid understanding of it is key to do so effectively. I hope this post has helped highlight some of the major capabilities in the space that can help make that journey easier.