Solve: AccessDeniedException or UnauthorizedOperation—Fixing IAM Errors in EC2 and Lambda


Solve: AccessDeniedException or UnauthorizedOperation—Fixing IAM Errors in EC2 and Lambda







Problem

You launch an EC2 instance, call an AWS API from inside Lambda, or spin up automation with the CLI — and suddenly hit a wall: UnauthorizedOperation, AccessDeniedException, or a plain An error occurred (AccessDenied). It’s unclear which policy blocked the action or even which part of the system is to blame.

This lack of clarity halts builds, baffles even senior engineers, and causes support tickets to pile up. IAM debugging outside of S3 can be even more obscure because the error messages tend to be generic, and the services involved often rely on multiple assumed roles and service-linked resources.


Clarifying the Issue

At the heart of most EC2 and Lambda access problems is a misunderstanding of who’s performing the action, what policy applies, and how it’s being constrained. You might grant a policy to a role, but if that role is assumed by a service, or if the user context involves a boundary or SCP, then your permissions are filtered through layers you didn’t account for.

A few common examples:
  • EC2 user tries to start instances and gets UnauthorizedOperation despite having ec2:StartInstances.
  • A Lambda function can't call DynamoDB, throwing AccessDeniedException, even though the role appears to have dynamodb:PutItem.
  • CLI calls fail under assumed roles because a session policy overrides the inline permissions.
  • IAM’s web of policies doesn't make debugging easy. But there is a structured way through it.


Why It Matters

IAM is the backbone of AWS security and automation. When it misbehaves, engineers lose confidence, business logic fails, and timelines slip. In regulated industries or enterprise environments, one misconfigured permission can cause compliance failures or outages.

It’s not enough to know how to grant access — you need to know how to trace access. Without a strong debugging practice, even small teams can end up with bloated, over-permissive roles just to "make things work." That’s dangerous, expensive, and hard to reverse.


Key Terms
  • AccessDeniedException – A general-purpose denial response, used by services like Lambda, DynamoDB, and STS.
  • UnauthorizedOperation – A more specific error from EC2 and similar services indicating a disallowed action.
  • Instance Profile – A container for an IAM role that EC2 instances assume when calling AWS services.
  • AssumeRoleWithSessionPolicy – An STS call that temporarily attaches a policy to a session, potentially limiting access beyond the base role.
  • Service-Linked Role – An AWS-managed IAM role preconfigured to support a specific service (e.g., AWSServiceRoleForEC2Spot).
  • STS Credentials – Temporary credentials generated for roles assumed by users, services, or applications.


Steps at a Glance
  1. Identify the exact user, role, or session that made the failing request.
  2. Simulate the IAM policy for that principal.
  3. Check for SCPs, permission boundaries, or session policies.
  4. Confirm that the correct service-linked role or instance profile is in use.
  5. Test access manually by assuming the role and calling the API.
  6. Review CloudTrail logs to see the evaluated policy context.


Detailed Steps

1. Identify Who Made the Request

Use CloudTrail or diagnostic logs to determine which user or role triggered the action. Often, EC2 uses an instance profile, and Lambda uses an execution role:

Bash
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=StartInstances

2. Simulate the Policy

Run this to simulate the role’s effective permissions:

Bash
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::111122223333:role/MyEC2Role \
  --action-names ec2:StartInstances \
  --resource-arns arn:aws:ec2:us-east-1:111122223333:instance/i-1234567890abcdef0 

If simulation returns
allowed, and the action still fails, something else is blocking it.

3. Inspect SCPs, Boundaries, and Sessions

First check SCPs:

Bash
aws organizations list-policies-for-target \
  --target-id 111122223333 \
  --filter SERVICE_CONTROL_POLICY

Then inspect permission boundaries:

Bash
aws iam get-role --role-name MyEC2Role

Finally, confirm whether the credentials are scoped by a session policy (common in federated or temporary credentials).

4. Confirm Role Is Attached Properly

For EC2:

Bash
aws ec2 describe-instances \
  --instance-ids i-1234567890abcdef0 \
  --query "Reservations[].Instances[].IamInstanceProfile"

You may see the ARN of the instance profile — verify it maps to a role with the necessary policies.

For Lambda:

Bash
aws lambda get-function-configuration \
  --function-name my-function \
  --query "Role"

Verify this is the right role and has the correct trust relationship.

5. Test Access by Assuming the Role

Assume the role and call the API:

Bash
aws sts assume-role \
  --role-arn arn:aws:iam::111122223333:role/MyEC2Role \
  --role-session-name debugSession

export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...

aws ec2 start-instances --instance-ids i-1234567890abcdef0


If this fails, you’ve confirmed the role is the problem. If it works, the issue might be with how the role is being assumed or passed during automation.

6. Review CloudTrail Logs for Final Clarity

Use CloudTrail logs to inspect the failing call:

Bash
fields @timestamp, eventName, errorCode, userIdentity.arn, requestParameters
| filter eventName="StartInstances" and errorCode="UnauthorizedOperation"
| sort @timestamp desc

This will often reveal the effective identity and policy context of the call.


Conclusion

AccessDeniedException and UnauthorizedOperation aren’t bugs — they’re messages from AWS that your access model has a logic error. IAM’s layered structure means that even a perfectly written policy might be overridden or misapplied by another layer.

Instead of guessing, follow a disciplined checklist: simulate policies, confirm roles and trust relationships, check for session and org-wide limits, and always test with assumed credentials. IAM is complex because it’s powerful — but it becomes manageable when you know how to trace its logic.


Need AWS Expertise?

We'd love to help you with your AWS projects.  Feel free to reach out to us at info@pacificw.com.


Written by Aaron Rose, software engineer and technology writer at Tech-Reader.blog.

Comments

Popular posts from this blog

The New ChatGPT Reason Feature: What It Is and Why You Should Use It

Raspberry Pi Connect vs. RealVNC: A Comprehensive Comparison

The Reasoning Chain in DeepSeek R1: A Glimpse into AI’s Thought Process