Traditionally, perimeter security was about what tools, devices, and procedures can be placed around a network to prevent bad things from going in or out. In a cloud platform, there’s an additional layer of controls to specify the access between services, resources, machines, and people that are defined via software. In Amazon Web Services (AWS), this is handled by Identity and Access Management (IAM). While AWS IAM is a service unto itself with its own resources and resource types, it’s also the central governing model of access to all other AWS resources.
InsightCloudSec by Rapid7 has spent the better part of a year building a product that ingests AWS IAM data to present it in a useful way to our customers. During product development, we learned about the many parts of this complex system: its patterns, its intricacies, its gotchas, and its shortfalls. This guide is a collection of data points that we’ve found as compelling evidence that AWS IAM is the most complex service that AWS offers. Unfortunately, AWS does not provide a developer-friendly way to ingest all of the IAM information across their service offerings, and the documentation that is available is somewhat inconsistent, so a big thanks goes out to the folks behind the Policy Sentry tool, which made collecting the figures for this guide much simpler.
Every AWS API request is evaluated by IAM in a 21-step process. This process starts with an assumption that the request for access should be denied, and then flows through 5 different types of policy evaluations—Service Control Policies (SCPs), Resource Policies, Permission Boundary Policies, Session Policies, and Identity Policies—to determine whether the request meets the required criteria to allow the desired behavior.
Since debuting in 2006 with 3 services, AWS has added an additional 245 services. That’s an average of 17 new services every year. But the number of AWS services don’t tell the whole story of the complexity in IAM. For each service, there can be multiple resource types—take EC2, for example. There are instances, VPCs, load balancers, EBS Volumes, and many more. Across all 248 services, there are 819 resource types. Eight percent of the services hold almost 40% of the resource types, with EC2 (52 resources) and SageMaker (27 resources) at the top of the list.
Of course, you can’t do much with a resource without an allowable action. When used in a policy, an action allows you to define access to an AWS API. Some sample actions for the S3 service are:
s3:ListBucket
s3:GetBucketLocation
s3:PutObject
The breakdown of those known actions to categories is as follows:
Category | Count |
---|---|
Write | 3,952 |
Read | 1,986 |
Listing | 1,411 |
Tagging | 311 |
Permission Management | 299 |
Across all AWS services and resources, there are almost 8,000 actions. The large number of actions available is also a good measure of the flexible and granular control that AWS gives the user. Typically, you provide an Amazon Resource Name (ARN) for actions in statements, but not all actions let you define an ARN. There are many that only support a wildcard (*) character. In fact, there are 2,985 actions in the documentation that are not associated with a resource, meaning that you cannot restrict them based on an ARN. While a large portion of those actions that do not support ARNs are Describe/List related, many are not. Some have resource IDs as required inputs in the API calls that correspond to the actions, which indicates they could possibly support ARN restrictions in the future.
There are five types of policies (SCPs, Resource Policies, Permission Boundaries, Session Policies, and Identity Policies) that can be associated with organizations, principals, and resources. Policies are a collection of statements that define the actions that can be performed on specified resources, and are evaluated when a principal makes a request to a resource. Statements have multiple elements, including Effect, Principal, Action, Resource, and Condition. There are some elements that are mutually exclusive as well, meaning you cannot use both in the same statement: NotPrincipal(Principal)
, NotAction(Action)
, and NotResource(Resource)
.
In a resource policy, there are seven different types of principals that can be defined. Of those principal types, three can have identity policies attached.
Policies can have one or more statements. The actions and effects are scoped to individual statements. This is useful when you have specific, conditionally-based scenarios for certain actions, but general intent for others. In the policy evaluation logic, it is ultimately the entire combinatorial effect of all the condition, resource, and principal elements on a specific action that determine your outcome.
Policies are not as simple as saying that a principal has s3:Listbucket
access to a bucket named “foobar,” because that access can be caveated with a conditional. Conditionals in IAM policies can be extremely useful to security administrators, but can introduce a level of dynamicity, which makes it time-consuming for humans to evaluate. Across all services and resource types, there are 449 unique Conditional Context Keys. These are the keys that are used to validate the conditional statement (PrincipalName
, for example). Of those 449 keys, 31 of them are Global Conditional Context Keys, meaning that they can be used with any resource type, leaving 421 Conditional Context Keys that are service-specific across 60 services.
Almost 80% of the 421 service-specific Conditional Context Keys are used by just 20% of the services that support them. EC2 leads the way with 66 keys, followed by S3 with 38. This is to be expected, as EC2 and S3 are two of the oldest and most-used AWS services, and the number of keys has grown with those services over time.
So far, we’ve covered IAM policies that have the potential for variables, lists, dictionaries, and operators. There are also two control mechanisms for looping through values to support a Conditional Statement in a policy: ForAnyValue
, ForAllValues
. Both of these can support one or more Conditional Context Keys with a list of values to iterate. Here’s a fun example of ForAnyValue
:
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
},
"ForAnyValue:StringNotLike": {
"aws:PrincipalArn": [
"arn:aws:iam::*:role/S3_ReadOnly"
],
"aws:PrincipalAccount": [
"123456789012",
"123456789123",
],
"aws:PrincipalOrgPaths": [
"o-abc/r-def/ou-ghijklmnop/*"
]
}
}
This condition will be evaluated as true if:
us-east-1
AND
arn:aws:iam::*:role/S3_ReadOnly
OR123456789012
or 123456789123
ORo-abc/r-def/ou-ghijklmnop/*
An ARN is a naming convention to uniquely identify an AWS resource. When writing a policy, you must use the ARN to specify the resource that is associated with the statement.
At a glance, the components of an ARN seem relatively simple:arn:partition:service:region:account-id:resource-id
But there are actually two more ARN types that don’t meet that pattern:arn:partition:service:region:account-id:resource-type/resource-id
arn:partition:service:region:account-id:resource-type:resource-id
But how does that account for ARNs like this?arn:partition:sagemaker:region:account-id:app/${DomainId}/${UserProfileName}/${AppType}/${AppName}
It turns out, there’s a lot of variation that comes in after the account-id string in the ARN. To make that more clear, let’s split an ARN up in two parts. Everything up to the account-id string (base ARN) and everything afterwards (Resource Path).
Let’s take this ARN for example:arn:partition:service:region:account-id:resource-type/resource-id
That breaks down into the following parts.:
Base ARN | Resource Path |
---|---|
arn:partition:service:region:account-id |
resource-type/resource-id |
If we use this method to evaluate all Base ARN types across all AWS Resource types, we find that 664 resource types (82%) use:arn:partition:service:region:account-id
There are slight variants of that ARN pattern:
There are outliers like Organizations service policies that have no account ID:arn:${Partition}:organizations::aws:policy/${PolicyType}/p-${PolicyId}
So let’s look at the other side of the ARN, the Resource Path. With resource paths, there is more variance in how they are defined.
The most common Resource Path patterns across resource types are:
FIXED/VARIABLE |
460 |
FIXED:VARIABLE |
71 |
FIXED/VARIABLE/FIXED/VARIABLE |
45 |
FIXED/VARIABLE/VARIABLE |
39 |
These account for approximately 83% of all Resource Path patterns.
Twenty-one Resource types have Resource Path patterns that are unique to that resource (not used anywhere else).
Greengrass is a service that stands out by constructing ARNs differently than all others. Specifically, it stands out because the service name “greengrass” is both parts of the ARN. Here is an example of one such ARN:arn:${Partition}:greengrass:${Region}:${Account}:/greengrass/bulk/deployments/${BulkDeploymentId}
The execute-api-general resource from the API Gateway service has a lot of variance in its ARN. In fact, the entire Resource Path from the ARN is entirely variable:arn:${Partition}:execute-api:${Region}:${Account}:${ApiId}/${Stage}/${Method}/${ApiSpecificResourcePath}
While these may not be complex on their own, it is important to point out that construction of ARNs can vary from service to service, so it’s important to know how each service uses ARNs.
Hopefully this guide has shed some light on the breadth, depth, flexibility, and overall complexity of AWS IAM. AWS IAM provides many different ways to define who and what can access your cloud resources. Request a demo of InsightCloudSec by Rapid7’s Cloud IAM Governance module or read our white paper, Gaining Control Over Cloud IAM Chaos, to better understand the access between your cloud principals and resources.
James Martin has 9 years of experience working with Amazon Web Services as both a practitioner and leader in organizations of all shapes and sizes. Currently, he is a Senior Engineering Manager for Rapid7’s Cloud Security Practice.