Repository navigation
Client Library should stop sending x-goog-api-client, which otherwise needs to be included in a signed URL #823
Description
Activity
- addedapi: storageIssues related to the googleapis/java-storage API.Issues related to the googleapis/java-storage API.
on May 11, 2021 - addedtriage meI really want to be triaged.I really want to be triaged.
on May 14, 2021 - addedstatus: investigatingThe issue is under investigation, which is determined to be non-trivial.The issue is under investigation, which is determined to be non-trivial.type: questionRequest for information or clarification. Not an issue.Request for information or clarification. Not an issue.and removedtriage meI really want to be triaged.I really want to be triaged.status: investigatingThe issue is under investigation, which is determined to be non-trivial.The issue is under investigation, which is determined to be non-trivial.
on May 14, 2021 Hi @blackhogz,
I've spent some time trying to reproduce the error (starting from the linked Javadoc snippet) that you're reporting but I haven't been able to.
Can you provide any more information on the environment/configuration you're using when you run into this error?
- What version(s) of
com.google.cloud:google-cloud-storageare you using? - When calling
storage.writer(signedURL)is it called from the same client used to generatesignedURL? - If
signedURLis being used somewhere other than from the client that generated it can you provide some details of where it is being used from?
- What version(s) of
Hi @BenWhitehead and thanks a lot for helping with the issue.
Here's the main file that demonstrates the issue (I've replaced some actual project/bucket name with placeholder), and the gradle settings.
// UploadMain.java package test; import java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import com.google.cloud.WriteChannel; import com.google.cloud.storage.BlobId; import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.HttpMethod; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; import static java.nio.charset.StandardCharsets.UTF_8; public class UploadMain { public static void main(String[] args) { // Generating the Signed URL utilizing *authenticated* storage service, to simulate what will happen on a backend // server. Storage authenticatedStorage = StorageOptions.getDefaultInstance() .toBuilder() .setProjectId("project-id-placeholder").build().getService(); String bucketName = "bucket-name-placeholder"; String blobName = "blob-name-placeholder"; BlobId blobId = BlobId.of(bucketName, blobName); BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType("application/octet-stream").build(); Map<String, String> extensionHeaders = new HashMap<>(); extensionHeaders.put("x-goog-resumable", "start"); // // This line below is critical and is what this issue (https://github.com/googleapis/java-storage/issues/823) // is about. Without it, the upload later with the unauthenticated storage will fail. // // extensionHeaders.put("x-goog-api-client", "gl-java/1.8.0_292 gccl/1.113.16 gax/1.63.0"); URL signedURL = authenticatedStorage.signUrl(blobInfo, 15, TimeUnit.MINUTES, Storage.SignUrlOption.httpMethod(HttpMethod.POST), Storage.SignUrlOption.withExtHeaders(extensionHeaders) ); System.out.println("The signed url is " + signedURL); // Using the Signed URL with another *unauthenticated* storage, to simulate what will happen on a client. Storage unauthenticatedStorage = StorageOptions.getUnauthenticatedInstance().getService(); byte[] content = "Hello, World!".getBytes(UTF_8); try (WriteChannel writer = unauthenticatedStorage.writer(signedURL)) { writer.write(ByteBuffer.wrap(content, 0, content.length)); } catch (IOException e) { e.printStackTrace(); } } }and
# build.gradle plugins { id 'java' } group 'org.example' version '1.0-SNAPSHOT' repositories { mavenCentral() } dependencies { compile 'com.google.cloud:google-cloud-storage:1.113.16' testCompile group: 'junit', name: 'junit', version: '4.12' }And to answer your questions:
- What version(s) of com.google.cloud:google-cloud-storage are you using?
'com.google.cloud:google-cloud-storage:1.113.16'
- When calling storage.writer(signedURL) is it called from the same client used to generate signedURL?
It is called from another client (an unauthenticated
Storageinstance) to simulate the real use case, where the server with a GCP service account credential signs an URL and the client, without any credential, utilizes the signed URL to access cloud storage service.- If signedURL is being used somewhere other than from the client that generated it can you provide some details of where it is being used from?
Yes, as you can see from the attached
UploadMain.java, it is used by another unauthenticatedStorageinstance, different from the client that generates the signed URL. In fact, I feel the example code given in the comment may be misleading and not representing the real use case scenario of signed URL, where the consumer of the URL and the producer are different Storage instances.Thanks again, let me know how I can help!
Thank you for helping out with a reproduction.
I agree with you, tying a signed url to a specific client cuts off the use case you've described. I'll have to reach out to some folks and get their opinion on the criticality of
x-goog-api-clientbeing part of a signed url and if it'll be okay for us to mark it as excluded similar tox-goog-encryption-key.Reacted by natheiheiThanks @BenWhitehead . Keep me posted! Let me know if anything I can help :D
Kindly ping? Any updates?
@blackhogz We've got a fix ready in #915, apologies for the delay there were a number of code paths and cases we had to track down to settle on this fix.
We're also investigating adding a dedicated Signed url option to include the
x-goog-resumable: startrather than having to provide it via extension headers.
Thanks for stopping by to let us know something could be better!
Is your feature request related to a problem? Please describe.
I'm following
java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
Lines 2680 to 2693 in 5e99061
WriterChannel writer = storage.writer(signedURL)but keeps getting 403.I have looked into the issue, apparently, the writer when it attempts to start a resumable upload, it attaches a header of 'x-goog-api-client'. This needs to be included in the canonical extension headers as part of the string to sign when the server is signing the URL. However, this is very inconvenient as the server when offering a signed URL to a client, shouldn't care about (and also hard to know) the client build version (e.g.
gl-java/1.8.0_292 gccl/1.113.16 gax/1.63.0).Describe the solution you'd like
Either x-goog-api-client is excluded from the canonical extension headers, similar to
x-goog-encryption-keyetc as in step 3 of https://cloud.google.com/storage/docs/access-control/signed-urls-v2#about-canonical-extension-headers. -- change on GCS.or the client library does not send
x-goog-api-clientheader.Describe alternatives you've considered
I'm trying to find a way to work around this but not fruitful. I don't see a way to configure the client library to not attach this header.