Fix kubectl Unauthorized After Terraform EKS Apply

Deepak Prasad
Tested on Ubuntu 26.04 LTS (Resolute Raccoon)
Package terraform 1.16.1
terraform-aws-modules/eks 21.25.0
hashicorp/aws 6.63.0
awscli 2.36.38
kubectl 1.36
Applies to Any host with Terraform, AWS CLI, and kubectl; EKS clusters created with terraform-aws-modules/eks v20+ in ap-south-1
Privilege AWS identity that can describe the EKS cluster and manage access entries; normal user on the workstation
Scope Fix error: You must be logged in to the server (Unauthorized) after a successful terraform apply on Amazon EKS — confirm which IAM principal kubectl uses, inspect EKS access entries, and grant API access with enable_cluster_creator_admin_permissions or access_entries. Does not cover building the VPC/EKS stack from scratch, IRSA, or kubeconfig file syntax.
Related guides install kubectl and kubeconfig
Fix Terraform AWS UnauthorizedOperation
Short on time?Beta Get the gist of this 10-minute read in seconds, then ask follow-up questions

terraform apply finished and the EKS cluster exists. You ran aws eks update-kubeconfig, but kubectl still fails.

You may see either of these messages:

text
error: You must be logged in to the server (the server has asked for the client to provide credentials)
text
error: You must be logged in to the server (Unauthorized)

Both indicate that kubectl could not authenticate successfully to the Kubernetes API. On EKS, a common cause after Terraform creates the cluster is that the AWS principal used by kubectl does not have the required EKS access entry or access policy. Expired or mismatched AWS credentials can produce similar symptoms, so confirm the active AWS identity first.

Terraform did not fail — the cluster exists. You are debugging authentication or EKS API access, not a failed apply.


What kubectl Unauthorized means after EKS apply

Before kubectl can list nodes, EKS must authenticate your AWS identity and authorize the API call. A successful terraform apply only proves AWS accepted Terraform's API calls. It does not automatically grant your workstation cluster-admin access.

On terraform-aws-modules/eks v20 and later:

  • Current module versions prefer EKS access entries for human and automation access
  • The module no longer manages legacy aws-auth user mappings the way older v19-era examples did
  • Because the default authentication mode is API_AND_CONFIG_MAP, legacy aws-auth mappings may still work, but they are not the preferred Terraform-managed path for new access

Common reasons you see Unauthorized right after apply:

  • enable_cluster_creator_admin_permissions is missing or false
  • kubectl uses a different IAM user, role, or SSO profile than Terraform used during apply
  • You upgraded the EKS module from v19 without adding access_entries for humans or CI roles
  • Temporary credentials from aws login expired between apply and kubectl

If aws eks update-kubeconfig itself fails, fix AWS credentials first — see Fix Terraform AWS credentials error. This article assumes the kubeconfig context was created and the API endpoint is reachable.

NOTE
The walkthrough below matches the EKS module layout in Deploy an EKS cluster on AWS with Terraform. That guide sets enable_cluster_creator_admin_permissions = true so kubectl works immediately. You land here when that line is missing or when another identity runs kubectl.

Confirm which AWS identity kubectl uses

kubectl on EKS authenticates through the AWS CLI get-token plugin. That token must come from an IAM principal that has an EKS access entry (and the right access policy).

Run this in the same shell where you run kubectl:

bash
aws sts get-caller-identity

Sample output for an account root identity:

output
{
    "UserId": "342524208658",
    "Account": "342524208658",
    "Arn": "arn:aws:iam::342524208658:root"
}

aws sts get-caller-identity shows the AWS identity currently used by the shell. For IAM users and root, the Arn can be compared directly with an access entry. For assumed roles or SSO sessions, STS returns an assumed-role/... session ARN such as arn:aws:sts::123456789012:assumed-role/AdminRole/deepak — the EKS access entry must instead contain the corresponding permanent IAM role ARN (arn:aws:iam::123456789012:role/AdminRole). EKS cannot use a temporary STS session principal as an access-entry target.

What to read in the JSON:

  • Arn — active identity for this shell; compare directly for IAM users and root, map to the permanent role ARN for assumed-role sessions
  • Account — your 12-digit AWS account ID
  • UserId — internal AWS identifier; useful when comparing with console users

Compare that identity to whoever ran terraform apply. If you use named profiles, check the profile Terraform used:

bash
aws sts get-caller-identity --profile terraform-admin

Then check the profile your shell uses for kubectl:

bash
aws sts get-caller-identity --profile daily-user

When the ARNs differ, Terraform may have created the cluster as terraform-admin while kubectl runs as daily-user. Only the identity that runs terraform apply gets an access entry from enable_cluster_creator_admin_permissions.


List EKS access entries on the cluster

On clusters using EKS API authentication, access entries associate IAM principals with Kubernetes access. The current terraform-aws-modules/eks v21 default authentication mode is API_AND_CONFIG_MAP, so access entries are the preferred path for human and automation access while legacy aws-auth mappings can still coexist.

Replace the cluster name and region with yours from terraform output or the AWS console.

List every principal that can reach the API:

bash
aws eks list-access-entries --cluster-name glc-eks-unauth-lab --region ap-south-1

Before the fix, only the managed node group role appeared — your administrative identity was missing:

output
{
    "accessEntries": [
        "arn:aws:iam::342524208658:role/lab-eks-node-group-82f09dee4228ef321fa1bba704"
    ]
}

Why that causes Unauthorized:

  • The managed node-group role may already appear as an access entry, while your administrative identity is missing
  • Service-linked roles cannot be EKS access-entry principals — they will not appear here
  • kubectl has no entry for your IAM user or role, so the API rejects the call

After you add cluster-creator admin permissions, run the same list command again:

bash
aws eks list-access-entries --cluster-name glc-eks-unauth-lab --region ap-south-1

Your administrative principal should now appear:

output
{
    "accessEntries": [
        "arn:aws:iam::342524208658:role/lab-eks-node-group-82f09dee4228ef321fa1bba704",
        "arn:aws:iam::342524208658:root"
    ]
}

The new line is the permanent IAM ARN granted access (root in this lab run). For assumed-role sessions, look for the permanent role ARN — not the assumed-role/... string from get-caller-identity.

To see details for one principal, use describe-access-entry with the ARN from the list:

bash
aws eks describe-access-entry --cluster-name glc-eks-unauth-lab --principal-arn arn:aws:iam::342524208658:root --region ap-south-1

Sample output:

output
{
    "accessEntry": {
        "clusterName": "glc-eks-unauth-lab",
        "principalArn": "arn:aws:iam::342524208658:root",
        "kubernetesGroups": [],
        "type": "STANDARD",
        "username": "arn:aws:iam::342524208658:root"
    }
}

Fields that matter:

  • principalArn — the permanent IAM user or role ARN granted cluster access; for assumed-role sessions, it will not literally equal the STS session ARN from get-caller-identity
  • type — STANDARD for IAM users and roles
  • kubernetesGroups — often empty when you use EKS access policies instead of RBAC groups

Cluster access policies attach separately. The EKS module creates both the access entry and the AmazonEKSClusterAdminPolicy association when you enable cluster-creator permissions.


Fix with enable_cluster_creator_admin_permissions

Use this when the same identity runs terraform apply and kubectl. Add one line inside the EKS module block:

hcl
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 21.25"

  name               = local.cluster_name
  kubernetes_version = "1.36"

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  eks_managed_node_groups = {
    lab = {
      instance_types = ["t3.small"]
      min_size       = 1
      max_size       = 1
      desired_size   = 1
    }
  }

  enable_cluster_creator_admin_permissions = true
}

What that line does:

  • Creates an EKS access entry for the IAM principal running terraform apply
  • Attaches AmazonEKSClusterAdminPolicy at cluster scope
  • Replaces the old automatic aws-auth mapping from EKS module v19

From your Terraform project directory, apply the change:

bash
terraform apply

Review the plan before you type yes. You should see two new resources:

text
# module.eks.aws_eks_access_entry.this["cluster_creator"] will be created
# module.eks.aws_eks_access_policy_association.this["cluster_creator_admin"] will be created

A successful apply ends with a line like Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Access-entry changes are eventually consistent. If kubectl still returns Unauthorized immediately after apply, wait a few seconds and retry before changing the configuration again.

Refresh kubeconfig if you changed regions or profiles:

bash
aws eks update-kubeconfig --region ap-south-1 --name glc-eks-unauth-lab

update-kubeconfig normally prints the context it added or updated:

output
Added new context arn:aws:eks:ap-south-1:342524208658:cluster/glc-eks-unauth-lab to /root/.kube/config

Confirm API access with:

bash
kubectl get nodes

Sample output:

output
NAME                                        STATUS   ROLES    AGE    VERSION
ip-10-0-2-222.ap-south-1.compute.internal   Ready    <none>   3m5s   v1.36.3-eks-cb19647

What to look for:

  • STATUS is Ready — the node joined the cluster
  • No Unauthorized error — your access entry is working

Fix with access_entries for other principals

enable_cluster_creator_admin_permissions only covers whoever runs terraform apply. Everyone else needs an explicit entry.

Who needs their own access_entries block:

  • CI/CD IAM roles that run kubectl or Helm
  • Teammates signing in through IAM roles or SSO (use the permanent role ARN)
  • Break-glass admin roles

IAM users are supported, but AWS recommends IAM roles with temporary credentials for people and automation. Add one block per IAM role (or user):

