AWS OIDC Provider Enumeration

Jun 21, 24

Yesterday, Nick Frichette tweeted out a discovery:

A screenshot of nick's tweet - noting With just an account ID, you can determine if they have a particular OIDC IDP in the account.

In a desperate rush to meet Daniel Grzelak’s challenge to “publish something” in the week after fwd:cloudsec, I immediately sharked some follow on research from Nick.

Dogfooding known_aws_accounts

As part of the fwd:cloudsec “Technical Oversight Committee,” I help maintain known_aws_accounts. This repository contains known AWS account IDs (used by vendors and AWS services).

Nick’s announcement immediately raised the question: “Which known AWS account IDs have the Github Actions OIDC provider configured?”

This was a good chance to demonstrate the utility of known_aws_accounts, and to build a practical demonstration of Nick’s research.

I’m sure there were better ways to do this, but I immediately rolled with an approach that simply ran the Github Action aws-actions/configure-aws-credentials@v3 on a placeholder role name, across each Account ID in our data set.

Building the matrix

In Github Actions, “a matrix strategy lets you use variables in a single job definition to automatically create multiple job runs that are based on the combinations of the variables.”

This strategy would allow me to use a single workflow to test a list of account IDs.

To generate the matrix, I wrote the following basic python:

import yaml
import json
import requests

# URL of the YAML file on GitHub
url = "https://raw.githubusercontent.com/fwdcloudsec/known_aws_accounts/main/accounts.yaml"

# Fetch the content from the URL
response = requests.get(url)

# Check if the request was successful
if response.status_code == 200:
    # Load the YAML content
    data = yaml.safe_load(response.text)
else:
    print(f"Failed to retrieve the file. HTTP Status code: {response.status_code}")
    exit(1)

# Extract all accounts into a list
accounts_list = []
for entry in data:
    accounts_list.extend(entry.get('accounts', []))

accounts_list = accounts_list[:255]

# Create a matrix with the accounts
matrix = {"include": [{"account": account} for account in accounts_list]}

# Print the matrix in JSON format
print(json.dumps(matrix))

Note that we truncate the list to the first 255 Account IDs. This is due to a limitation in Github that a matrix will generate a maximum of 256 jobs per workflow run. The result is I actually split this into multiple workflows to cover the whole list of 418 Account IDs. I’ll leave that as an exercise for the reader!

The workflow

The python script could then be called within a Github Actions workflow, which would check each account ID

on: [workflow_dispatch]
permissions:
  id-token: write # This is required for requesting the JWT
  contents: read  # This is required for actions/checkout
jobs:
  generate-matrix:
    runs-on: ubuntu-latest
    outputs:
      matrix: $
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.x'
          
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install pyyaml requests
          
      - name: Generate matrix
        id: generate-matrix
        run: |
          python generate-matrix-first256.py > matrix.json
          echo "::set-output name=matrix::$(cat matrix.json)"

  check:
    needs: generate-matrix
    runs-on: ubuntu-latest
    strategy:
      matrix: $
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Configure AWS credentials
        id: configure-aws
        uses: aws-actions/configure-aws-credentials@v3
        with:
          role-to-assume: arn:aws:iam::$:role/example-role
          role-session-name: samplerolesession
          aws-region: us-east-1
          disable-retry: true
        continue-on-error: true

The results

There are 418 accounts in the known_aws_accounts dataset.

Of those, the following 29 vendors had the Github Actions OIDC configured, across 34 accounts.

cloudsploit / 057012691312
New Relic / 061190967865
Rockset / 216690786812
Sysdig / 263844535661, 761931097553
Cloudinary / 232482882421
n0ps / 202279780353
Spacelift / 324880187172
unusd.cloud / 398997493752
rev.com / 414502572119
Upsolver / 428641199958
Lacework / 434813966438
JupiterOne / 612791702201
Axonius.com / 802876684602
Fivetran / 834469178297
Sumo Logic / 926226587429
CloudCraft / 968898580625
Starburst / 179619298502
Monte Carlo / 190812797848
Plerion / 173985203412,044642040396,588158338731,736689547456
Barracuda Networks / 151784055945
ProsperOps / 205499583182
sst.dev / 226609089145
Mackerel / 217452466226
RudderStack / 422074288268
Turbot / 453761072151
Contrast Security / 490991382221,763284681916
CrowdStrike / 517716713836
Claroty / 883241448326
Serverless / 802587217904

In addition, the following two accounts had an additional error: Could not assume role with OIDC: Incorrect token audience

Scale / 307185671274
Snyk / 198361731867

I can only assume this means they have either a misconfigured Github OIDC provider or are using a custom action. The token audience must be sts.amazonaws.com with the official action.

Takeaways

So, what can we do with this information?

I’m not really sure. All we really learn is that these vendors currently or previously setup (hopefully, in that case, actually used) Github Actions.

I do wonder about the attack surface and isolation implications. On a quick check, there are vendors in this list that don’t use Github Actions with regards to the service for which they request trust in their Account ID. I wonder if it would be idea for these trusted third parties to isolate the trusted account from their other infrastructure, as a sort of “bastion” account, which would mean removing unnecessary attack surface.