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

service/s3: presigned PutObject with Tagging set does not add tagging to object in S3 bucket. #2762

Open
ivanphdz opened this issue Oct 4, 2022 · 7 comments
Labels
bug This issue is a bug. p2 This is a standard priority issue service-api General API label for AWS Services.

Comments

@ivanphdz
Copy link

ivanphdz commented Oct 4, 2022

Describe the bug

Objects uploaded to S3 with presigned Put Object URL with Tagging parameter set, do not have the tagging metadata. The SDK hoists the x-amz-tagging header to the query string, which is ignored by S3. S3 requires the x-amz-tagging to be a signed header.

Expected Behavior

x-amz-tagging must be a signed header

http://localhost:4566/sample-bucket/my-file3.txt?x-amz-server-side-encryption=AES256&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test%2F20221005%2Fuus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221004T125303Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host%3Bx-amz-tagging&X-Amz-Signature=f5d10a2bdf42fc460ece2f070f9269c21ba301de3c01af59c52dcac27a12sq23

Current Behavior

Example

def get_presigned_url(bucket, object_key)
  url = bucket.object(object_key).presigned_url(:put, expires_in: 300, server_side_encryption: "AES256", tagging: "key1=value1")
  puts "Created presigned URL: #{url}."
  URI(url)
rescue Aws::Errors::ServiceError => e
  puts "Couldn't create presigned URL for #{bucket.name}:#{object_key}. Here's why: #{e.message}"
end

Result

http://localhost:4566/sample-bucket/my-file3.txt?x-amz-server-side-encryption=AES256&x-amz-tagging=key1%3Dvalue1&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test%2F20221005%2Fuus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20221004T125303Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host&X-Amz-Signature=f5d10a2bdf42fc460ece2f070f9269c21ba301de3c01af59c52dcac27a12sq23

Reproduction Steps

require "aws-sdk-s3"
require "net/http"
require 'uri'

#"x-amz-tagging" => "Key2=Value2&Key1=Value1"

def get_presigned_url(bucket, object_key)
  url = bucket.object(object_key).presigned_url(:put, expires_in: 300, server_side_encryption: "AES256", tagging: "key1=value1")
  puts "Created presigned URL: #{url}."
  URI(url)
rescue Aws::Errors::ServiceError => e
  puts "Couldn't create presigned URL for #{bucket.name}:#{object_key}. Here's why: #{e.message}"
end

def run_demo
  bucket_name = "sample-bucket"
  object_key = "my-file3.txt"
  object_content = "This is the content of my-file.txt."

  bucket = Aws::S3::Bucket.new(bucket_name)
  presigned_url = get_presigned_url(bucket, object_key)
  return unless presigned_url
  response = Net::HTTP.start(presigned_url.host) do |http|
    http.send_request("PUT", presigned_url.request_uri, object_content, "content_type" => "")
  end

  case response
  when Net::HTTPSuccess
    puts "Content uploaded!"
  else
    puts response.value
  end
end

