AWS Builder Center
AWS IAM: A Practical Guide to Securing Access with Least Privilege

AWS IAM: A Practical Guide to Securing Access with Least Privilege

Learn how AWS Identity and Access Management (IAM) helps you securely manage identities, permissions, and access to AWS resources. This practical guide explains users, groups, roles, policies, least privilege, MFA, conditions, and Access Analyzer, with real-world examples and best practices for building secure AWS environments.

Cloud/DevOps
When building applications on AWS, we often focus on compute, storage, databases, networking, and deployment. But there is another component that determines who can actually access those resources: AWS Identity and Access Management (IAM).
IAM is one of the most important AWS services to understand because a poorly designed permission model can turn a small security mistake into a major cloud security incident.
In this guide, we will explore AWS IAM from a practical perspective, including users, groups, roles, policies, permissions, least privilege, MFA, and IAM Access Analyzer.



What is AWS IAM?

AWS Identity and Access Management allows you to control who can access AWS resources, what they can do, and under what conditions.
In simple terms:
IAM answers the question: "Who can do what, to which AWS resource, and under which conditions?"
For example, you might want:
  • A developer to read objects from an S3 bucket.
  • A Lambda function to write records to DynamoDB.
  • A DevOps engineer to manage EC2 instances.
  • A CI/CD pipeline to deploy applications.
  • An administrator to manage multiple AWS services.
Instead of giving everyone full access, IAM allows you to define specific permissions for each identity or workload.
AWS describes IAM as a service for controlling access to AWS services and resources, including the conditions under which access is permitted.



Understanding the Main IAM Components

Before creating policies, it is important to understand the main building blocks of IAM.

1. IAM Users

An IAM user represents an individual identity within an AWS account.
For example:
text
Alice
Bob
Charlie

Historically, organizations often created one IAM user for every employee.
However, AWS now recommends that human users use federation and temporary credentials where possible rather than relying on long-lived IAM user credentials.
For centralized workforce access, AWS IAM Identity Center is generally a better approach.



2. IAM Groups

An IAM group allows you to organize users and apply permissions to multiple users.
For example:
text
Developers
├── Alice
├── Bob
└── Charlie

Operations
├── David
└── Sarah

You can attach a policy to the  Developers  group instead of attaching the same policy individually to every developer.
This makes permission management easier as an organization grows.



3. IAM Roles

IAM roles are extremely important for modern AWS architectures.
A role provides permissions that can be assumed by an AWS service, user, application, or another trusted identity.
For example:
text
Lambda Function
|
| assumes
v
IAM Role
|
v
DynamoDB

The Lambda function does not need you to place an AWS access key directly inside your application code.
Instead, the Lambda function assumes an IAM role and receives temporary credentials.
AWS recommends using IAM roles and temporary credentials for workloads accessing AWS resources.
This is one of the most important concepts for anyone learning AWS, especially if you are interested in DevOps or cloud engineering.



4. IAM Policies

