At the time of writing, this functionality exists but has yet to be announced or documented. It works, though!

EDIT: Here is the functionality on the GitHub roadmap.

GitHub Actions has new functionality that can vend OpenID Connect credentials to jobs running on the platform. This is very exciting for AWS account administrators as it means that CI/CD jobs no longer need any long-term secrets to be stored in GitHub. But enough of that, here’s how it works:

First, an AWS IAM OIDC identity provider and an AWS IAM role that GitHub Actions can assume. You can do that by deploying this CloudFormation template to your account.

Parameters:
  GithubOrg: # can also be a regular user
    Type: String
    Default: aidansteele
  FullRepoName:
    Type: String
    Default: aidansteele/aws-federation-github-actions

Resources:
  Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: ExampleGithubRole
      ManagedPolicyArns: [arn:aws:iam::aws:policy/ReadOnlyAccess]
      AssumeRolePolicyDocument:
        Statement:
          - Effect: Allow
            Action: sts:AssumeRoleWithWebIdentity
            Principal:
              Federated: !Ref GithubOidc
            Condition:
              StringLike:
                token.actions.githubusercontent.com:sub: !Sub repo:${FullRepoName}:*

  GithubOidc:
    Type: AWS::IAM::OIDCProvider
    Properties:
      Url: https://token.actions.githubusercontent.com
      ThumbprintList: [6938fd4d98bab03faadb97b34396831e3780aea1]
      ClientIdList: 
        - !Sub https://github.com/${GithubOrg}

Outputs:
  Role:
    Value: !GetAtt Role.Arn      

Ok, this new role can now be assumed by GitHub Actions, but crucially: only by jobs in my aidansteele/aws-federation-github-actions repo. Without that condition, any repo on GitHub could assume this role.

Next, the GitHub workflow definition. Put this in a repo:

# .github/workflows/example.yml
name: Example
on:
  push:

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - run: sleep 5 # there's still a race condition for now

      - name: Configure AWS
        run: |
          export AWS_ROLE_ARN=arn:aws:iam::0123456789012:role/ExampleGithubRole
          export AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/awscreds
          export AWS_DEFAULT_REGION=us-east-1

          echo AWS_WEB_IDENTITY_TOKEN_FILE=$AWS_WEB_IDENTITY_TOKEN_FILE >> $GITHUB_ENV
          echo AWS_ROLE_ARN=$AWS_ROLE_ARN >> $GITHUB_ENV
          echo AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION >> $GITHUB_ENV

          curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value' > $AWS_WEB_IDENTITY_TOKEN_FILE

      - run: aws sts get-caller-identity # just an example. why not deploy something?

Tada, you now have a GitHub Actions workflow that assumes your role. It works because the AWS SDKs (and AWS CLI) support using the AWS_WEB_IDENTITY_TOKEN_FILE and AWS_ROLE_ARN environment variables since AWS EKS needed this.

Some potential trust policies

Maybe you want an IAM role that can be assumed by any branch in any repo in your GitHub org, e.g. with relatively few permissions needed for PRs. You can do this:

Effect: Allow
Action: sts:AssumeRoleWithWebIdentity
Principal:
  Federated: !Ref GithubOidc
