Skip to main content
Cloud Core Concepts

IAM Roles & Policies: The Secure Keycard System for Your Cloud Building

Imagine you work in a large office building. You have a keycard that opens the front door, your floor, and maybe the break room. It does not open the server room, the CEO's office, or the supply closet unless you specifically need access. That is exactly how AWS Identity and Access Management (IAM) roles and policies work — but for your cloud resources. This guide breaks down IAM roles and policies as a secure keycard system, showing you how to grant the right access to the right entities at the right time, without overcomplicating things. We will cover why this matters now, how roles and policies work under the hood, a concrete walkthrough, edge cases, and the limits of this approach. By the end, you will have a mental model that makes IAM decisions clearer and safer. 1.

Imagine you work in a large office building. You have a keycard that opens the front door, your floor, and maybe the break room. It does not open the server room, the CEO's office, or the supply closet unless you specifically need access. That is exactly how AWS Identity and Access Management (IAM) roles and policies work — but for your cloud resources. This guide breaks down IAM roles and policies as a secure keycard system, showing you how to grant the right access to the right entities at the right time, without overcomplicating things. We will cover why this matters now, how roles and policies work under the hood, a concrete walkthrough, edge cases, and the limits of this approach. By the end, you will have a mental model that makes IAM decisions clearer and safer.

1. Why IAM Roles and Policies Matter More Than Ever

Cloud environments have become sprawling. A typical organization might have dozens of EC2 instances, Lambda functions, S3 buckets, RDS databases, and third-party integrations. Each of these needs permissions to talk to other services: an EC2 instance might need to read from an S3 bucket, a Lambda function might need to write to DynamoDB, and a CI/CD pipeline might need to deploy code to ECS. Without a structured way to manage these permissions, you end up with two common problems: over-permissioning (giving too much access) and under-permissioning (giving too little, causing breakage).

Over-permissioning is dangerous. If a developer accidentally leaves a wide-open policy on a role used by an application, an attacker who compromises that application can escalate privileges across your account. Under-permissioning, on the other hand, leads to production outages: a service suddenly cannot access its database, and your team scrambles to fix a policy while users are impacted. IAM roles and policies exist to solve this tension. They let you define exactly what actions a principal (a user, service, or federated identity) can perform on which resources, under which conditions.

Why now? Because cloud adoption has accelerated, and so have security incidents. Breaches involving misconfigured IAM policies are among the most common cloud security failures. The Capital One breach in 2019, for instance, involved a misconfigured web application firewall that allowed an attacker to assume a role with excessive permissions. While that specific case involved a WAF, the root cause was a chain of IAM misconfigurations. Practitioners report that IAM is one of the most confusing areas of cloud security — and that confusion leads to mistakes.

This guide is for anyone who manages cloud infrastructure: developers, DevOps engineers, security professionals, and architects. You do not need to be an IAM expert to follow along. We will use analogies and plain language to build your understanding step by step. After reading, you will be able to design a role and policy structure that follows the principle of least privilege, audit existing permissions for risk, and troubleshoot common access issues. Let's start with the core idea.

2. The Core Idea: Roles as Keycards, Policies as Door Locks

Think of an IAM role as a keycard that grants temporary access to a set of resources. Unlike a user, which is a permanent identity tied to a person, a role is assumed by an entity (a user, service, or application) when it needs to perform actions. The role itself has no credentials; it is a container for permissions. When an entity assumes a role, it receives temporary security credentials that allow it to act within the permissions of that role.

Now, policies are the door locks. Each policy is a JSON document that specifies what actions are allowed or denied on which resources. You attach policies to roles (or users or groups) to define the permissions. So when a service assumes a role, it gets the combined permissions of all policies attached to that role. The policy says, 'You may read from this S3 bucket but not delete,' and the role enforces that.

This separation is powerful. You can create one role for an EC2 instance that needs read access to a database, and another role for a Lambda function that needs write access to the same database. Each role has its own set of policies, and you can update policies without touching the role itself. If you later decide that the EC2 instance also needs write access, you just update the policy — no need to change the role or the instance configuration.

How policies evaluate requests

When a request comes in, AWS evaluates all applicable policies in a specific order. By default, all requests are denied. An explicit allow in a policy overrides that default denial. However, an explicit deny always overrides any allow. This means you can create a 'deny' policy to block dangerous actions even if another policy allows them. For example, you might have a policy that allows full access to S3, but a separate policy that explicitly denies deleting objects in a critical bucket. The deny wins.

