Skip to main content
Applies to:
  • Plan:
  • Deployment:

Summary

Issue: BTQL queries using shape => 'summary' fail with 500 (surfaced as 502 at the GCE load balancer) with SigningError: Permission 'iam.serviceAccounts.signBlob' denied. Queries without shape => 'summary' succeed. Cause: The shape => 'summary' code path uses @google-cloud/storage to generate V4 signed GCS URLs when result sets exceed 4 MB. This requires iam.serviceAccounts.signBlob, which is implicitly available on GKE Autopilot but must be explicitly granted on GKE Standard with Workload Identity. Resolution: Grant roles/iam.serviceAccountTokenCreator to the Braintrust service account and apply the two related bucket IAM bindings.

Resolution steps

Step 1: Add the missing IAM bindings via Terraform

Apply three resources matching upstream PR #13:
# Allows the Braintrust SA to call signBlob on itself
resource "google_service_account_iam_member" "braintrust_token_creator" {
  service_account_id = google_service_account.braintrust.name
  role               = "roles/iam.serviceAccountTokenCreator"
  member             = "serviceAccount:${google_service_account.braintrust.email}"
}

# Brainstore SA: object admin on the API bucket (required for topics feature)
resource "google_storage_bucket_iam_member" "brainstore_api_bucket_gcs_object_admin" {
  bucket = google_storage_bucket.api.name
  role   = "roles/storage.objectAdmin"
  member = "serviceAccount:${google_service_account.brainstore.email}"
}

# Brainstore SA: legacy bucket reader on the API bucket
resource "google_storage_bucket_iam_member" "brainstore_api_bucket_gcs_reader" {
  bucket = google_storage_bucket.api.name
  role   = "roles/storage.legacyBucketReader"
  member = "serviceAccount:${google_service_account.brainstore.email}"
}
No pod restarts are required. IAM changes take effect immediately.

Step 2: Verify the fix

Run a query with shape => 'summary' against a large result set and confirm a 200 response:
SELECT COUNT(*)
FROM project_logs('your-project-id', shape => 'summary')
WHERE created >= '2026-03-10' AND created < '2026-03-11'

Background

Why only large result sets fail

Results under 4 MB are returned inline — no signed URL is generated and signBlob is never called. Results over 4 MB are materialized to GCS and returned as a signed URL, which requires iam.serviceAccounts.signBlob. This is why some shape => 'summary' queries succeed while aggregation queries on large result sets always fail. The threshold is configurable via the RESPONSE_BUCKET_OVERFLOW_THRESHOLD environment variable on the API pod.

GKE Autopilot vs. GKE Standard

On GKE Autopilot, the default node service account has broad enough permissions to cover signBlob implicitly. On GKE Standard with Workload Identity, the pod service account only has explicitly granted roles. The roles/iam.serviceAccountTokenCreator binding must be added manually if your fork predates the upstream Terraform change.