Fix Terraform AWS UnauthorizedOperation IAM Errors

Tested on Ubuntu 26.04 LTS (Resolute Raccoon)
Package terraform 1.16.1
hashicorp/aws 6.63.0
awscli 2.36.38
Applies to Any host with Terraform and the AWS provider; examples use ap-south-1
Privilege AWS identity that can call sts:GetCallerIdentity and sts:DecodeAuthorizationMessage; normal user on the workstation
Scope Diagnose UnauthorizedOperation and AccessDenied from the HashiCorp AWS provider during terraform plan or apply — confirm caller identity, decode the authorization failure message, read the denied API action and resource, and fix IAM permissions. Does not cover creating IAM users, organization SCP authoring, or replacing the AWS credentials error when no identity is found at all.
Related guides Terraform providers
AWS CLI tutorial
Short on time?Beta Get the gist of this 7-minute read in seconds, then ask follow-up questions

You ran terraform apply, Terraform reached AWS, and the provider returned a 403-style failure:

text
Error: creating EC2 Instance: operation error EC2: RunInstances, …
api error UnauthorizedOperation: You are not authorized to perform this operation.
… Encoded authorization failure message: wXYQTDpQds6-FfUhMJvMDEqHX1PcU9DPX_xJFxPDi1n…

That is different from missing credentials. AWS recognized your IAM identity and refused one API call.


What UnauthorizedOperation means

  • Your user or role can authenticate, but the IAM policy does not allow the action Terraform just called
  • The error may name the action directly (ec2:RunInstances, s3:CreateBucket) or hide details behind an encoded blob
  • Fixing it is an IAM permission change for that identity — not a Terraform syntax problem
NOTE
If Terraform cannot find credentials at all, you get No valid credential sources found instead — see Fix Terraform AWS credentials error. This article assumes AWS already knows who you are.

Confirm which identity Terraform uses

Before you change IAM policies, verify which principal signed the failed request. Terraform uses the same credential chain as the AWS CLI in that shell.

bash
aws sts get-caller-identity

Sample output:

output
{
    "UserId": "AIDAU7QAIUYJBSXAT2W5",
    "Account": "342524208658",
    "Arn": "arn:aws:iam::342524208658:user/deploy-user"
}

The Arn line is the identity you need to fix. If you use a named profile, run the same check with that profile active:

bash
aws sts get-caller-identity --profile production

When AWS_PROFILE or a profile argument in the provider block points at the wrong profile, you may be debugging permissions for user A while Terraform runs as user B.


Two shapes of permission errors

Not every 403 includes an encoded message.

Plain AccessDenied

AWS prints the action and resource directly:

text
api error AccessDenied: User: arn:aws:iam::342524208658:user/deploy-user is not
authorized to perform: s3:CreateBucket on resource: "arn:aws:s3:::my-tf-bucket"
because no identity-based policy allows the s3:CreateBucket action

Read the perform: clause and add that action to the identity policy (or ask your admin to).

UnauthorizedOperation with encoded message

Common on EC2 and some other services. AWS hides the details behind a long blob. Copy the string after Encoded authorization failure message: from your Terraform output. A failed terraform apply on an aws_instance prints something like this:

text
Error: creating EC2 Instance: operation error EC2: RunInstances, …
api error UnauthorizedOperation: You are not authorized to perform this operation.
User: arn:aws:iam::342524208658:user/deploy-user is not authorized to perform: ec2:RunInstances …
Encoded authorization failure message: wXYQTDpQds6-FfUhMJvMDEqHX1PcU9DPX_xJFxPDi1n…

Even when the text already names ec2:RunInstances, decode the blob — it confirms the principal, resource ARN, and whether a Service Control Policy or permission boundary blocked the call. Terraform does not decode this for you automatically.


Decode the authorization failure message

Copy the encoded string from your Terraform error into a shell variable so you do not truncate it by mistake:

bash
ENC='wXYQTDpQds6-FfUhMJvMDEqHX1PcU9DPX_xJFxPDi1nL1JH4zv3t6q-6OsKBuJCJ9lB_EKCV6HLWmAMiq44G1C4mslFq4lGl0jy9Jr8u-xDADn84pYjw-G3XsiNHrp9cqPDGaBZRDqmVeUfKTWptBoCirn1FaxpjhpLaohZxJt2GVsQQtpF0zAW0LbRdYzmXUYf7gWjweMObo6kKoTCLuONaVfq26zOtOTiwTAf8tY29pgqmXTzNwcZG_My3nw9nVRi_zc0fEqQCp7dXmpW681aGGJreAhIt5O655Gg6l2ROfTmuss8JsAn4jyYxtR9xVu7SxVDSDPTg3iebk2B56wVLohiG4cfeWDCU7XJOqdYnRHuYgV4W9syo_t6CoQdx5Ev6iOzLqzs2eqTjNfAzhPMp7sRkBArnRAhIsA5SzsA4_YWejYTnpAAlSpTIbyZ4SNJd845GuvEMKi1FZOO8_1dAW-8yHjmydHkylLlwrjQzgVrgV_Q8EIDBTRAQ63H821h9eQyV805ryJUJdRTb6F9h1JcJhZ7P3I3OD4l8KSzMK1QUTEiyv-36akIvm__D9Ln1QAsVKl2XETMmaqcKgjKgk1WzeTRcAIHwIQaJojbaGmUVquSw5DDjM9R0f597ERjEpd7KtavDeTP2EeSXMslml1I3MTMyJkz4KVfzJ2PdCS4iOA9gmIjiPCqlRtJjols6YW0AImVniEz5wn_gZTWHp_n2VrLqAbtpH5ZaHWw8fHg3Erk-SuxxqL016SJ2qo2SOQXms_Wuw0nL8wj_QyarpWK2j2mmRdIZSVil48P59xMG9t5nxXj2ekis'