Trust policies: who can use the keycard

A role also has a trust policy that defines who is allowed to assume it. This is like saying which people or services are allowed to pick up the keycard. The trust policy specifies the principal (an AWS account, a user, a service like EC2) that can assume the role. Without a trust policy, the role cannot be used by anyone. This is a common source of confusion: you can attach all the right permission policies to a role, but if the trust policy does not include the principal that needs it, the role is effectively inaccessible.

Managed vs. inline policies

AWS offers two types of policies: managed and inline. Managed policies are standalone policies that you can attach to multiple principals. They come in two flavors: AWS managed (prebuilt by AWS) and customer managed (you create them). Inline policies are embedded directly into a single user, group, or role. Managed policies are easier to maintain because you update one policy and all attached principals get the change. Inline policies are useful when you need a policy that is tightly coupled to a single principal and should not be reused. For most scenarios, customer managed policies give you the best balance of control and maintainability.

3. How It Works Under the Hood — The Mechanics of Role Assumption

Let's look at the technical process when an EC2 instance assumes a role. First, you create a role with a trust policy that allows the EC2 service to assume it. Then you attach a permission policy that grants, say, read access to an S3 bucket. Next, you launch an EC2 instance and associate the role with it via an instance profile (a container for roles that EC2 uses). When the instance starts, the AWS credentials service automatically provides temporary credentials to the instance via the instance metadata service. The instance can then use those credentials to make API calls, and AWS evaluates the policies attached to the role to determine if each call is allowed.

The AWS credential chain

The instance does not store long-term credentials. Instead, it retrieves temporary credentials from the instance metadata service (IMDS) at http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name. These credentials have a limited lifetime (typically 6 hours) and are automatically rotated. This is far more secure than embedding access keys in code or configuration files.

Policy evaluation logic

When an API call is made, AWS evaluates all policies that apply to the request. This includes identity-based policies (attached to the role) and resource-based policies (attached to the resource, like an S3 bucket policy). The evaluation follows these steps: 1) If an explicit deny is found, the request is denied. 2) If an explicit allow is found, the request is allowed. 3) If no explicit allow or deny is found, the request is denied by default. This is known as the 'default deny' principle. AWS also supports conditions in policies, such as requiring multi-factor authentication or restricting access to a specific IP range.

Session tags and attribute-based access control

When a role is assumed, you can pass session tags that provide additional context about the session. These tags can be used in policy conditions to implement attribute-based access control (ABAC). For example, you can create a policy that allows access only if the principal's department tag matches the resource's department tag. ABAC is more scalable than role-based access control because you can write one policy that works for many roles, using tags to differentiate permissions. However, it requires careful planning of your tagging strategy.

4. Worked Example: Creating a Role for a Web Application

Let's walk through a realistic scenario. You have a web application running on EC2 that needs to read images from an S3 bucket and write logs to CloudWatch. You want to follow least privilege: the application should only have the permissions it absolutely needs. Here are the steps.

Step 1: Create the permission policy

Create a customer managed policy named WebAppPolicy with the following JSON:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::my-app-images/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}

This policy allows only get-object on the specific bucket and log-related actions on any log group. Note that we restrict the S3 resource to the bucket's contents, not the bucket itself. This prevents accidental deletion or listing of the bucket.

Step 2: Create the role with trust policy

Create a role named WebAppRole. In the trust policy, specify EC2 as a trusted entity:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Attach the WebAppPolicy to this role.

Step 3: Launch EC2 with the role

When launching an EC2 instance, under 'Advanced details', select the IAM instance profile that corresponds to WebAppRole. AWS automatically creates an instance profile for you when you create the role via the console, or you can create one manually. Once the instance is running, any AWS SDK call made from within the instance will automatically use the role's credentials. For example, a Python boto3 script using boto3.client('s3') will pick up the role credentials without any additional configuration.

Step 4: Test and verify

SSH into the instance and run a command like aws s3 ls s3://my-app-images/. This should list the objects (because the policy allows s3:GetObject, which includes list operations on objects). Now try to delete an object: aws s3 rm s3://my-app-images/somefile.jpg. This should fail with an access denied error because the policy does not include s3:DeleteObject. You have successfully implemented least privilege.

5. Edge Cases and Exceptions — When the Keycard System Gets Tricky

IAM roles and policies are powerful, but they have edge cases that can trip you up. Let's examine a few common ones.