Condition:
  StringLike:
    token.actions.githubusercontent.com:sub: repo:your-github-org/*

Maybe you want an IAM role scoped only to workflows on the main branches, because this will be doing sensitive deployments. In that case, you can do:

Effect: Allow
Action: sts:AssumeRoleWithWebIdentity
Principal:
  Federated: !Ref GithubOidc
Condition:
  StringLike:
    token.actions.githubusercontent.com:sub: repo:your-github-org/*:ref:refs/heads/main

FAQ

What does the JWT look like?

{
  "actor": "aidansteele",
  "aud": "https://github.com/aidansteele",
  "base_ref": "",
  "event_name": "push",
  "exp": 1631672856,
  "head_ref": "",
  "iat": 1631672556,
  "iss": "https://token.actions.githubusercontent.com",
  "job_workflow_ref": "aidansteele/aws-federation-github-actions/.github/workflows/test.yml@refs/heads/main",
  "jti": "8ea8373e-0f9d-489d-a480-ac37deexample",
  "nbf": 1631671956,
  "ref": "refs/heads/main",
  "ref_type": "branch",
  "repository": "aidansteele/aws-federation-github-actions",
  "repository_owner": "aidansteele",
  "run_attempt": "1",
  "run_id": "1235992580",
  "run_number": "5",
  "sha": "bf96275471e83ff04ce5c8eb515c04a75d43f854",
  "sub": "repo:aidansteele/aws-federation-github-actions:ref:refs/heads/main",
  "workflow": "CI"
}

And the CloudTrail entry?

{
  "awsRegion": "us-east-1",
  "eventCategory": "Management",
  "eventID": "096c33c2-7d1d-49c6-a87b-fb4bbb5d43d6",
  "eventName": "AssumeRoleWithWebIdentity",
  "eventSource": "sts.amazonaws.com",
  "eventTime": "2021-09-15T03:00:36Z",
  "eventType": "AwsApiCall",
  "eventVersion": "1.08",
  "managementEvent": true,
  "readOnly": true,
  "recipientAccountId": "0123456789012",
  "requestID": "d62256aa-fe9b-4fe4-bd7b-8a3917e35d13",
  "requestParameters": {
    "roleArn": "arn:aws:iam::0123456789012:role/ExampleGithubRole",
    "roleSessionName": "botocore-session-1631674835"
  },
  "resources": [
    {
      "ARN": "arn:aws:iam::0123456789012:role/ExampleGithubRole",
      "accountId": "0123456789012",
      "type": "AWS::IAM::Role"
    }
  ],
  "responseElements": {
    "assumedRoleUser": {
      "arn": "arn:aws:sts::0123456789012:assumed-role/ExampleGithubRole/botocore-session-1631674835",
      "assumedRoleId": "AROAY99999AOBPS6VNUFM:botocore-session-1631674835"
    },
    "audience": "https://github.com/aidansteele",
    "credentials": {
      "accessKeyId": "ASIAY29999OMG3MKNAG",
      "expiration": "Sep 15, 2021 4:00:36 AM",
      "sessionToken": "IQ[trimmed]lg=="
    },
    "provider": "arn:aws:iam::0123456789012:oidc-provider/token.actions.githubusercontent.com",
    "subjectFromWebIdentityToken": "repo:aidansteele/aws-federation-github-actions:ref:refs/heads/main"
  },
  "sourceIPAddress": "104.211.45.236",
  "tlsDetails": {
    "cipherSuite": "ECDHE-RSA-AES128-GCM-SHA256",
    "clientProvidedHostHeader": "sts.us-east-1.amazonaws.com",
    "tlsVersion": "TLSv1.2"
  },
  "userAgent": "aws-cli/2.2.35 Python/3.8.8 Linux/5.8.0-1040-azure exe/x86_64.ubuntu.20 prompt/off command/sts.get-caller-identity",
  "userIdentity": {
    "identityProvider": "arn:aws:iam::0123456789012:oidc-provider/token.actions.githubusercontent.com",
    "principalId": "arn:aws:iam::0123456789012:oidc-provider/token.actions.githubusercontent.com:https://github.com/aidansteele:repo:aidansteele/aws-federation-github-actions:ref:refs/heads/main",
    "type": "WebIdentityUser",
    "userName": "repo:aidansteele/aws-federation-github-actions:ref:refs/heads/main"
  }
}

Can I use those JWT claims as role session tags?

Not directly, unfortunately. AWS requires role session tags to follow a fairly specific format - one that I doubt GitHub Actions will implement. But you could have a token vending machine… stay tuned.

EDIT: I built an (still cooling down after coming out of the oven) example of how you could use all those JWT claims as role session tags. Take a look at glassechidna/ghaoidc and let me know your thoughts.