Skip to content

Support authentication with external_account with Workload Identity Federation #606

@alexeyinkin

Description

@alexeyinkin

I try to run an app from a GitHub workflow. It needs to communicate with Google Cloud. The workflow uses Workload Identity Federation to authenticate. It produces a key of the type external_account while googleapis_auth currently only handles the type service_account.

Given that Workflow Identity Federation is the recommended way to access Google Cloud resources, I think this should be fixed.

Steps to Reproduce

1. Create a project and configure Workload Identity Federation:

export PROJECT='my-project-id'
export ORGANIZATION='my-organization-id'
export GITHUB_USERNAME='my-github-username'
export REPO='my-github-repo'

gcloud projects create $PROJECT --name=$PROJECT --organization=$ORGANIZATION
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT --format='value(projectNumber)')

gcloud services enable cloudbilling.googleapis.com --project=$PROJECT
gcloud services enable iam.googleapis.com --project=$PROJECT
gcloud iam service-accounts create "testing" --project=$PROJECT

gcloud projects add-iam-policy-binding $PROJECT \
  --member="serviceAccount:testing@$PROJECT.iam.gserviceaccount.com" \
  --role="roles/owner" \
  --project=$PROJECT

gcloud iam workload-identity-pools create "github" --location="global" --project=$PROJECT

gcloud iam \
  workload-identity-pools providers create-oidc "github" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --location="global" \
  --workload-identity-pool="github" \
  --project=$PROJECT

gcloud projects add-iam-policy-binding $PROJECT \
  --member="principalSet://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/github/attribute.repository/$GITHUB_USERNAME/$REPO" \
  --role="roles/viewer" \
  --project=$PROJECT

2. Create a Dart app

pubspec.yaml:

name: test1
publish_to: none

environment:
  sdk: ^3.2.2

dependencies:
  googleapis_auth: ^1.4.1

main.dart:

import 'dart:io';

import 'package:googleapis_auth/auth_io.dart';

Future<void> main() async {
  print(File(Platform.environment['GOOGLE_APPLICATION_CREDENTIALS']!).readAsStringSync());
  await clientViaApplicationDefaultCredentials(scopes: []);
}

3. Set up the workflow

Create the repository variables: PROJECT_NAME, PROJECT_NUMBER.

The workflow:

on:
  - workflow_dispatch

jobs:
  test:
    permissions:
      id-token: write

    runs-on: ubuntu-latest
    steps:

      - uses: actions/checkout@v4

      - uses: 'google-github-actions/auth@v2.1.2'
        with:
          workload_identity_provider: 'projects/${{ vars.PROJECT_NUMBER }}/locations/global/workloadIdentityPools/github/providers/github'
          service_account: 'testing@${{ vars.PROJECT_NAME }}.iam.gserviceaccount.com'

      - uses: dart-lang/setup-dart@v1
        with:
          sdk: 3.3.2

      - name: 'Test'
        run: |
          dart pub get
          dart main.dart

4. Run the workflow

Expected

No error

Actual

Run dart pub get
  dart pub get
  dart main.dart
  shell: /usr/bin/bash -e {0}
  env:
    CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE: /home/runner/work/my-github-repo/my-github-repo/gha-creds-5647adab863c00a9.json
    GOOGLE_APPLICATION_CREDENTIALS: /home/runner/work/my-github-repo/my-github-repo/gha-creds-5647adab863c00a9.json
    GOOGLE_GHA_CREDS_PATH: /home/runner/work/my-github-repo/my-github-repo/gha-creds-5647adab863c00a9.json
    CLOUDSDK_CORE_PROJECT: my-project-id
    CLOUDSDK_PROJECT: my-project-id
    GCLOUD_PROJECT: my-project-id
    GCP_PROJECT: my-project-id
    GOOGLE_CLOUD_PROJECT: my-project-id
    DART_HOME: /opt/hostedtoolcache/dart/3.3.2/x64
    PUB_CACHE: /home/runner/.pub-cache
    PUB_TOKEN: ***
Resolving dependencies...
+ args 2.4.2
+ async 2.11.0
+ collection 1.18.0
+ crypto 3.0.3
+ google_identity_services_web 0.3.1+1
+ googleapis_auth 1.5.0
+ http 1.2.1
+ http_parser 4.0.2
+ meta 1.12.0
+ path 1.9.0
+ source_span 1.10.0
+ string_scanner 1.2.0
+ term_glyph 1.2.1
+ typed_data 1.3.2
+ web 0.5.1
Changed 15 dependencies!
{"type":"external_account","audience":"//iam.googleapis.com/projects/my-project-number/locations/global/workloadIdentityPools/github/providers/github","subject_token_type":"urn:ietf:params:oauth:token-type:jwt","token_url":"https://sts.googleapis.com/v1/token","credential_source":{"url":"https://pipelinesghubeus14.actions.githubusercontent.com/***/00000000-0000-0000-0000-000000000000/_apis/distributedtask/hubs/Actions/plans/***/jobs/5264e576-3c6f-51f6-f055-fab409685f20/idtoken?api-version=2.0&audience=https%3A%2F%2Fiam.googleapis.com%2Fprojects%2Fmy-project-number%2Flocations%2Fglobal%2FworkloadIdentityPools%2Fgithub%2Fproviders%2Fgithub","headers":{"Authorization":"***"},"format":{"type":"json","subject_token_field_name":"value"}},"service_account_impersonation_url":"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/testing@my-project-id.iam.gserviceaccount.com:generateAccessToken"}
Unhandled exception:
Invalid argument(s): The given credentials are not of type service_account (was: external_account).
#0      new ServiceAccountCredentials.fromJson (package:googleapis_auth/src/service_account_credentials.dart:55:7)
#1      fromApplicationsCredentialsFile (package:googleapis_auth/src/adc_utils.dart:59:31)
<asynchronous suspension>
#2      clientViaApplicationDefaultCredentials (package:googleapis_auth/auth_io.dart:61:12)
<asynchronous suspension>
#3      main (file:///home/runner/work/my-github-repo/my-github-repo/main.dart:7:3)
<asynchronous suspension>
Error: Process completed with exit code 255.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions