<?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: Vijay Mourya</title>
    <description>The latest articles on DEV Community by Vijay Mourya (@vjmourya).</description>
    <link>https://dev.to/vjmourya</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1066915%2F8968dfb2-50f7-4e3f-932d-00f1fcb99bc3.JPG</url>
      <title>DEV Community: Vijay Mourya</title>
      <link>https://dev.to/vjmourya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vjmourya"/>
    <language>en</language>
    <item>
      <title>Authenticate GitLab Pipelines to AWS with OIDC (No Static Keys 🚀)</title>
      <dc:creator>Vijay Mourya</dc:creator>
      <pubDate>Mon, 01 Sep 2025 16:52:56 +0000</pubDate>
      <link>https://dev.to/vjmourya/authenticate-gitlab-pipelines-to-aws-with-oidc-no-static-keys--4apd</link>
      <guid>https://dev.to/vjmourya/authenticate-gitlab-pipelines-to-aws-with-oidc-no-static-keys--4apd</guid>
      <description>&lt;h2&gt;
  
  
  🚀 Goodbye Static AWS Keys, Hello GitLab OIDC
&lt;/h2&gt;

&lt;p&gt;Be honest — have you ever stored &lt;strong&gt;AWS keys in GitLab CI/CD variables&lt;/strong&gt; and then thought:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What if someone leaks these keys?"
&lt;/li&gt;
&lt;li&gt;"How do I rotate them across projects?"
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yeah… we’ve all been there.  &lt;/p&gt;

&lt;p&gt;Luckily, AWS + GitLab have a much better way: &lt;strong&gt;OIDC authentication&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;With OIDC, your GitLab jobs can request &lt;strong&gt;short-lived AWS credentials on the fly&lt;/strong&gt;. No static keys. No rotation pain. More security. 🎉  &lt;/p&gt;




&lt;h2&gt;
  
  
  🗺 The Big Picture
&lt;/h2&gt;

