AWS Error Guide: 'UnauthorizedOperation' EC2 API Permission Failures
Fix the EC2 UnauthorizedOperation error: diagnose missing ec2 permissions, condition keys, encoded authorization messages, SCPs, wrong roles, and resource scoping.
- #aws
- #troubleshooting
- #errors
- #ec2
Overview
UnauthorizedOperation is EC2’s way of saying the calling principal is not authorized to perform the requested EC2 action. It is the EC2-specific cousin of AccessDenied: the request passed authentication but failed authorization. EC2 often returns the failure with an encoded authorization message — an opaque blob you decode with STS to see exactly which statements allowed or denied the call.
You see it from the CLI or an SDK:
An error occurred (UnauthorizedOperation) when calling the RunInstances operation: You are not authorized to perform this operation. Encoded authorization failure message: 8sJ2k...very-long-encoded-string...Qp9
It occurs across EC2 control-plane actions — RunInstances, TerminateInstances, CreateTags, AuthorizeSecurityGroupIngress, CreateVolume, and more. Because EC2 actions frequently touch multiple resources (instance, subnet, security group, AMI, key pair), a policy that grants the action on one but not all of them still produces UnauthorizedOperation.
Symptoms
- An EC2 API call fails with
UnauthorizedOperationand an encoded message. - The same operation works for an admin but fails for a scoped role/user.
- A Terraform/CloudFormation EC2 step fails authorization despite an attached EC2 policy.
- The failure appears only for certain instance types, tags, or subnets.
aws ec2 run-instances --instance-type t3.micro --count 1 \
--image-id ami-0abcd1234ef567890 --subnet-id subnet-0aaa1111
An error occurred (UnauthorizedOperation) when calling the RunInstances operation: You are not authorized to perform this operation. Encoded authorization failure message: 8sJ2k...Qp9
aws sts get-caller-identity --query Arn --output text
arn:aws:iam::123456789012:role/deploy-role
Common Root Causes
1. The action is not granted at all
The policy lacks the specific EC2 action (e.g. has ec2:Describe* but not ec2:RunInstances).
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/deploy-role \
--action-names ec2:RunInstances \
--query 'EvaluationResults[0].[EvalActionName,EvalDecision]' --output text
ec2:RunInstances implicitDeny
implicitDeny confirms the action was never allowed — add it to the policy.
2. A condition key blocks the specific request
The action is allowed, but a Condition restricts it — e.g. only certain instance types, regions, or required tags. A request outside those bounds is denied.
aws iam get-role-policy --role-name deploy-role --policy-name EC2Launch \
--query 'PolicyDocument.Statement[?Action==`ec2:RunInstances`].Condition' --output json
[
{"StringEquals": {"ec2:InstanceType": "t3.micro"}}
]
Launching anything other than t3.micro fails this condition — match the constraint or broaden it.
3. Missing permission on a referenced resource
RunInstances needs authorization on the AMI, subnet, security group, key pair, and (if tagging) ec2:CreateTags. A policy scoped to the instance ARN only, missing the others, fails.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/deploy-role \
--action-names ec2:RunInstances ec2:CreateTags \
--resource-arns arn:aws:ec2:us-east-1:123456789012:subnet/subnet-0aaa1111 \
--query 'EvaluationResults[].[EvalActionName,EvalDecision]' --output text
ec2:RunInstances allowed
ec2:CreateTags implicitDeny
Tag-on-launch needs ec2:CreateTags; missing it makes the whole launch fail.
4. An SCP or permissions boundary denies it org-wide
EC2 actions are common SCP targets (region restrictions, instance-type guardrails). The IAM policy may allow the call, but the org SCP or a permissions boundary denies it.
aws organizations list-policies-for-target --target-id 123456789012 \
--filter SERVICE_CONTROL_POLICY --query 'Policies[].Name' --output text
FullAWSAccess RestrictExpensiveInstances
If RestrictExpensiveInstances denies the chosen type/region, no IAM grant overrides it.
5. Wrong identity (instance profile vs. user) calling
Automation may run under an instance profile or assumed role rather than the user you expected, and that role lacks the permission.
aws sts get-caller-identity --query Arn --output text
arn:aws:sts::123456789012:assumed-role/ci-runner/i-0abc123
If you expected a user but see an assumed-role, diagnose that role’s policy, not the user’s.
6. A required iam:PassRole is missing
Launching with an instance profile requires iam:PassRole for that role. Without it, EC2 returns UnauthorizedOperation even when RunInstances itself is allowed.
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/deploy-role \
--action-names iam:PassRole \
--resource-arns arn:aws:iam::123456789012:role/app-instance-role \
--query 'EvaluationResults[0].EvalDecision' --output text
implicitDeny
implicitDeny on iam:PassRole blocks launches that attach app-instance-role.
Diagnostic Workflow
Step 1: Decode the encoded authorization message
aws sts decode-authorization-message \
--encoded-message "<ENCODED_BLOB_FROM_ERROR>" \
--query DecodedMessage --output text | python3 -m json.tool
The decoded JSON shows allowed: false, the principal, the action, and the matched/failed statements — including SCP context. This is the fastest path to the real cause.
Step 2: Confirm the calling identity
aws sts get-caller-identity --query Arn --output text
Ensure you are diagnosing the role/user that actually made the call.
Step 3: Simulate the action and its companions
aws iam simulate-principal-policy \
--policy-source-arn <PRINCIPAL_ARN> \
--action-names ec2:RunInstances ec2:CreateTags iam:PassRole \
--resource-arns <SUBNET_ARN> <ROLE_ARN>
RunInstances rarely fails alone — check CreateTags and PassRole together.
Step 4: Rule out SCPs and permissions boundaries
aws organizations list-policies-for-target --target-id <ACCOUNT_ID> \
--filter SERVICE_CONTROL_POLICY --query 'Policies[].Name' --output text
aws iam get-role --role-name <ROLE> --query 'Role.PermissionsBoundary'
A region/type guardrail at the org level explains an otherwise-correct IAM policy.
Step 5: Inspect conditions on the granting statement
aws iam get-role-policy --role-name <ROLE> --policy-name <POLICY> \
--query 'PolicyDocument.Statement[?contains(to_string(Action),`RunInstances`)].Condition' \
--output json
A StringEquals/StringLike on instance type, tags, or region that the request does not satisfy is a frequent culprit.
Example Root Cause Analysis
A deployment pipeline’s RunInstances step failed with UnauthorizedOperation, even though deploy-role had a policy explicitly allowing ec2:RunInstances on the right region. The team decoded the message:
aws sts decode-authorization-message --encoded-message "8sJ2k...Qp9" \
--query DecodedMessage --output text | python3 -m json.tool
{
"allowed": false,
"context": {
"action": "iam:PassRole",
"resource": "arn:aws:iam::123456789012:role/app-instance-role"
}
}
The decoded message named iam:PassRole, not RunInstances. The launch attached an instance profile (app-instance-role), and deploy-role had no iam:PassRole permission for it — so EC2 rejected the whole call.
Confirming with the simulator:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/deploy-role \
--action-names iam:PassRole \
--resource-arns arn:aws:iam::123456789012:role/app-instance-role \
--query 'EvaluationResults[0].EvalDecision' --output text
implicitDeny
Fix: add an iam:PassRole statement to deploy-role scoped to app-instance-role (with a iam:PassedToService condition of ec2.amazonaws.com). The next deploy launched the instance with its profile attached.
Prevention Best Practices
- Always decode the encoded authorization message first — it names the failing action and statement, often revealing a companion permission like
iam:PassRoleyou would never guess fromRunInstancesalone. - Grant
RunInstancestogether with the companions it needs:ec2:CreateTagsfor tag-on-launch andiam:PassRole(scoped, withiam:PassedToService) for instance profiles. - Scope EC2 policies to the resources the call touches (AMI, subnet, security group, key pair), not just the instance ARN.
- Keep org SCP guardrails (region/type restrictions) documented so engineers do not chase an IAM policy that is actually correct.
- Confirm the calling identity with
sts get-caller-identity; automation often runs as an assumed role, not the user you expect. - For turning an encoded EC2 authorization blob into a plain-language cause, the free incident assistant can summarize the decoded message. More EC2 walkthroughs are in the AWS guides.
Quick Command Reference
# Decode the encoded authorization message
aws sts decode-authorization-message --encoded-message "<BLOB>" \
--query DecodedMessage --output text | python3 -m json.tool
# Who is calling?
aws sts get-caller-identity --query Arn --output text
# Simulate RunInstances and its companions
aws iam simulate-principal-policy --policy-source-arn <PRINCIPAL_ARN> \
--action-names ec2:RunInstances ec2:CreateTags iam:PassRole \
--resource-arns <SUBNET_ARN> <ROLE_ARN>
# Org SCPs and permissions boundary
aws organizations list-policies-for-target --target-id <ACCOUNT_ID> \
--filter SERVICE_CONTROL_POLICY --query 'Policies[].Name' --output text
aws iam get-role --role-name <ROLE> --query 'Role.PermissionsBoundary'
# Conditions on the granting statement
aws iam get-role-policy --role-name <ROLE> --policy-name <POLICY> \
--query 'PolicyDocument.Statement[?contains(to_string(Action),`RunInstances`)].Condition' --output json
Conclusion
UnauthorizedOperation is EC2’s authorization failure — the request authenticated but was not allowed. The usual root causes:
- The EC2 action is not granted at all (implicit deny).
- A
Condition(instance type, region, tag) blocks the specific request. - A missing permission on a referenced resource (AMI, subnet, SG) or
ec2:CreateTags. - An org SCP or permissions boundary denying the action.
- The call running under a different identity than expected.
- A missing
iam:PassRolefor the attached instance profile.
Decode the encoded authorization message first — it points straight at the failing action and statement — then add the specific grant, often a companion permission rather than the headline action.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.