AWS Certified CloudOps Engineer - Associate

IAM Policies and Roles

The building blocks of every authorization decision on AWS: principals and identities, the elements of a JSON policy, identity-based versus resource-based policies, managed versus inline, and why a role carries two policies instead of one.

Intermediate 26 minutes 7 Learning Objectives
  1. Distinguish a principal from an identity and name the policy types AWS attaches to each
  2. Read a JSON policy statement element by element and predict what it permits
  3. Choose between an identity-based policy and a resource-based policy for a given access requirement
  4. Explain why an IAM role carries both a trust policy and a permissions policy, and which request each one gates
  5. Pass a role to an EC2 instance with an instance profile and describe how the application receives credentials
  6. Compare the AWS STS operations that issue temporary credentials by caller, lifetime, and MFA support
  7. State what changes about policy evaluation when a request crosses an account boundary

Your application runs on 40 EC2 instances and needs to read from an S3 bucket. The direct answer is to create an IAM user, generate an access key, and drop it into a config file on the instance. It works on the first instance. Then the key gets baked into the AMI, the AMI gets shared, someone pastes the key into a support ticket, and the rotation you scheduled for next quarter now means touching 40 machines at once. There is no expiry on that key, and nothing in AWS will ever tell you it has escaped.

Almost everything in this domain exists to make that pattern unnecessary. This lesson builds the vocabulary the rest of the domain assumes: what a policy is made of, which kinds of policy AWS attaches where, and why a role is the answer to the access-key problem rather than a slightly nicer wrapper around it.

Principals, identities, and what a policy actually is

A principal is whatever makes a request. That can be the account root user, an IAM user, a role session, or an AWS service acting on your behalf. An identity is the IAM object a principal comes from: a user, a group, or a role.

A policy is a JSON document that, once attached to an identity or a resource, defines permissions. When a principal sends a request, AWS collects every policy that applies and decides allow or deny. The default matters: every request is denied unless a policy allows it, with the single exception of the root user, which has full access.

AWS supports 9 policy types. You will meet all of them across this domain, and you only need 3 of them for this lesson:

Policy typeAttached toGrants permissions?
Identity-basedUser, group, or roleYes
Resource-basedA resource (bucket, queue, key, role)Yes
Permissions boundaryA user or roleNo, it caps
SCP and RCP (Organizations)Root, OU, or accountNo, they cap
Session policyPassed at session creationNo, it caps
VPC endpoint policyA VPC endpointNo, it caps traffic through the endpoint
ACLA resource, non-JSON syntaxYes, cross-account only
AWS RAM resource shareA shared resourceYes

Notice the third column. Only 3 of these types actually hand out permissions. The rest set ceilings, which is why "the policy allows it" and "the request succeeds" are different statements. The next lesson is entirely about the difference.

The anatomy of a JSON policy

Every JSON policy has optional top-level information and one or more statements. AWS applies a logical OR across the statements in a policy and across all the policies that apply, so any single allow is enough (unless something denies).

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadReportsBucket",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::finance-reports",
        "arn:aws:s3:::finance-reports/*"
      ],
      "Condition": {
        "Bool": { "aws:SecureTransport": "true" }
      }
    }
  ]
}

The elements, and the traps in each:

  • Version is the policy language version, not a version of your policy. Use 2012-10-17. An older or missing value silently disables policy variables.
  • Sid is an optional label. It has no effect on evaluation, but it is what shows up when you are hunting for the statement that denied something, so name them.
  • Effect is Allow or Deny.
  • Principal names who the policy applies to. It is required in a resource-based policy and forbidden in an identity-based policy, where the principal is implied by whatever the policy is attached to. Seeing a Principal block tells you immediately which kind of policy you are reading.
  • Action lists service actions in service:Operation form, with * allowed.
  • Resource lists ARNs. The bucket and the objects inside it are 2 different ARNs, which is why the example above lists both. A policy with only arn:aws:s3:::finance-reports allows listing but not reading a single object.
  • Condition makes the statement apply only when the condition is true. Conditions are the subject of the next lesson.

One rule worth internalizing now: a statement with no matching condition simply does not apply. It does not deny; it drops out of the evaluation and leaves the request to whatever else allows it, which is usually nothing.

Identity-based versus resource-based policies

Both grant permissions. They differ in which question they answer.

