July 15, 2026
Principle of least privilege: AWS Roles
The controls you have to limit access in AWS

By Grayden
3 min read
AWS Roles provide perhaps the most sophisticated set of options for limiting permissions.
AWS Roles have two major components: Permissions and Trust Relationships.
Permissions define which privileges a role has, and under which conditions. They look something like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:CreateSecret",
"secretsmanager:UpdateSecret",
"secretsmanager:DescribeSecret",
"secretsmanager:DeleteSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue"
],
"Resource": "arn:aws:secretsmanager:*:*:secret:events!connection/*",
"Condition": {
"StringEquals": {
"aws:ResourceAccount": "${aws:PrincipalAccount}"
}
}
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:CreateSecret",
"secretsmanager:UpdateSecret",
"secretsmanager:DescribeSecret",
"secretsmanager:DeleteSecret",
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue"
],
"Resource": "arn:aws:secretsmanager:*:*:secret:events!connection/*",
"Condition": {
"StringEquals": {
"aws:ResourceAccount": "${aws:PrincipalAccount}"
}
}
}
]
}Trust Relationships define who or what can assume the role and make use of its privileges.
They look something like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ecs.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ecs.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}Let's dive into some of the options each of these give you and how they can help, starting with Permissions.
Some AWS Services will generate roles for you. If you have this option, use it, these role templates must meet stringent requirements and will save you the trouble of figuring out which permissions you need.
Resources
In my opinion, the easiest quick-win in a Permission. You can define what resources (EC2 Instances, DynamoDB Tables, SQS Queues, State Machines, etc.) your role should be allowed to target.
Imagine you have a role that gives your app permission to start a State Machine execution. State Machines are powerful things; they can do anything from sending a message to an SNS topic to kicking off Batch Jobs. Limiting which State Machine your role is allowed to start reduces your risk.
Examples:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAllOnStateMachine1",
"Effect": "Allow",
"Action": [
"*"
],
"Resource": "arn:aws:states:us-east-1:123456789012:statemachine:Machine1",
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAllOnTable1",
"Effect": "Allow",
"Action": [
"*"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789013:table/Table1",
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAllOnStateMachine1",
"Effect": "Allow",
"Action": [
"*"
],
"Resource": "arn:aws:states:us-east-1:123456789012:statemachine:Machine1",
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAllOnTable1",
"Effect": "Allow",
"Action": [
"*"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789013:table/Table1",
]
}Actions
The next tool you have is limiting Actions. This can get a bit trickier, since you need to know specifically what you are trying to do and how a given service works. But they are a crucial control, take the example of S3 buckets. Some people should be able to read contents of buckets, others should be able to update and delete contents. Here's a few examples of how that would work.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadOnAllBuckets",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "*",
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadWriteOnAllBuckets",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "*",
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadOnAllBuckets",
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "*",
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadWriteOnAllBuckets",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "*",
]
}The good news is that AWS is pretty good about telling you which action that you tried to take was disallowed, so watch for errors and expand permissions as needed.
Conditions
This is a topic in itself.
Conditions are wide-ranging, and like actions and resources, the available conditions vary from service to service. Some condition keys are available across all services.
Some easily recognizable conditions include:
- aws:ResourceTag: Filters by a tag on the target resource.
- aws:SourceIP: The IP that the request in question originated from.
- aws:MultiFactorAuthPresent: Whether or not the request originated with multi-factor auth.
Conditions are written using condition operators. Here are some of the condition operators (not an exhaustive list):
- StringEquals
- StringNotEquals
- StringLike
- StringNotLike
- NumericLessThan
- NumericGreaterThan
- ArnLike
- IpAddress
- NotIpAddress
Here are some example policies using conditions.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RequireMfaAndOfficeIp",
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances"
],
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
},
"IpAddress": {
"aws:SourceIp": "198.51.100.0/24"
}
}
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowUploadToTaggedProjectsWithEncryption",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::example-bucket/*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/Department": "Engineering"
},
"StringEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
}
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RequireMfaAndOfficeIp",
"Effect": "Allow",
"Action": [
"ec2:StartInstances",
"ec2:StopInstances"
],
"Resource": "*",
"Condition": {
"Bool": {
"aws:MultiFactorAuthPresent": "true"
},
"IpAddress": {
"aws:SourceIp": "198.51.100.0/24"
}
}
}
]
}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowUploadToTaggedProjectsWithEncryption",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::example-bucket/*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/Department": "Engineering"
},
"StringEquals": {
"s3:x-amz-server-side-encryption": "AES256"
}
}
}
]
}Trust Relationships
Finally we return to trust relationships. This defines what system or person is allowed to use a role, thereby gaining access to the Permissions assigned to it. While Permissions limit what kinds of actions a Role can take, Trust Relationships limit who can take on that role.
The example below shows access limited by 3 different dimensions.
- Service: In this case Lambda is allowed to assume the role.
- Source Account: But only Lambda Functions that exist in a given account.
- Soruce ARN: This Source ARN maintains the limit at the account level, but you can replace the "*" at the end with a particular Lambda function that should be allowed to assume this role.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOrgAccountsAndLambdaService",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "444455556666"
},
"ArnLike": {
"aws:SourceArn": "arn:aws:lambda:us-east-1:444455556666:function:*"
}
}
}
]
}{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOrgAccountsAndLambdaService",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "444455556666"
},
"ArnLike": {
"aws:SourceArn": "arn:aws:lambda:us-east-1:444455556666:function:*"
}
}
}
]
}Further Reading
IAM JSON policy elements: Condition operators Describes the operators that you can use in the Condition element of the IAM JSON policy language.
Actions, resources, and condition keys for AWS services Lists all of the available actions, resources, and condition context keys that can be used in IAM policies to control…
AWS global condition context keys Describes each of the AWS global condition keys available to use in IAM policies.