Search code examples
amazon-s3aws-sdk

Any way to use presigned URL uploads and enforce tagging?


Is there any way to issue a presigned URL to a client to upload a file to S3, and ensure that the uploaded file has certain tags? Using the Python SDK here as an example, this generates a URL as desired:

s3.generate_presigned_url('put_object', 
                          ExpiresIn=3600,
                          Params=dict(Bucket='foo', 
                                      Key='bar', 
                                      ContentType='text/plain', 
                                      Tagging='foo=bar'))

This is satisfactory when uploading while explicitly providing tags:

$ curl 'https://foo.s3.amazonaws.com/bar?AWSAccessKeyId=...&Signature=...&content-type=text%2Fplain&x-amz-tagging=foo%3Dbar&Expires=1538404508' \
  -X PUT
  -H 'Content-Type: text/plain' \
  -H 'x-amz-tagging: foo=bar' \
  --data-binary foobar

However, S3 also accepts the request when omitting -H 'x-amz-tagging: foo=bar', which uploads the object without tags. Since I don't have control over the client, that's… bad.

I've tried creating an empty object first and tagging it, then issuing the presigned URL to it, but PUTting the object replaces it entirely, including removing any tags.

I've tried issuing a presigned POST URL, but that doesn't seem to support the tagging parameter at all:

s3.generate_presigned_post('foo', 'bar', {'tagging': '<Tagging><TagSet><Tag><Key>Foo</Key><Value>Bar</Value></Tag></TagSet></Tagging>'})
$ curl https://foo.s3.amazonaws.com/ \
  -F key=bar \
  -F 'tagging=<Tagging><TagSet><Tag><Key>Foo</Key><Value>Bar</Value></Tag></TagSet></Tagging>'
  -F AWSAccessKeyId=... \
  -F policy=... \
  -F signature=... \
  -F file=@/tmp/foo

<Error><Code>AccessDenied</Code><Message>Invalid according to Policy:
Extra input fields: tagging</Message>...

I simply want to let a client upload a file directly to S3, and ensure that it's tagged a certain way in the process. Any way to do that?


Solution

  • Try the following code:

    fields = {
        "tagging": "<Tagging><TagSet><Tag><Key>foo</Key><Value>bar</Value></Tag></TagSet></Tagging>"
        "x-amz-meta-u1": "value1",
        "x-amz-meta-u2": "value2"
    }
    conditions = [{key: value} for key, value in fields.items()]
    
    
    presignedurl = s3_client.generate_presigned_post(
        bucket_name, "YOUR_BUCKET_NAME",
        Fields=copy.deepcopy(fields),
        Conditions=copy.deepcopy(conditions)
    )