Decode with the AWS CLI using the same credentials that received the error:

bash
aws sts decode-authorization-message --encoded-message "$ENC" --output json | jq -r '.DecodedMessage' | jq .

Sample output (trimmed to the fields you act on):

output
{
  "allowed": false,
  "explicitDeny": false,
  "context": {
    "principal": {
      "arn": "arn:aws:iam::342524208658:user/deploy-user"
    },
    "action": "RunInstances",
    "resource": "arn:aws:ec2:ap-south-1:342524208658:instance/*"
  }
}

How to read the JSON:

  • context.action — IAM API name to allow (ec2:RunInstances maps to the RunInstances EC2 API)
  • context.resource — ARN pattern Terraform targeted
  • context.principal.arn — confirms which identity was denied
  • explicitDeny: true — look for an SCP, permission boundary, or explicit Deny statement instead of adding only Allow
  • allowed: false with empty failures — still useful; the action and resource fields tell you what to request

Your identity also needs sts:DecodeAuthorizationMessage to run this command. If decode itself fails with AccessDenied, ask an administrator to decode the blob or grant that action on *.


Fix the missing IAM permission

You now know the API action Terraform needs. The fix depends on your role in the account.

If you manage IAM policies yourself, attach an Allow for the decoded action on the right resource scope. For ec2:RunInstances:

json
{
  "Effect": "Allow",
  "Action": [
    "ec2:RunInstances",
    "ec2:DescribeInstances",
    "ec2:CreateTags",
    "ec2:TerminateInstances"
  ],
  "Resource": "*"
}

Terraform often needs related Describe* calls during plan and refresh, not only the create action. Start with the decoded action, then add describe permissions if the next error names them.

If you are on a team, open a ticket with:

  • The Terraform resource address (for example aws_instance.web)
  • The decoded action and resource
  • The principal ARN from get-caller-identity
  • The AWS region from your provider block

If explicitDeny is true, a broader Allow in your user policy will not help until someone removes or narrows the deny — often an organization SCP or permissions boundary.

After the policy change, wait a few seconds for IAM propagation, then re-run Terraform:

bash
terraform plan

When the plan is clean, apply again and confirm at the prompt:

bash
terraform apply

Type yes only after the destroy/create list looks correct.


Troubleshooting

Symptom Likely cause Fix
UnauthorizedOperation with encoded message Missing Allow for the API action in the identity policy Decode message; add action on the resource scope
AccessDenied with explicit perform: clause Same as above, but AWS already printed the action Add the named action; decode optional
DecodeAuthorizationMessage AccessDenied Your identity cannot call STS decode Ask admin to decode, or grant sts:DecodeAuthorizationMessage
InvalidAuthorizationMessage on decode Wrong blob copied, or different credentials than the failed call Re-copy from Terraform output; export the same profile or keys
Plan works, apply fails on a different action Plan does not call every API that apply uses Decode the apply error; add the second action (common with iam:PassRole)
Fixed RunInstances, now DescribeTags fails Provider refresh needs extra describe permissions Add the newly named ec2:Describe* action
Same policy works in console, not in Terraform Different identity or region Compare aws sts get-caller-identity with the decoded principal.arn

For broader Terraform failure stages, see Terraform troubleshooting.


Summary

UnauthorizedOperation during terraform plan or apply means AWS authenticated your identity but denied a specific API call. That is an IAM permissions problem, not missing credentials and not a bug in your .tf syntax.

Start by confirming which principal Terraform uses with aws sts get-caller-identity. When the error includes Encoded authorization failure message, copy the full string and run aws sts decode-authorization-message with the same credentials. The decoded JSON names the action, resource, and principal so you can request the right Allow instead of guessing.

Some errors already state the missing action (s3:CreateBucket, iam:PassRole) without an encoded blob — treat those the same way and add the named permission. After IAM propagation, run terraform plan and terraform apply again and read the plan before you confirm. If decode shows explicitDeny: true, look for organization SCPs or permission boundaries, not only user policy edits.


References


Frequently Asked Questions

Is UnauthorizedOperation the same as No valid credential sources found?

No. No valid credential sources found means Terraform could not sign requests at all. UnauthorizedOperation means AWS knew your identity and refused one action.

Do I need decode when the error already names ec2:RunInstances?

Decoding is still useful. It confirms the principal and resource ARN and shows whether an explicit deny blocked the call.

Can I decode with my personal admin user if CI failed?

Only if you use the same credentials that received the encoded error. Otherwise AWS returns InvalidAuthorizationMessage.

Why did plan succeed but apply fail?

Plan often skips create APIs. Apply calls them. The permission gap appears at apply time.

Does adding AdministratorAccess fix this?

It would, but it violates least privilege. Add only the actions Terraform needs for your module.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)