Skip to content

Presigned URLs should use Query parameters for Additional request parameters (instead of headers) #2484

Closed as not planned
@FlorianSW

Description

Describe the feature

AWS S3 allows additional request parameters to be set when a request is issued. In my case, I want to add the expected bucket owner to a GetObject request to S3. These parameters can either be added via a header or a query parameter.

When using presigned URLs, their main intended use case is to encapsulate the entire request in the URL for later usage by a thrid-party who should not need to know additional information to add to the request (except executing the presigned URL).[1]

When creating a presigned URL in the aws-go-sdk-v3 with the following code (a GetObject request with an expected bucket owner parameter set):

package main

import (
	"context"
	"fmt"
	"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"
	"os"
	"strings"
)

func main() {
	if len(os.Args) != 4 {
		println("Usage: go run test_bucket_owner.go ACCOUNT_ID BUCKET_NAME OBJECT_KEY")
		os.Exit(1)
	}

	account := os.Args[1]
	bucket := os.Args[2]
	key := os.Args[3]
	ctx := context.Background()
	cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("eu-west-1"))
	if err != nil {
		panic(err)
	}
	c := s3.NewFromConfig(cfg)
	p := s3.NewPresignClient(c)
	i := s3.GetObjectInput{
		ExpectedBucketOwner: aws.String(account),
		Bucket:              aws.String(bucket),
		Key:                 aws.String(key),
	}
	u, err := p.PresignGetObject(ctx, &i)
	if err != nil {
		panic(err)
	}
	println(u.URL)
	for k, vs := range u.SignedHeader {
		fmt.Printf("%s: %s\n", k, strings.Join(vs, ","))
	}
	println(u.Method)
}

the URL will look something like this:
https://<bucket-name>.s3.eu-west-1.amazonaws.com/<object-key>?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<access-key-id>%2F20240209%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20240209T095920Z&X-Amz-Expires=900&X-Amz-Security-Token=IQoJb3Jp...&X-Amz-SignedHeaders=host%3Bx-amz-expected-bucket-owner&x-id=GetObject&X-Amz-Signature=e856...

A caller of this URL is required to add the X-Amz-Expected-Bucket-Owner header with the same value as provided by the creator of the pre-signed URL. This adds a burden and requires additional information that needs to be passed from the creator of the pre-signed URL to the executor of the URL. It also seems to be in contrast to the intended behaviour of pre-signed URLs, as they are now not entirely expressed as a URL anymore.

When executing the above code to create a pre-signed URL, the URL should then look something like that, all parameters should be included as query parameters. This is supported by the S3 API:
https://<bucket-name>.s3.eu-west-1.amazonaws.com/<object-key>?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<access-key-id>%2F20240209%2Feu-west-1%2Fs3%2Faws4_request&X-Amz-Date=20240209T100408Z&X-Amz-Expires=900&X-Amz-Security-Token=IQoJb3Jp...&X-Amz-SignedHeaders=host&x-amz-expected-bucket-owner=<aws-account-id>&x-id=GetObject&X-Amz-Signature=7ef0789...

Executing this URL will work without needing to add any additional header values.

[1] Refer to "Using query parameters to authenticate requests is useful when you want to express a request entirely in a URL." (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html)

Use Case

I have two components, which work with objects in a S3 bucket, one of them having actual aws credentials to access objects from buckets (the buckets are owned by different accounts). The service that has credentials for these buckets creates pre-signed URLs for the other component to use and work with these objects. As the buckets are configured by customers of these both services, I want to use the expected bucket owner as an additional safety net to ensure that both services are talking to buckets that are owned by the customer we intend them to be owned by. This should protect against a customer deleting a bucket and it being re-created by a malicious actor.

The service that creates pre-signed URLs is expected to pass a URL only to the other service. No other information is communicated at this step. This does, as of now, not work with presigned URLs and the aws-sdk-go-v2.

Proposed Solution

No response

Other Information

No response

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

AWS Go SDK V2 Module Versions Used

v1.24.1

Go version used

go version go1.21.6 darwin/arm64

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

Labels

feature-requestA feature should be added or improved.investigatingThis issue is being investigated and/or work is in progress to resolve the issue.p2This is a standard priority issue

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions