LanguageEnglish

Best practices for securely assuming AWS IAM roles from GitHub Actions

2025-08-08
2025-08-26

When building CI/CD with GitHub Actions, there are times when you want to access AWS resources, right? In such cases, it is recommended to assume an IAM role through a flow that uses OpenID Connect (OIDC).

This article will introduce the flow up to assuming an IAM role and the specific steps involved!

Methods for Assuming Roles#

First, when considering how to assume an IAM role, it's necessary to understand all the available options. Methods for assuming IAM roles are documented in the faviconAWS documentation.

Among these, OpenID Connect (OIDC) is the most robust choice when assuming an IAM role from outside AWS.

Benefits of Using OIDC#

There are several important benefits to assuming AWS IAM roles from GitHub Actions using OpenID Connect (OIDC).

  • No need to store secrets statically: There's no need to store long-term AWS access keys or secrets in GitHub secrets. This significantly reduces the risk of credential leakage.
  • Automated credential rotation: When using OIDC, temporary credentials are automatically generated, eliminating the need for manual credential updates or rotation.
  • Fine-grained access control: Using IAM trust policies, you can restrict access based on specific repositories, branches, tags, etc., enabling security design that follows the principle of least privilege.
  • Improved auditability: All actions are associated with specific GitHub workflows, making logging and auditing easier in AWS CloudTrail.
  • Simplified management: Instead of managing multiple service accounts or access keys, you only need to set up trust relationship-based integration.

Due to these benefits, OIDC has become the current best practice for integrating GitHub Actions with AWS.

This time, we will also use role assumption via OIDC.

Flow Until Role Assumption#

The role assumption procedure in GitHub Actions introduced here follows the same general flow as AWS IAM Web Identity.

Steps#

  1. Create Identity Provider
  2. Create IAM role
  3. Create GitHub Actions configuration file

Creating the Identity Provider#

Create a new Identity provider from IAM > Identity providers. The values to enter are fixed.

  • Provider URL: https://token.actions.githubusercontent.com
  • Audience: sts.amazonaws.com

Creating the IAM Role#

When creating an IAM role from the Management Console, use the following configuration.

  • Identity provider: Select the IdP you created

Once you select the IdP, you can enter claim information. The sub section provides input assistance separated into username, repository name, and branch name.

  • Audience: Select sts.amazonaws.com
  • GitHub organization: Organization name or username
  • GitHub repository: Repository name (optional)
  • GitHub branch: Branch name (optional)

The trust policy will look like this. Please use this as a reference when creating with IaC.

json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::{account}:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                },
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:{user_or_org}/{repo}:*"
                }
            }
        }
    ]
}

Creating GitHub Workflows#

By using aws-actions/configure-aws-credentials, you can automatically retrieve credentials and assume the role.

Note that this example includes GitHub Actions for Rust because test code is prepared in Rust this time.

yaml
name: Fetch SSM Parameter

on:
  workflow_dispatch:

env:
  AWS_REGION: "ap-northeast-1"

permissions:
  id-token: write
  contents: read

jobs:
  test:
    name: Unit Test
    runs-on: ubuntu-latest
    steps:
      - name: Clone Repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: configure aws credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.IAM_ROLE_ARN }}
          role-session-name: samplerolesession
          aws-region: ${{ env.AWS_REGION }}

      - name: Setup Rust Toolchain
        uses: actions-rust-lang/setup-rust-toolchain@v1

      - name: Run
        run: cargo run
Example Rust code used this time
rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;

    let client = aws_sdk_ssm::Client::new(&config);

    let value = client
        .get_parameter()
        .name("/message")
        .send()
        .await?
        .parameter
        .and_then(|p| p.value)
        .unwrap_or_default();

    println!("{}", value);

    Ok(())
}

Ikuma Yamashita
Cloud engineer. Works on infrastructure-related tasks professionally, but has been spotted dedicating private time exclusively to systems programming. Shows a preference for using Rust. Enjoys illustration as a hobby.