Does your GitHub Actions or GitLab CI pipeline contains these secrets: AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY? That's considered a vulnerable bad practice.

It means if someone gains access to your repository secrets or CI environment, they potentially have AWS credentials that remain valid until you manually rotate them.

A much better approach is OIDC (OpenID Connect).

Instead of storing credentials, your pipeline proves its identity to AWS and AWS issues temporary credentials that expire automatically.

Think of it like this:

❌ Traditional approach = Permanent office key
✅ OIDC = Visitor pass that's valid for only a short time

So what is OIDC?

OIDC is an identity protocol.

GitHub Actions and GitLab CI can generate a signed identity token that says who they are and AWS verifies that identity and, if it matches your configured rules, allows the workflow to assume an IAM Role using temporary credentials.

No access keys or secrets stored in your repository.

Register GitHub (token.actions.githubusercontent.com) or GitLab (gitlab.com or your self-managed instance) as a trusted OIDC Identity Provider.
Register GitHub (token.actions.githubusercontent.com) or GitLab (gitlab.com or your self-managed instance) as a trusted OIDC Identity Provider.

How does the authentication actually work?

When the pipeline is triggered the runner generates JWT token with issuer, audience, etc, and make validation request to AWS.
AWS validates token against role and returns temporary credentials
Runner can now access AWS resources per what the role allows.

The pipeline exchanges its identity for temporary AWS credentials.
The pipeline exchanges its identity for temporary AWS credentials.

Step 1: Configure AWS
First, create an OIDC Identity Provider in AWS IAM.

For GitHub:
https://token.actions.githubusercontent.com

For GitLab:
https://gitlab.com
(or your self-managed GitLab URL)

Next, create an IAM Role that trusts this provider.

The trust policy can also restrict access so that only a specific group, project, repository or branch can assume the role.

Restricting any other repository from accessing AWS using that IAM role.

Restrict role assumption to specific repositories, branches, or environments for least-privilege access.
Restrict role assumption to specific repositories, branches, or environments for least-privilege access.

GitHub Actions example

name: Deploy

on:
  push:
    branches:
      - main

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-deploy-role
          aws-region: eu-west-1

      - name: Verify identity
        run: aws sts get-caller-identity

      - name: Deploy
        run: aws s3 ls
Enter fullscreen mode Exit fullscreen mode

GitHub exchanges its OIDC identity for temporary AWS credentials.
GitHub exchanges its OIDC identity for temporary AWS credentials.

One important requirement is enabling OIDC permissions:

permissions:
  id-token: write
  contents: read
Enter fullscreen mode Exit fullscreen mode

Then simply configure AWS credentials:

GitLab CI example

GitLab also supports OIDC by issuing an identity token to the job.

stages:
  - aws-configure

aws-configure:
  stage: aws-configure
  image:
    name: amazon/aws-cli:latest
    entrypoint: [""]

  id_tokens:
    AWS_OIDC_TOKEN:
      aud: https://gitlab.com

  script:
    - > 
      STS_ASSUME_ROLE_OUTPUT=$(aws sts assume-role-with-web-identity 
      --role-arn "${OIDC_ROLE_ARN}" 
      --role-session-name "gitlab-ci-${CI_PIPELINE_ID}" 
      --web-identity-token "${AWS_OIDC_TOKEN}" 
      --duration-seconds 3600 
      --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" 
      --output text
      )

    - export AWS_ACCESS_KEY_ID=$(echo "$STS_ASSUME_ROLE_OUTPUT" | awk '{print $1}')
    - export AWS_SECRET_ACCESS_KEY=$(echo "$STS_ASSUME_ROLE_OUTPUT" | awk '{print $2}')
    - export AWS_SESSION_TOKEN=$(echo "$STS_ASSUME_ROLE_OUTPUT" | awk '{print $3}')

    - aws sts get-caller-identity
Enter fullscreen mode Exit fullscreen mode

Here, AWS STS returns temporary credentials that expire automatically after the configured duration.

GitLab authenticates using OIDC and receives short-lived credentials from AWS STS.
GitLab authenticates using OIDC and receives short-lived credentials from AWS STS.

Why is this considered a best practice?

  • No long-lived AWS secrets stored in CI/CD
  • Credentials expire automatically
  • Easy to restrict access by repository, branch, or environment
  • Follows the principle of least privilege
  • Eliminates the operational burden of rotating access keys

Modern cloud authentication is moving away from "Who knows the secret?" toward "Can you prove who you are?"

OIDC is one of the simplest ways to adopt that model for your CI/CD pipelines.

#AWS #DevOps #DevSecOps #CloudSecurity #GitHubActions #GitLabCI #OIDC #IAM #STS #CICD