An identity-based policy answers "what can this identity do?" and lives on the user, group, or role. A resource-based policy answers "who can touch this resource?" and lives on the resource: an S3 bucket policy, an SQS queue policy, a KMS key policy, a Lambda function policy, an IAM role trust policy.

Identity-basedResource-based
Attached toUser, group, roleThe resource itself
Principal elementNot allowedRequired
Managed or inlineBothInline only, there are no managed resource-based policies
Cross-accountNames the resource in another accountNames the principal in another account

Within one account the two are unioned: an allow in either is enough, and an explicit deny in either wins. That is why Zhang, with no identity-based policy at all, can still read a queue whose queue policy names him.

Two exceptions are worth memorizing because the exam likes them. IAM role trust policies and KMS key policies must explicitly allow the principal. The union shortcut does not save you there: an identity-based policy granting kms:Decrypt on a key whose key policy never mentions you is not enough.

Managed versus inline policies

Identity-based policies come in 2 forms, and the choice is about reuse and lifecycle rather than power.

Managed policies are standalone objects you attach to many identities. AWS managed policies are written and maintained by AWS (AmazonS3ReadOnlyAccess, AdministratorAccess, and the job-function policies). Customer managed policies are yours. Edit one and every attachment changes at once.

Inline policies are embedded directly in a single user, group, or role. They have a strict one-to-one relationship with that identity and are deleted when it is deleted.

AWS's own guidance is to start with AWS managed policies for a working baseline and then narrow to customer managed policies as you learn which permissions the workload actually uses. AWS managed policies are written to be useful for every AWS customer, which is exactly why they are rarely least privilege for yours.

Use inline when the permission must never outlive the identity, or when you want to guarantee nobody accidentally attaches it somewhere else.

Roles: two policies, two questions

Here is the concept that carries the rest of the domain. A role is an identity with permissions, like a user, but with 2 differences that change everything:

  1. It is not tied to one person. Anyone or anything allowed by the role can assume it.
  2. It has no long-term credentials. No password, no access key. Assuming a role produces temporary credentials that expire.

That second point is what solves the opening problem. Nothing to bake into an AMI, nothing to rotate, nothing to leak permanently.

To make that work, a role carries 2 policies that gate 2 different requests:

  • The trust policy is a resource-based policy on the role. It answers who may become this role. It is the only thing consulted when someone calls sts:AssumeRole. Wildcards are not allowed in an ARN in the Principal element of a trust policy.
  • The permissions policy is an identity-based policy on the role. It answers what the resulting session may do. It is never consulted during the assume-role call.

A minimal trust policy for an EC2 workload:

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

And for a cross-account human:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::111122223333:root" },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": { "sts:ExternalId": "a1b2c3d4-audit-2026" }
      }
    }
  ]
}

The sts:ExternalId condition is the standard defense against the confused deputy problem. When you give a third party (an auditor, a monitoring vendor) a role in your account, they hold roles for many customers. Without an external ID, a customer who learns your role ARN could ask the vendor to assume it on their behalf. The external ID is a secret you and the vendor share, and it makes the trust policy specific to your relationship rather than to the vendor as a whole.

This is also the single most useful diagnostic split in IAM. The 2 gates fail with different error messages. not authorized to perform: sts:AssumeRole points at the trust policy. not authorized to perform: s3:GetObject points at the permissions policy. The last lesson in this topic turns that observation into a full procedure.

How a role reaches an EC2 instance: the instance profile

A role is an IAM object. EC2 needs a container to attach it to, and that container is an instance profile.

The rule that produces exam questions: an instance profile can contain only 1 IAM role, and that limit cannot be raised. A role can appear in many instance profiles, but never the reverse.

Where the confusion starts is that the console hides the object. Create a role for EC2 in the console and it creates an instance profile with the same name automatically. Create the same role from the CLI or the API and you get a role and nothing else, so the launch wizard (which lists instance profile names, not role names) shows you nothing.

# from the CLI, these are 3 separate steps
aws iam create-role \
  --role-name AppServerRole \
  --assume-role-policy-document file://trust-policy.json

aws iam create-instance-profile --instance-profile-name AppServerProfile

aws iam add-role-to-instance-profile \
  --instance-profile-name AppServerProfile \
  --role-name AppServerRole

# attach to a running instance
aws ec2 associate-iam-instance-profile \
  --instance-id i-02573cafcfEXAMPLE \
  --iam-instance-profile Name=AppServerProfile

