Skip to content

aws-oidc

helps configure and implement GitHub OIDC authentication with AWS for secure, keyless access from GitHub Actions to AWS resources

IDE:
claude
codex
vscode
Version:
0.0.0

GitHub OIDC Authentication for AWS

Overview

OpenID Connect (OIDC) allows your GitHub Actions workflows to access resources in AWS without needing to store AWS credentials as long-lived GitHub secrets. This eliminates the security risks associated with static credentials and provides temporary, scoped access tokens.

Core Concepts

What is OIDC?

  • Federated Identity: AWS trusts GitHub's OIDC provider to authenticate workflows
  • Temporary Tokens: Short-lived tokens replace static credentials
  • Scoped Access: Fine-grained permissions based on repository, branch, and environment
  • Security: No secrets stored in GitHub; tokens are generated on-demand

Key Components

  1. OIDC Provider: AWS trusts https://token.actions.githubusercontent.com
  2. IAM Role: AWS role that GitHub Actions can assume
  3. Trust Policy: Defines which repositories/branches can assume the role
  4. Permissions Policy: Defines what AWS actions the role can perform

Setup Methods

Method 1: Manual Configuration

Step 1: Create OIDC Provider in AWS

  1. Sign in to AWS Management Console
  2. Navigate to IAM → Identity Providers
  3. Click Add Provider
  4. Select OpenID Connect as provider type
  5. Configure:
    • Provider URL: https://token.actions.githubusercontent.com
    • Audience: sts.amazonaws.com
  6. Click Add Provider

Step 2: Create IAM Role with Trust Policy

  1. Go to IAM → Roles
  2. Click Create Role
  3. Select Web identity as trusted entity
  4. Choose the OIDC provider created earlier
  5. Configure trust policy:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Principal": {
                "Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/token.actions.githubusercontent.com"
            },
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": [
                        "sts.amazonaws.com"
                    ]
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": [
                        "repo:<ORG>/<REPO>:ref:refs/heads/main"
                    ]
                }
            }
        }
    ]
}

Step 3: Attach Permissions Policies

Attach necessary AWS policies (e.g., AmazonS3ReadOnlyAccess, custom policies)

Method 2: Terraform Infrastructure as Code

ensure the aws account has a terraform state bucket, utilize dojo chat mode to help create that first if one is not present.

Repository Structure

Based on the OptumInsight-Platform/dp-nucleus-aws-bootstrap-account repository:

tf/
├── main.tf              # Core OIDC and IAM resources
├── variables.tf         # Input variables
├── output.tf           # Output values
├── policies/
│   └── github_action_policy.tftpl  # IAM policy template
└── tfvars/
    └── dev_variables.tfvars        # Environment-specific values

Key Terraform Resources

1. OIDC Provider

resource "aws_iam_openid_connect_provider" "github" {
  client_id_list = [
    "sts.amazonaws.com"
  ]

  thumbprint_list = [
    data.tls_certificate.github.certificates.0.sha1_fingerprint
  ]

  url = "https://token.actions.githubusercontent.com"

  tags = local.required_tags
}

2. IAM Role with Trust Policy

resource "aws_iam_role" "github_action_role" {
  name = "${var.team}-github-action-role-${var.namespace}"
  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement : [
      {
        Effect = "Allow",
        Principal : {
          Federated : aws_iam_openid_connect_provider.github.arn
        },
        Action : "sts:AssumeRoleWithWebIdentity",
        Condition : {
          StringEquals : {
            "token.actions.githubusercontent.com:aud" : "sts.amazonaws.com"
          },
          StringLike : {
            "token.actions.githubusercontent.com:sub" : "repo:${var.github_org}/${var.github_repo_prefix}*"
          }
        }
      }
    ]
  })

  tags = local.required_tags
}

3. Custom IAM Policy

resource "aws_iam_policy" "github_action_policy" {
  name        = "${var.team}-github-action-policy-${var.namespace}"
  description = "Policy for GitHub Actions"

  policy = templatefile("${path.module}/policies/github_action_policy.tftpl", local.custom_template_vars)
  tags = local.required_tags
}

