Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Presign url's require side loading request-payer header #2764

Closed
2 tasks done
Maldris opened this issue Aug 27, 2024 · 6 comments · Fixed by #2768
Closed
2 tasks done

Presign url's require side loading request-payer header #2764

Maldris opened this issue Aug 27, 2024 · 6 comments · Fixed by #2768
Assignees
Labels
bug This issue is a bug. p2 This is a standard priority issue service-api This issue is due to a problem in a service API, not the SDK implementation.

Comments

@Maldris
Copy link

Maldris commented Aug 27, 2024

Acknowledgements

  • I have searched (https:/aws/aws-sdk/issues?q=is%3Aissue) for past instances of this issue
  • I have verified all of my SDK modules are up-to-date (you can perform a bulk update with go get -u github.com/aws/aws-sdk-go-v2/...)

Describe the bug

When presigning a object get request to service a thumbnail to a client, I attempt to sign the url like so

req, err := s3.NewPresignClient(s3Client).PresignGetObject(context.Background(), &s3.GetObjectInput{
	Bucket:              &u.Host,
	Key:                 &u.Path,
	RequestPayer:        types.RequestPayerRequester,
}, s3.WithPresignExpires(time.Minute))

This returns a url, and Signed header, with the client required to specify the X-Amz-Request-Payer: requester header.

Expected Behavior

Normally I would expect a signed URL to be relatively self contained for a GET operation and not require a specific header to be set if I redirect a client to a URL (and as the buckets differ by use case we can't simply set that for all clients).

Current Behavior

Instead, the client receives a

<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
</Error>

As it did not contain the mandatory header.

Reproduction Steps

Using the USGS landsat bucket as an example:

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/s3"
	"github.com/aws/aws-sdk-go-v2/service/s3/types"
)

func main() {
	cfg, err := config.LoadDefaultConfig(context.Background())
	if err != nil {
		panic(err)
	}

	svc := s3.NewPresignClient(s3.NewFromConfig(cfg))
	req, err := svc.PresignGetObject(context.Background(), &s3.GetObjectInput{
		Bucket: aws.String("usgs-landsat"),
		Key:    aws.String("collection02/level-2/standard/oli-tirs/2024/201/032/LC09_L2SP_201032_20240129_20240130_02_T1/LC09_L2SP_201032_20240129_20240130_02_T1_thumb_large.jpeg"),
		RequestPayer:        types.RequestPayerRequester,
	}, func(o *s3.PresignOptions) {
		o.Expires = 20 * time.Minute
	})
	if err != nil {
		panic(err)
	}

	fmt.Println(req.Method, req.URL, req.SignedHeader)
}

Possible Solution

This is very similar to the issues reported in
#2484 #2508 and #2662
and similar changes may suffice

In that case, remove entry from the RequiredSignedHeaders map in aws/signer/internal/v4/headers.go
This should allow the parameter to be signed as the x-amz-request-payer query parameter.

