Details
-
Bug
-
Status: Open
-
Major
-
Resolution: Unresolved
-
2.3.0
-
None
-
None
-
Debian
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)
Description
(Disclaimer: I'm not 100% sure whether this is a bug report or a feature request, as I've found only scant information available)
We have the use case where we wish to provide a signed aws-s3 PUT URL to a browser client which will be used to process a file upload (non-chunked, single URL). We are using version 2.3.0 (the latest public release as of this writing).
The jclouds API has support for this via BlobStoreContext#getSigner , i.e. BlobRequestSigner#signPutBlob(String containerName, Blob blob, long timeInSeconds).
However, there seems to be no support for signing custom headers as part of the URL.
The relevant AWS Documentation (link here) states that:
For added security, you should sign all the request headers that you plan to include in your request.
I dug around the jclouds-core code and saw that, for pre-signed PUT requests, a PhantomPayload is used which appears, for all intents and purposes, as a real payload with checksum and content-length.
However, although this information is available at the time of signing, the only headers which are part of the signed headers are those which are either specifically annotated as such (e.g.: org.jclouds.s3.S3Client#putObject, which contains @Headers(keys = EXPECT, values = "100-continue") or are otherwise part of normal request flow depending on media type (i.e. content-type, accept).
The way I access the API is as follows:
@Override public URI getPreSignedPutUrl(final String blobId, final long contentLength, final String checksum, final String mediaType) { BlobStoreContext context = blobStoreContextProvider.getBlobStoreContext(); BlobRequestSigner signer = context.getSigner(); Blob expectedBlob = context.getBlobStore().blobBuilder(blobId) .forSigning() .contentType(mediaType) .contentMD5(HashCode.fromString(checksum)) .contentLength(contentLength) .build(); HttpRequest request = signer.signPutBlob(this.bucket.getName(), expectedBlob, 60); return request.getEndpoint(); }
What I expected to happen was:
- A pre-signed URL is created with the blob I pass it.
- The blob's content-md5 and content-length values are passed through as part of the signed headers.
- The pre-signed URL includes these values so that only a specific file (or something that has exactly that checksum and content length) can be uploaded to said URL.
What did happen was:
- A pre-signed URL is created with the blob I pass it.
- RestAnnotationProcessor and the default S3Client API both seem to not take any of the blob metadata into account
- The pre-signed URL includes only the host header as a signed header.
As a result, I can upload any file I wish to the pre-signed PUT URL, not just the one that I specifically intended, although metadata like content checksum and length is available and could also be signed within the request.
For what it's worth, I did try to extend the existing BlogRequestSigner to include PutOptions (which have an effect on signed headers, as far as I can tell, see: RestAnnotationProcessor#apply:282), but got lots of Guice binding errors that I was unable to resolve. I am relatively new to Guice, so that might have been user error - as the documentation is lacking though, I'm unsure if that's the preferred way.
Lastly, https://issues.apache.org/jira/browse/JCLOUDS-1161 exists which seems loosely related to my issue. As I understand it though, the "workaround" described therein should no longer be necessary (since V4 is automatically used for aws-s3 since ~2.2.0) and it does not help in my case either as it has more to do with which signer is used, and not with the tuning of the resulting URL.
Is the current behaviour, as described above, correct? Is the lack of a support for additional signed headers intentional? Is there another way that I'm not quite seeing?
Any feedback would be greatly appreciated.