AWS IAM deep dive: How the policy evaluation logic works

How IAM decides whether or not to allow a request based on policies

Author's image
Tamás Sallai
6 mins

IAM policies define who can do what in an AWS account. They are used everywhere, ranging from allowing users to change their passwords to giving read-only S3 access to a Lambda function all the way through having a security auditor with cross-account access to investigate. An AWS account is only as secure as its policies.

Access control happens when a request reaches the AWS APIs. At this point, IAM kicks in and looks into the applicable policies and decides whether it allows or denies the request. Because of this, IAM policies do not affect when users call your webserver directly. They only protect AWS API calls.

Policy structure

A policy is a JSON document with a strictly defined structure. It contains one or more statements which are the basic building blocks of access control. An example policy JSON looks like this:

{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Action": [
				"s3:GetObject"
			],
			"Effect": "Allow",
			"Resource": "<bucket>/text.txt"
		}
	]
}

Each statement can contain an Effect, an Action, a Pricipal, a Resource, and a Condition, and depending on where it’s used, a different set is required and supported.

Learn the services needed to build a serverless HTTP-based API on AWS from our free email course .

Filters

All properties of a statement except the Effect is a filter. For every request, IAM needs to collect all applicable statements and it uses the Action, Principal, Resource, and Condition elements to do it.

The Action is what the user is doing, such as downloading an object from an S3 bucket is an s3:GetObject action.

The Principal is who is doing the action. For identity-based policies, where the policy is attached to a user, this element is implied and is missing from the statement.

The Resource is the target of the operation, such as an S3 bucket, or an object inside the bucket. A resource-based policy can only define the resource it is attached to or resources under it (such as objects inside the bucket, but it can not affect other buckets).

Finally, the Condition allows other constraints on the request context. Using this element allows fine-grained control, but requires a lot of trial-and-error to get it right.

All the above elements have a Not counterpart that matches everything but. While Action: ["s3:GetObject"] matches only the s3:GetObject action, the NotAction: ["s3:GetObject"] matches all the actions, except s3:GetObject. NotPrincipal and NotResource work similarly, while Condition supports Not… versions of operators, such as StringLike has a StringNotLike counterpart.

Request context

The counterpart of the filters is the request context which IAM constructs when it needs to evaluate access. It contains all the information of the request, and the policy filters are matched against its contents.

For example, when a user tries to download an object from an S3 bucket, the request context contains the Principal, the Resource, the Action, and other metadata about the call.

Principal: user1
Action: s3:GetObject
Resource: <bucket>/text.txt

Thinking about a request in terms of a list of attributes makes it easy to reason about how the filters apply to it, especially when you use Not… elements or Conditions.

For example, the above request context is matched by this policy, attached to user1 (in which case the Principal is implied):

{
	"Action": [
		"s3:GetObject"
	],
	"Effect": "Allow",
	"Resource": "<bucket>/text.txt"
}

And by this one (NotPrincipal means everybody except):

{
	"Effect": "Deny",
	"NotPrincipal": {
		"AWS": "<iam>/user2"
	},
	"Action": "s3:GetObject",
	"Resource": "<bucket>/text.txt"
}

Evaluation logic

After collecting the effective statements using the filters, it uses the policy evaluation logic to decide if the request is allowed. The AWS documentation has a descriptive image to summarize how it works:

Policy evaluation logic

It seems complicated, but most of the chart is just about two simple principles:

  • There is a Deny => denied
  • There is no Allow => denied

All the other parts are to show which policies are enough and which ones are optional.

Policy types

There are different types of policies, depending on what they are attached to:

  • SCP: attached to the account via Organizations
  • Resource-based policy: attached to the resource that is being called, such as an S3 bucket
  • Permission boundary: attached to an identity as a boundary
  • Session policy: attached to the assume role session
  • Identity-based policy: attached to an identity, such as an IAM user

Account«S3Bucket»Bucket[]«User»User[]«IAMRole»Role[]«IAMPermissions»Session Policy[]«IAMPermissions»Permission Boundary[]«IAMPermissions»Resource Policy[]«IAMPermissions»Identity Policy[]«IAMPermissions»Service Control Policy[]assume roleassume rolereadIAM policy types

The SCP, the permissions boundary, and the session-based policy are specific to a use-case, and they are optional. If you don’t use them then they don’t affect the policy evaluation, IAM skips over them. The two most important types are the identity-based and resource-based policies. The majority of policies are of these two types.

When we take out the parts that are for narrow use-cases the evaluation logic becomes a lot simpler:

Collect all applicable policiesFilters by Principal, Resource, Action, ConditionIdentity-based policiesDeniedDeniedAllowedAllowedResource-based policyIs there a Deny?No denyAllows?Is there an Allow?Identity-, and resource-based policies evaluation

The key point is that it is enough if either the resource policy or the identity policy allows the operation. This bucket policy allows the user to read the objects even if the user has no permissions on its own:

{
	"Effect": "Allow",
	"Principal": {
		"AWS": "<iam>/user2"
	},
	"Action": [
		"s3:GetObject"
	],
	"Resource": "<bucket>/text.txt"
}

This is useful especially when there is no Principal, such as making a bucket public:

{
	"Effect": "Allow",
	"Principal": "*",
	"Action": [
		"s3:GetObject"
	],
	"Resource": "<bucket>/*"
}

Some resources, such as IAM Roles and KMS keys are more restricted. In these cases, the resource policy needs to either allow the identity or the account in the form of arn:aws:iam::<accountid>:root. If the policy does not allow either the identity or the account then the request will be denied.

Collect all applicable policiesFilters by Principal, Resource, Action, ConditionIdentity-based policiesDeniedDeniedDeniedAllowedAllowedResource-based policyIs there a Deny?No denyAllows the identity?Allows the account?Is there an Allow?Some resource-based policy must delegate to the account

Conclusion

When a request reaches the AWS APIs, IAM collects all the applicable policies. It also constructs a request context containing all the info regarding the request.

When it moves into the evaluation phase, it first uses the Action, Principal, Resource, and Condition filters to select the applicable policies. Then it follows the policy evaluation logic to decide whether the matching policies deny or allow the operation.

The two most important policy types are resource, and identity-based policies. These two make up the vast majority of all policies used, and the other types are for specific use-cases.

13 October 2020
In this article