โšก Onwuachi Control Plane

OpenID Connect (OIDC)

Overview

OpenID Connect (OIDC) is an identity protocol built on top of OAuth 2.0. It adds a standardized identity layer that allows an application or service to verify who or what is making a request without directly handling the user’s password or requiring long-lived credentials.

OIDC is commonly associated with user authentication and Single Sign-On (SSO), but the same underlying trust model can also be used for workload identity federation.

In a modern DevOps environment, GitHub Actions can use OIDC to authenticate to cloud providers such as AWS without storing long-lived cloud access keys in GitHub repository secrets.

This creates a trust chain:

GitHub Actions
      โ”‚
      โ”‚ Requests short-lived OIDC token
      โ–ผ
GitHub OIDC Provider
      โ”‚
      โ”‚ Signed JWT
      โ–ผ
AWS IAM OIDC Provider
      โ”‚
      โ”‚ Validates token claims
      โ–ผ
IAM Role Trust Policy
      โ”‚
      โ”‚ Allows AssumeRoleWithWebIdentity
      โ–ผ
Temporary AWS Credentials
      โ”‚
      โ–ผ
AWS Services

The result is a CI/CD system where GitHub workflows can receive short-lived AWS credentials only when they satisfy the conditions defined by AWS IAM.

This eliminates the need to maintain long-lived AWS access keys in GitHub Actions for supported workflows.

Why It Matters

Traditional CI/CD authentication often relies on long-lived credentials stored as secrets:

GitHub Actions
      โ”‚
      โ”‚ AWS_ACCESS_KEY_ID
      โ”‚ AWS_SECRET_ACCESS_KEY
      โ–ผ
AWS

This creates several security concerns:

OIDC changes the model.

Instead of GitHub storing an AWS secret, the workflow proves its identity to AWS using a signed OIDC token.

AWS then evaluates the token against an IAM role trust policy.

If the token satisfies the trust conditions, AWS issues temporary credentials.

No static AWS secret
        โ”‚
        โ–ผ
GitHub issues signed identity token
        โ”‚
        โ–ผ
AWS validates token
        โ”‚
        โ–ผ
IAM evaluates trust policy
        โ”‚
        โ–ผ
Temporary credentials issued
        โ”‚
        โ–ผ
Workflow accesses only permitted AWS resources

The security improvement is not simply “OIDC is more secure.”

The more important architectural benefit is that identity and authorization become explicit.

The trust policy determines:

“Which GitHub identity is allowed to assume this role?”

The IAM permissions policy determines:

“What can that identity do after assuming the role?”

This creates a clean separation between authentication and authorization.


Where It Fits

OIDC sits between an identity issuer and the system that needs to trust that identity.

For GitHub Actions and AWS:

GitHub
  โ”‚
  โ”‚ Issues OIDC JWT
  โ–ผ
GitHub Actions Workflow
  โ”‚
  โ”‚ Presents JWT
  โ–ผ
AWS IAM OIDC Provider
  โ”‚
  โ”‚ Establishes trust in GitHub's issuer
  โ–ผ
IAM Role Trust Policy
  โ”‚
  โ”‚ Evaluates token claims
  โ–ผ
STS AssumeRoleWithWebIdentity
  โ”‚
  โ”‚ Returns temporary credentials
  โ–ผ
AWS APIs

In the platform-foundation architecture, OIDC is part of the CI/CD security boundary.

It is used to replace static AWS credentials with temporary credentials for workflows that need to interact with AWS.

A simplified architecture is:

Developer
    โ”‚
    โ”‚ Git push / workflow dispatch
    โ–ผ
GitHub Repository
    โ”‚
    โ–ผ
GitHub Actions
    โ”‚
    โ”‚ OIDC token
    โ–ผ
AWS IAM
    โ”‚
    โ”œโ”€โ”€ Trust Policy
    โ”‚      โ””โ”€โ”€ Who can assume the role?
    โ”‚
    โ””โ”€โ”€ Permissions Policy
           โ””โ”€โ”€ What can the role do?
    โ”‚
    โ–ผ