Required Variables

variable "namespace" {
  default     = "dev"
  type        = string
  description = "Environment stage (prod, dev, qa)"
}

variable "team" {
  type        = string
  description = "Team name"
}

variable "github_org" {
  type        = string
  description = "GitHub organization name"
}

variable "github_repo_prefix" {
  type        = string
  description = "GitHub repository prefix pattern"
}

variable "s3_primary_bucket" {
  type        = string
  description = "Primary S3 bucket name"
}

variable "aws_managed_policy_attachment" {
  type = list(string)
  description = "List of AWS managed policies to attach"
  default = []
}

Example Configuration

# tfvars/dev_variables.tfvars
namespace          = "dev"
team               = "nucleus"
github_org         = "OptumInsight-Platform"
github_repo_prefix = "dp-nucleus"
s3_primary_bucket  = "dp-nucleus-us-east-1-dev"
ask_id             = "AIDE_0086287"

aws_managed_policy_attachment = [
  "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
  "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
]

Deployment Commands

cd tf
terraform init
terraform plan -var-file=tfvars/dev_variables.tfvars -out=tfplan
terraform apply tfplan

GitHub Actions Integration

Required Permissions

permissions:
  id-token: write   # Required for OIDC token generation
  contents: read    # Required for repository access

Basic Workflow Example

name: Deploy to AWS
on:
  push:
    branches:
      - main

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: uhg-runner
    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::<AWS_ACCOUNT_ID>:role/<ROLE_NAME>
          role-session-name: GitHubOIDCSession
          aws-region: us-east-1
          
      - name: Verify authentication
        run: |
          aws sts get-caller-identity
          aws s3api list-buckets --query 'Buckets[].Name'

Advanced Workflow with Multiple Environments

name: Multi-Environment Deployment
on:
  push:
    branches:
      - main
      - develop

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: uhg-runner
    strategy:
      matrix:
        environment: [dev, staging, prod]
        include:
          - environment: dev
            aws-role: arn:aws:iam::123456789012:role/github-action-role-dev
            condition: github.ref == 'refs/heads/develop'
          - environment: staging
            aws-role: arn:aws:iam::123456789012:role/github-action-role-staging
            condition: github.ref == 'refs/heads/main'
          - environment: prod
            aws-role: arn:aws:iam::987654321098:role/github-action-role-prod
            condition: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch'
    
    if: ${{ matrix.condition }}
    environment: ${{ matrix.environment }}
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ matrix.aws-role }}
          role-session-name: GitHubOIDCSession-${{ matrix.environment }}
          aws-region: us-east-1
          
      - name: Deploy to ${{ matrix.environment }}
        run: |
          echo "Deploying to ${{ matrix.environment }}"
          aws sts get-caller-identity

Integration with Optum Artifactory

name: Build, Scan, and Deploy
on:
  push:
    branches: [main]

permissions:
  actions: read
  contents: write
  pull-requests: write
  security-events: write
  checks: write
  id-token: write

jobs:
  build-and-deploy:
    runs-on: [uhg-runner]
    steps:
      - uses: actions/checkout@v4
      
      # Configure Artifactory
      - name: Configure Artifactory Connection
        id: artifactory-setup
        uses: uhg-pipelines/epl-jf/configure-saas-connection@latest
        with:
          jfrog-project-key: your-project-key
          npm-setup: true
          
      # Build and publish to Artifactory
      - name: Build and Scan
        uses: optum-eeps/epl-actions/node-build-scan@v1
        with:
          jfrog-project-key: your-project-key
          jfrog-build-name: ${{ steps.artifactory-setup.outputs.jfrog-build-name }}
          jfrog-build-number: ${{ steps.artifactory-setup.outputs.jfrog-build-number }}
          npm-publish: true
          
      # Configure AWS credentials
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          role-session-name: GitHubOIDCSession
          aws-region: us-east-1
          
      # Deploy to AWS
      - name: Deploy to AWS
        run: |
          aws s3 sync ./dist s3://${{ secrets.S3_BUCKET }}/
          aws cloudfront create-invalidation --distribution-id ${{ secrets.CLOUDFRONT_ID }} --paths "/*"