run_demo ```

### Possible Solution

_No response_

### Additional Information/Context

_No response_

### Gem name ('aws-sdk', 'aws-sdk-resources' or service gems like 'aws-sdk-s3') and its version

aws-sdk-s3

### Environment details (Version of Ruby, OS environment)

3.1.2
@ivanphdz ivanphdz added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Oct 4, 2022
@ivanphdz ivanphdz changed the title (short issue description) service/s3: presigned PutObject with Tagging set does not add tagging to object in S3 bucket. Oct 4, 2022
@mullermp
Copy link
Contributor

mullermp commented Oct 4, 2022

Thanks for opening an issue. I agree that the tagging for pre-signed URL is not working correctly. Were you able to get this case to succeed? I made some local code changes to the SDK that has the tagging header on the query string (&x-amz-tagging=key1%3Dvalue1) AND also a signed header (X-Amz-SignedHeaders=host%3Bx-amz-server-side-encryption%3Bx-amz-tagging), and the request succeeds, but I don't see the tag on the object in the console! I tried without hoisting the header, but also signing it, as you suggested, and that returns a 403, likely signature mismatch.

@mullermp mullermp added investigating Issue is being investigated and removed bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Oct 4, 2022
@mullermp
Copy link
Contributor

mullermp commented Oct 4, 2022

A url with x-amz-server-side-encryption hoisted but not signed seems to work as expected, the object is encrypted, and when not present, it is not. So I don't think the requirement is that the header must be signed. It might be that S3 just shouldn't be ignoring x-amz-tagging.

@mullermp
Copy link
Contributor

mullermp commented Oct 4, 2022

I'll follow up with S3 on this. A work around is that you can provide the header to your Net::HTTP request and it succeeds. Instead of presigned_url, you can use presigned_request which returns a tuple of URL and headers to send.

  url, headers = bucket.object(object_key).presigned_request(:put, expires_in: 300, server_side_encryption: "AES256", tagging: "Key1=Value1")
  puts "Created presigned URL: #{url}."
  [URI(url), headers]


  presigned_url, headers = get_presigned_url(bucket, object_key)
  return unless presigned_url
  response = Net::HTTP.start(presigned_url.host) do |http|
    http.send_request("PUT", presigned_url.request_uri, object_content, headers)
  end

@ivanphdz
Copy link
Author

ivanphdz commented Oct 4, 2022

yeah S3 ignores the x-amz-tagging query param, you need to provide it as a header to get the objects tagged correctly, I realized that because I test the same scenario in golang, and the Go-lang SDK is adding the x-amz-tagging values in the Signature calculation header and you need to provide the x-amz-tagging with the same values as a header to work.

package main

import (
	"context"
	"fmt"
	"time"

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

func main() {

	client := s3.New(options)
	name := "bucketname"
	fmt.Println("Upload an object to the bucket")

	fmt.Println("Create Presign client")
	presignClient := s3.NewPresignClient(client)
	presignParams := &s3.PutObjectInput{
		Bucket:  aws.String(name),
		Key:     aws.String("myfilego.txt"),
		Tagging: aws.String("Key1=Value2"),
	}

	// Apply an expiration via an option function
	presignDuration := func(po *s3.PresignOptions) {
		po.Expires = 5 * time.Minute
	}

	presignResult, err := presignClient.PresignPutObject(context.TODO(), presignParams, presignDuration)

	if err != nil {
		panic("Couldn't get presigned URL for PutObject")
	}

	fmt.Printf("Presigned URL For object: %s\n", presignResult.URL)
}

Result

curl --request PUT 'https://bucketname.s3.amazonaws.com/myfilego.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA3XE3RPJZRSVRC44W%2F2022100ew%2Fus-east-2%2Fs3%2Faws4_request&X-Amz-Date=20221004T165857Z&X-Amz-Expires=300&X-Amz-SignedHeaders=host%3Bx-amz-tagging&x-id=PutObject&X-Amz-Signature=293e5d1a6504f98846fc51ebe709e21f7579ee58bb5cdf418c0b2adc7514dffdawe'
--header 'x-amz-tagging: Key1=Value2'

(I added the header manually, but this works as expected)

I agree it's really confusing anyway, thanks I will test the workaround.

@mullermp
Copy link
Contributor

mullermp commented Oct 4, 2022

Yes. That's why we have both presigned_url and presigned_request. In theory the URL with hoisted query params should be working! It works with SSE. Calling presigned_request will not hoist but it will return you the header (x-amz-tagging) that you need to include with your request. I do think this is a bug on S3's side. The chances are slim that it will be fixed but I'll get in contact with them.

@mullermp mullermp added service-api General API label for AWS Services. and removed investigating Issue is being investigated labels Oct 4, 2022
@mullermp
Copy link
Contributor

We've created an internal tracking ticket with S3 regarding this. In the mean time, I would rely on sending the header directly instead of hoisting it. The presigned_request method should work for your case.

@alextwoods alextwoods added investigating Issue is being investigated bug This issue is a bug. and removed investigating Issue is being investigated bug This issue is a bug. labels Oct 24, 2022
@mullermp
Copy link
Contributor

Soft update, S3 acknowledges that this is a bug and is working on a fix.

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 General API label for AWS Services.
Projects
None yet
Development

No branches or pull requests

4 participants