GCP - Federation Abuse

{{#include ../../../banners/hacktricks-training.md}}

OIDC - Github Actions Abuse

GCP

In order to give access to the Github Actions from a Github repo to a GCP service account the following steps are needed:

  • Create the Service Account to access from github actions with the desired permissions:
projectId=FIXME
gcloud config set project $projectId

# Create the Service Account
gcloud iam service-accounts create "github-demo-sa"
saId="github-demo-sa@${projectId}.iam.gserviceaccount.com"

# Enable the IAM Credentials API
gcloud services enable iamcredentials.googleapis.com

# Give permissions to SA

gcloud projects add-iam-policy-binding $projectId \
    --member="serviceAccount:$saId" \
    --role="roles/iam.securityReviewer"
  • Generate a new workload identity pool:
# Create a Workload Identity Pool
poolName=wi-pool

gcloud iam workload-identity-pools create $poolName \
  --location global \
  --display-name $poolName

poolId=$(gcloud iam workload-identity-pools describe $poolName \
  --location global \
  --format='get(name)')
  • Generate a new workload identity pool OIDC provider that trusts github actions (by org/repo name in this scenario):
attributeMappingScope=repository # could be sub (GitHub repository and branch) or repository_owner (GitHub organization)

gcloud iam workload-identity-pools providers create-oidc $poolName \
  --location global \
  --workload-identity-pool $poolName \
  --display-name $poolName \
  --attribute-mapping "google.subject=assertion.${attributeMappingScope},attribute.actor=assertion.actor,attribute.aud=assertion.aud,attribute.repository=assertion.repository" \
  --issuer-uri "https://token.actions.githubusercontent.com"

providerId=$(gcloud iam workload-identity-pools providers describe $poolName \
  --location global \
  --workload-identity-pool $poolName \
  --format='get(name)')
  • Finally, allow the principal from the provider to use a service principal:
gitHubRepoName="repo-org/repo-name"
gcloud iam service-accounts add-iam-policy-binding $saId \
  --role "roles/iam.workloadIdentityUser" \
  --member "principalSet://iam.googleapis.com/${poolId}/attribute.${attributeMappingScope}/${gitHubRepoName}"
⚠️ Warning
Note how in the previous member we are specifying the **`org-name/repo-name`** as conditions to be able to access the service account (other params that makes it **more restrictive** like the branch could also be used). However it's also possible to **allow all github to access** the service account creating a provider such the following using a wildcard:
# Create a Workload Identity Pool
poolName=wi-pool2

gcloud iam workload-identity-pools create $poolName \
  --location global \
  --display-name $poolName

poolId=$(gcloud iam workload-identity-pools describe $poolName \
  --location global \
  --format='get(name)')

gcloud iam workload-identity-pools providers create-oidc "$poolName" \
  --project="$projectId" \
  --location="global" \
  --workload-identity-pool="$poolName" \
  --display-name="CTF provider" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,\
attribute.actor=assertion.actor,\
attribute.repository=assertion.repository,\
attribute.aud=assertion.aud" \
  --attribute-condition="assertion.repository_owner!=''"

providerId=$(gcloud iam workload-identity-pools providers describe $poolName \
  --location global \
  --workload-identity-pool $poolName \
  --format='get(name)')

# CHECK THE WILDCARD
gcloud iam service-accounts add-iam-policy-binding "${saId}" \
  --project="${projectId}" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/${poolId}/*"
⚠️ Warning
In this case anyone could access the service account from github actions, so it's important always to **check how the member is defined**.\ It should be always something like this: `attribute.{custom_attribute}`:`principalSet://iam.googleapis.com/projects/{project}/locations/{location}/workloadIdentityPools/{pool}/attribute.{custom_attribute}/{value}`

Github

Remember to change ${providerId} and ${saId} for their respective values:

name: Check GCP action
on:
  workflow_dispatch:
  pull_request:
    branches:
      - main

permissions:
  id-token: write

jobs:
  Get_OIDC_ID_token:
    runs-on: ubuntu-latest
    steps:
      - id: "auth"
        name: "Authenticate to GCP"
        uses: "google-github-actions/auth@v2.1.3"
        with:
          create_credentials_file: "true"
          workload_identity_provider: "${providerId}" # In the providerId, the numerical project ID (12 digit number) should be used instead of the alphanumeric project ID. ex: projects/123123123123/locations/global/workloadIdentityPools/iam-lab-7-gh-pool/providers/iam-lab-7-gh-pool-oidc-provider'
          service_account: "${saId}" # <sa-name>@<proj-id>.iam.gserviceaccount.com
          activate_credentials_file: true
      - id: "gcloud"
        name: "gcloud"
        run: |-
          gcloud config set project <project-id>
          gcloud config set account '${saId}'
          gcloud auth login --brief --cred-file="${{ steps.auth.outputs.credentials_file_path }}"
          gcloud auth list
          gcloud projects list
          gcloud secrets list

{{#include ../../../banners/hacktricks-training.md}}