Once attached, the AWS SDKs on the instance find the credentials through the instance metadata service without any configuration, and the service rotates them before they expire. To change what an instance can do, replace the instance profile rather than swapping the role inside it: removing a role from an instance profile takes up to an hour to take effect, because the change has to propagate.

Service roles and service-linked roles

Two role flavors carry names the exam uses precisely.

A service role is a role that an AWS service assumes to act on your behalf. You create it, you write its trust policy naming the service principal, and you can edit its permissions. A CodeBuild build role and the EC2 role above are service roles.

A service-linked role is created and owned by the service. It appears in your account, its permissions are defined by the service, and an administrator can view but not edit them. You also cannot delete it until you delete the resources that depend on it, which is a guard against orphaning resources the service can no longer manage.

If a question hands you a role you did not create and asks why its policy cannot be tightened, it is a service-linked role.

Getting temporary credentials: the STS operations

AWS Security Token Service issues every temporary credential on AWS. The 5 operations differ by who can call them and how long the result lives.

OperationWho can callLifetime (min, max, default)MFA inputSession policy
AssumeRoleIAM user or a role with existing temporary credentials15 min, role max session duration, 1 hrYesYes
AssumeRoleWithSAMLAnyone with a SAML response from a known IdP15 min, role max session duration, 1 hrNoYes
AssumeRoleWithWebIdentityAnyone with an OIDC JWT from a known IdP15 min, role max session duration, 1 hrNoYes
GetFederationTokenIAM user or root userIAM user: 15 min, 36 hr, 12 hr. Root: 15 min, 1 hr, 1 hrNoYes
GetSessionTokenIAM user or root userIAM user: 15 min, 36 hr, 12 hr. Root: 15 min, 1 hr, 1 hrYesNo

Three details decide questions here.

The maximum session duration setting on the role is the ceiling for every assume-role variant, configurable up to 12 hours. Ask for more than the role allows and the call fails rather than returning a shorter session.

Only AssumeRole and GetSessionToken accept MFA information. That is what makes aws:MultiFactorAuthPresent true for the resulting session, which the next lesson uses to enforce MFA on sensitive actions.

GetSessionToken cannot sign you in to the console through the federation endpoint, while GetFederationToken can. If a scenario needs single sign-on to the console from a custom identity broker, GetFederationToken is the operation.

Role chaining and session duration

Role chaining is using one role's credentials to assume a second role. It is common in pipelines and cross-account tooling, and it carries a hard limit: a chained session is capped at 1 hour, regardless of the maximum session duration configured on the target role. Passing DurationSeconds greater than 3600 on a chained call makes the operation fail.

It is tempting to read that as "the session gets truncated to an hour." It does not. The call errors out, which is why a pipeline that worked with a 4-hour session from a user's credentials breaks the day someone inserts an intermediate role.

Cross-account access: the rule that changes

Inside one account, identity-based and resource-based policies are unioned. Across accounts, both sides must allow the request. The principal's account must grant an identity-based allow for the action on the target resource, and the resource's account must name that principal in a resource-based policy or a role trust policy. One side alone always fails.

That single asymmetry explains most cross-account tickets. A bucket policy that generously allows arn:aws:iam::111122223333:root does nothing until an administrator in account 111122223333 also grants some identity the matching s3:GetObject permission. Naming the account root in a bucket policy delegates to that account; it does not grant to every principal in it.

Exam tips

  • Principal in the policy means it is a resource-based policy. No Principal means identity-based. That one element identifies the policy type faster than reading the rest.
  • "Application on EC2 needs to call an AWS service" always means an instance profile with a role, never an access key on the instance.
  • 1 role per instance profile, and a role can live in many instance profiles. Removing a role from an instance profile takes up to 1 hour, so the fix in a scenario is to replace the instance profile.
  • Two error messages, two policies: sts:AssumeRole denied means the trust policy, the target action denied means the permissions policy.
  • Role chaining caps at 1 hour and fails rather than truncating.
  • The words "third party", "vendor", or "auditor" in a cross-account role scenario point at sts:ExternalId and the confused deputy problem.
  • Cross-account needs an allow on both sides. Same-account needs an allow on either side, except for role trust policies and KMS key policies, which must name the principal.
  • A role whose permissions you cannot edit is a service-linked role.

The rule to carry out of this lesson: on AWS, credentials should have an expiry, and the way you get an expiry is a role. Everything else here is bookkeeping around that one idea. The next lesson takes the policies you can now read and answers the harder question of what happens when 5 of them apply to the same request and disagree.