I'm aware that this query parameter is indeed supported by the server due to the work around used for a similar issue in V1 (see additional information).
As a result, this change should work, but may also require the argument to be lower cased (I know some of these arguments will attempt to pass it as title case, and I'm unsure if the server accepts the argument in any case, or only lower).

Additional Information/Context

A similar issue was present in v1, but was able to be worked around by manipulating the request (no longer viable in v2)

An example of this would be:

params := &s3.GetObjectInput{
		Bucket:               aws.String(bucket),
		Key:                  aws.String(key),
}
req, _ := client.GetObjectRequest(params)
q := req.HTTPRequest.URL.Query()
if requestPayer {
	q.Set("x-amz-request-payer", "requester")
}
req.HTTPRequest.URL.RawQuery = q.Encode()
urlStr, err := req.Presign(time.Minute)
if err != nil {
	panic(err)
}

AWS Go SDK V2 Module Versions Used

bitbucket.org/arlula/landsatIntegration github.com/aws/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/aws/protocol/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/feature/ec2/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/feature/s3/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/internal/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/internal/endpoints/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/internal/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/internal/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/internal/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/internal/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/internal/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/internal/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/[email protected]
bitbucket.org/arlula/landsatIntegration github.com/aws/aws-sdk-go-v2/service/[email protected]

Compiler and Version used

1.23.0

Operating System and version

Windows 10

@Maldris Maldris added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Aug 27, 2024
@Maldris
Copy link
Author

Maldris commented Aug 27, 2024

I'm assuming that the triage team will be able to delete those bot posts, as I cannot

@Maldris
Copy link
Author

Maldris commented Aug 27, 2024

also, I was reading the source further and realised, the Range header will have the same issue if you wanted to sign a request to a range within a file

again, this issue occurred in v1, our presign get object helper for v1 looked something like (still simplified as it had a bunch of debug reporting and extras not relevant here):

func GetS3RedirectForResource(bucket, key, name, contentType string, byteRange *Range, requestPayer bool, client *s3.S3) (string, error) {
	exp := time.Now().Add(time.Second * 15)
	params := &s3.GetObjectInput{
		Bucket:               aws.String(bucket),
		Key:                  aws.String(key),
		ResponseExpires:      &exp,
	}
	if name != "" {
		params.ResponseContentDisposition = aws.String(`attachment; filename="` + name + `"`)
	}
	if contentType != "" {
		params.ResponseContentType = aws.String(contentType)
	}
	req, _ := client.GetObjectRequest(params)
	q := req.HTTPRequest.URL.Query()
	if byteRange != nil {
		q.Set("range", byteRange.String())
	}
	if requestPayer {
		q.Set("x-amz-request-payer", "requester")
	}
	req.HTTPRequest.URL.RawQuery = q.Encode()
	urlStr, err := req.Presign(time.Minute)
	if err != nil {
		return "", errors.Wrap(err, "Error getting S3 presigned url")
	}
	return urlStr, nil
}

Where range was a helper decoder in our package for decoding range request headers for validation

@RanVaknin RanVaknin self-assigned this Aug 27, 2024
@RanVaknin
Copy link
Contributor

RanVaknin commented Aug 27, 2024

Hi @Maldris ,

In that case, remove entry from the RequiredSignedHeaders map in aws/signer/internal/v4/headers.go
This should allow the parameter to be signed as the x-amz-request-payer query parameter.

Unlike expected-bucket-owner that was hoisted in v1, and signed in v2 (breaking change), this header you are describing did not change its behavior from v1 to v2, therefore we are not going to change its behavior at this point.

A similar issue was present in v1, but was able to be worked around by manipulating the request (no longer viable in v2)

You can implement a middleware that does the same thing in v2 by using the snippet from my comment here.

In that comment I also explain the rationale behind signing most things instead of hoisting. There are efforts from S3 to standardize the behavior of the presigned URLs, but without having the server side support we cannot change the implementation.

I understand that this is counter intuitive, and I just want to emphasize that we are aware this issue of hoisting vs signing in presigned URLs is a pain point across all SDKs. There are active efforts both from the SDK team and from S3 to address this, but we are unable to commit to an ETA.

Let me know if you are able to get that middleware to work.

Thanks,
Ran~

@RanVaknin RanVaknin added guidance Question that needs advice or information. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. p2 This is a standard priority issue and removed needs-triage This issue or PR still needs to be triaged. bug This issue is a bug. labels Aug 27, 2024
@lucix-aws lucix-aws added bug This issue is a bug. service-api This issue is due to a problem in a service API, not the SDK implementation. and removed guidance Question that needs advice or information. response-requested Waiting on additional info and feedback. Will move to "closing-soon" in 7 days. labels Aug 30, 2024
Copy link

This issue is now closed. Comments on closed issues are hard for our team to see.
If you need more assistance, please open a new issue that references this one.

@Maldris
Copy link
Author

Maldris commented Sep 2, 2024

@RanVaknin, I'm sorry that this got closed over a weekend, as I don't believe this issue is actually completed.

Re the PR #2768 by @bhavya2109sharma, that PR doesn't appear to work, and now a request will not work even if a client sends the header.

The cause appears to be that the header uses the title case form X-Amz-Request-Payer, which is what that PR also uses.
However, the server accepts the query parameter in lower case form x-amz-request-payer as shown in my example above (and the same for Range v range).


To address your comments however, as described above, these are features that the S3 server does support, and we actively use in one of our older systems (where that snippet I included above came from), so the comment:

but without having the server side support we cannot change the implementation.

is somewhat moot as the server does support both of these features.


As for your middleware suggestion, we are investigating if we can utilize it. However, as out main application is designed to deploy to multiple clouds, and these behaviours are mediated through a CDK to abstract cloud specific behaviour, this pattern is both harder to implement, and may put us in breach of our requirements for deploying to military and governmental clients (part of the reason we are reviewing and removing sdk v1 from our older stable systems).

I have a working prototype using this pattern for now, it involves quite a bit of a kludge to happen at all, but it does work, the telling thing will be if Platform One flags it, as it does breach their rules for hardened deployment images and coding standards.

@RanVaknin RanVaknin reopened this Sep 2, 2024
@RanVaknin RanVaknin added the investigating This issue is being investigated and/or work is in progress to resolve the issue. label Sep 2, 2024
Copy link

github-actions bot commented Sep 3, 2024

This issue is now closed. Comments on closed issues are hard for our team to see.
If you need more assistance, please open a new issue that references this one.

@lucix-aws lucix-aws removed the investigating This issue is being investigated and/or work is in progress to resolve the issue. label Sep 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. p2 This is a standard priority issue service-api This issue is due to a problem in a service API, not the SDK implementation.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants
@Maldris @bhavya2109sharma @RanVaknin @lucix-aws and others