From e7afe649867383e7318b035614d9abd085e3a212 Mon Sep 17 00:00:00 2001 From: Morgan Wowk Date: Thu, 2 Apr 2026 15:37:33 -0700 Subject: [PATCH] fix: Use IAM Sign Blob for artifact signed URLs on GKE Workload Identity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signed URL implementation was broken on GKE because Workload Identity credentials (compute_engine.Credentials) are token-based and have no private key, so generate_signed_url() would fail with "you need a private key to sign credentials". Fix this by using the IAM Sign Blob API via google.auth.iam.Signer, which lets the service account sign on its own behalf without a JSON key file. This avoids credential leak risk and maintenance overhead of rotating SA keys. Requires roles/iam.serviceAccountTokenCreator to be granted to the oasis-backend SA on itself — see the paired terraform-the-cloud change. Co-Authored-By: Claude Sonnet 4.6 --- cloud_pipelines_backend/api_server_sql.py | 33 ++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/cloud_pipelines_backend/api_server_sql.py b/cloud_pipelines_backend/api_server_sql.py index 413d806..916f619 100644 --- a/cloud_pipelines_backend/api_server_sql.py +++ b/cloud_pipelines_backend/api_server_sql.py @@ -1051,16 +1051,31 @@ def get_signed_artifact_url( f"The get_signed_artifact_url method only supports Google Cloud Storage URIs, but got {artifact_data.uri=}." ) + from google.auth import compute_engine + from google.auth import iam + from google.auth.transport import requests as google_requests from google.cloud import storage - from google import auth - - # Avoiding error: "you need a private key to sign credentials." - # "the credentials you are currently using just contains a token. - # "see https://googleapis.dev/python/google-api-core/latest/auth.html#setting-up-a-service-account for more details." - credentials = auth.default( - scopes=["https://www.googleapis.com/auth/cloud-platform.read-only"] - )[0] - storage_client = storage.Client(credentials=credentials) + from google.oauth2 import service_account + + # When running on GKE with Workload Identity, google.auth.default() returns + # token-based Compute Engine credentials that have no private key and cannot + # sign URLs directly. Instead, we use the IAM Sign Blob API, which lets the + # service account sign on its own behalf — no JSON key required. This requires + # iam.serviceAccounts.signBlob to be granted to the SA on itself. + auth_request = google_requests.Request() + credentials = compute_engine.Credentials() + credentials.refresh(auth_request) + signer = iam.Signer( + request=auth_request, + credentials=credentials, + service_account_email=credentials.service_account_email, + ) + signing_credentials = service_account.Credentials( + signer=signer, + service_account_email=credentials.service_account_email, + token_uri="https://oauth2.googleapis.com/token", + ) + storage_client = storage.Client(credentials=signing_credentials) blob = storage.Blob.from_string(uri=artifact_data.uri, client=storage_client) signed_url = blob.generate_signed_url( # Expiration is required. Max expiration value is 7 days.