Security Best Practices

1. Least Privilege Access

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::specific-bucket/*"
            ]
        }
    ]
}

2. Restrict Repository Access

{
    "StringLike": {
        "token.actions.githubusercontent.com:sub": [
            "repo:OptumInsight-Platform/specific-repo:ref:refs/heads/main",
            "repo:OptumInsight-Platform/specific-repo:ref:refs/heads/develop"
        ]
    }
}

3. Environment-Specific Roles

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

4. Time-Limited Sessions

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
    role-session-name: GitHubOIDCSession
    role-duration-seconds: 3600  # 1 hour maximum
    aws-region: us-east-1

Troubleshooting

Common Issues

1. "No OpenIDConnect provider found"

# Verify OIDC provider exists
aws iam list-open-id-connect-providers

2. "AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity"

  • Check trust policy conditions
  • Verify repository name and branch in trust policy
  • Ensure workflow has id-token: write permission

3. "Invalid identity token"

  • Verify audience is set to sts.amazonaws.com
  • Check token expiration settings
  • Ensure workflow runs on supported runner

4. "Role session name is invalid"

# Use valid session name format
role-session-name: GitHubOIDCSession-${{ github.run_id }}

Debugging Steps

- name: Debug OIDC Token
  env:
    ACTIONS_ID_TOKEN_REQUEST_TOKEN: ${{ env.ACTIONS_ID_TOKEN_REQUEST_TOKEN }}
    ACTIONS_ID_TOKEN_REQUEST_URL: ${{ env.ACTIONS_ID_TOKEN_REQUEST_URL }}
  run: |
    # Get the token for debugging
    curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
         "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq .

Advanced Patterns

1. Cross-Account Access

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::ACCOUNT-A:role/github-action-role"
            },
            "Action": "sts:AssumeRole",
            "Condition": {
                "StringEquals": {
                    "sts:ExternalId": "unique-external-id"
                }
            }
        }
    ]
}

2. Conditional Access Based on PR Labels

- name: Configure AWS credentials for production
  if: contains(github.event.pull_request.labels.*.name, 'deploy-prod')
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: ${{ secrets.AWS_PROD_ROLE_ARN }}
    aws-region: us-east-1

3. Multi-Region Deployment

strategy:
  matrix:
    region: [us-east-1, us-west-2, eu-west-1]
steps:
  - name: Configure AWS credentials for ${{ matrix.region }}
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
      aws-region: ${{ matrix.region }}

Getting Latest Versions

GitHub CLI Commands

# Get latest aws-actions/configure-aws-credentials version
gh api repos/aws-actions/configure-aws-credentials/releases/latest --jq '.tag_name'

# Check Optum bootstrap repository updates
gh api repos/OptumInsight-Platform/dp-nucleus-aws-bootstrap-account/commits/main --jq '.[0].sha'

# List available example repositories
gh api orgs/OptumInsight-Platform/repos --jq '.[] | select(.name | contains("bootstrap")) | .name'

Implementation Checklist

Pre-Setup

  • AWS account with IAM permissions
  • GitHub repository in OptumInsight-Platform organization
  • Terraform installed (if using IaC approach)
  • AWS CLI configured locally

OIDC Configuration

  • OIDC provider created in AWS
  • IAM role created with trust policy
  • Permissions policies attached
  • Trust policy restricts to specific repositories/branches

GitHub Actions

  • Workflow includes id-token: write permission
  • Role ARN configured as repository secret
  • Test workflow validates authentication
  • Error handling implemented

Security Validation

  • Least privilege principle applied
  • Repository access properly restricted
  • Session duration appropriate
  • Monitoring and alerting configured

Documentation

  • Team training on OIDC concepts
  • Runbooks for troubleshooting
  • Security policies documented
  • Regular review schedule established

Support Resources


Remember: OIDC eliminates the need for long-lived AWS credentials in GitHub, significantly improving security posture while maintaining automation capabilities.