This article compares authentication from GitHub Actions to AWS using the standard way passing the GitHub Actions OIDC Access Token to AWS STS compared to passing the same token to AWS Cognito Identity.

GitHub Actions OIDC Access Token

When we authenticate with AWS STS using the GitHub Actions OIDC Access Token as explained here, we do it with a token having claims similar to below. (Example of full access token here.)

{
  "sub": "repo:catnekaise/example-repo:environment:dev",
  "aud": "sts.amazonaws.com",
  "environment": "dev",
  "ref": "refs/heads/main",
  "sha": "example-sha",
  "repository": "catnekaise/example-repo",
  "repository_owner": "catnekaise",
  "repository_visibility": "private",
  "repository_id": "2",
  "run_id": "example-run-id",
  "run_number": "4",
  "runner_environment": "github-hosted",
  "actor": "djonser",
  "event_name": "workflow_dispatch",
  "ref_type": "branch",
  "job_workflow_ref": "catnekaise/example-repo/.github/workflows/test.yml@refs/heads/main",
  "iss": "https://token.actions.githubusercontent.com"
}

Trust Policy - GitHub Actions Access Token

Trust policies on AWS IAM Roles can only match against the aud and sub claims of the GitHub access token. It’s possible to customize the sub claim by concatenate multiple claims into the sub claim. Doing so creates a long string that gets quite complex to write condition statements for in the trust policy.

A trust policy could look similar to below after the sub claim has been customized with a few extra claims. The condition for sub attempts to match for repo (repository), context (environment), job_workflow_ref and runner_environment.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::111111111111:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:catnekaise/example-repo:environment:dev:*job_workflow_ref:catnekaise/shared-workflows/.github/workflows/deploy.yml@refs/heads/main:runner_environment:self_hosted*"
        },
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

Cognito Identity Access Token

Using Cognito Identity the same GitHub Actions OIDC Access Token will first be exchanged for a Cognito Identity OIDC Access Token. When this exchange occurs, a selection of the many claims in the GitHub token will be mapped by Cognito Identity into principal tags inside the new access token.

Assuming the claims actor, job_workflow_ref, repository, environment and runner_environment was selected for claim mapping, the new token will look similar to below:

{
  "kid": "eu-west-13",
  "typ": "JWS",
  "alg": "RS512"
}
{
  "sub": "eu-west-1:22222222-example",
  "aud": "eu-west-1:11111111-example",
  "amr": [
    "authenticated",
    "token.actions.githubusercontent.com",
    "arn:aws:iam::111111111111:oidc-provider/token.actions.githubusercontent.com:OIDC:repo:catnekaise/example-repo:environment:dev"
  ],
  "https://aws.amazon.com/tags": {
    "principal_tags": {
      "actor": [
        "djonser"
      ],
      "job_workflow_ref": [
        "catnekaise/example-repo/.github/workflows/oidc.yaml@refs/heads/main"
      ],
      "repository": [
        "catnekaise/example-repo"
      ],
      "environment": [
        "dev"
      ],
      "runner_environment": [
        "self-hosted"
      ]
    }
  },
  "iss": "https://cognito-identity.amazonaws.com",
  "https://cognito-identity.amazonaws.com/identity-pool-arn": "arn:aws:cognito-identity:eu-west-1:111111111111:identitypool/eu-west-1:11111111-example",
  "exp": 1234567890,
  "iat": 1234567890
}

The aud claim has value matching the Cognito Identity Pool ID and the sub claim is an ID generated by Cognito Identity based on the sub claim that was present in the GitHub Actions OIDC access token.

Trust Policy - Cognito Identity Access Token

Using this new access token a trust policy can be created to match on the individual claims instead of having to matching everything inside the token.actions.githubusercontent.com:sub condition. Such a trust policy could look like:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": [
        "sts:AssumeRoleWithWebIdentity",
        "sts:TagSession"
      ],
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "eu-west-1:11111111-example",
          "aws:requestTag/repository": ["catnekaise/example-repo"],
          "aws:requestTag/environment": ["dev"],
          "aws:requestTag/runner_environment": ["self-hosted"],
          "aws:requestTag/job_workflow_ref": ["catnekaise/shared-workflows/.github/workflows/deploy.yml@refs/heads/main"]
          
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }
    }
  ]
}

Using Pictures

In all scenarios the workflow runs with the permission id-token set to write and starts of by creating a GitHub Actions OIDC Access token as explained here.

GitHub Actions and AWS STS

When authenticating as explained here, these are the things that occur when interacting with AWS STS.

GitHub Actions and Cognito Identity Basic (Classic) AuthFlow

Using the Basic AuthFlow, the token exchange is made with Cognito Identity and this new token can be used to authenticate with any role in any AWS account that has a trust policy trusting the claims of this token.

GitHub Actions and Cognito Identity Enhanced (Simplified) AuthFlow

Using the Enhanced AuthFlow, the token exchange with Cognito Identity still occurs, but the operations GetOpenIdToken and AssumeRoleWithWebIdentity are bundled into a single operation called GetCredentialsForIdentity and the Cognito Identity access token is never returned to the workflow.

Using the enhanced auth-flow, the AWS IAM roles must be in the same account as the Cognito Identity Pool. There’s also some extra (optional) features available when using the enhanced auth-flow.

What is the catch?

In order to do this, Cognito Identity has to be introduced as a component in your CI/CD environment. Cognito Identity is primarily intended (marketed) for use in mobile apps, so it may feel strange to do this. Also, introducing Cognito Identity can increase complexity and administration of a CI/CD environment, but at the same time it can also reduce the complexity and time required for administration. Ultimately you’ll have to evaluate the use case for yourself and your organization to determine if it adds value or not.

Additional Context

The reason why we would use Cognito Identity for this is that we need a third party that we can trust to convert our initial credentials (GitHub token) to another credential without allowing the GitHub Actions workflows any option to modify the claims. Using Cognito Identity we use a native AWS service that is free of charge to accomplish this.

Attribute Based Access Control

Because the GitHub Actions OIDC Access Token claims are now principal/session tags in our role session, they can be used for ABAC. Read more about ABAC for AWS and have look at this tutorial to learn more if you are unfamiliar with ABAC.

Next

Have a look here for additional resources on the topic of GitHub Actions ABAC in AWS.