&lt;p&gt;Here’s the 30-second flow of what happens when a GitLab pipeline hits AWS using OIDC:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpsllulttebc5k0p2zzp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpsllulttebc5k0p2zzp.png" alt="OIDC auth flow" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🔑 Why OIDC Rocks
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Short-lived creds&lt;/strong&gt; (expire ~1h, customizable TTL).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoped&lt;/strong&gt; (repo + branch conditions).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No stored secrets&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works with &lt;em&gt;any&lt;/em&gt; GitLab repo&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ✅ Step 0 — Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS account (IAM admin).&lt;/li&gt;
&lt;li&gt;GitLab.com repo (works with self-hosted if OIDC enabled).&lt;/li&gt;
&lt;li&gt;GitLab version &lt;strong&gt;15.7+&lt;/strong&gt; (needed for &lt;code&gt;id_tokens:&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;AWS CLI v2 in your runner/job.&lt;/li&gt;
&lt;li&gt;(Optional) Terraform for IaC.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🏗 Step 1 — Add OIDC Identity Provider in AWS
&lt;/h2&gt;

&lt;p&gt;👉 In &lt;strong&gt;IAM → Identity providers → Add&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type: &lt;code&gt;OpenID Connect&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;URL: &lt;code&gt;https://gitlab.com&lt;/code&gt; (or self-hosted URL)&lt;/li&gt;
&lt;li&gt;Audience: &lt;code&gt;https://gitlab.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terraform snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_openid_connect_provider"&lt;/span&gt; &lt;span class="s2"&gt;"gitlab"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;url&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://gitlab.com"&lt;/span&gt;
  &lt;span class="nx"&gt;client_id_list&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"https://gitlab.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;thumbprint_list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"9e99a48a9960b14926bb7f3b02e22da2b0ab7280"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# GitLab CA&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🛡 Step 2 — Create IAM Role + Trust Policy
&lt;/h2&gt;

&lt;p&gt;Trust only the repo + branch you want. Example:&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::&amp;lt;ACCOUNT_ID&amp;gt;:oidc-provider/gitlab.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;"gitlab.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;"https://gitlab.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"gitlab.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;"project_path:mygroup/myrepo:ref_type:branch:ref: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;h2&gt;
  
  
  ⚙️ Step 3 — Ask for a Token in GitLab CI Job
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;.gitlab-ci.yml&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;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazon/aws-cli:2.15.0&lt;/span&gt;
  &lt;span class="na"&gt;id_tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;OIDC_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://gitlab.com&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aws sts assume-role-with-web-identity \&lt;/span&gt;
        &lt;span class="s"&gt;--role-arn "arn:aws:iam::&amp;lt;ACCOUNT_ID&amp;gt;:role/gitlab-oidc-role" \&lt;/span&gt;
        &lt;span class="s"&gt;--role-session-name "gl-$CI_PROJECT_ID-$CI_PIPELINE_ID" \&lt;/span&gt;
        &lt;span class="s"&gt;--web-identity-token "$OIDC_TOKEN" \&lt;/span&gt;
        &lt;span class="s"&gt;--duration-seconds 3600 &amp;gt; creds.json&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export AWS_ACCESS_KEY_ID=$(jq -r .Credentials.AccessKeyId creds.json)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export AWS_SECRET_ACCESS_KEY=$(jq -r .Credentials.SecretAccessKey creds.json)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export AWS_SESSION_TOKEN=$(jq -r .Credentials.SessionToken creds.json)&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aws sts get-caller-identity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🔐 Step 4 — Safer: Token File Method
&lt;/h2&gt;

&lt;p&gt;Instead of exporting keys, use AWS SDK auto-assume:&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;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazon/aws-cli:2.15.0&lt;/span&gt;
  &lt;span class="na"&gt;id_tokens&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;OIDC_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;aud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://gitlab.com&lt;/span&gt;
  &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;echo "$OIDC_TOKEN" &amp;gt; /tmp/web_identity_token&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export AWS_ROLE_ARN="arn:aws:iam::&amp;lt;ACCOUNT_ID&amp;gt;:role/gitlab-oidc-role"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export AWS_WEB_IDENTITY_TOKEN_FILE="/tmp/web_identity_token"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;export AWS_ROLE_SESSION_NAME="gl-$CI_PROJECT_ID"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aws sts get-caller-identity&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;aws s3 ls s3://my-bucket&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧪 Step 5 — Test &amp;amp; Tighten
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Run pipeline → check &lt;code&gt;aws sts get-caller-identity&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Lock trust policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow only &lt;code&gt;tags&lt;/code&gt; or &lt;code&gt;release/*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;StringLike&lt;/code&gt; for wildcards.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Attach &lt;strong&gt;least-privilege IAM policies&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  🛠 Troubleshooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AccessDenied&lt;/strong&gt; → condition mismatch (&lt;code&gt;aud&lt;/code&gt; or &lt;code&gt;sub&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification key error&lt;/strong&gt; → wrong issuer URL.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty OIDC token&lt;/strong&gt; → forgot &lt;code&gt;id_tokens:&lt;/code&gt; block.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔒 Best Practices
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prefer token-file method.&lt;/li&gt;
&lt;li&gt;Scope trust (repo + ref).&lt;/li&gt;
&lt;li&gt;Separate roles per env (dev/prod).&lt;/li&gt;
&lt;li&gt;Audit with CloudTrail.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🎉 Wrapping Up
&lt;/h2&gt;

&lt;p&gt;You just kicked static AWS keys out of your GitLab pipelines.&lt;/p&gt;

&lt;p&gt;✅ Short-lived creds.&lt;br&gt;
✅ Branch/repo scoped trust.&lt;br&gt;
✅ No secret storage.&lt;/p&gt;

&lt;p&gt;Now your team can deploy to AWS with confidence. 🚀&lt;/p&gt;




</description>
      <category>aws</category>
      <category>gitlab</category>
      <category>security</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
