-
Notifications
You must be signed in to change notification settings - Fork 391
Description
Please make sure you have searched for information in the following guides.
- Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues
- Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js
- Check our Troubleshooting guide: https://github.com/googleapis/google-cloud-node/blob/main/docs/troubleshooting.md
- Check our FAQ: https://github.com/googleapis/google-cloud-node/blob/main/docs/faq.md
- Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md
- Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs
- Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples
A screenshot that you have tested with "Try this API".
The api is not the problem.
Link to the code that reproduces this issue. A link to a public Github Repository or gist with a minimal reproduction.
https://gist.github.com/instilled/0d98c90cd59cf625f496ddebbf0d522d
A step-by-step description of how to reproduce the issue, based on the linked reproduction.
import { Storage } from "@google-cloud/storage";
import { GoogleAuth, Impersonated } from "google-auth-library";
const auth = new GoogleAuth({
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
});
const sourceClient = await auth.getClient();
const impersonatedCreds = new Impersonated({
sourceClient,
targetPrincipal: "my-sa@project.iam.gserviceaccount.com",
targetScopes: ["https://www.googleapis.com/auth/devstorage.read_write"],
lifetime: 3600,
});
const storage = new Storage({
authClient: impersonatedCreds,
projectId: "my-project",
});
// This fails with multiple errors
const [signedUrl] = await storage
.bucket("my-bucket")
.file("test.txt")
.getSignedUrl({
version: "v4",
action: "write",
expires: Date.now() + 3600000,
});A clear and concise description of what the bug is, and what you expected to happen.
Environment
- @google-cloud/storage version: 7.18.0
- google-auth-library version: 10.5.0
- Node.js version: 24.x (Bun 1.3.5)
- OS: macOS
Problem
When using Impersonated credentials from google-auth-library@10.x with @google-cloud/storage@7.x, calling getSignedUrl() fails. There are three separate issues:
Issue 1: authClient option is ignored
The Storage constructor ignores the authClient option and creates its own GoogleAuth instance internally:
const storage = new Storage({
authClient: impersonatedCreds, // This is ignored!
projectId: "my-project",
});
console.log(storage.authClient === impersonatedCreds); // false - Storage creates its own GoogleAuthIssue 2: Impersonated class lacks getCredentials() method
The URLSigner class calls this.auth.getCredentials() at https://github.com/googleapis/nodejs-storage/blob/main/src/signer.ts#L168, but the Impersonated class from google-auth-library@10.x doesn't implement this method.
Issue 3: Impersonated.sign() returns object instead of string
The URLSigner expects sign() to return a base64 string, but Impersonated.sign() returns { keyId: string, signedBlob: string }:
// Storage expects:
const signature: string = await auth.sign(blobToSign);
// Impersonated returns:
const result = await impersonatedCreds.sign(data);
// result = { keyId: "...", signedBlob: "..." }
This causes: SigningError: The first argument must be of type string, Buffer, ArrayBuffer...A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. **
This is expected google best practices to issue signed url with least privilege, e.g. as per https://docs.cloud.google.com/storage/docs/access-control/best-practices-access-control, https://docs.cloud.google.com/iam/docs/service-account-impersonation
There's somehow related but not identical issues #2427 #2381
An alternative would likely be to use the API, but this is an incomplete solution due to additional roundtrip costs incurred.
Workaround
import { Storage } from "@google-cloud/storage";
import { GoogleAuth, Impersonated } from "google-auth-library";
const SA_EMAIL = "some-sa@my-project.iam.gserviceaccount.com";
const auth = new GoogleAuth({
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
});
const sourceClient = await auth.getClient();
const impersonatedCreds = new Impersonated({
sourceClient,
targetPrincipal: SA_EMAIL,
delegates: [],
targetScopes: ["https://www.googleapis.com/auth/devstorage.read_write"],
lifetime: 3600,
});
// Patch 1: Add missing getCredentials method (Storage SDK requires this)
(impersonatedCreds as any).getCredentials = async () => ({ client_email: SA_EMAIL });
// Patch 2: Wrap sign method to return just signedBlob string (Storage expects string, not object)
const originalSign = impersonatedCreds.sign.bind(impersonatedCreds);
(impersonatedCreds as any).sign = async (data: string) => {
const result = await originalSign(data);
return result.signedBlob;
};
const storage = new Storage({
authClient: impersonatedCreds as any,
projectId: "my-project",
});
// Patch 3: Force override authClient after construction
storage.authClient = impersonatedCreds as any;
const [signedUrl] = await storage
.bucket("my-bucket")
.file("test.mp4")
.getSignedUrl({
version: "v4",
action: "resumable",
contentType: "text/plain",
expires: new Date(Date.now() + 60 * 60 * 1000),
});
console.log(signedUrl);