hcl
access_entries = {
    deploy-role = {
      principal_arn = "arn:aws:iam::342524208658:role/deploy-role"
      policy_associations = {
        admin = {
          policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
          access_scope = {
            type = "cluster"
          }
        }
      }
    }
  }

Policy choices:

  • AmazonEKSClusterAdminPolicy — full cluster admin (lab and break-glass)
  • AmazonEKSViewPolicy — read-only API access
  • Namespace-scoped access_scope — limit access to one namespace in production

Run terraform apply after you add entries.

Emergency CLI grant (without Terraform)

An administrator can grant access immediately, then backport the change into .tf:

Create the access entry:

bash
aws eks create-access-entry --cluster-name glc-eks-unauth-lab --principal-arn arn:aws:iam::342524208658:role/deploy-role --region ap-south-1

Attach the cluster admin policy:

bash
aws eks associate-access-policy --cluster-name glc-eks-unauth-lab --principal-arn arn:aws:iam::342524208658:role/deploy-role --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy --access-scope type=cluster --region ap-south-1

Import CLI-created resources into Terraform later — otherwise the next terraform apply may try to recreate them.


Troubleshooting

Symptom Likely cause Fix
You must be logged in to the server (Unauthorized) Missing EKS access entry or policy for the IAM principal behind kubectl Add enable_cluster_creator_admin_permissions or access_entries; confirm with aws eks list-access-entries
the server has asked for the client to provide credentials Expired credentials, token failure, or missing access entry Confirm aws sts get-caller-identity; refresh aws login credentials; then check access entries
terraform apply user works, teammate fails Only cluster creator received admin, or teammate uses assumed-role ARN without matching permanent role entry Add access_entries for the teammate's permanent IAM role ARN
Access entry exists, still Unauthorized Missing policy association, propagation delay, or wrong policy scope Wait a few seconds and retry; run aws eks list-associated-access-policies for that principal
Worked yesterday on module v19 Module no longer manages legacy aws-auth mappings the old way Prefer access entries; legacy aws-auth may still work under API_AND_CONFIG_MAP but is not the Terraform-managed path
update-kubeconfig fails AWS credentials or cluster name/region mismatch Fix credentials guide; verify terraform output cluster name

For broader Terraform failure stages, see Terraform troubleshooting.


Summary

terraform apply on EKS can succeed while kubectl still returns Unauthorized. The cluster is healthy — your IAM principal may be missing from the EKS access list, or kubectl may be using expired or mismatched AWS credentials.

After terraform-aws-modules/eks v20, access entries are the preferred Terraform-managed path for human and automation access. Legacy aws-auth mappings can still coexist when the cluster uses API_AND_CONFIG_MAP.

Fix in short:

  1. Run aws sts get-caller-identity in the shell that runs kubectl — map assumed-role sessions to the permanent IAM role ARN
  2. Run aws eks list-access-entries and confirm that permanent ARN is listed
  3. Add enable_cluster_creator_admin_permissions = true when the same principal runs Terraform and kubectl
  4. Add access_entries for every other IAM role (or user) that needs API access
  5. Re-run terraform apply, wait a few seconds for propagation, then kubectl get nodes and look for Ready

Keep access entries in Terraform so module upgrades and cluster recreations do not silently remove API access.


References


Frequently Asked Questions

Is this the same as No valid credential sources found?

No. Missing AWS credentials stop Terraform or the get-token plugin before EKS is contacted. Unauthorized means AWS signed the request, but EKS denied API access for that principal.

Do I still edit the aws-auth ConfigMap?

On new clusters with API_AND_CONFIG_MAP, legacy aws-auth mappings may still work, but access entries are the preferred Terraform-managed path. The v21 module does not manage mapUsers the way older v19 examples did.

Why did enable_cluster_creator_admin_permissions fix only one user?

It grants admin to the IAM principal that runs terraform apply. Everyone else needs their own access_entries block.

Can I use mapUsers in aws-auth instead?

Only when the cluster authentication mode still includes the ConfigMap (API_AND_CONFIG_MAP or CONFIG_MAP) and you deliberately manage it outside Terraform. For new access, use access_entries in the EKS module.

kubectl works for get nodes but not helm install

The principal may have view access only. Associate AmazonEKSClusterAdminPolicy or a narrower policy that includes the API groups Helm needs.

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

Linux Administrator

Highly skilled at managing Debian, Ubuntu, CentOS, Oracle Linux, and Red Hat servers. Proficient in bash scripting, Ansible, and AWX central server management, he handles server operations on OpenStack, KVM, Proxmox, and VMware.

  • Debian
  • Ubuntu
  • Linux
  • Red Hat Enterprise Linux
  • Shell Script
  • System Administration