AWS IAM Roles vs Users and Groups: Nuances for Granular Access Control in Complex Cloud Environments
AWS IAM roles provide temporary, short-lived credentials for service-to-service and cross-account access, while users and groups manage permanent identities for humans, with roles enabling finer granularity through trust policies and session-based permissions.
In multi-account AWS environments, mastering the distinctions between IAM users, groups, and roles is essential for implementing least-privilege security. According to the bregman-arie/devops-exercises repository, each identity type serves a distinct purpose in complex cloud architectures, offering different levels of granularity and credential lifecycle management that directly impact your security posture.
IAM Users: Permanent Identities with Long-Lived Credentials
IAM users are permanent identities representing individuals or services that sign into the AWS console or access APIs using long-lived credentials—specifically an access key and secret key. As documented in topics/aws/exercises/create_user/exercise.md, users are appropriate for human operators and CI/CD service accounts that require dedicated identities.
Permissions attach directly to users or via group membership, and a single user can belong to multiple groups, inheriting the union of all attached policies. However, because these credentials are static, they require regular rotation and must never be embedded in code or container images. The root user represents a special case of an IAM user that should never be used for routine operational tasks.
IAM Groups: Logical Containers for Permission Aggregation
IAM groups act strictly as logical containers for users and possess no credentials of their own. According to the IAM best-practice checklist in topics/aws/README.md, groups bundle users sharing common job functions—such as Developers or Read-Only—allowing administrators to attach a single policy to the group rather than maintaining redundant per-user policies.
A user can be a member of multiple groups, creating a matrix of capabilities where effective permissions equal the union of all group policies. This aggregation model eliminates permission duplication while maintaining clear separation of duties across teams.
IAM Roles: Temporary Credentials for Services and Cross-Account Access
IAM roles are identities without long-lived credentials. Trusted entities—including users, AWS services, or external federated identities—assume these roles temporarily to receive short-lived session tokens. The topics/aws/exercises/create_role/exercise.md file demonstrates how this mechanism supports three critical patterns:
- Service-to-service access: EC2 instance profiles, Lambda execution roles, and ECS task roles allow workloads to obtain permissions automatically without embedded secrets
- Cross-account access: A role in Account A trusts a principal in Account B, enabling selective resource sharing without creating duplicate user identities across organizational boundaries
- Federated access: SAML/OIDC identities from corporate Active Directory or Google assume roles after authentication, bridging external identity providers with AWS permissions
Trust Policies vs Permission Policies
A role’s architecture provides granular control through two distinct policy types:
- Permission policy: Defines what actions the role can perform on which resources
- Trust policy (
AssumeRolePolicyDocument): Defines who can assume the role, creating a clean separation between authorization and authentication
This separation allows you to grant a single role to many principals while keeping the permission set immutable. Revoking the trust policy instantly blocks all downstream access, and sessions automatically expire—defaulting to one hour with a maximum configurable duration of 12 hours.
Implementation Patterns for Granular Access Control
Human operators requiring read-only access across projects: Implement User → Group mappings (e.g., ReadOnlyGroup) where the group aggregates read-only policies and users inherit them through membership.
Lambda functions accessing specific resources: Configure a Lambda execution role with an inline policy strictly limited to required actions—such as writing to a specific DynamoDB table and reading from a designated S3 bucket—eliminating permanent credentials entirely.
Third-party vendor auditing without credential sharing: Deploy a cross-account role in your account that the vendor’s account can assume, scoped exclusively to read-only monitoring APIs via the trust policy and permission boundaries.
Temporary CI/CD build instances: Attach an instance profile role to EC2 instances with a short session duration (e.g., 2 hours), ensuring temporary credentials automatically expire without requiring manual key rotation in the runner configuration.
Code Examples for Implementation
The following JSON policy grants a role permission to read a specific S3 bucket and write to a particular DynamoDB table, demonstrating least-privilege granularity:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:aws:s3:::my-data-bucket",
"arn:aws:s3:::my-data-bucket/*"
]
},
{
"Effect": "Allow",
"Action": ["dynamodb:PutItem", "dynamodb:UpdateItem"],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/ProcessingResults"
}
]
}
The trust policy allowing EC2 instances to assume this role requires specifying the service principal:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
For infrastructure-as-code implementations, this Terraform snippet creates the role and attaches it to an EC2 instance:
resource "aws_iam_role" "ec2_s3_dynamo_role" {
name = "ec2-s3-dynamo-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
resource "aws_iam_role_policy" "policy" {
name = "s3-dynamo-access"
role = aws_iam_role.ec2_s3_dynamo_role.id
policy = file("policy.json")
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
iam_instance_profile = aws_iam_instance_profile.ec2_profile.name
}
To manage group membership via the AWS CLI, as shown in topics/aws/exercises/create_user/exercise.md:
# Create a group with read-only S3 access
aws iam create-group --group-name S3ReadOnlyGroup
# Attach the managed policy
aws iam attach-group-policy \
--group-name S3ReadOnlyGroup \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Add an existing user to the group
aws iam add-user-to-group \
--group-name S3ReadOnlyGroup \
--user-name alice
Summary
- Users and groups are optimal for human identities and grouping similar permission sets, with groups enabling matrix-based access control through multiple membership
- Roles provide the foundation for temporary, service-to-service, and cross-account access, eliminating long-lived credentials and supporting just-in-time privilege elevation
- Trust policies separate who can assume a role from what the role can do, enabling precise control over authentication sources
- Least-privilege implementation requires starting with the most restrictive policy and expanding only as necessary, using IAM Access Advisor and Credential Reports from the repository's exercises to audit permissions over time
Frequently Asked Questions
When should I use an IAM role instead of a user for programmatic access?
Use IAM roles whenever an AWS service (like EC2, Lambda, or ECS) needs to access other AWS resources, or when implementing cross-account access patterns. Roles eliminate the need to distribute, rotate, and protect long-lived access keys, significantly reducing the risk of credential leakage in code repositories or container images.
Can an IAM user belong to multiple groups simultaneously?
Yes, IAM users can be members of multiple groups, and their effective permissions equal the union of all policies attached to those groups. This matrix model allows flexible permission compositions—such as combining a "Developers" group with a "SecurityAudit" group—without creating redundant individual policies for each user.
How do trust policies enable secure cross-account access without sharing credentials?
The trust policy (AssumeRolePolicyDocument) specifies which external account principals or services can assume a role in your account. This allows Account B to access resources in Account A by assuming a role temporarily, receiving short-lived session tokens instead of requiring you to create and share static user credentials with the external party.
What is the maximum duration for temporary credentials issued by IAM roles?
By default, temporary credentials issued by sts:AssumeRole expire after 1 hour, but you can configure the maximum session duration up to 12 hours when creating or updating the role. For EC2 instance profiles specifically, the credentials automatically rotate and remain valid indefinitely while the instance runs, though each individual session token respects the configured duration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →