<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mayank Thakur</title>
    <description>The latest articles on DEV Community by Mayank Thakur (@mayankthakur001).</description>
    <link>https://dev.to/mayankthakur001</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4089964%2Fc1654db4-8e98-44bb-9ec0-8a42bb19f29d.png</url>
      <title>DEV Community: Mayank Thakur</title>
      <link>https://dev.to/mayankthakur001</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mayankthakur001"/>
    <language>en</language>
    <item>
      <title>3 Tier Application On EKS</title>
      <dc:creator>Mayank Thakur</dc:creator>
      <pubDate>Sat, 22 Aug 2026 16:45:45 +0000</pubDate>
      <link>https://dev.to/mayankthakur001/3-tier-application-on-eks-oc7</link>
      <guid>https://dev.to/mayankthakur001/3-tier-application-on-eks-oc7</guid>
      <description>&lt;p&gt;I built a 3-tier application running on Amazon EKS, deployed by a GitHub Actions pipeline that runs on every merge to &lt;code&gt;main&lt;/code&gt;. The first version of that pipeline had two secrets sitting in the repo settings: &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Github- &lt;a href="https://github.com/itzmayank01/3-tier-user-platform-devops" rel="noopener noreferrer"&gt;https://github.com/itzmayank01/3-tier-user-platform-devops&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;They worked. They were also a bad idea, and getting rid of them taught me more about EKS authentication than anything else in the project.&lt;/p&gt;

&lt;p&gt;This post is the setup I ended up with. It includes the part that broke for me and that most tutorials skip: getting past IAM is only half the job, because EKS has its own authorization layer on top.&lt;/p&gt;

&lt;p&gt;Why long-lived keys are a problem&lt;/p&gt;

&lt;p&gt;An IAM access key does not expire. If it leaks, it stays valid until someone notices and revokes it. In a CI pipeline that risk is real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The key lives in repository settings, and anyone with admin access to the repo can see who added it&lt;/li&gt;
&lt;li&gt;A malicious or careless workflow change can print it, exfiltrate it, or use it for something unrelated&lt;/li&gt;
&lt;li&gt;Rotating it means updating every repo that uses it, so in practice nobody rotates it&lt;/li&gt;
&lt;li&gt;A fork or a compromised third-party action widens the blast radius&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OIDC replaces that with a token that lives for the length of a single job.&lt;/p&gt;

&lt;p&gt;How OIDC actually works here&lt;/p&gt;

&lt;p&gt;Four steps, and it helps to hold the whole picture in your head before you start:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GitHub Actions mints a short-lived JSON Web Token describing the job: which repo, which branch, which workflow&lt;/li&gt;
&lt;li&gt;Your workflow sends that token to AWS STS&lt;/li&gt;
&lt;li&gt;AWS checks the token signature against a registered identity provider, then checks the claims inside it against your IAM role's trust policy&lt;/li&gt;
&lt;li&gt;If both pass, STS hands back temporary credentials that expire when the job ends&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No secret is stored anywhere. The trust is in the claims, not in a shared string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Register GitHub as an identity provider in IAM
&lt;/h2&gt;

&lt;p&gt;You only do this once per AWS account.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam create-open-id-connect-provider &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://token.actions.githubusercontent.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--client-id-list&lt;/span&gt; sts.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have used GitHub OIDC before, you may remember passing a certificate thumbprint. AWS no longer requires you to manage that for this provider - it verifies and rotates the certificate itself. If your tooling still demands a value, older guides use &lt;code&gt;6938fd4d98bab03faadb97b34396831e3780aea1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Check that it landed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam list-open-id-connect-providers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Create the IAM role and its trust policy
&lt;/h2&gt;

&lt;p&gt;This is where most of the security lives. The trust policy decides &lt;em&gt;which&lt;/em&gt; GitHub jobs are allowed to assume this role.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;trust-policy.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Federated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRoleWithWebIdentity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"token.actions.githubusercontent.com:aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts.amazonaws.com"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"StringLike"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"token.actions.githubusercontent.com:sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"repo:my-org/my-repo:ref:refs/heads/main"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws iam create-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role-name&lt;/span&gt; GitHubActionsEKSDeployRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--assume-role-policy-document&lt;/span&gt; file://trust-policy.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Read the &lt;code&gt;sub&lt;/code&gt; condition twice.&lt;/strong&gt; It is the only thing standing between "my main branch can deploy" and "anyone who opens a pull request against my repo can deploy". Some patterns and what they mean:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Who can assume the role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;repo:my-org/my-repo:ref:refs/heads/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only jobs running on the main branch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;repo:my-org/my-repo:environment:production&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Only jobs targeting the &lt;code&gt;production&lt;/code&gt; environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;repo:my-org/my-repo:pull_request&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Any pull request, including from forks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;repo:my-org/*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every repo in the org&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;repo:*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anyone on GitHub. Never do this.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I use the environment form for production, because GitHub environments let me add a required reviewer on top. IAM enforces which jobs can assume the role; GitHub enforces who can trigger those jobs.&lt;/p&gt;

&lt;p&gt;Then attach permissions. The deploy role needs very little - enough to look up the cluster endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eks:DescribeCluster"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:eks:ap-south-1:111122223333:cluster/my-cluster"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the same job also pushes images, add the ECR actions it needs (&lt;code&gt;ecr:GetAuthorizationToken&lt;/code&gt;, &lt;code&gt;ecr:BatchCheckLayerAvailability&lt;/code&gt;, &lt;code&gt;ecr:PutImage&lt;/code&gt;, &lt;code&gt;ecr:InitiateLayerUpload&lt;/code&gt;, &lt;code&gt;ecr:UploadLayerPart&lt;/code&gt;, &lt;code&gt;ecr:CompleteLayerUpload&lt;/code&gt;). I prefer two separate roles - one for build and push, one for deploy - so a compromised build step cannot touch the cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: The workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to EKS&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure AWS credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::111122223333:role/GitHubActionsEKSDeployRole&lt;/span&gt;
          &lt;span class="na"&gt;role-session-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gha-deploy-${{ github.run_id }}&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-south-1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update kubeconfig&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws eks update-kubeconfig --name my-cluster --region ap-south-1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;kubectl set image deployment/frontend \&lt;/span&gt;
            &lt;span class="s"&gt;frontend=111122223333.dkr.ecr.ap-south-1.amazonaws.com/frontend:${{ github.sha }}&lt;/span&gt;
          &lt;span class="s"&gt;kubectl rollout status deployment/frontend --timeout=120s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to notice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;permissions: id-token: write&lt;/code&gt; is not optional.&lt;/strong&gt; Without it, GitHub never mints the token and the credentials step fails with something like &lt;code&gt;Could not assume role with OIDC&lt;/code&gt;. This is the single most common failure, and the error message does not point at the missing permission. Also note that declaring a &lt;code&gt;permissions&lt;/code&gt; block resets &lt;em&gt;all&lt;/em&gt; permissions to none, so you have to list &lt;code&gt;contents: read&lt;/code&gt; explicitly if your job checks out code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tag images by commit SHA, not &lt;code&gt;latest&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;${{ github.sha }}&lt;/code&gt; makes every deploy traceable to a commit, and rolling back is just pointing at the previous SHA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: The part tutorials skip
&lt;/h2&gt;

&lt;p&gt;At this point my pipeline authenticated to AWS perfectly and then died on the &lt;code&gt;kubectl&lt;/code&gt; step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: You must be logged in to the server (Unauthorized)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The credentials were fine. The problem is that &lt;strong&gt;EKS has two separate layers&lt;/strong&gt;. IAM decides whether you can talk to the cluster's API endpoint. Kubernetes RBAC decides what you can do once you are there. A brand new IAM role has an AWS identity and no Kubernetes identity at all.&lt;/p&gt;

&lt;p&gt;You have to explicitly map the role into the cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  The modern way: EKS access entries
&lt;/h3&gt;

&lt;p&gt;Access entries are an EKS API, so you manage cluster access with the same tooling as everything else. First check what your cluster is using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks describe-cluster &lt;span class="nt"&gt;--name&lt;/span&gt; my-cluster &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'cluster.accessConfig'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;authenticationMode&lt;/code&gt; is &lt;code&gt;CONFIG_MAP&lt;/code&gt;, switch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks update-cluster-config &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; my-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--access-config&lt;/span&gt; &lt;span class="nv"&gt;authenticationMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;API_AND_CONFIG_MAP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create the entry and give it a scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks create-access-entry &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; my-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--principal-arn&lt;/span&gt; arn:aws:iam::111122223333:role/GitHubActionsEKSDeployRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; STANDARD

aws eks associate-access-policy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; my-cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--principal-arn&lt;/span&gt; arn:aws:iam::111122223333:role/GitHubActionsEKSDeployRole &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--policy-arn&lt;/span&gt; arn:aws:eks::aws:cluster-access-policy/AmazonEKSEditPolicy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--access-scope&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;namespace,namespaces&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AmazonEKSEditPolicy&lt;/code&gt; scoped to one namespace is usually right for a deploy role. It can update workloads and nothing else. Resist reaching for &lt;code&gt;AmazonEKSClusterAdminPolicy&lt;/code&gt; - it is the &lt;code&gt;AdministratorAccess&lt;/code&gt; of EKS.&lt;/p&gt;

&lt;p&gt;One direction-of-travel note: you can move &lt;code&gt;CONFIG_MAP&lt;/code&gt; to &lt;code&gt;API_AND_CONFIG_MAP&lt;/code&gt; to &lt;code&gt;API&lt;/code&gt;, but you cannot go back. Migrate deliberately.&lt;/p&gt;

&lt;h3&gt;
  
  
  The older way: the aws-auth ConfigMap
&lt;/h3&gt;

&lt;p&gt;If you are on an older cluster still using &lt;code&gt;CONFIG_MAP&lt;/code&gt; mode, you edit &lt;code&gt;aws-auth&lt;/code&gt; in &lt;code&gt;kube-system&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-auth&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kube-system&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mapRoles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;- rolearn: arn:aws:iam::111122223333:role/GitHubActionsEKSDeployRole&lt;/span&gt;
      &lt;span class="s"&gt;username: github-actions&lt;/span&gt;
      &lt;span class="s"&gt;groups:&lt;/span&gt;
        &lt;span class="s"&gt;- deployers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then bind &lt;code&gt;deployers&lt;/code&gt; to a Role or ClusterRole with normal Kubernetes RBAC. Two warnings if you go this route: a bad edit here can lock everyone out of the cluster, including you, and if your IAM role has a path (&lt;code&gt;/ci/GitHubActionsEKSDeployRole&lt;/code&gt;), you must strip the path out of the ARN before EKS will match it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas worth knowing before you start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Could not assume role with OIDC&lt;/code&gt;&lt;/strong&gt; - the missing &lt;code&gt;id-token: write&lt;/code&gt; permission, nine times out of ten&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Not authorized to perform sts:AssumeRoleWithWebIdentity&lt;/code&gt;&lt;/strong&gt; - your &lt;code&gt;sub&lt;/code&gt; claim does not match. Print &lt;code&gt;github.ref&lt;/code&gt; and &lt;code&gt;github.workflow_ref&lt;/code&gt; in a debug step and compare them character by character with the trust policy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;You must be logged in to the server (Unauthorized)&lt;/code&gt;&lt;/strong&gt; - IAM is fine, the cluster mapping is missing. This is Step 4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;kubectl&lt;/code&gt; hangs, then times out&lt;/strong&gt; - your cluster API endpoint is private and a hosted runner cannot reach it. You need a self-hosted runner inside the VPC, or a VPC-connected alternative&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The role works from your laptop but not from CI&lt;/strong&gt; - you are probably testing with a user that has broader permissions than the role. Assume the role locally with &lt;code&gt;aws sts assume-role&lt;/code&gt; and retest&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What you actually get
&lt;/h2&gt;

&lt;p&gt;After the migration my repo has zero AWS secrets. The credentials a job holds are valid for that job only, scoped to one namespace, on one branch. If someone gets a malicious workflow merged, the worst case is bounded by an IAM policy and a Kubernetes RBAC binding instead of by whatever those static keys happened to be allowed to do.&lt;/p&gt;

&lt;p&gt;That is a much better position to defend, and it took an afternoon.&lt;/p&gt;

&lt;p&gt;Next in this series: cutting that same pipeline from five minutes to two and a half with self-hosted runners and parallel stages.&lt;/p&gt;




</description>
      <category>aws</category>
      <category>cicd</category>
      <category>devops</category>
      <category>kubernetes</category>
    </item>
  </channel>
</rss>