Temporary AWS Credentials
    โ”‚
    โ”œโ”€โ”€ Terraform
    โ”œโ”€โ”€ Packer
    โ”œโ”€โ”€ ECR
    โ”œโ”€โ”€ S3
    โ”œโ”€โ”€ SSM
    โ”œโ”€โ”€ CloudFront
    โ””โ”€โ”€ Other AWS services

The Big Picture

The most important concept is that OIDC itself does not grant AWS permissions.

OIDC establishes identity.

AWS IAM determines authorization.

The complete chain is:

Identity
   โ”‚
   โ–ผ
GitHub OIDC Token
   โ”‚
   โ”‚ "This workflow is from this repository,
   โ”‚  branch, environment, and workflow."
   โ–ผ
AWS IAM Trust Policy
   โ”‚
   โ”‚ "I trust this identity under these conditions."
   โ–ผ
STS AssumeRoleWithWebIdentity
   โ”‚
   โ”‚ "Here are temporary credentials."
   โ–ผ
IAM Permissions Policy
   โ”‚
   โ”‚ "These are the AWS actions you may perform."
   โ–ผ
AWS Resources

This distinction is critical.

A workflow can successfully authenticate to AWS and still receive:

AccessDenied

That means the OIDC trust relationship worked, but the IAM permissions policy did not authorize the requested action.

Conversely, a workflow may have a perfectly designed IAM permissions policy but fail before receiving credentials because its OIDC token does not satisfy the IAM trust policy.

There are therefore two separate questions when troubleshooting:

  1. Can the workflow assume the IAM role?
  2. Once assumed, does the role have permission to perform the requested action?

OIDC vs. OAuth 2.0

OIDC and OAuth 2.0 are related but solve different problems.

OAuth 2.0

OAuth 2.0 is primarily an authorization framework.

It answers:

“What is this client allowed to access?”

OAuth commonly issues an access token that a client presents to an API.

For example:

Application
    โ”‚
    โ”‚ Access Token
    โ–ผ
API
    โ”‚
    โ–ผ
Protected Resource

OAuth 2.0 does not, by itself, define a standardized way to authenticate the identity of an end user.


OpenID Connect

OIDC adds an identity layer on top of OAuth 2.0.

It answers:

“Who is this identity?”

OIDC introduces an ID token, normally a signed JWT containing identity claims.

A simplified comparison:

ConceptOAuth 2.0OpenID Connect
Primary purposeAuthorizationAuthentication / identity
Main questionWhat can you access?Who are you?
Common tokenAccess tokenID token
Standardized user identityNoYes
Built on OAuth 2.0N/AYes
Common useAPI accessLogin and SSO

A useful mental model:

OAuth 2.0
"What are you allowed to do?"

OIDC
"Who are you?"

OIDC may also be used alongside OAuth access tokens when an application needs both identity and API authorization.


Core Concepts

Identity Provider

The Identity Provider (IdP) authenticates an identity and issues tokens.

Examples include:

For a traditional user login:

User
  โ”‚
  โ–ผ
Identity Provider
  โ”‚
  โ”‚ Authenticates user
  โ–ผ
OIDC Token
  โ”‚
  โ–ผ
Application

For GitHub Actions:

GitHub Actions Workflow
  โ”‚
  โ–ผ
GitHub OIDC Issuer
  โ”‚
  โ”‚ Issues signed JWT
  โ–ผ
AWS

Relying Party

The Relying Party (RP) is the application or system that trusts the identity provider.

In a traditional OIDC login:

Google / Entra ID / Okta
        โ”‚
        โ–ผ
Application

The application is the relying party.

In GitHub-to-AWS workload identity federation:

GitHub
   โ”‚
   โ–ผ
AWS IAM

AWS is effectively relying on GitHub’s OIDC identity assertions.


ID Token

An OIDC ID token is typically a signed JWT.

It contains claims about the authenticated identity.

A simplified token structure is:

header.payload.signature

The payload may contain claims such as:

iss
aud
sub
iat
exp

GitHub Actions OIDC tokens also contain GitHub-specific claims that can identify information such as the repository, ref, workflow, and environment.

The token is used to establish identity.

It is not the same thing as an AWS access key or AWS secret key.


Access Token

An OAuth access token represents authorization to access a protected resource.

For example:

Application
    โ”‚
    โ”‚ Access Token
    โ–ผ
API

An access token answers:

“What resource access has been granted?”

An ID token answers:

“Who authenticated?”

Do not assume that an ID token should be presented to an arbitrary API.


UserInfo Endpoint

OIDC providers may expose a UserInfo endpoint that allows a relying party to retrieve additional identity claims.

This is more common in traditional user authentication scenarios.

GitHub Actions-to-AWS workload federation does not depend on the UserInfo endpoint in the same way a typical “Sign in with Google” application does.


Discovery Document

OIDC providers commonly publish a discovery document under:

/.well-known/openid-configuration

The discovery document describes provider metadata such as:

This is primarily relevant to traditional OIDC client integrations.


OIDC Authentication Flow

A traditional user-centric OIDC flow looks roughly like this:

1. User
      โ”‚
      โ”‚ Click "Sign In"
      โ–ผ
2. Application
      โ”‚
      โ”‚ Redirect
      โ–ผ
3. Identity Provider
      โ”‚
      โ”‚ Authenticate user
      โ”‚ MFA / password / passkey
      โ–ผ
4. Identity Provider
      โ”‚
      โ”‚ Authorization response
      โ–ผ
5. Application
      โ”‚
      โ”‚ Receives tokens
      โ–ผ
6. Application
      โ”‚
      โ”‚ Validates ID Token
      โ–ผ
7. Authenticated Session

For modern applications, Authorization Code Flow with PKCE is generally preferred.

The older Implicit Flow should generally not be selected for new implementations.


GitHub Actions OIDC

GitHub Actions can use OIDC to authenticate workloads to cloud providers.

The model is:

GitHub Actions
      โ”‚
      โ”‚ Requests OIDC token
      โ–ผ
GitHub OIDC Issuer
      โ”‚
      โ”‚ Signs JWT
      โ–ผ
Cloud Provider
      โ”‚
      โ”‚ Validates token
      โ–ผ
Federated Identity
      โ”‚
      โ–ผ
Temporary Credentials

The workflow does not need to store a long-lived cloud access key.

For GitHub Actions, the workflow must explicitly request permission to obtain an OIDC token:

permissions:
  id-token: write
  contents: read

The critical permission is:

id-token: write

Without it, the workflow cannot request the GitHub OIDC token required for federation.


GitHub OIDC with AWS

AWS uses IAM and AWS Security Token Service (STS) to establish workload identity federation.

The important AWS API is:

sts:AssumeRoleWithWebIdentity

The simplified flow is:

GitHub Actions Workflow
        โ”‚
        โ”‚ 1. Request OIDC token
        โ–ผ
GitHub OIDC Provider
        โ”‚
        โ”‚ 2. Signed JWT
        โ–ผ
AWS IAM OIDC Provider
        โ”‚
        โ”‚ 3. Validate issuer and token claims
        โ–ผ
IAM Role Trust Policy
        โ”‚
        โ”‚ 4. Check conditions
        โ–ผ
AWS STS
        โ”‚
        โ”‚ 5. Issue temporary credentials
        โ–ผ
GitHub Actions
        โ”‚
        โ”‚ 6. AWS CLI / Terraform / Packer / SDK
        โ–ผ
AWS Resources

The AWS credentials issued to the workflow are temporary.

The workflow does not need:

AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY

stored as long-lived GitHub secrets for the OIDC authentication path.


AWS IAM OIDC Provider

AWS must trust GitHub’s OIDC issuer.

The IAM OIDC provider represents that trust relationship.

A Terraform configuration may look similar to:

resource "aws_iam_openid_connect_provider" "github" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = [
    "sts.amazonaws.com"
  ]
}

The exact provider configuration should follow the current AWS and GitHub documentation and your organization’s Terraform standards.

