Secure EKS API Access with Authentication and Authorization Controls using Terraform

Amazon EKS hosts containerized workloads, but any entity that needs to communicate with the cluster, whether to install addons, deploy applications via Helm, or check pod statuses with kubectl, must first have access to the cluster. By default, the IAM principal that creates the cluster receives implicit administrative access. However, for subsequent operations, a different principal is often involved. The CI/CD pipeline to create the EKS cluster might use an automation role, while the platform team uses a separate role for manual troubleshooting. Each of these principals needs explicit access to the cluster.

By default, EKS uses the CONFIG_MAP authentication mode, which relies on a Kubernetes ConfigMap called aws-auth to map IAM principals (roles and users) to Kubernetes RBAC groups. This ConfigMap is inside the cluster as a Kubernetes resource, which means it can only be edited by someone who already has cluster access via kubectl. Hence, the drawbacks include the risk that a malformed edit can lock everyone out of the cluster (requiring AWS support to recover), and changes aren’t tracked in AWS CloudTrail since they’re Kubernetes API calls rather than AWS API calls. Additionally, granting granular access (such as read-only access to a specific namespace) requires coordinating between two systems, first mapping the IAM principal in the ConfigMap, then separately creating Kubernetes RBAC Roles and RoleBindings within the cluster to define what that principal can do.

The modern mechanism is API-based authentication, which replaces the ConfigMap with EKS Access Entries managed via the AWS API. Using this mode, you define who can access the cluster and what they can do using aws_eks_access_entry and aws_eks_access_policy_association resources in Terraform, respectively. These entries are managed outside the cluster, tracked in CloudTrail, and can’t be accidentally deleted by someone running kubectl. AWS provides managed access policies (such as AmazonEKSClusterAdminPolicy and AmazonEKSViewPolicy) that map to common Kubernetes RBAC roles, and access can be scoped to the entire cluster or to specific namespaces within a single Terraform resource, consolidating authentication and authorization in one place.

In this article, I’ll migrate an existing EKS cluster from the default ConfigMap authentication to API-only mode, configure access entries for the GitHub Actions pipeline role, and demonstrate granular authorization by granting a separate reader role view-only access using Terraform.

There are two scenarios for enabling API authentication. For a new cluster, you can set authentication_mode = "API" directly at creation time since there is no existing ConfigMap-based access to preserve and manage. For an existing cluster created without the access_config block (or in CONFIG_MAP mode), the migration is a two-step process because EKS does not allow jumping directly from CONFIG_MAP to API. The intermediate step is API_AND_CONFIG_MAP, which enables access entries while preserving existing ConfigMap-based access. Once the access entries are in place and verified, the mode can be changed to API only. This progression is one-way: once the access entry method is enabled, it cannot be disabled. For more details, refer to the AWS documentation on changing authentication mode.

In our case, this EKS cluster did not have the access_config block defined previously. Hence, I had to follow the two-step migration approach. In the first iteration, I set authentication_mode to API_AND_CONFIG_MAP, which allowed existing authentications to continue working while the access entry and access policy association resources were being provisioned. When access entries are enabled, EKS automatically creates an access entry for the original cluster creator, preserving that principal’s administrative access. In the second iteration, I changed the value to API, completing the migration.

Solution Overview

This solution adds access configuration to the existing EKS cluster. The changes are in the infrastructure configuration. These are: an access_config block on the cluster resource, and aws_eks_access_entry and aws_eks_access_policy_association resources for each principal that needs cluster access.
EKS Access Entries architecture diagram
This article addresses two related challenges: first, migrating the cluster’s authentication mechanism from ConfigMap to API mode, and second, demonstrating how access entries provide granular authorization by assigning different policies to different principals.

Since this was a two-step process, I organized the migration into two iterations:

1. First iteration: Set authentication mode to API_AND_CONFIG_MAP, add an access entry with AmazonEKSClusterAdminPolicy for the GitHub Actions pipeline role, and create a platform reader IAM role (without an access entry).
2. Second iteration: Switch to API mode and add an access entry with AmazonEKSViewPolicy for the platform reader role to demonstrate granular access control.

You can find the complete implementation in my GitHub repository: kunduso-org/aws-eks-terraform (branch: eks-api-access). The code includes Terraform configurations, GitHub Actions CI/CD, and security scanning with Checkov.

Prerequisites

This use case builds on the first article in this series, Provision a Secure Amazon EKS Cluster using Terraform and GitHub Actions. Please go through that before starting here. The eks-api-access branch also includes components from Article 2 and Article 3. Those components are not required for access entry configuration, but the branch builds on them.

First Iteration: Enable Access Entries

Implementation

In this iteration, I added the access_config block to the existing EKS cluster and created an access entry for the GitHub Actions pipeline role that deploys into this cluster. To validate that access entries are enforced, I also created a separate IAM role without granting it an access entry, confirming it cannot reach the Kubernetes API.

Step 1: Add the access_config block to the EKS cluster
The access_config block accepts two properties: authentication_mode and bootstrap_cluster_creator_admin_permissions. Setting authentication_mode to API_AND_CONFIG_MAP enables access entries while preserving any existing ConfigMap-based access during the transition.
eks-cluster.tf access_config block
Important: The bootstrap_cluster_creator_admin_permissions attribute must be explicitly set to true when adding access_config to an existing cluster. This cluster was originally created without an access_config block, which means AWS applied the default (true) at creation time, but that value was never tracked in the Terraform state. Adding access_config without this property causes Terraform to detect a change on a create-time-only attribute, which forces a cluster replacement. Setting it explicitly to true matches what already exists on the AWS side, resulting in no diff and no replacement.