Policies define permissions.
An IAM policy is normally written as JSON.
Here is a simple example:
JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::my-example-bucket/*"
}
]
}

This policy allows the identity to read objects from a specific S3 bucket.
The important parts are:
text
Effect
Action
Resource


Effect

Defines whether access is allowed or denied.
JSON
"Effect": "Allow"

or:
JSON
"Effect": "Deny"


Action

Defines what operation is allowed.
For example:
JSON
"Action": "s3:GetObject"

Other examples include:
text
ec2:StartInstances
ec2:StopInstances
dynamodb:GetItem
dynamodb:PutItem
s3:ListBucket


Resource

Defines which resource the action applies to.
For example:
JSON
"Resource": "arn:aws:s3:::my-example-bucket/*"

AWS provides a detailed reference for IAM policy elements, including  Effect ,  Action ,  Resource ,  Principal , and  Condition .



The Principle of Least Privilege

One of the most important IAM concepts is least privilege.
The idea is simple:
Give an identity only the permissions it actually needs to perform its job.
Suppose a Lambda function only needs to read records from DynamoDB.
You could give it:
text
AdministratorAccess

But that would be excessive.
A better policy would allow only:
text
dynamodb:GetItem
dynamodb:Query

and only against the required DynamoDB table.
This reduces the potential blast radius if the workload is compromised.
AWS recommends granting only the permissions required for a task and progressively reducing broader permissions toward least privilege.
OWASP also recommends enforcing least privilege as a core authorization security principle.
External security reference:  OWASP Authorization Cheat Sheet 



Avoid This IAM Policy

One of the most dangerous patterns for everyday workloads is:
JSON
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}

This essentially grants unrestricted access to everything covered by that policy.
There are legitimate situations where administrative access is required, but it should not be the default permission model for applications and workloads.
AWS Security Hub includes a control that identifies customer-managed IAM policies granting  Action: "*"  over  Resource: "*" , because this can violate least-privilege principles.
Instead of:
JSON
"Action": "*"

try to identify the specific operations your application requires.
For example:
JSON
"Action": [
"s3:GetObject",
"s3:PutObject"
]

And instead of:
JSON
"Resource": "*"

scope the policy to the required resource whenever the AWS service supports it.



IAM Policy Conditions

IAM policies can become much more powerful when you use conditions.
For example:
JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-example-bucket/*",
"Condition": {
"Bool": {
"aws:SecureTransport": "true"
}
}
}
]
}

The  Condition  element allows additional restrictions to be applied to a permission.
Conditions can be based on things such as:
  • MFA
  • IP address
  • tags
  • encryption
  • secure transport
  • request context
  • time
  • identity attributes
AWS documents the  Condition  element and its operators in the IAM policy reference.



How AWS Decides Whether to Allow a Request

A useful way to understand IAM is to imagine a request like this:
text
Developer
|
| "Read this S3 object"
v
AWS
|
| Authentication
v
Policy Evaluation
|
+---- Identity-based policies
|
+---- Resource-based policies
|
+---- Permissions boundaries
|
+---- SCPs / RCPs
|
v
Allow or Deny

AWS starts with requests being implicitly denied in the general policy evaluation model. An explicit allow is required, while an explicit deny overrides an allow.
This explains an important IAM rule:
text
Explicit Deny
↓
Overrides
↓
Explicit Allow

Understanding policy evaluation becomes especially important when working with multiple AWS accounts, resource policies, permissions boundaries, and AWS Organizations.



IAM for a Serverless Application

Let's connect IAM to a practical architecture.
Imagine we have a serverless task-management application:
text
Users
|
v
CloudFront
|
v
S3
Frontend
|
v
API Gateway
|
v
Lambda
|
IAM Role
|
v
DynamoDB

The Lambda function should not have access to every AWS service.
Instead, we create a dedicated IAM role:
text
TaskLambdaRole

and give it only the permissions it needs.
For example:
JSON
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/Tasks"
}
]
}

Now the Lambda function can work with the  Tasks  table without automatically receiving administrative access to the rest of the AWS account.
This is a much better security model than embedding access keys in application code.



Why You Should Not Put AWS Access Keys in Your Code

A common beginner mistake looks like this:
Python
AWS_ACCESS_KEY_ID = "AKIA..."
AWS_SECRET_ACCESS_KEY = "..."

inside application code.
This is dangerous.
If the source code is pushed to GitHub, included in a Docker image, copied into a public artifact, or exposed through a compromised system, the credentials may also be exposed.
For AWS workloads, prefer IAM roles and temporary credentials whenever possible. AWS explicitly recommends temporary credentials for workloads.
For secrets that genuinely need to be stored, consider services such as:
  • AWS Secrets Manager
  • AWS Systems Manager Parameter Store
Learn more:  AWS Secrets Manager 



Protect the AWS Root User

The AWS account root user is extremely powerful.
It should not be used for normal daily administration.
AWS recommends protecting the root user credentials and using other identities for everyday operations. AWS also recommends MFA for the root user and advises against creating root-user access keys.
A good mental model is:
text
Root User
|
+-- Emergency / account-level tasks
|
X-- Not everyday development
X-- Not application workloads
X-- Not CI/CD

For normal administration, use appropriate IAM roles or IAM Identity Center.



Enable MFA

Multi-factor authentication adds another security layer.
Instead of relying only on:
text
Username + Password

MFA requires another authentication factor.
AWS recommends MFA and specifically recommends phishing-resistant options such as passkeys and security keys where possible.
For organizations, centralized identity management with IAM Identity Center can also provide a more scalable approach to workforce access.



IAM Access Analyzer

One of my favorite IAM tools is IAM Access Analyzer.
IAM Access Analyzer can help identify unintended external access, validate policies, and generate least-privilege policies based on access activity.
For example:
text
Existing IAM Role
|
v
CloudTrail Access Activity
|
v
IAM Access Analyzer
|
v
Required Permissions
|
v
More Restrictive Policy

AWS states that Access Analyzer can generate policies based on access activity recorded in AWS CloudTrail and can also provide policy validation checks and recommendations.



A Practical IAM Workflow

When creating permissions for a new application, I recommend thinking through the following process:

Step 1: Identify the workload

Ask:
text
Who or what needs access?

Examples:
text
Developer
Lambda
EC2
ECS
CI/CD pipeline
GitHub Actions


Step 2: Identify the required resources

For example:
text
S3 bucket
DynamoDB table
Secrets Manager secret
ECR repository
CloudWatch logs


Step 3: Identify required actions

Instead of:
text
*

identify the specific actions:
text
s3:GetObject
dynamodb:GetItem
dynamodb:PutItem
logs:CreateLogGroup


Step 4: Restrict the resources

Whenever possible, replace:
text
Resource: *

with a specific ARN.

Step 5: Add conditions

Ask whether access can be restricted further using:
text
MFA
Tags
IP address
Encryption
Secure transport
Principal attributes


Step 6: Test the policy

Use IAM policy validation and Access Analyzer to identify potential problems.

Step 7: Monitor and refine

Permissions should not be treated as something you configure once and forget.
Review unused permissions and reduce access as your understanding of the workload improves.
AWS recommends regularly reviewing and removing unused users, roles, permissions, policies, and credentials.



IAM Best Practices Checklist

Here is my practical IAM checklist:
  • ✅ Protect the AWS root user.
  • ✅ Enable MFA.
  • ✅ Prefer temporary credentials.
  • ✅ Use IAM roles for workloads.
  • ✅ Avoid hard-coded AWS credentials.
  • ✅ Follow least privilege.
  • ✅ Avoid unnecessary  Action: "*"  permissions.
  • ✅ Avoid unnecessary  Resource: "*"  permissions.
  • ✅ Use IAM policies with conditions when appropriate.
  • ✅ Use IAM Access Analyzer.
  • ✅ Review unused permissions.
  • ✅ Remove unused credentials.
  • ✅ Use IAM Identity Center for centralized workforce access where appropriate.
  • ✅ Use permissions boundaries when delegating permissions management.
  • ✅ Use AWS Organizations guardrails as your environment grows.
AWS's current IAM security guidance covers these practices in detail.



Final Thoughts

AWS IAM may look complicated when you first encounter users, groups, roles, policies, trust policies, resource policies, permissions boundaries, and SCPs.
But the fundamental idea is actually simple:
Give the right identity the minimum permissions required to perform the required task.
If you are learning AWS, don't treat IAM as something you learn only when you encounter an  AccessDenied  error.
Learn IAM early.
Understanding IAM will help you build more secure applications, design better DevOps pipelines, troubleshoot AWS permissions, and work confidently with services such as Lambda, EC2, S3, DynamoDB, ECS, and CloudFormation.
For cloud and DevOps engineers, IAM is not just another AWS service. It is one of the foundations of secure cloud architecture.



Further Reading

AWS Resources

External Security Resources

Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article