Cross-account access

Sometimes you need a role in Account A to access resources in Account B. You can do this by creating a role in Account B with a trust policy that allows principals from Account A to assume it. Then, in Account A, you attach a policy that allows sts:AssumeRole on that role. This is how organizations manage access between accounts, such as allowing a central logging account to read logs from all other accounts. The tricky part is that the trust policy must specify the exact account ID (or organization) and may require additional conditions like MFA. Also, the role in Account B must have a permission policy that grants access to the resources. Without both sides configured correctly, the assumption fails.

Service-linked roles

Some AWS services create roles on your behalf, called service-linked roles. These roles are predefined by the service and include all the permissions the service needs to perform its functions. For example, AWS Auto Scaling uses a service-linked role to launch and terminate instances. You cannot modify the trust policy of a service-linked role, but you can modify its permission policy in some cases. Be aware that these roles exist; if you see a role you did not create, it might be a service-linked role.

Permissions boundaries

A permissions boundary is a managed policy that sets the maximum permissions a role can have. Even if you attach a policy that grants full admin access, if the role's permissions boundary only allows S3 read access, the effective permissions are limited to S3 read. This is useful for delegating role creation to other teams: you can set a boundary that prevents them from escalating privileges beyond a certain scope. However, boundaries can be confusing because they interact with other policies in non-obvious ways. The effective permissions are the intersection of the identity-based policy and the permissions boundary.

Session duration and credential rotation

When a role is assumed, the temporary credentials have a configurable session duration, up to 12 hours (or 1 hour for roles assumed by a user). After that, the credentials expire and the entity must re-authenticate. For long-running processes like EC2 instances, the instance metadata service automatically refreshes the credentials before they expire, so you rarely need to handle expiration explicitly. But for applications that cache credentials, you must ensure they refresh before expiration. Failure to do so results in authentication errors.

6. Limits of the Approach — What IAM Roles and Policies Cannot Do

While IAM roles and policies are the foundation of cloud security, they are not a silver bullet. Understanding their limits helps you avoid over-reliance and plan for complementary controls.

No protection against compromised credentials

If an attacker gains access to the temporary credentials of a role (for example, by exploiting an application vulnerability that exposes the instance metadata service), they can use that role's permissions until the credentials expire. IAM policies cannot prevent this; you need additional layers like network security groups, web application firewalls, and monitoring. AWS now offers IMDSv2, which requires session-based requests to mitigate SSRF attacks. Always use IMDSv2 on EC2 instances.

Policy complexity can lead to mistakes

As your organization grows, the number of roles and policies can explode. Managing hundreds of policies manually becomes error-prone. Teams often create overly permissive policies to avoid troubleshooting, defeating the purpose of least privilege. Tools like AWS IAM Access Analyzer and policy validation can help, but they require discipline. Consider using infrastructure as code (Terraform, CloudFormation) to version and review policy changes.

No real-time monitoring by default

IAM itself does not alert you when a policy is misconfigured or when a role is used in an unusual way. You must enable AWS CloudTrail to log all API calls and set up Amazon GuardDuty to detect anomalous behavior. Without monitoring, you may not know that a role has been compromised until it is too late. Always combine IAM with logging and detective controls.

Limitations with resource-based policies

Some services, like S3 and SQS, support resource-based policies that grant access to principals directly. This can create a situation where a user has access via both a role policy and a bucket policy, leading to confusion about effective permissions. The evaluation logic is well-defined, but it is easy to misconfigure. For example, a bucket policy that grants public read access will override any role restrictions. Always review both identity-based and resource-based policies when troubleshooting.

Scaling ABAC requires planning

Attribute-based access control (ABAC) is a powerful way to scale permissions, but it requires a consistent tagging strategy across all resources and principals. If tags are missing or inconsistent, policies may not work as expected. Implementing ABAC across a large organization requires governance and automation. Many teams start with role-based access control and gradually introduce ABAC for specific use cases.

To move forward, start with a simple role structure for your most critical workloads. Use managed policies to avoid duplication. Enable CloudTrail and set up a baseline of normal activity. Review your policies quarterly, removing any that are unused or overly permissive. Consider using IAM Access Analyzer to identify unused roles and policies. Finally, invest in training: the most secure system is one that your team understands. When everyone on the team can read a policy and know what it does, you reduce the risk of misconfiguration.

Share this article:

Comments (0)

No comments yet. Be the first to comment!