Step 2: Create the access entry for the GitHub Actions role
The aws_eks_access_entry resource registers an IAM role ARN as a principal that can authenticate to the cluster. The aws_eks_access_policy_association then defines what that principal can do by attaching a managed access policy. Here, the GitHub Actions role receives AmazonEKSClusterAdminPolicy with cluster-wide scope, granting the pipeline full administrative access to deploy infrastructure, install Helm charts, and apply manifests.
eks-access.tf access entry and policy association
Since this is the same role that originally created the cluster, EKS automatically grants it administrative access via bootstrap_cluster_creator_admin_permissions. The explicit access entry is technically redundant, but I include it so that the access grant is visible in Terraform state and version controlled alongside the rest of the infrastructure. If the bootstrap permission were ever revoked or the cluster were recreated with a different creator, the explicit entry would ensure the pipeline continues to work.

Step 3: Create the platform reader IAM role
To demonstrate that access entries control cluster access, I created a second IAM role (app-14-platform-reader) outside of the EKS infrastructure code. This role exists solely to validate the access entry mechanism in this iteration and to receive scoped permissions in the next.
IAM create-role output for platform reader
The role has a single inline policy that allows eks:DescribeCluster on the cluster resource. This is the minimum IAM permission needed to run aws eks update-kubeconfig and generate a kubeconfig file. No managed policies are attached, and no EKS access entry exists for this role yet.
IAM role policy and attached policies
Deployment

The github_actions_role_arn variable is marked as sensitive and receives its value via the -var flag in the workflow’s terraform plan and terraform apply steps. The value comes from the IAM_ROLE GitHub Actions secret, which is already configured because it is the same role the pipeline uses to authenticate with AWS. The only workflow change was adding -var="github_actions_role_arn=${{ secrets.IAM_ROLE }}" to both commands.

After merging PR #44, the pipeline completes successfully, confirming that the access entry is in place and that the API_AND_CONFIG_MAP mode works alongside the existing bootstrap permissions.

Validation

To confirm that access entries are enforced, I assumed the platform reader role and attempted to interact with the cluster. The aws sts get-caller-identity output confirms the assumed role. The aws eks update-kubeconfig command succeeds because the role has the eks:DescribeCluster IAM permission. However, kubectl get nodes fails with “You must be logged in to the server.” This proves that IAM permissions alone are not enough. Without an EKS access entry, the role cannot authenticate to the Kubernetes API.
First iteration validation - reader role denied

Second Iteration: Switch to API-Only Mode

Implementation

Now that the access_config block was defined and the access entry was registered for the GitHub Actions IAM role, this iteration tightened the authentication mode from API_AND_CONFIG_MAP to API, completing the one-way migration. I also created an access entry for the platform reader role with AmazonEKSViewPolicy to demonstrate that access entries provide granular, scoped authorization: the reader can view cluster resources but cannot make changes.

Step 4: Switch authentication mode to API
The only change to eks-cluster.tf was updating authentication_mode from "API_AND_CONFIG_MAP" to "API". This completes the one-way migration. From this point, the ConfigMap is no longer consulted for authentication, and access entries are the sole mechanism.
eks-cluster.tf with API authentication mode
Step 5: Add the access entry for the platform reader role
To demonstrate how access entries grant scoped permissions to additional principals, I added an entry for the platform reader role created in Step 3. The same pattern from Step 2 applies: an aws_eks_access_entry registers the role as a principal, and an aws_eks_access_policy_association attaches AmazonEKSViewPolicy with cluster-wide scope. This policy grants read access (get, list, watch) to most namespace-scoped Kubernetes resources, such as pods, services, configmaps, and namespaces, but not to cluster-level resources, such as nodes.
eks-access.tf platform reader access entry
Deployment

After merging PR #45, the same infrastructure workflow deploys the authentication mode change and the new access entry. No workflow modifications were needed since the platform_reader_role_arn variable has a default value. With this deployment, the cluster is now in API-only mode, and the platform reader role has an access entry with view permissions.

Validation

To validate granular access control, I assumed the platform reader role and ran two commands against the cluster.
Second iteration validation - view access and write denied
kubectl get namespaces returns all five namespaces, confirming the role can read cluster resources through its AmazonEKSViewPolicy access entry. However, kubectl create namespace test fails with “Forbidden,” proving the role cannot create or modify resources. The access entry grants exactly what the policy allows and nothing more.

Compare this to the first iteration, where the same role was completely denied with “You must be logged in to the server.” The difference is the access entry. Without one, the role cannot authenticate at all. With one, the role authenticates, and the attached policy defines what it can do.

Conclusion

In this article, I migrated an existing EKS cluster from the default ConfigMap authentication to API-only mode and configured access entries for two principals with different permission levels. The migration followed a two-step process (API_AND_CONFIG_MAP, then API) to avoid breaking existing access during the transition. The GitHub Actions pipeline role received full cluster admin access, while a platform reader role received view-only access, demonstrating how access entries consolidate authentication and authorization into a single, version-controlled configuration.

In the next article, I’ll build on this by integrating AWS Secrets Manager with the EKS cluster using the CSI Secrets Store Driver.

If you have any questions or suggestions, feel free to comment or get in touch.

Leave a Reply