The important concepts are:

Issuer:
https://token.actions.githubusercontent.com

Audience:
sts.amazonaws.com

STS Action:
sts:AssumeRoleWithWebIdentity

The OIDC provider establishes that AWS recognizes GitHub as a trusted token issuer.

It does not automatically give every GitHub repository access to the AWS account.

The IAM role’s trust policy determines which GitHub identities can assume the role.


IAM Trust Policies

An IAM role has a trust policy that defines who or what may assume it.

A simplified GitHub Actions trust relationship looks like:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<account-id>:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:Onwuachi/platform-foundation:ref:refs/heads/main"
        }
      }
    }
  ]
}

This establishes a trust boundary.

The role is not saying:

“Any GitHub workflow can use me.”

It is saying:

“I trust tokens issued by GitHub’s OIDC provider, provided the token’s claims satisfy these conditions.”

The aud condition ensures the token is intended for AWS STS.

The sub condition restricts which GitHub identity can assume the role.

For example:

repo:Onwuachi/platform-foundation:ref:refs/heads/main

restricts the role to the main branch of the specified repository.


GitHub OIDC Claims

Claims are statements contained in the OIDC token.

The most important claims to understand when integrating GitHub Actions with AWS are:

iss

The issuer.

For GitHub Actions:

https://token.actions.githubusercontent.com

This identifies who issued the token.

AWS establishes trust in this issuer through the IAM OIDC provider.


aud

The audience.

For AWS federation, the expected audience is commonly:

sts.amazonaws.com

The trust policy can enforce this:

"StringEquals": {
  "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}

sub

The subject.

The sub claim is one of the most important claims for restricting GitHub Actions access.

A branch-specific example:

repo:Onwuachi/platform-foundation:ref:refs/heads/main

A GitHub environment may produce a subject representing the environment rather than a branch-specific subject, depending on the workflow configuration.

This distinction matters when designing production trust policies.

Always verify the actual GitHub token claims generated by the workflow before designing a restrictive trust policy.


repository

Identifies the GitHub repository.

Example:

Onwuachi/platform-foundation

This can be useful for understanding and debugging token identity.


ref

Identifies the Git reference associated with the workflow.

Examples:

refs/heads/main
refs/heads/develop
refs/tags/v1.2.0

This can help distinguish branch and tag-based deployment identities.


workflow

Identifies the GitHub Actions workflow associated with the token.

This can be useful when separating permissions between different deployment workflows.

For example:

terraform-deploy.yml
packer-build.yml
ci-cd-hugo.yml

environment

Identifies the GitHub environment associated with a workflow when an environment is used.

This can be useful for implementing stronger controls around environments such as:

dev
uat
stage
prod

Production deployments can be tied to a protected GitHub environment and a dedicated AWS IAM role.


Scoped IAM Roles

OIDC determines who can authenticate.

IAM policies determine what that identity can do.

A strong architecture avoids giving every workflow the same broad IAM role.

Instead, roles should be separated according to their operational responsibility and blast radius.

For example:

GitHub Actions
      โ”‚
      โ”œโ”€โ”€ Terraform Role
      โ”‚      โ””โ”€โ”€ Infrastructure provisioning
      โ”‚
      โ”œโ”€โ”€ Packer Role
      โ”‚      โ””โ”€โ”€ AMI builds / SSM / EC2
      โ”‚
      โ”œโ”€โ”€ Deployment Role
      โ”‚      โ””โ”€โ”€ ECR / S3 / CloudFront
      โ”‚
      โ””โ”€โ”€ Backup / Operations Role
             โ””โ”€โ”€ Backup and operational tasks

This creates a clearer security boundary.


Terraform Deployment Role

Terraform is often the workflow with the largest AWS blast radius.

Depending on the infrastructure managed by Terraform, it may require access to:

A dedicated Terraform role is therefore preferable to reusing a narrowly scoped Packer role.

Example conceptual architecture:

GitHub Actions
      โ”‚
      โ”‚ OIDC
      โ–ผ
github-terraform-role
      โ”‚
      โ”œโ”€โ”€ EC2 / VPC
      โ”œโ”€โ”€ IAM
      โ”œโ”€โ”€ S3
      โ”œโ”€โ”€ SSM
      โ”œโ”€โ”€ Secrets Manager
      โ”œโ”€โ”€ Route 53
      โ””โ”€โ”€ Other Terraform-managed resources

The permissions should be scoped as tightly as practical.

Using broad permissions such as:

ec2:*

may be acceptable as a deliberate simplification for a personal platform or lab environment, but it should be recognized as a known tradeoff.

The goal should still be to reduce permissions over time where the operational cost of maintaining a least-privilege policy is justified.


Packer Role

Packer has a narrower purpose.

The role may need access to:

Conceptually:

GitHub Actions
      โ”‚
      โ”‚ OIDC
      โ–ผ
github-oidc-role
      โ”‚
      โ”œโ”€โ”€ Build EC2 instance
      โ”œโ”€โ”€ Run SSM commands
      โ”œโ”€โ”€ Create AMI
      โ”œโ”€โ”€ Create snapshots
      โ””โ”€โ”€ Pass required instance role

The Packer role should not automatically receive permissions for unrelated infrastructure management.

This is an example of minimizing blast radius through role separation.


Build and Deployment Roles

Application build and deployment workflows generally have a different permission profile.

For example, a Hugo deployment may need:

ECR
S3
CloudFront

An application container build may only need:

ECR

A deployment workflow may need:

S3
CloudFront

A conceptual deployment role might therefore look like:

github-deploy-role
      โ”‚
      โ”œโ”€โ”€ ECR
      โ”‚
      โ”œโ”€โ”€ S3
      โ”‚
      โ””โ”€โ”€ CloudFront

The exact role design depends on whether applications share deployment boundaries or require stronger isolation.

For a smaller platform, one shared deployment role may be reasonable.

For higher-risk production systems, separate roles per application or environment may be preferable.


GitHub OIDC and Secret Elimination

One of the primary benefits of migrating CI/CD workflows to OIDC is removing static cloud credentials.

The old model:

GitHub Secret
    โ”‚
    โ”œโ”€โ”€ AWS_ACCESS_KEY_ID
    โ””โ”€โ”€ AWS_SECRET_ACCESS_KEY
             โ”‚
             โ–ผ
        AWS Account

The OIDC model:

GitHub Actions
    โ”‚
    โ”‚ OIDC JWT
    โ–ผ
AWS IAM
    โ”‚
    โ”‚ Temporary credentials
    โ–ผ
AWS Account

This should be treated as a security architecture improvement, not simply a credential-management convenience.

When migrating workflows, search for static credential usage:

grep -R "AWS_ACCESS_KEY_ID" .github/workflows/
grep -R "AWS_SECRET_ACCESS_KEY" .github/workflows/
grep -R "aws-access-key" .github/workflows/
grep -R "aws-secret-key" .github/workflows/

Also look for secrets being unnecessarily printed to logs.

For example, a step such as:

- name: Debug MongoDB URL
  run: echo "MongoDB Atlas URL: ${{ secrets.MONGODB_ATLAS_URL }}"

should be removed unless there is a legitimate, safe reason to expose that value.

Secrets should never be printed to CI/CD logs.


Real-World Example

A GitHub Actions workflow using AWS OIDC may look like:

name: Terraform Deploy

on:
  workflow_dispatch:
    inputs:
      environment:
        description: "Target environment"
        required: true
        default: "dev"

permissions:
  id-token: write
  contents: read

jobs:
  terraform:
    name: Deploy to ${{ inputs.environment }}
    runs-on: ubuntu-latest

    environment:
      name: ${{ inputs.environment }}

    defaults:
      run:
        working-directory: infra

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::<account-id>:role/github-terraform-role
          aws-region: us-east-1

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

      - name: Set up Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Terraform Init
        run: terraform init

      - name: Terraform Plan
        run: terraform plan

      - name: Terraform Apply
        run: terraform apply -auto-approve

The important sequence is:

permissions:
  id-token: write
        โ”‚
        โ–ผ
GitHub issues OIDC token
        โ”‚
        โ–ผ
configure-aws-credentials
        โ”‚
        โ–ผ
AWS STS AssumeRoleWithWebIdentity
        โ”‚
        โ–ผ
github-terraform-role
        โ”‚
        โ–ผ
Temporary AWS credentials
        โ”‚
        โ–ผ
Terraform

The workflow does not need to store an AWS access key and secret key.


GitHub OIDC Migration Strategy

OIDC migrations should be performed systematically.

Do not simply replace every workflow’s AWS credentials with the same IAM role.

First classify workflows by responsibility.

For a platform such as platform-foundation, a practical classification might be:

WorkflowResponsibilityRecommended Role
Terraform deployInfrastructure provisioningTerraform role
Packer buildAMI creationPacker role
Hugo deployStatic site deploymentDeployment role
API buildContainer image publishingBuild/deployment role
WordPress buildContainer image publishingBuild/deployment role
Roll servicesSSM operationsOperations/Packer role
Backup workflowBackup operationsBackup role
Legacy workflowsUnknownReview before migration

The recommended migration sequence is:

1. Identify active workflows
        โ”‚
        โ–ผ
2. Identify static AWS credentials
        โ”‚
        โ–ผ
3. Identify each workflow's AWS actions
        โ”‚
        โ–ผ
4. Define IAM role boundaries
        โ”‚
        โ–ผ
5. Create OIDC trust relationships
        โ”‚
        โ–ผ
6. Apply IAM role using existing bootstrap credentials
        โ”‚
        โ–ผ
7. Confirm role exists
        โ”‚
        โ–ผ
8. Update workflow to use OIDC
        โ”‚
        โ–ผ
9. Verify AWS identity
        โ”‚
        โ–ผ
10. Run workflow
        โ”‚
        โ–ผ
11. Review CloudTrail / IAM failures
        โ”‚
        โ–ผ
12. Remove obsolete static credentials

The sequencing matters.

If the workflow is updated before the IAM role exists, the workflow cannot authenticate.

The bootstrap process is therefore:

Existing AWS Credentials
        โ”‚
        โ”‚ One-time bootstrap
        โ–ผ
Create OIDC Provider / IAM Role
        โ”‚
        โ–ผ
Verify Role
        โ”‚
        โ–ผ
Migrate GitHub Workflow
        โ”‚
        โ–ผ
OIDC Authentication
        โ”‚
        โ–ผ
Temporary Credentials
        โ”‚
        โ–ผ
Remove Static Credentials

The objective is to make the static credential path unnecessary, not to maintain both paths indefinitely.


Debugging OIDC

OIDC troubleshooting should be separated into two categories:

Authentication / Trust
        โ”‚
        โ””โ”€โ”€ Can GitHub assume the role?

Authorization
        โ”‚
        โ””โ”€โ”€ Can the assumed role perform the AWS action?

Step 1: Verify the AWS Identity

The simplest diagnostic step is:

- name: Who am I?
  run: aws sts get-caller-identity

A successful response confirms that the workflow obtained AWS credentials and successfully authenticated.

If this fails, investigate the OIDC trust relationship.


Step 2: Check id-token Permission

Confirm the workflow contains:

permissions:
  id-token: write
  contents: read

Without:

id-token: write

the workflow cannot request the OIDC token.


Step 3: Check the IAM OIDC Provider

Verify:

Issuer:
https://token.actions.githubusercontent.com

Verify that AWS IAM has an OIDC provider configured for GitHub.


Step 4: Check the Audience

For AWS, the trust policy commonly expects:

sts.amazonaws.com

A mismatch between the token’s audience and the trust policy can cause role assumption failures.


Step 5: Check the Subject

The sub claim must match the IAM trust policy.

For example:

repo:Onwuachi/platform-foundation:ref:refs/heads/main

If the workflow runs from another branch:

refs/heads/develop

the trust relationship above will not match.

Similarly, GitHub environment-based workflows may use a different subject structure.

Do not guess the subject format when implementing a restrictive production trust policy.

Verify the actual token claims generated by the workflow.


Step 6: Check the IAM Role ARN

Confirm the workflow is assuming the intended role:

with:
  role-to-assume: arn:aws:iam::<account-id>:role/github-terraform-role

A typo or incorrect account ID can cause authentication failures.


Step 7: Check IAM Permissions

If:

aws sts get-caller-identity

succeeds but Terraform or another AWS command fails with:

AccessDenied

the OIDC authentication succeeded.

The problem is now likely the IAM permissions policy.

Review:

CloudTrail can help identify the exact denied API operation.


Common Failure Modes

AccessDenied During Role Assumption

Likely causes:


Role Assumption Works but AWS API Calls Fail

This usually means:

OIDC authentication succeeded
        โ”‚
        โ–ผ
IAM authorization failed

The IAM permissions policy needs to be reviewed.


Workflow Works on main but Not a Feature Branch

A trust policy restricted to:

repo:Onwuachi/platform-foundation:ref:refs/heads/main

will intentionally reject workflows running on other branches.

This is o desirable for production deployments.

A common pattern is:

Feature branches
    โ”‚
    โ””โ”€โ”€ No production AWS access

main
    โ”‚
    โ””โ”€โ”€ Production deployment role

GitHub Environment: prod
    โ”‚
    โ””โ”€โ”€ Additional protection / approval

Static Credentials Still Exist

A successful OIDC migration does not automatically remove old secrets.

After verifying the workflow:

  1. Confirm OIDC authentication works.
  2. Confirm the workflow performs successfully.
  3. Confirrkflow no longer references static AWS credentials.
  4. Remove obsolete repository or organization secrets.
  5. Audit remaining workflows.

The goal is to eliminate unused credentials rather than simply adding OIDC alongside them.


Best Practices


Engineering Analogy

Think of GitHub OIDC as the identity badge and IAM as the access-control system.

Imagine an employee entering a secure building.

GitHub OIDC Token
        โ”‚
        โ–ผ
Idey Badge
        โ”‚
        โ–ผ
AWS IAM Trust Policy
        โ”‚
        โ”‚ "Is this a valid person
        โ”‚  from a trusted organization?"
        โ–ผ
Temporary Credentials
        โ”‚
        โ–ผ
IAM Permissions
        โ”‚
        โ”‚ "Which rooms can they enter?"
        โ–ผ
AWS Resources

The trust policy answers:

“Do I trust this identity?”

The permissions policy answers:

“What is this identity allowed to do?”

Having a valid badge does not mean the employee can enter every room.

Similsuccessfully assuming an AWS IAM role does not mean the workflow can perform every AWS operation.


Pro Tip

When debugging GitHub OIDC, always separate role assumption from AWS permissions.

Start with:

aws sts get-caller-identity

If that fails, troubleshoot:

GitHub OIDC
IAM OIDC Provider
Trust Policy
Claims

If that succeeds, troubleshoot:

IAM Permissions
Resource ARNs
Explicit Denies
SCPs
Permission Boundaries

This simple separation prevents a lot of wasted time.

Another important operational lesson is to create the IAM role before migrating the workflow.

For a bootstrap migration:

1. Use existing authentication
2. Create OIDC provider / IAM role
3. Verify role
4. Update GitHub workflow
5. Test workflow
6. Remove static credentials

Do not reverse steps 2 and 4.


Key Takeaways

The fundamental architecture is:

GitHub Workflow
      โ”‚
      โ”‚ Identity
      โ–ผ
OIDC Token
      โ”‚
      โ”‚ Trust
      โ–ผ
AWS IAM Role
      โ”‚
      โ”‚ Authorization
      โ–ผ
IAM Permissions
      โ”‚
      โ”‚ Temporary Credentials
      โ–ผ
AWS Services

OIDC establishes who you are. IAM determines what you can do.


Related Articles


References

System Context

โ† Back to Kb